tokenformat

package
v1.9.0 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package tokenformat provides pluggable token generation strategies. Access tokens can be opaque (hex) or JWT; refresh tokens are always opaque.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidToken  = errors.New("tokenformat: invalid token")
	ErrTokenExpired  = errors.New("tokenformat: token expired")
	ErrUnsignedToken = errors.New("tokenformat: unsigned or tampered token")
)

Sentinel errors.

Functions

func GenerateRefreshToken

func GenerateRefreshToken() (string, error)

GenerateRefreshToken creates an opaque refresh token. This is a standalone function because refresh tokens are always opaque regardless of the access token format.

func IsJWT

func IsJWT(token string) bool

IsJWT returns true if the token string appears to be a JWT (has 3 dot-separated parts).

Types

type ActClaim

type ActClaim struct {
	Subject string    `json:"sub"`
	Act     *ActClaim `json:"act,omitempty"`
}

ActClaim is the RFC 8693 `act` claim: the party acting on behalf of the subject. It nests, so a chain of delegations is a chain of ActClaims with the immediate actor outermost. Subject carries a principal.Ref in its "kind:id" string form.

type Confirmation

type Confirmation struct {
	JKT string `json:"jkt,omitempty"`
}

Confirmation is the RFC 7800 cnf claim. Only the jkt member is used.

type Format

type Format interface {
	// Name returns the format identifier ("opaque" or "jwt").
	Name() string

	// GenerateAccessToken produces a token string from claims.
	GenerateAccessToken(claims TokenClaims) (string, error)

	// ValidateAccessToken parses and validates a token, returning the
	// embedded claims. Returns ErrInvalidToken or ErrTokenExpired on
	// failure.
	ValidateAccessToken(token string) (*TokenClaims, error)
}

Format generates and validates access tokens. Refresh tokens are always opaque (must be revocable via the store) so they are not part of this interface.

type JWT

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

JWT generates and validates JWT access tokens.

func NewJWT

func NewJWT(cfg JWTConfig) (*JWT, error)

NewJWT creates a new JWT token format with the given configuration.

func (*JWT) Algorithm

func (j *JWT) Algorithm() string

Algorithm returns the signing algorithm name.

func (*JWT) ConfiguredAudience

func (j *JWT) ConfiguredAudience() string

ConfiguredAudience returns the app-wide default "aud" this format stamps on tokens whose claims carry no audience of their own. Empty when unset.

Callers need it so a session row can record the same audience the minted token actually carries. When the row says nothing and the claim says "https://api.example.com", the two disagree, and any check that reads one after the other gets two different answers about the same credential.

func (*JWT) GenerateAccessToken

func (j *JWT) GenerateAccessToken(claims TokenClaims) (string, error)

func (*JWT) HMACKey

func (j *JWT) HMACKey() ([]byte, bool)

HMACKey returns the symmetric signing key when this JWT format uses an HMAC algorithm (HS256/HS384/HS512). The second return is false for RSA or ECDSA configurations, where no symmetric key exists.

This is intended for callers that need a process-wide symmetric secret to derive other HMAC keys from (e.g. the dashboard nonce signer). Never expose the returned bytes over the network.

func (*JWT) KeyID

func (j *JWT) KeyID() string

KeyID returns the configured key ID for JWKS.

func (*JWT) Name

func (j *JWT) Name() string

func (*JWT) PublicKey

func (j *JWT) PublicKey() any

PublicKey returns the public key for JWKS exposure.

func (*JWT) ValidateAccessToken

func (j *JWT) ValidateAccessToken(tokenStr string) (*TokenClaims, error)

type JWTConfig

type JWTConfig struct {
	// SigningMethod is the JWT signing algorithm (e.g., jwt.SigningMethodRS256,
	// jwt.SigningMethodES256, jwt.SigningMethodHS256).
	SigningMethod jwt.SigningMethod

	// SigningKey is the private key used to sign tokens.
	// - RSA: *rsa.PrivateKey
	// - ECDSA: *ecdsa.PrivateKey
	// - HMAC: []byte
	SigningKey any

	// VerifyKey is the public key used to verify tokens.
	// For HMAC, this should be the same as SigningKey.
	// - RSA: *rsa.PublicKey
	// - ECDSA: *ecdsa.PublicKey
	// - HMAC: []byte
	VerifyKey any

	// KeyID is the "kid" header value for key rotation support.
	KeyID string

	// Issuer is the "iss" claim (e.g., "https://auth.example.com").
	Issuer string

	// Audience is the "aud" claim (e.g., "api-service").
	Audience string
}

JWTConfig configures the JWT token format.

type Opaque

type Opaque struct{}

Opaque generates 64-character hex tokens (the default AuthSome format). Validation always returns ErrInvalidToken because opaque tokens must be resolved via the session store — they carry no embedded claims.

func (Opaque) GenerateAccessToken

func (Opaque) GenerateAccessToken(_ TokenClaims) (string, error)

func (Opaque) Name

func (Opaque) Name() string

func (Opaque) ValidateAccessToken

func (Opaque) ValidateAccessToken(_ string) (*TokenClaims, error)

type TokenClaims

type TokenClaims struct {
	UserID    string   `json:"sub"`
	AppID     string   `json:"app_id"`
	EnvID     string   `json:"env_id,omitempty"`
	OrgID     string   `json:"org_id,omitempty"`
	SessionID string   `json:"sid"`
	Scopes    []string `json:"scopes,omitempty"`
	// Audience holds the resource identifiers this token was granted for
	// (RFC 8707), emitted as the `aud` claim. Empty means unrestricted.
	//
	// This overrides JWTConfig.Audience when set. The config value is a single
	// static audience for a whole app, which predates resource indicators and
	// stays as the fallback so existing deployments are unaffected.
	Audience []string `json:"aud,omitempty"`
	// DPoPJKT is the RFC 7638 thumbprint this token is bound to (RFC 9449).
	// Empty means an unbound bearer token. Serialised as the cnf.jkt claim.
	DPoPJKT   string `json:"-"`
	IssuedAt  time.Time
	ExpiresAt time.Time

	// Act is present for a delegated token and nil otherwise. Impersonation
	// emits no act claim at all (RFC 8693 section 1.1), which is why the full
	// record lives on the session row rather than in the token.
	Act *ActClaim `json:"act,omitempty"`

	// PrincipalKind names the kind of caller this token belongs to, using the
	// values the principal package defines: "user", "agent", "workload" or
	// "service_account". Empty means user, so tokens minted before this field
	// existed keep validating.
	//
	// It is a string and not principal.Kind because tokenformat sits below
	// principal in the import graph, and a claims struct is a wire format that
	// should not drag a domain package in behind it.
	PrincipalKind string `json:"pk,omitempty"`

	// PrincipalID is the caller's id, duplicating what sub carries. It is here
	// so a consumer can read the caller without first deciding whether sub
	// means a user. That ambiguity is what made the JWT auth path parse an
	// empty sub and panic on a machine token.
	PrincipalID string `json:"pid,omitempty"`

	// ClientID names the OAuth2 client a token was issued to, and is empty
	// for a first-party login. It is the RFC 9068 client_id claim. The auth
	// middleware reads it to park a third-party token until a RequireScope
	// route admits it; an opaque token gets the same answer from its session
	// row, but a JWT is validated without loading one, so the token has to
	// say it.
	ClientID string `json:"client_id,omitempty"`
}

TokenClaims carries the identity payload embedded in an access token.

Jump to

Keyboard shortcuts

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