tokenformat

package
v1.9.1 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: MIT Imports: 10 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.

func (*TokenClaims) IsDelegated added in v1.9.1

func (c *TokenClaims) IsDelegated() bool

IsDelegated reports whether the token acts for its sub without being that user's own sign-in. It does when it carries an RFC 8693 act claim, or when it belongs to a principal other than a user and still names a user as sub. A machine token whose sub is the caller itself acts for nobody. An impersonated token carries no act claim (RFC 8693 section 1.1), so it reads as the user's own, the same as an impersonation session.

Jump to

Keyboard shortcuts

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