Documentation
¶
Overview ¶
Package auth provides JWT and API-key authentication for forge-generated Connect RPC services.
The library exposes a Validator that authenticates incoming Connect requests against a configured provider ("jwt", "api_key", "both", or "none") and a Connect interceptor that attaches the resulting Claims to the request context.
Generated forge projects import this package via a thin shim in pkg/middleware/auth_gen.go. The shim wires the project's Config from forge.yaml and exposes a project-local Claims alias (type Claims = auth.Claims).
Index ¶
- Constants
- type APIKeyConfig
- type Claims
- type Config
- type HMACValidator
- type HMACValidatorConfig
- type InterceptorOptions
- type JWKSValidator
- type JWKSValidatorConfig
- type JWTConfig
- type KeyValidator
- type MultiValidator
- type TokenValidator
- type UserResolver
- type Validator
- func (v *Validator) AuthenticateHeaders(ctx context.Context, headers http.Header, opts InterceptorOptions) (*Claims, error)
- func (v *Validator) Close() error
- func (v *Validator) ConnectInterceptor(opts InterceptorOptions, ...) connect.Interceptor
- func (v *Validator) Interceptor(opts InterceptorOptions, ...) connect.UnaryInterceptorFunc
- func (v *Validator) IsUnauthenticatedProcedure(procedure string, skipMethods []string) bool
- func (v *Validator) Provider() string
- func (v *Validator) SetKeyValidator(kv KeyValidator)
- func (v *Validator) Validate(token string) (*Claims, error)
Constants ¶
const ( ProviderNone = "none" ProviderJWT = "jwt" ProviderAPIKey = "api_key" ProviderBoth = "both" )
Provider names recognized by Config.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type APIKeyConfig ¶
type APIKeyConfig struct {
// Header is the HTTP header name carrying the API key.
// Defaults to "X-API-Key" when empty.
Header string
}
APIKeyConfig holds API-key-specific settings.
func (APIKeyConfig) EffectiveHeader ¶
func (a APIKeyConfig) EffectiveHeader() string
EffectiveHeader returns Header or the "X-API-Key" default.
type Claims ¶
type Claims struct {
UserID string `json:"user_id"`
Email string `json:"email"`
OrgID string `json:"org_id"`
Role string `json:"role"`
Roles []string `json:"roles"`
Raw map[string]any `json:"raw,omitempty"`
}
Claims is the canonical claims shape produced by all forge auth flows.
The struct mirrors the per-project pkg/middleware/Claims that earlier forge versions generated. The generated shim re-exports it via type Claims = auth.Claims so existing project code (referring to middleware.Claims) keeps compiling.
Raw carries the full decoded JWT payload (or any provider-specific map a UserResolver chooses to attach). Projects that need access to claims outside the fixed fields — Supabase user_metadata, Auth0 app_metadata, Clerk org_permissions, etc. — should read from Raw instead of forking the Claims type.
type Config ¶
type Config struct {
// Provider is one of "jwt", "api_key", "both", "none". Required.
Provider string
// JWT configures JWT validation. Used when Provider is "jwt" or "both".
JWT JWTConfig
// APIKey configures API key validation. Used when Provider is
// "api_key" or "both". A [KeyValidator] must be supplied separately
// via [Validator.SetKeyValidator] or [Config.KeyValidator].
APIKey APIKeyConfig
// SkipMethods is the list of fully-qualified Connect procedure names
// that bypass auth (in addition to the built-in /Health/ skip).
SkipMethods []string
// KeyValidator validates API keys. Required when APIKey auth is enabled.
KeyValidator KeyValidator
// TokenValidators, when non-empty, replaces the built-in single-secret
// JWT path with an ordered fallback chain. Each entry validates the
// bearer token independently; the first to accept wins. Use this when
// a service must accept tokens from more than one issuer — typically
// during an auth-provider migration (e.g. Supabase HMAC tokens
// alongside Auth0 JWKS tokens).
//
// When empty the legacy JWT (single secret / JWKSURL) path is used,
// preserving backwards compatibility for projects that only need one
// validator.
TokenValidators []TokenValidator
// UserResolver, when non-nil, projects the raw decoded JWT payload onto
// [Claims]. Use it when provider-specific claim shapes (Supabase
// user_metadata, Auth0 app_metadata, Clerk org_permissions, etc.) need
// to drive the Claims you hand to downstream code.
//
// When nil the validator uses built-in shape extraction (sub, email,
// org_id, role, roles) and still attaches the full payload to
// Claims.Raw so downstream code can inspect it.
UserResolver UserResolver
}
Config configures a Validator.
Provider selects the authentication scheme. JWT, APIKey and SkipMethods further customize each scheme. The library reads JWT_SECRET from the environment when JWTConfig.Secret is empty (preserving the legacy behaviour of the generated auth_gen.go template).
type HMACValidator ¶
type HMACValidator struct {
// contains filtered or unexported fields
}
HMACValidator validates JWTs signed with an HMAC algorithm (HS256/384/512).
Zero value is not usable; construct with NewHMACValidator. Secret is resolved from SecretEnv at construction time; rotating secrets requires constructing a new validator (which is cheap — no I/O).
func NewHMACValidator ¶
func NewHMACValidator(cfg HMACValidatorConfig) (*HMACValidator, error)
NewHMACValidator constructs a validator for HMAC-signed JWTs.
Returns an error when no secret can be resolved (neither Secret, SecretEnv, nor JWT_SECRET is set) or when SigningMethod is not an HS* algorithm.
func (*HMACValidator) ValidateToken ¶
func (v *HMACValidator) ValidateToken(tokenString string) (*Claims, error)
ValidateToken implements TokenValidator.
type HMACValidatorConfig ¶
type HMACValidatorConfig struct {
// SigningMethod is the expected JWT alg (e.g. "HS256"). Required.
SigningMethod string
// Secret is the symmetric secret. If empty, SecretEnv is consulted; if
// SecretEnv is also empty, "JWT_SECRET" is consulted as a last resort.
Secret string
// SecretEnv is the environment variable name to read the secret from
// when Secret is empty. Useful for the canonical config-file shape:
//
// - type: hmac
// secret_env: SUPABASE_JWT_SECRET
SecretEnv string
// Issuer, when set, is enforced via jwt.WithIssuer.
Issuer string
// Audience, when set, is enforced via jwt.WithAudience.
Audience string
// Resolver, when non-nil, is given the raw JWT payload so projects can
// project provider-specific shapes onto [Claims] (and into Claims.Raw).
// Nil means use the built-in shape extraction.
Resolver UserResolver
}
HMACValidatorConfig configures an HMACValidator.
type InterceptorOptions ¶
type InterceptorOptions struct {
// SkipMethods overrides Config.SkipMethods when non-nil.
SkipMethods []string
// AllowDevMode, when true, skips real authentication and injects
// DevClaims when no Authorization header is present. Only honour this
// in non-production builds; the constructor does not gate on env.
AllowDevMode bool
// DevClaims is injected when AllowDevMode is true and the request has
// no credentials. Defaults to a non-nil empty *Claims.
DevClaims *Claims
}
InterceptorOptions configures Validator.Interceptor behaviour beyond the fields already on Config.
type JWKSValidator ¶
type JWKSValidator struct {
// contains filtered or unexported fields
}
JWKSValidator validates JWTs whose signing key comes from a JWKS endpoint (or any other jwt.Keyfunc source — the pkg/auth library deliberately does not depend on a JWKS client implementation; the jwt-auth pack supplies one).
Construct via NewJWKSValidator with a pre-built jwt.Keyfunc (e.g. one returned by github.com/MicahParks/keyfunc/v3.NewDefaultCtx).
func NewJWKSValidator ¶
func NewJWKSValidator(cfg JWKSValidatorConfig) (*JWKSValidator, error)
NewJWKSValidator constructs a validator that delegates key resolution to cfg.KeyFunc (typically a JWKS-backed implementation).
func (*JWKSValidator) ValidateToken ¶
func (v *JWKSValidator) ValidateToken(tokenString string) (*Claims, error)
ValidateToken implements TokenValidator.
type JWKSValidatorConfig ¶
type JWKSValidatorConfig struct {
// SigningMethod is the expected JWT alg. Defaults to "RS256".
SigningMethod string
// KeyFunc resolves the signing key for the token. Required.
KeyFunc jwt.Keyfunc
// Issuer, when set, is enforced via jwt.WithIssuer.
Issuer string
// Audience, when set, is enforced via jwt.WithAudience.
Audience string
// Resolver, when non-nil, projects the raw JWT payload onto [Claims].
Resolver UserResolver
}
JWKSValidatorConfig configures a JWKSValidator.
type JWTConfig ¶
type JWTConfig struct {
// SigningMethod is the expected JWT alg value (e.g. "HS256", "RS256").
// Defaults to "RS256" when empty.
SigningMethod string
// Issuer, when set, is enforced via jwt.WithIssuer.
Issuer string
// Audience, when set, is enforced via jwt.WithAudience.
Audience string
// JWKSURL, when set, signals JWKS-based key resolution. The current
// implementation does not auto-fetch JWKS (matching the legacy template
// behaviour) — callers that need JWKS should populate Secret with a
// fetched key or supply a KeyFunc.
JWKSURL string
// Secret is the symmetric secret (HS*) or PEM-encoded public key
// (RS*/ES*). When empty the validator falls back to os.Getenv("JWT_SECRET").
Secret string
// KeyFunc, when non-nil, fully overrides key resolution. Useful for tests
// and for callers wiring custom JWKS clients.
KeyFunc jwt.Keyfunc
}
JWTConfig holds JWT-specific settings.
func (JWTConfig) EffectiveSigningMethod ¶
EffectiveSigningMethod returns SigningMethod or the "RS256" default.
type KeyValidator ¶
KeyValidator validates an API key and returns the associated claims. Implementations typically look up the key in a database or cache.
type MultiValidator ¶
type MultiValidator struct {
// contains filtered or unexported fields
}
MultiValidator tries each underlying validator in order and returns the first set of claims that parses cleanly.
When every validator rejects the token, MultiValidator returns the LAST validator's error (the chain typically ends with the strictest validator, so its error is the most informative). Use NewMultiValidator to construct.
func NewMultiValidator ¶
func NewMultiValidator(validators ...TokenValidator) (*MultiValidator, error)
NewMultiValidator constructs an ordered fallback chain. At least one underlying validator is required.
func (*MultiValidator) ValidateToken ¶
func (m *MultiValidator) ValidateToken(tokenString string) (*Claims, error)
ValidateToken implements TokenValidator by trying each underlying validator in order.
type TokenValidator ¶
type TokenValidator interface {
// ValidateToken parses tokenString and returns the parsed Claims. The
// returned error must be non-nil iff the token is invalid for THIS
// validator. MultiValidator uses error to decide whether to try the
// next validator in the chain.
ValidateToken(tokenString string) (*Claims, error)
}
TokenValidator validates a single bearer JWT and returns parsed Claims.
Implementations are expected to be safe for concurrent use. Each Validator represents one issuer/key configuration (e.g. one HMAC secret, one JWKS endpoint); compose multiple via MultiValidator when a service needs to accept tokens from more than one issuer (typical during an auth-provider migration — Supabase HMAC tokens alongside Auth0 JWKS tokens).
type UserResolver ¶
type UserResolver interface {
// Resolve receives the full decoded JWT payload (as a map, the
// shape jwt-go hands us) and must return a populated [Claims]. The
// raw payload should typically be copied onto Claims.Raw so
// downstream callers can still inspect provider-specific fields.
Resolve(rawClaims map[string]any) (*Claims, error)
}
UserResolver is an optional hook that translates a raw decoded JWT payload into Claims. Projects implement it when they need to consume provider-specific claim shapes (e.g. Supabase user_metadata, Auth0 app_metadata, Clerk org_role/org_permissions) without forking the Claims type.
The resolver is wired via Config.UserResolver; when nil the validator falls back to the built-in shape extraction (sub, email, org_id, role, roles) and still populates Claims.Raw with the full payload.
type Validator ¶
type Validator struct {
// contains filtered or unexported fields
}
Validator authenticates Connect RPC requests against a configured provider. Construct with NewValidator; reuse a single Validator per service.
func NewValidator ¶
NewValidator returns a Validator wired for cfg.Provider.
It returns an error when Provider is unrecognized. The "none" provider is allowed and produces a Validator whose Interceptor is a no-op (useful for tests and for projects that haven't enabled auth yet).
func (*Validator) AuthenticateHeaders ¶
func (v *Validator) AuthenticateHeaders(ctx context.Context, headers http.Header, opts InterceptorOptions) (*Claims, error)
AuthenticateHeaders authenticates a request given its headers and returns the parsed claims (or an error). It is the testable core of Validator.Interceptor — the interceptor wraps this with the procedure-skip logic and context plumbing.
func (*Validator) Close ¶
Close releases any resources held by the Validator. Currently a no-op; kept for forward compatibility with JWKS-cache implementations.
func (*Validator) ConnectInterceptor ¶
func (v *Validator) ConnectInterceptor(opts InterceptorOptions, withClaims func(context.Context, *Claims) context.Context) connect.Interceptor
ConnectInterceptor returns a full connect.Interceptor (unary + streaming handler) that authenticates each request and stores the resulting claims on ctx via withClaims.
Use this in preference to Validator.Interceptor for any new wiring — connect-go silently bypasses connect.UnaryInterceptorFunc for streaming RPCs, which would otherwise leave streaming endpoints unauthenticated.
Streaming clients are pass-through: the auth interceptor is server-side.
func (*Validator) Interceptor ¶
func (v *Validator) Interceptor(opts InterceptorOptions, withClaims func(context.Context, *Claims) context.Context) connect.UnaryInterceptorFunc
Interceptor returns a unary-only Connect interceptor that authenticates each request and stores the resulting claims in the context using the supplied claims-context helper.
withClaims is the function the user's pkg/middleware exposes for putting claims into context (typically middleware.ContextWithClaims).
This entrypoint is kept for backwards compatibility with projects that wired auth as a connect.UnaryInterceptorFunc. New code should prefer Validator.ConnectInterceptor, which also covers streaming RPCs.
func (*Validator) IsUnauthenticatedProcedure ¶
IsUnauthenticatedProcedure reports whether procedure should bypass auth. This includes the built-in /Health/ skip plus any explicit skip list.
func (*Validator) SetKeyValidator ¶
func (v *Validator) SetKeyValidator(kv KeyValidator)
SetKeyValidator replaces the configured KeyValidator. Useful when the validator is constructed before the storage backend is ready.