Documentation
¶
Overview ¶
Package core provides framework-agnostic JWT validation logic that can be used across different transport layers (HTTP, gRPC, etc.).
The Core type encapsulates the validation logic and can be wrapped by transport-specific adapters to provide JWT middleware functionality for various frameworks.
Package core provides framework-agnostic JWT validation logic that can be used across different transport layers (HTTP, gRPC, etc.).
The Core type encapsulates the validation logic without dependencies on any specific transport protocol. This allows the same validation code to be reused across multiple frameworks and transports.
Architecture ¶
The core package implements the "Core" in the Core-Adapter pattern:
┌─────────────────────────────────────────────┐
│ Transport Adapters │
│ (HTTP, gRPC, Gin, Echo - Framework Specific)│
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Core Engine (THIS PACKAGE) │
│ (Framework-Agnostic Validation Logic) │
│ • Token Validation │
│ • Credentials Optional Logic │
│ • Logger Integration │
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Validator │
│ (JWT Parsing & Verification) │
└─────────────────────────────────────────────┘
Basic Usage ¶
Create a Core instance with a validator and options:
import (
"github.com/auth0/go-jwt-middleware/v3/core"
"github.com/auth0/go-jwt-middleware/v3/validator"
)
// Create validator
val, err := validator.New(
validator.WithKeyFunc(keyFunc),
validator.WithAlgorithm(validator.RS256),
validator.WithIssuer("https://issuer.example.com/"),
validator.WithAudience("my-api"),
)
if err != nil {
log.Fatal(err)
}
// Create core with validator
c, err := core.New(
core.WithValidator(val),
core.WithCredentialsOptional(false),
)
if err != nil {
log.Fatal(err)
}
// Validate token
claims, err := c.CheckToken(ctx, tokenString)
if err != nil {
// Handle validation error
}
Type-Safe Context Helpers ¶
The package provides generic context helpers for type-safe claims retrieval:
// Store claims in context
ctx = core.SetClaims(ctx, claims)
// Retrieve claims with type safety
claims, err := core.GetClaims[*validator.ValidatedClaims](ctx)
if err != nil {
// Claims not found
}
// Check if claims exist
if core.HasClaims(ctx) {
// Claims are present
}
Error Handling ¶
The package provides structured error handling with ValidationError:
claims, err := c.CheckToken(ctx, tokenString)
if err != nil {
// Check for sentinel errors
if errors.Is(err, core.ErrJWTMissing) {
// Token missing
}
if errors.Is(err, core.ErrJWTInvalid) {
// Token invalid
}
// Check for ValidationError with error codes
var validationErr *core.ValidationError
if errors.As(err, &validationErr) {
switch validationErr.Code {
case core.ErrorCodeTokenExpired:
// Handle expired token
case core.ErrorCodeInvalidSignature:
// Handle signature error
}
}
}
Logging ¶
Optional logging can be configured to debug the validation flow:
c, err := core.New(
core.WithValidator(val),
core.WithLogger(logger), // slog.Logger or compatible
)
The logger will output:
- Token validation attempts
- Success/failure with duration
- Credentials optional behavior
Context Keys ¶
The package uses an unexported context key type to prevent collisions:
type contextKey int
This ensures that claims stored by this package cannot accidentally conflict with other context values in your application.
Index ¶
- Constants
- Variables
- func GetClaims[T any](ctx context.Context) (T, error)
- func HasClaims(ctx context.Context) bool
- func HasDPoPContext(ctx context.Context) bool
- func SetAuthScheme(ctx context.Context, scheme AuthScheme) context.Context
- func SetClaims(ctx context.Context, claims any) context.Context
- func SetDPoPContext(ctx context.Context, dpopCtx *DPoPContext) context.Context
- func SetDPoPMode(ctx context.Context, mode DPoPMode) context.Context
- type AuthScheme
- type Core
- type DPoPContext
- type DPoPMode
- type DPoPProofClaims
- type Logger
- type Option
- type TokenClaims
- type ValidationError
- type Validator
Constants ¶
const ( ErrorCodeDPoPProofMissing = "dpop_proof_missing" ErrorCodeDPoPProofInvalid = "dpop_proof_invalid" ErrorCodeDPoPBindingMismatch = "dpop_binding_mismatch" ErrorCodeDPoPHTMMismatch = "dpop_htm_mismatch" ErrorCodeDPoPHTUMismatch = "dpop_htu_mismatch" ErrorCodeDPoPATHMismatch = "dpop_ath_mismatch" ErrorCodeDPoPProofExpired = "dpop_proof_expired" ErrorCodeDPoPProofTooNew = "dpop_proof_too_new" ErrorCodeBearerNotAllowed = "bearer_not_allowed" ErrorCodeDPoPNotAllowed = "dpop_not_allowed" )
DPoP-specific error codes Note: Error codes provide granular details for logging and debugging. The sentinel errors group these into two categories for error handling.
const ( ErrorCodeTokenMissing = "token_missing" //nolint:gosec // False positive: this is not a credential ErrorCodeTokenMalformed = "token_malformed" ErrorCodeTokenExpired = "token_expired" ErrorCodeTokenNotYetValid = "token_not_yet_valid" //nolint:gosec // False positive: this is not a credential ErrorCodeInvalidSignature = "invalid_signature" ErrorCodeInvalidAlgorithm = "invalid_algorithm" ErrorCodeInvalidIssuer = "invalid_issuer" ErrorCodeInvalidAudience = "invalid_audience" ErrorCodeInvalidClaims = "invalid_claims" ErrorCodeJWKSFetchFailed = "jwks_fetch_failed" ErrorCodeJWKSKeyNotFound = "jwks_key_not_found" ErrorCodeConfigInvalid = "config_invalid" ErrorCodeValidatorNotSet = "validator_not_set" ErrorCodeClaimsNotFound = "claims_not_found" ErrorCodeInvalidToken = "invalid_token" ErrorCodeInvalidRequest = "invalid_request" )
Common error codes
Variables ¶
var ( // ErrInvalidDPoPProof is returned when DPoP proof validation fails. // This covers: missing proof, invalid JWT, HTM/HTU mismatch, expired/future iat. // The specific error code in ValidationError.Code provides granular details. ErrInvalidDPoPProof = errors.New("DPoP proof is invalid") // ErrDPoPBindingMismatch is returned when the JKT doesn't match the cnf claim. // This is kept separate as it indicates a token binding issue, not a proof validation issue. ErrDPoPBindingMismatch = errors.New("DPoP proof public key does not match token cnf claim") // ErrBearerNotAllowed is returned in DPoP required mode. ErrBearerNotAllowed = errors.New("bearer tokens are not allowed (DPoP required)") // ErrDPoPNotAllowed is returned in DPoP disabled mode. ErrDPoPNotAllowed = errors.New("DPoP tokens are not allowed (Bearer only)") )
DPoP-specific sentinel errors Per DPOP_ERRORS.md: All DPoP proof validation errors (except binding mismatch) are combined under ErrInvalidDPoPProof for simplified error handling.
var ( // ErrJWTMissing is returned when the JWT is missing from the request. ErrJWTMissing = errors.New("jwt missing") // ErrJWTInvalid is returned when the JWT is invalid. // This is typically wrapped with more specific validation errors. ErrJWTInvalid = errors.New("jwt invalid") // ErrInvalidToken is returned when the access token itself is invalid. // Used for token-level errors (e.g., DPoP scheme without cnf claim). ErrInvalidToken = errors.New("invalid access token") // ErrInvalidRequest is returned when the request format is invalid. // Used for protocol violations (e.g., Bearer + DPoP proof combination). ErrInvalidRequest = errors.New("invalid request") // ErrClaimsNotFound is returned when claims cannot be retrieved from context. ErrClaimsNotFound = errors.New("claims not found in context") )
Sentinel errors for JWT validation.
Functions ¶
func GetClaims ¶
GetClaims retrieves claims from the context with type safety using generics.
This is a type-safe alternative to manually type-asserting the claims from the context. It returns an error if the claims are not found or if the type assertion fails.
Example usage:
claims, err := core.GetClaims[*validator.ValidatedClaims](ctx)
if err != nil {
return err
}
// Use claims...
func HasDPoPContext ¶
HasDPoPContext checks if a DPoP context exists in the context. Returns true for DPoP-bound tokens, false for Bearer tokens.
Example usage:
if core.HasDPoPContext(ctx) {
dpopCtx := core.GetDPoPContext(ctx)
// Handle DPoP-specific logic...
}
func SetAuthScheme ¶
func SetAuthScheme(ctx context.Context, scheme AuthScheme) context.Context
SetAuthScheme stores the authorization scheme in the context. This is used by adapters to track which auth scheme was used in the request.
func SetClaims ¶
SetClaims stores claims in the context. This is a helper function for adapters to set claims after validation.
func SetDPoPContext ¶
func SetDPoPContext(ctx context.Context, dpopCtx *DPoPContext) context.Context
SetDPoPContext stores DPoP context in the context. This is a helper function for adapters to set DPoP context after validation.
DPoP context contains information about the validated DPoP proof, including the public key thumbprint, issued-at timestamp, and the raw proof JWT.
Types ¶
type AuthScheme ¶
type AuthScheme string
AuthScheme represents the authorization scheme used in the request. This is used to enforce RFC 9449 Section 6.1 which specifies that Bearer tokens without cnf claims should ignore DPoP headers.
const ( // AuthSchemeBearer represents Bearer token authorization. AuthSchemeBearer AuthScheme = "bearer" // AuthSchemeDPoP represents DPoP token authorization. AuthSchemeDPoP AuthScheme = "dpop" // AuthSchemeUnknown represents an unknown or missing authorization scheme. AuthSchemeUnknown AuthScheme = "" )
func GetAuthScheme ¶
func GetAuthScheme(ctx context.Context) AuthScheme
GetAuthScheme retrieves the authorization scheme from the context. Returns AuthSchemeUnknown if no scheme was set.
Example usage:
scheme := core.GetAuthScheme(ctx)
if scheme == core.AuthSchemeDPoP {
// Handle DPoP-specific logic...
}
type Core ¶
type Core struct {
// contains filtered or unexported fields
}
Core is the framework-agnostic JWT validation engine. It contains the core logic for token validation without any dependency on specific transport protocols (HTTP, gRPC, etc.).
func New ¶
New creates a new Core instance with the provided options.
The Core must be configured with at least a TokenValidator using WithValidator. All other options are optional and will use sensible defaults if not provided.
Example:
core, err := core.New(
core.WithValidator(validator),
core.WithCredentialsOptional(true),
core.WithLogger(logger),
)
if err != nil {
log.Fatal(err)
}
func (*Core) CheckToken ¶
CheckToken validates a JWT token string and returns the validated claims.
This is the core validation logic that is framework-agnostic:
- If token is empty and credentialsOptional is true, returns (nil, nil)
- If token is empty and credentialsOptional is false, returns ErrJWTMissing
- Otherwise, validates the token using the configured validator
The returned claims (any) should be type-asserted by the caller to the expected claims type (typically *validator.ValidatedClaims).
func (*Core) CheckTokenWithDPoP ¶
func (c *Core) CheckTokenWithDPoP( ctx context.Context, accessToken string, authScheme AuthScheme, dpopProof string, httpMethod string, requestURL string, ) (claims any, dpopCtx *DPoPContext, err error)
CheckTokenWithDPoP validates an access token with optional DPoP proof. This is the primary validation method that handles both Bearer and DPoP tokens.
Parameters:
- ctx: Request context
- accessToken: JWT access token string
- authScheme: The authorization scheme from the request (Bearer, DPoP, or Unknown)
- dpopProof: DPoP proof JWT string (empty for Bearer tokens)
- httpMethod: HTTP method for HTM validation (empty for Bearer tokens)
- requestURL: Full request URL for HTU validation (empty for Bearer tokens)
Returns:
- claims: Validated token claims (TokenClaims interface)
- dpopCtx: DPoP context (nil for Bearer tokens)
- error: Validation error or nil
The authScheme parameter is used to enforce RFC 9449 Section 6.1 which specifies that Bearer tokens without cnf claims should ignore DPoP headers.
When dpopProof is empty, this method behaves identically to CheckToken for Bearer tokens.
type DPoPContext ¶
type DPoPContext struct {
// PublicKeyThumbprint (jkt) from the validated DPoP proof.
// Can be used for session binding, audit logging, rate limiting, etc.
PublicKeyThumbprint string
// IssuedAt timestamp from the DPoP proof.
// Useful for audit trails and debugging.
IssuedAt time.Time
// TokenType is always "DPoP" when this context exists.
// Helps distinguish DPoP tokens from Bearer tokens.
TokenType string
// PublicKey is the validated public key from the DPoP proof JWK.
// Can be used for additional cryptographic operations if needed.
PublicKey any
// DPoPProof is the raw DPoP proof JWT string.
// Useful for logging and audit purposes.
DPoPProof string
}
DPoPContext contains validated DPoP information for the application. This is created by Core after successful DPoP validation and can be stored in the request context alongside the validated claims.
func GetDPoPContext ¶
func GetDPoPContext(ctx context.Context) *DPoPContext
GetDPoPContext retrieves DPoP context from the context. Returns nil if no DPoP context exists (e.g., for Bearer tokens).
Example usage:
dpopCtx := core.GetDPoPContext(ctx)
if dpopCtx != nil {
log.Printf("DPoP token from key: %s", dpopCtx.PublicKeyThumbprint)
}
type DPoPMode ¶
type DPoPMode int
DPoPMode represents the operational mode for DPoP token validation.
const ( // DPoPAllowed accepts both Bearer and DPoP tokens (default, non-breaking). // This mode allows gradual migration from Bearer to DPoP tokens. DPoPAllowed DPoPMode = iota // DPoPRequired only accepts DPoP tokens and rejects Bearer tokens. // Use this mode when all clients have been upgraded to support DPoP. DPoPRequired // DPoPDisabled only accepts Bearer tokens and ignores DPoP headers. // Use this mode to explicitly opt-out of DPoP support. DPoPDisabled )
func GetDPoPMode ¶
GetDPoPMode retrieves the DPoP mode from the context. Returns DPoPAllowed if no mode was set (default).
Example usage:
mode := core.GetDPoPMode(ctx)
if mode == core.DPoPRequired {
// Only accept DPoP tokens
}
type DPoPProofClaims ¶
type DPoPProofClaims interface {
// GetJTI returns the unique identifier (jti) of the DPoP proof.
GetJTI() string
// GetHTM returns the HTTP method (htm) from the DPoP proof.
GetHTM() string
// GetHTU returns the HTTP URI (htu) from the DPoP proof.
GetHTU() string
// GetIAT returns the issued-at timestamp (iat) from the DPoP proof.
GetIAT() int64
// GetATH returns the access token hash (ath) from the DPoP proof, if present.
// Returns empty string if the ath claim is not included in the proof.
GetATH() string
// GetPublicKeyThumbprint returns the calculated JKT from the DPoP proof's JWK.
GetPublicKeyThumbprint() string
// GetPublicKey returns the public key from the DPoP proof's JWK.
GetPublicKey() any
}
DPoPProofClaims represents the essential claims extracted from a DPoP proof. This interface allows the core to work with different DPoP proof claim implementations.
type Logger ¶
type Logger interface {
Debug(msg string, args ...any)
Info(msg string, args ...any)
Warn(msg string, args ...any)
Error(msg string, args ...any)
}
Logger defines an optional logging interface for the core middleware.
type Option ¶
Option is a function that configures the Core. Options return errors to enable validation during construction.
func WithCredentialsOptional ¶
WithCredentialsOptional configures whether credentials are optional.
When set to true, requests without tokens will be allowed to proceed without validation. The claims will be nil in the context.
When set to false (default), requests without tokens will return ErrJWTMissing.
Use this option carefully - requiring authentication by default is more secure.
func WithDPoPIATLeeway ¶
WithDPoPIATLeeway sets the clock skew allowance for future iat claims in DPoP proofs. This allows DPoP proofs with iat timestamps slightly in the future due to clock drift.
Default: 30 seconds
Adjust this if you have different clock skew requirements:
core, _ := core.New(
core.WithValidator(validator),
core.WithDPoPIATLeeway(60 * time.Second), // More lenient: 60s
)
func WithDPoPMode ¶
WithDPoPMode configures the DPoP operational mode.
Modes:
- DPoPAllowed (default): Accept both Bearer and DPoP tokens
- DPoPRequired: Only accept DPoP tokens, reject Bearer tokens
- DPoPDisabled: Only accept Bearer tokens, ignore DPoP headers
Example:
core, _ := core.New(
core.WithValidator(validator),
core.WithDPoPMode(core.DPoPRequired),
)
func WithDPoPProofOffset ¶
WithDPoPProofOffset sets the maximum age offset for DPoP proofs. This determines how far in the past a DPoP proof's iat timestamp can be.
Default: 300 seconds (5 minutes)
Use a shorter duration for high-security environments:
core, _ := core.New(
core.WithValidator(validator),
core.WithDPoPProofOffset(60 * time.Second), // Stricter: 60s
)
func WithLogger ¶
WithLogger sets an optional logger for the Core.
When configured, the Core will log debug information about token extraction, validation success/failure, and timing information.
If you need custom metrics or callbacks, consider wrapping the Core in your own implementation that delegates to the Core for validation.
Example:
logger := slog.Default()
core, _ := core.New(
core.WithValidator(validator),
core.WithLogger(logger),
)
func WithValidator ¶
WithValidator sets the validator for the Core. This is a required option. The validator must implement both ValidateToken and ValidateDPoPProof methods.
type TokenClaims ¶
type TokenClaims interface {
// GetConfirmationJKT returns the jkt from the cnf claim, or empty string if not present.
GetConfirmationJKT() string
// HasConfirmation returns true if the token has a cnf claim.
HasConfirmation() bool
}
TokenClaims represents the essential claims from an access token. This interface allows the core to work with different token claim implementations.
type ValidationError ¶
type ValidationError struct {
// Code is a machine-readable error code (e.g., "token_expired", "invalid_signature")
Code string
// Message is a human-readable error message
Message string
// Details contains the underlying error
Details error
}
ValidationError wraps JWT validation errors with additional context. It provides structured error information that can be used for logging, metrics, and returning appropriate error responses.
func NewValidationError ¶
func NewValidationError(code, message string, details error) *ValidationError
NewValidationError creates a new ValidationError with the given code and message.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
Error implements the error interface.
func (*ValidationError) Is ¶
func (e *ValidationError) Is(target error) bool
Is allows the error to be compared with ErrJWTInvalid.
func (*ValidationError) Unwrap ¶
func (e *ValidationError) Unwrap() error
Unwrap returns the underlying error for error unwrapping.
type Validator ¶
type Validator interface {
ValidateToken(ctx context.Context, token string) (any, error)
ValidateDPoPProof(ctx context.Context, proofString string) (DPoPProofClaims, error)
}
Validator defines the interface for JWT and DPoP validation. Implementations should validate tokens and DPoP proofs, returning the validated claims.