Help us shape the future of Go! Take the 2026 Go Developer Survey

authtoken

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 22, 2026 License: AGPL-3.0 Imports: 5 Imported by: 0

Documentation

Overview

Package authtoken mints and verifies the short-lived, purpose-scoped tokens behind the self-service account flows: password reset, email verification, and email change. Tokens are signed JWTs (HS256), reusing the app's JWT secret rather than hand-rolling a MAC — the same choice as internal/tracking.

Two properties make the single secret safe to share across flows:

  • A `prp` (purpose) claim is validated on parse, so a token minted for one flow (say, signup verification) cannot be replayed against another (reset).
  • The HMAC signing key is *derived per token* from (purpose, userID, binding), where `binding` is a value that changes when the action completes — the user's password hash for reset, their current email for a change. Once the action lands, the binding no longer matches and the token stops verifying, giving single-use semantics with no extra database column.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Purpose

type Purpose string

Purpose scopes a token to one flow. It is both baked into the signing key and carried as a validated claim.

const (
	PurposePasswordReset Purpose = "pwreset"
	PurposeEmailVerify   Purpose = "email_verify"
	PurposeEmailChange   Purpose = "email_change"
	// PurposeIdentityNotMe is the "this was not me" link of the notice mailed when a
	// Sign-in provider was linked silently (ADR 0048). It carries the Linked identity to
	// undo and has no binding: once the identity is gone the link has nothing left to do.
	PurposeIdentityNotMe Purpose = "identity_not_me"
	// PurposeUserDeletion is the one-time link mailed to a User with no password to confirm
	// their own User deletion (ADR 0050). Keyed to the User id and session epoch, never to
	// the address.
	PurposeUserDeletion Purpose = "user_deletion"
	// PurposeLoginChallenge is the challenge between the password step and the
	// Second factor step of a login (ADR 0020).
	PurposeLoginChallenge Purpose = "login_challenge"
	// PurposeStaffLoginChallenge is the same step for a Staff member (ADR 0026). Its
	// own purpose, and a signer on the Staff member secret, keep it from ever verifying
	// as a User's challenge: Staff member and User ids share no space.
	PurposeStaffLoginChallenge Purpose = "staff_login_challenge"
)

type Signer

type Signer struct {
	// contains filtered or unexported fields
}

Signer mints and parses authtoken JWTs against a single secret.

func New

func New(secret string) *Signer

New builds a Signer from the shared JWT secret, on the wall clock.

func (*Signer) Mint

func (s *Signer) Mint(purpose Purpose, userID int64, binding string, ttl time.Duration, extra map[string]string) (string, error)

Mint signs a token for purpose + userID, keyed to binding (the value that changes when the action completes, giving single-use for free) and expiring after ttl. extra carries purpose-specific string claims (e.g. the requested new email for a change); nil is fine.

func (*Signer) Parse

func (s *Signer) Parse(token string, purpose Purpose, bindingFor func(userID int64) (string, error)) (int64, map[string]string, error)

Parse validates a token for the expected purpose and returns the user id plus any extra claims. bindingFor loads the current binding value for the decoded user id (e.g. their password hash) so a completed action invalidates the token; it is called with the claimed user id before the signature is verified.

func (*Signer) WithClock

func (s *Signer) WithClock(now func() time.Time) *Signer

WithClock returns a copy of the Signer that mints and checks expiry against now (tests move it past a token's lifetime instead of sleeping). nil keeps the clock.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL