auth

package
v0.1.45 Latest Latest
Warning

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

Go to latest
Published: Oct 14, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

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

View Source
const (
	ProviderNone   = "none"
	ProviderJWT    = "jwt"
	ProviderAPIKey = "api_key"
	ProviderBoth   = "both"
)

Provider names recognized by Config.

Variables

This section is empty.

Functions

func JWKSKeyfunc

func JWKSKeyfunc(urls ...string) (jwt.Keyfunc, error)

JWKSKeyfunc builds an auto-refreshing jwt.Keyfunc backed by one or more JWKS endpoints. Signing keys are fetched once at construction (so a misconfigured or unreachable endpoint fails fast, at server boot) and then refreshed in the background for the life of the process.

The fail-fast half is what NoErrorReturnFirstHTTPReq buys: keyfunc's NewDefault DEFAULTS to swallowing the first fetch error and logging it, which turns a typo'd JWKS URL into an endless stream of per-request 401s nobody can attribute. A server that cannot reach its issuer's keys cannot authenticate anyone, so refusing to boot is both louder and no less available.

It is the seam Clerk and Firebase use internally; call it directly when wiring a custom JWKS issuer through NewJWKSValidator.

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 ClerkOpts

type ClerkOpts struct {
	// JWKSURL is the Clerk instance's JWKS endpoint, e.g.
	// https://<your-subdomain>.clerk.accounts.dev/.well-known/jwks.json
	// (typically read from CLERK_JWKS_URL). Required unless KeyFunc is set.
	JWKSURL string

	// Issuer, when set, is enforced (iss). Clerk's issuer is the instance's
	// Frontend API URL, e.g. https://<your-subdomain>.clerk.accounts.dev.
	// Strongly recommended in production so a token minted for a different
	// Clerk instance is rejected.
	Issuer string

	// Audience, when set, is enforced (aud).
	Audience string

	// KeyFunc overrides JWKS fetching. Set it to inject a pre-built keyfunc
	// (tests, or a custom JWKS client); when nil the validator builds one
	// from JWKSURL via [JWKSKeyfunc].
	KeyFunc jwt.Keyfunc
}

ClerkOpts configures a Clerk validator.

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 gRPC health-check 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. Every value is supplied by the caller — this package reads no environment variable of its own, so the key a validator trusts is always the key its constructor was handed.

type FirebaseOpts

type FirebaseOpts struct {
	// ProjectIDs is the set of accepted Firebase project IDs (typically read
	// from FIREBASE_PROJECT_IDS, comma-separated). A token is accepted only
	// when its aud is one of these AND its iss is
	// securetoken.google.com/<that same project>. At least one is required.
	// Accepting several lets one binary serve clients from multiple Firebase
	// projects.
	ProjectIDs []string

	// KeyFunc overrides Google's JWKS endpoint (tests, or a custom key
	// source). When nil the validator builds one from the well-known
	// Firebase JWKS URL via [JWKSKeyfunc].
	KeyFunc jwt.Keyfunc
}

FirebaseOpts configures a Firebase validator.

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. The secret is captured at construction; rotating it means 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 Secret is empty or 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. Required — this package reads no
	// environment variable of its own, so the key a validator trusts is
	// always the key its constructor was handed. Pass it from the app's
	// typed config (declare the field `sensitive: true` and it arrives from
	// the environment's secret provider, never as an inline manifest
	// value); an app accepting several issuers passes each their own.
	Secret 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
}

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 caller 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, is the issuer's JWKS endpoint. [NewValidator]
	// fetches it ONCE at construction (so an unreachable or malformed
	// endpoint fails at server boot, not on the first request) and refreshes
	// it in the background for the life of the process — the same
	// [JWKSKeyfunc] machinery [Clerk] and [Firebase] use. This is the path
	// every standard OIDC issuer (Auth0, Keycloak, Zitadel, Supabase, Okta)
	// takes.
	//
	// Mutually exclusive with Secret: two key sources for one validator is a
	// configuration error, and [NewValidator] rejects it rather than picking
	// a winner. A service that must genuinely accept tokens from more than
	// one issuer composes them explicitly via [Config.TokenValidators].
	JWKSURL string

	// Secret is the symmetric secret (HS*) or PEM-encoded public key
	// (RS*/ES*). Mutually exclusive with JWKSURL.
	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

func (j JWTConfig) EffectiveSigningMethod() string

EffectiveSigningMethod returns SigningMethod or the "RS256" default.

type KeyValidator

type KeyValidator interface {
	ValidateKey(ctx context.Context, key string) (*Claims, error)
}

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 Clerk

func Clerk(opts ClerkOpts) (*Validator, error)

Clerk returns a Validator for Clerk session tokens. Clerk issues RS256 JWTs whose signing keys come from a per-instance JWKS endpoint; the claim shape (sub / org_id / org_role / org_permissions) is mapped onto Claims by [clerkResolver].

// in SetupAuth(cfg *config.Config) — the values are typed config
// fields, declared in proto/config/v1/config.proto and pinned per
// environment, never read out of the process environment here.
validator, err := auth.Clerk(auth.ClerkOpts{
    JWKSURL: cfg.JwtJwksUrl,
    Issuer:  cfg.JwtIssuer, // optional but recommended
})
return validator.Validate, nil

func Firebase

func Firebase(opts FirebaseOpts) (*Validator, error)

Firebase returns a Validator for Firebase ID tokens. Firebase issues RS256 JWTs signed with Google's rotating keys; each accepted project pins its own iss+aud, so per-project enforcement is expressed as one JWKSValidator per project composed through a MultiValidator. Claims are mapped by [firebaseResolver] (sub = uid).

// A value this app's config does not carry yet gets a new field in
// proto/config/v1/config.proto — that is the one place a
// per-deployment value is declared.
validator, err := auth.Firebase(auth.FirebaseOpts{
    ProjectIDs: strings.Split(cfg.FirebaseProjectIds, ","),
})

func NewValidator

func NewValidator(cfg Config) (*Validator, error)

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

func (v *Validator) Close() error

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

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

func (v *Validator) IsUnauthenticatedProcedure(procedure string, skipMethods []string) bool

IsUnauthenticatedProcedure reports whether procedure should bypass auth. This includes the built-in gRPC health-check skip plus any explicit skip list.

func (*Validator) Provider

func (v *Validator) Provider() string

Provider returns the configured provider name.

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.

func (*Validator) Validate

func (v *Validator) Validate(token string) (*Claims, error)

Validate authenticates a single bearer JWT and returns the parsed claims. Useful outside the interceptor (e.g. webhook auth, CLI tooling).

Jump to

Keyboard shortcuts

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