authn

package
v0.1.0 Latest Latest
Warning

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

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

Documentation

Overview

Package authn verifies the delegated human's identity. This is separate from - and a prerequisite for - the capability/reversibility machinery: every policy decision downstream trusts DelegatedUser/DelegatedRole, so those values must come from a signed token, never from a plain string the caller wrote into the request itself. Two modes behind one Verifier type: HS256 with a shared secret (local/dev, paired with cmd/devtoken), or RS256 against a real IdP's published JWKS (NewJWKSVerifier). Callers don't know which is active.

JWKS (RFC 7517) fetch/parse for real-IdP mode (Verifier.jwksURL set via NewJWKSVerifier). Hand-rolled against the stdlib rather than a third- party JWKS library, deliberately - this project already has an established pattern for exactly this shape of problem (fetch a set of public signing keys from a discovery endpoint, cache by id, refresh on demand) hand-rolled against stdlib crypto in internal/capability/keyring.go for the biscuit signing key, and the actual parsing needed here (base64url-decode an RSA modulus/exponent into a crypto/rsa.PublicKey) is small enough that pulling in a dependency for it isn't worth it on a security-critical verification path. RSA only (RS256/384/512) for now - what every major enterprise IdP (Okta, Auth0, Azure AD, Google) issues by default; an EC key in the document is skipped, not silently mistaken for something it isn't.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Mint

func Mint(secret, user, role string, ttl time.Duration) (string, error)

Mint is used by cmd/devtoken only - a real deployment never mints its own identity tokens, an IdP does. Exported so the dev CLI can call it without duplicating the claims shape.

Types

type Claims

type Claims struct {
	DelegatedUser string `json:"delegated_user"`
	DelegatedRole string `json:"delegated_role"`
	jwt.RegisteredClaims
}

type Verifier

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

func NewJWKSVerifier

func NewJWKSVerifier(jwksURL, issuer, audience, userClaim, roleClaim string) (*Verifier, error)

NewJWKSVerifier is real-IdP mode: tokens are verified against RS256 keys fetched from jwksURL (RFC 7517), not a shared secret. issuer is mandatory, not optional - without issuer (and, when set, audience) validation, a token legitimately issued by the same IdP for a *different* application could be replayed against this proxy, a real cross-app token confusion risk, not a theoretical one. userClaim/ roleClaim let a real IdP's own claim names (Auth0's namespaced custom claims, Okta's custom authorization-server claims, an Azure AD app's optional claims - very unlikely to be named exactly "delegated_user"/ "delegated_role" out of the box) map onto this proxy's two required fields via config rather than code; empty defaults to those same two names, so an operator only sets these if their IdP actually differs.

Fetches the JWKS once, synchronously, before returning - an operator misconfiguring the URL should find out at startup, not on the first real login - then refreshes it in the background every 10 minutes, and additionally on-demand whenever a token names a "kid" not in the current cache (the same reasoning GET /biscuit-keys's own discovery endpoint uses: the IdP rotating its signing key should never require a synchronized manual update on this side).

func NewOpenVerifier

func NewOpenVerifier(secret string, fallback Claims) *Verifier

NewOpenVerifier is zero-config mode's verifier (cmd/rinwall's --mcp flag): a request arriving with NO Authorization header is treated as the given local identity instead of being rejected, so getting started doesn't require minting a token first.

The narrowness matters. This makes a MISSING credential succeed; it does not make a BAD one succeed. A request that does present a token is verified exactly as strictly as ever, and a forged or expired one is still rejected rather than quietly falling back to the local identity - which would turn this convenience into a trivial authentication bypass. Two useful consequences: an operator can hand out real tokens to exercise role policy without restarting out of this mode, and approvals still require a genuine, distinct identity, so the self-approval check stays real.

cmd/rinwall only ever pairs this with a loopback-only listener - see its refusal to bind a non-local address in zero-config mode.

func NewVerifier

func NewVerifier(secret string) *Verifier

func (*Verifier) FromRequest

func (v *Verifier) FromRequest(r *http.Request) (Claims, error)

FromRequest extracts and verifies the bearer token from the standard Authorization header - the one place both adapters pull delegated identity from.

func (*Verifier) IsOpen

func (v *Verifier) IsOpen() bool

IsOpen reports whether a missing credential is accepted, so callers can say so in a startup banner rather than leaving it implicit.

func (*Verifier) Verify

func (v *Verifier) Verify(tokenString string) (Claims, error)

Verify checks signature, expiry, and that the required delegated-identity claims are present. In ordinary (HS256) mode it deliberately does not accept "none" or any asymmetric algorithm switch - only the configured HMAC secret. In JWKS mode (see NewJWKSVerifier) it verifies against the discovered RS256 keys instead, and additionally enforces issuer (and audience, if configured).

Jump to

Keyboard shortcuts

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