token

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Apr 28, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Index

Constants

View Source
const (
	PurposeClient  = "client"
	PurposeSession = "session"
	PurposeCode    = "code"
	PurposeAccess  = "access"
	PurposeRefresh = "refresh"
	// PurposeConsent guards the short-lived blob that carries
	// validated /authorize parameters across the consent-page POST.
	// Distinct from PurposeSession so a sealed session cannot be
	// replayed as a consent token (or vice versa) — the AAD tag
	// enforces it.
	PurposeConsent = "consent"
)

Purpose constants bind every sealed payload to a specific role via AEAD additional-data (AAD). A ciphertext minted with one purpose cannot be opened as any other, which closes the sealed-type confusion family without relying on JSON typ discriminators alone. The Typ field on each sealed struct is a belt-and-braces check layered on top of the AAD binding.

Variables

View Source
var ErrTokenExpired = errors.New("token expired")

ErrTokenExpired is returned by Validate when the bearer's ExpiresAt has already passed. Sentinel — separate from the generic "validation failed" surface so the middleware can bucket expired-token traffic under a dedicated metric reason. Without this, operators can't tell "clients walked away / forgot to refresh" (benign, expected) from "attacker probing with forged tokens" (attack signal): both look like invalid_token spikes.

Functions

This section is empty.

Types

type Claims

type Claims struct {
	TokenID   string    `json:"tid"`
	Typ       string    `json:"typ"`
	Audience  string    `json:"aud"`
	Resource  string    `json:"res,omitempty"`
	Subject   string    `json:"sub"`
	Email     string    `json:"email"`
	Groups    []string  `json:"grp,omitempty"`
	ClientID  string    `json:"cid"`
	IssuedAt  time.Time `json:"iat"`
	ExpiresAt time.Time `json:"exp"`
}

Claims represents the internal access token payload.

Audience binds the token to the proxy base URL that issued it (cross-instance replay defense — two deployments sharing the same TOKEN_SIGNING_SECRET cannot honour each other's tokens).

Resource is the RFC 8707 resource indicator this token was minted for — for the single-mount proxy this is always {baseURL}{mountPath}. Sealed and validated separately from Audience so a future multi-mount or multi-tenant deployment that shares an origin cannot honour a token minted for another mount (RFC 8707 §2.2). Empty on tokens minted before this field existed; treated as "unknown" by the middleware (rejected only when the middleware was constructed with a non-empty expected resource — back-compat for callers that haven't opted in).

type Manager

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

Manager handles AES-GCM encryption for all stateless tokens and sealed payloads. All instances sharing the same secret can seal/open each other's payloads, enabling horizontal scaling without shared storage.

Multi-key rotation (G4.1): Manager supports one primary key (used for every Seal) and zero or more secondary keys (tried in order on Open, after the primary fails). A rolling key rotation looks like:

  1. Steady state: primary=A, secondaries=[].
  2. Rotation prepared: primary=B, secondaries=[A]. Tokens minted going forward are sealed with B; tokens minted before the rotation still decrypt because A remains in the try list.
  3. Bleed-in wait: at least one access-token TTL + one refresh- token TTL pass (1h + 7d ≈ 7d) so every in-flight A-sealed token either expires or rotates to a B-sealed one.
  4. Rotation complete: primary=B, secondaries=[]. A is safe to destroy.

Without this, a key rotation was a flag day — every existing session broke on the cutover.

func NewManager

func NewManager(secret []byte) (*Manager, error)

NewManager creates a token manager from a single signing secret (min 32 bytes). Equivalent to NewManagerWithRotation(secret) — kept for callers that do not need key rotation.

func NewManagerWithRotation added in v1.0.0

func NewManagerWithRotation(primary []byte, secondaries ...[]byte) (*Manager, error)

NewManagerWithRotation creates a token manager with one primary signing secret and zero or more secondary secrets. New payloads are always sealed with primary; Open tries primary first, then each secondary in the order given. Every secret must be at least 32 bytes. Duplicate secrets are allowed but wasted.

func (*Manager) Issue

func (m *Manager) Issue(audience, subject, email, clientID string, groups []string, ttl time.Duration, resource string) (string, *Claims, error)

Issue creates a new opaque access token.

audience binds the token to a specific proxy deployment so it cannot be replayed against a sibling instance that happens to share the same signing secret.

resource is the RFC 8707 resource indicator this token was minted for. Sealed alongside audience so a future multi-mount deployment sharing the proxy origin cannot accept a token minted for a different mount (RFC 8707 §2.2). Pass "" when the caller does not participate in the resource binding (legacy / non-MCP callers).

func (*Manager) OpenJSON

func (m *Manager) OpenJSON(sealed string, v any, purpose string) error

OpenJSON decrypts a sealed string and unmarshals the JSON payload into v. purpose must match the value passed to SealJSON.

func (*Manager) SealCount added in v1.0.0

func (m *Manager) SealCount() uint64

SealCount returns the current number of successful seals. Exposed for tests and operator introspection; not part of the OAuth flow.

func (*Manager) SealJSON

func (m *Manager) SealJSON(v any, purpose string) (string, error)

SealJSON encrypts a JSON-serializable value with the given purpose AAD.

func (*Manager) SetLogger added in v1.0.0

func (m *Manager) SetLogger(l *zap.Logger)

SetLogger attaches a zap logger for the one-shot seal-rotation warning. Safe to call before any seals happen; passing nil disables the warning.

func (*Manager) SetSealMetric added in v1.0.0

func (m *Manager) SetSealMetric(fn func(purpose string))

SetSealMetric attaches a per-seal observer (typically a Prometheus CounterVec.WithLabelValues call) that the Manager invokes on every successful seal, labelled by purpose. Decouples the token package from the metrics package; main.go wires the actual counter at startup. Passing nil disables observation (tests).

func (*Manager) Validate

func (m *Manager) Validate(tokenStr string) (*Claims, error)

Validate decodes and validates an opaque access token, returning its claims. Enforces AAD purpose=access + belt-and-braces typ check + non-empty subject/audience + non-zero issued-at, so a decode regression in any single layer doesn't collapse the defense.

Jump to

Keyboard shortcuts

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