Documentation
¶
Overview ¶
Package auth provides authentication utilities for the Nucleus framework, including password hashing, JWT management, and session handling.
Index ¶
- Constants
- Variables
- func CheckPassword(password, hash string) bool
- func ClientIPFromRequest(r *http.Request) string
- func ContextWithClaims(ctx context.Context, claims *Claims) context.Context
- func FederatedCallbackPath(instance string) string
- func FederatedStartPath(instance string) string
- func HashPassword(password string) (string, error)
- func RegisterBackend(name string, factory BackendFactory) error
- func RegisterSessionStore(name string, factory SessionStoreFactory) error
- func RegisteredBackends() []string
- func RegisteredSessionStores() []string
- func RuntimeMetadataMiddleware(sm *SessionManager, identity SessionRuntimeIdentity, minInterval time.Duration) func(http.Handler) http.Handler
- type Backend
- type BackendConfig
- type BackendFactory
- type Chain
- type ChainConfig
- type Claims
- type FederatedConfig
- type FederatedInstance
- type FederatedSet
- func (s *FederatedSet) Begin(ctx context.Context, instance string) (redirectURL, stateToken string, err error)
- func (s *FederatedSet) CallbackURL(instance string) string
- func (s *FederatedSet) Complete(ctx context.Context, instance, stateToken string, r *http.Request) (*federated.User, error)
- func (s *FederatedSet) Instances() []FederatedInstance
- func (s *FederatedSet) Names() []string
- func (s *FederatedSet) PendingCount() int
- type JWK
- type JWKSet
- type JWTManager
- func (m *JWTManager) Audience() string
- func (m *JWTManager) CurrentKID() string
- func (m *JWTManager) Generate(userID, username, role string) (string, error)
- func (m *JWTManager) JWKS() JWKSet
- func (m *JWTManager) JWKSHandler() http.HandlerFunc
- func (m *JWTManager) Middleware() func(http.Handler) http.Handler
- func (m *JWTManager) OptionalJWTMiddleware() func(http.Handler) http.Handler
- func (m *JWTManager) RemoveKey(kid string) error
- func (m *JWTManager) RotateKey(key SigningKey, makeCurrent bool) error
- func (m *JWTManager) SetAudience(aud string)
- func (m *JWTManager) Validate(tokenString string) (*Claims, error)
- type MemcachedSessionStore
- func (s *MemcachedSessionStore) All() (map[string][]byte, error)
- func (s *MemcachedSessionStore) AllCtx(ctx context.Context) (map[string][]byte, error)
- func (s *MemcachedSessionStore) Commit(token string, b []byte, expiry time.Time) error
- func (s *MemcachedSessionStore) CommitCtx(ctx context.Context, token string, b []byte, expiry time.Time) error
- func (s *MemcachedSessionStore) Delete(token string) error
- func (s *MemcachedSessionStore) DeleteCtx(ctx context.Context, token string) error
- func (s *MemcachedSessionStore) Find(token string) ([]byte, bool, error)
- func (s *MemcachedSessionStore) FindCtx(ctx context.Context, token string) ([]byte, bool, error)
- type RedisSessionStore
- func (s *RedisSessionStore) All() (map[string][]byte, error)
- func (s *RedisSessionStore) AllCtx(ctx context.Context) (map[string][]byte, error)
- func (s *RedisSessionStore) Commit(token string, b []byte, expiry time.Time) error
- func (s *RedisSessionStore) CommitCtx(ctx context.Context, token string, b []byte, expiry time.Time) error
- func (s *RedisSessionStore) Delete(token string) error
- func (s *RedisSessionStore) DeleteCtx(ctx context.Context, token string) error
- func (s *RedisSessionStore) Find(token string) ([]byte, bool, error)
- func (s *RedisSessionStore) FindCtx(ctx context.Context, token string) ([]byte, bool, error)
- type SQLSessionStore
- func (s *SQLSessionStore) All() (map[string][]byte, error)
- func (s *SQLSessionStore) AllCtx(ctx context.Context) (map[string][]byte, error)
- func (s *SQLSessionStore) Commit(token string, b []byte, expiry time.Time) error
- func (s *SQLSessionStore) CommitCtx(ctx context.Context, token string, b []byte, expiry time.Time) error
- func (s *SQLSessionStore) Delete(token string) error
- func (s *SQLSessionStore) DeleteCtx(ctx context.Context, token string) error
- func (s *SQLSessionStore) Find(token string) ([]byte, bool, error)
- func (s *SQLSessionStore) FindCtx(ctx context.Context, token string) ([]byte, bool, error)
- type SQLSessionStoreConfig
- type SessionCache
- func (c *SessionCache) Flush(ctx context.Context)
- func (c *SessionCache) Forget(ctx context.Context, key string)
- func (c *SessionCache) Get(ctx context.Context, key string) (string, bool)
- func (c *SessionCache) GetBool(ctx context.Context, key string) (bool, bool)
- func (c *SessionCache) GetInt(ctx context.Context, key string) (int, bool)
- func (c *SessionCache) Has(ctx context.Context, key string) bool
- func (c *SessionCache) Put(ctx context.Context, key string, value string, ttl time.Duration)
- func (c *SessionCache) PutBool(ctx context.Context, key string, value bool, ttl time.Duration)
- func (c *SessionCache) PutInt(ctx context.Context, key string, value int, ttl time.Duration)
- func (c *SessionCache) Remember(ctx context.Context, key string, ttl time.Duration, fn func() (string, error)) (string, error)
- func (c *SessionCache) RememberBool(ctx context.Context, key string, ttl time.Duration, fn func() (bool, error)) (bool, error)
- func (c *SessionCache) RememberInt(ctx context.Context, key string, ttl time.Duration, fn func() (int, error)) (int, error)
- type SessionConfig
- type SessionInfo
- type SessionManager
- func (s *SessionManager) ActiveSessions(ctx context.Context) ([]SessionInfo, error)
- func (s *SessionManager) Destroy(ctx context.Context) error
- func (s *SessionManager) Exists(ctx context.Context, key string) bool
- func (s *SessionManager) Flash(ctx context.Context, key, value string)
- func (s *SessionManager) FlashBool(ctx context.Context, key string, value bool)
- func (s *SessionManager) FlashInt(ctx context.Context, key string, value int)
- func (s *SessionManager) Forget(ctx context.Context, keys []string)
- func (s *SessionManager) GetBool(ctx context.Context, key string) bool
- func (s *SessionManager) GetFlash(ctx context.Context, key string) string
- func (s *SessionManager) GetFlashBool(ctx context.Context, key string) bool
- func (s *SessionManager) GetFlashInt(ctx context.Context, key string) int
- func (s *SessionManager) GetInt(ctx context.Context, key string) int
- func (s *SessionManager) GetString(ctx context.Context, key string) string
- func (s *SessionManager) Invalidate(ctx context.Context) error
- func (s *SessionManager) Keep(ctx context.Context, keys []string)
- func (s *SessionManager) Middleware() func(http.Handler) http.Handler
- func (s *SessionManager) Now(ctx context.Context, key, value string)
- func (s *SessionManager) Pull(ctx context.Context, key string) string
- func (s *SessionManager) PullBool(ctx context.Context, key string) bool
- func (s *SessionManager) PullInt(ctx context.Context, key string) int
- func (s *SessionManager) Put(ctx context.Context, key, value string)
- func (s *SessionManager) PutBool(ctx context.Context, key string, value bool)
- func (s *SessionManager) PutInt(ctx context.Context, key string, value int)
- func (s *SessionManager) Reflash(ctx context.Context)
- func (s *SessionManager) Remove(ctx context.Context, key string)
- func (s *SessionManager) RenewToken(ctx context.Context) error
- func (s *SessionManager) SCS() *scs.SessionManager
- func (s *SessionManager) SetSessionStore(store SessionStore)
- func (s *SessionManager) SetStore(store scs.Store)
- type SessionRuntimeIdentity
- type SessionStore
- type SessionStoreFactory
- type SessionStoreParams
- type SigningAlgorithm
- type SigningKey
- type User
- type UserProvider
Constants ¶
const ( // SessionMetaFirstSeenAtKey stores the first server-side observation timestamp (RFC3339). SessionMetaFirstSeenAtKey = "__nucleus_first_seen_at" // SessionMetaLastSeenAtKey stores the latest server-side observation timestamp (RFC3339). SessionMetaLastSeenAtKey = "__nucleus_last_seen_at" // SessionMetaPodKey stores the pod identifier handling the session request. SessionMetaPodKey = "__nucleus_runtime_pod" // SessionMetaHostKey stores the host/node identifier handling the session request. SessionMetaHostKey = "__nucleus_runtime_host" // SessionMetaInstanceKey stores a composed runtime instance identifier. SessionMetaInstanceKey = "__nucleus_runtime_instance" // SessionMetaRemoteIPKey stores the latest observed client IP for the session. SessionMetaRemoteIPKey = "__nucleus_remote_ip" )
const DefaultFederatedPendingTTL = 15 * time.Minute
DefaultFederatedPendingTTL is how long a started sign-in stays valid. Long enough for somebody to type a password and answer a second factor, short enough that an abandoned flow is not a lasting foothold.
Variables ¶
var ( // ErrInvalidCredentials reports a certain rejection. See // backend.ErrInvalidCredentials. ErrInvalidCredentials = backend.ErrInvalidCredentials // source. See backend.ErrBackendUnavailable. ErrBackendUnavailable = backend.ErrBackendUnavailable // ErrUserNotFound is what a UserProvider returns when the username // does not exist. A provider may return it, or any other error; the // adapter in user_provider_backend.go maps every lookup failure to the // same outcome on purpose. // // It belongs in this block, with the other two: it used to be declared // separately with its own errors.New and the SAME message text, so a // leaf backend returning backend.ErrUserNotFound was unrecognisable to // code comparing against this one — and identical messages left nothing // to see in a log. ErrUserNotFound = backend.ErrUserNotFound )
var ErrNilSessionManager = errors.New("auth: nil session manager")
ErrNilSessionManager is returned by ActiveSessions when called on a nil or zero-initialised SessionManager (one not created via NewSessionManager).
var ErrSessionStoreNotIterable = errors.New("auth: session store does not support enumeration")
ErrSessionStoreNotIterable is returned by ActiveSessions when the configured session store does not support enumeration (a custom store implementing neither All nor AllCtx; the built-in stores all support it since the non-functional cookie store was removed in v0.12.0, DEP-2026-006).
Functions ¶
func CheckPassword ¶
CheckPassword compares a plaintext password against a bcrypt hash. Returns true if they match, false otherwise (including on malformed hashes).
func ClientIPFromRequest ¶
ClientIPFromRequest returns the client IP of a request: the host part of r.RemoteAddr.
It reads no forwarding header on purpose. X-Forwarded-For and X-Real-IP are set by whoever sent the request unless a proxy the deployment trusts overwrote them, and the router's RealIP middleware already rewrites RemoteAddr from those headers — for the proxies listed in trusted_proxies, and for nobody else. Reading the headers again here would let any client choose the IP that lands in its session metadata and in the audit trail, which is exactly what trusted_proxies exists to prevent.
func ContextWithClaims ¶
ContextWithClaims returns a copy of ctx carrying the given claims — exactly what JWTManager.Middleware stores after validating a bearer token, including the observability user-id propagation for log attribution. It is the bridge for applications that authenticate by other means — typically a server-side session — and still need the authorization layer (Enforcer.Middleware, Enforcer.RequireRole) to see the request's subject: load the session, build a *Claims carrying the subject and role, and wrap the request context in a middleware that runs before those checks. A nil claims returns ctx unchanged.
Two caveats. Claims values are trusted as-is — build them only from server-side state (a session), never from request-supplied input. And an empty claims.UserID still overwrites the observability user-id slot with ""; pass a non-empty UserID when injecting a real subject.
func FederatedCallbackPath ¶
func FederatedStartPath ¶
FederatedStartPath and FederatedCallbackPath are the routes an application MOUNTS for an instance. The framework does not register them: it owns the flow (Begin issues the anti-forgery state and holds the pending sign-in, Complete verifies it before the provider is consulted), and the application owns the two handlers, because what happens after a successful callback — which session manager, which landing page, which account gets linked — is the application's decision and not the framework's.
Use these helpers rather than writing the paths by hand: CallbackURL is derived from the same functions, so the URL logged at startup and registered with the identity provider is the one your route actually serves. Hand-written paths drift, and a callback that does not match is a sign-in that fails only in production.
mux.Handle(auth.FederatedStartPath("corp"), startHandler(set, "corp"))
mux.Handle(auth.FederatedCallbackPath("corp"), callbackHandler(set, "corp"))
They are functions rather than constants because the instance name is in them.
func HashPassword ¶
HashPassword generates a bcrypt hash of the given password with cost 12.
func RegisterBackend ¶
func RegisterBackend(name string, factory BackendFactory) error
RegisterBackend makes an authentication backend selectable by name from configuration. It delegates to backend.Register; a new backend should call that one and avoid importing this package at all.
func RegisterSessionStore ¶
func RegisterSessionStore(name string, factory SessionStoreFactory) error
RegisterSessionStore makes a session backend selectable by name from configuration (`session_store`). It delegates to sessionstore.Register.
func RegisteredBackends ¶
func RegisteredBackends() []string
RegisteredBackends returns every selectable backend name, sorted.
func RegisteredSessionStores ¶
func RegisteredSessionStores() []string
RegisteredSessionStores returns every selectable store name, sorted.
func RuntimeMetadataMiddleware ¶
func RuntimeMetadataMiddleware(sm *SessionManager, identity SessionRuntimeIdentity, minInterval time.Duration) func(http.Handler) http.Handler
RuntimeMetadataMiddleware updates runtime metadata fields in existing sessions so shared stores can expose where each session is actively served.
Types ¶
type Backend ¶
Backend authenticates a username and password against one identity source. See backend.Backend.
func NewUserProviderBackend ¶
func NewUserProviderBackend(name string, provider UserProvider) (Backend, error)
NewUserProviderBackend wraps a UserProvider as an authentication backend, so an application's own user table takes its place in the chain alongside a directory or any other source.
It is usually the LAST entry: `[ldap, local]` means the directory answers first and the local table is what still works the morning the directory does not.
TIMING. This adapter cannot make an implementation constant-time; it only refrains from making things worse. Every failure — user absent, password wrong, lookup error — leaves through one path with one error, so the adapter adds no distinguishable branch. Equalising the WORK is the provider's job: if ValidateCredentials returns early for an unknown user without hashing anything, it answers faster than for a real user with a wrong password, and that difference is a user enumerator no wrapper can hide. Hash against a dummy value before returning, the way the admin login in this suite already does.
type BackendConfig ¶
BackendConfig carries a backend's own `auth.<name>.*` subtree.
type BackendFactory ¶
BackendFactory builds a Backend from its configuration subtree.
type Chain ¶
type Chain struct {
// contains filtered or unexported fields
}
Chain authenticates against an ORDERED list of backends, stopping at the first one that accepts.
The order is the point, and it is why this is a list and not the map the backends themselves live in. The deployment everyone actually wants is "the directory first, a local account second": when the directory is unreachable, somebody still has to be able to get in and fix it. A map cannot express that, and a set of independent backends cannot either.
func NewChain ¶
NewChain builds a chain from registered backend names, in order.
It is the convenience form for a chain whose backends need no configuration of their own; NewChainFrom is the full form.
func NewChainFrom ¶
func NewChainFrom(cfg ChainConfig) (*Chain, error)
NewChainFrom builds a chain from registered backend names, in order, handing each backend the configuration subtree that belongs to it.
func (*Chain) Authenticate ¶
Authenticate walks the chain in order and returns the first acceptance.
A backend that REJECTS ends the attempt; one that is UNAVAILABLE is skipped. The two look alike — neither produced a user — but their first causes are opposite, and giving them the same effect is a fail-open: a rejection is the identity source's verdict on these credentials, while an unreachable backend proves nothing at all.
Concretely, that is what stops a stale local row from being a bypass. If an employee's directory account is revoked and her local account still carries the old password, the directory's rejection ends the attempt and the local backend never gets its turn. Only an unreachable directory falls through to it, which is the break-glass path the ordering exists for.
The consequence is worth stating plainly: a chain is a FALLBACK for unavailability, not a way to federate several user populations. Every account must be acceptable to the first backend that recognises the request, because anything behind a rejection is unreachable by design.
Rejection covers both "no such user" and "wrong password": pkg/auth/backend collapses them into ErrInvalidCredentials on purpose, since a backend that told them apart would publish a user enumerator — and, because the chain stops on rejection, would publish it for every backend behind it too.
The caller can still tell the two outcomes apart. If every backend rejected, the answer is ErrInvalidCredentials. If any backend was unavailable and none accepted, the error says so and names it, because "wrong password" and "the directory is down" send an operator hunting in different places.
type ChainConfig ¶
type ChainConfig struct {
// Backends is the ORDERED list of registered names to consult.
Backends []string
// ProviderConfig maps a backend name to its `auth.<name>.*` subtree.
// A name with no entry gets an empty BackendConfig, which is the
// normal case for a backend that needs no settings.
ProviderConfig map[string]map[string]any
}
ChainConfig declares the ordered chain and carries each backend's own configuration subtree.
type Claims ¶
type Claims struct {
UserID string `json:"uid"`
Username string `json:"username"`
Role string `json:"role"`
jwt.RegisteredClaims
}
Claims holds the JWT payload with user identity information.
type FederatedConfig ¶
type FederatedConfig struct {
// Instances are the declarations, in the order an operator wrote them
// — which is the order sign-in buttons should appear in.
Instances []FederatedInstance
// ProviderConfig maps an INSTANCE name to its `auth.<name>.*` subtree,
// the same channel a credential backend is configured through.
ProviderConfig map[string]map[string]any
// CallbackBase is the absolute base URL this application is reached
// at, without a trailing slash — "https://app.example.com". The
// callback URL a provider registers with its identity provider is
// built from it, so it has to be the address the BROWSER uses, not
// the one the process binds.
CallbackBase string
// PendingTTL bounds how long a started sign-in may sit unfinished.
// Zero means DefaultFederatedPendingTTL.
PendingTTL time.Duration
}
FederatedConfig builds the set of configured identity providers.
type FederatedInstance ¶
type FederatedInstance struct {
// Name identifies this instance: the `auth.<name>.*` subtree, the URL
// segment, the value a sign-in link asks for.
Name string `koanf:"name"`
// Provider is the registered type — "oidc", "saml".
Provider string `koanf:"provider"`
// DisplayName is what a sign-in button says. Empty falls back to Name,
// which is right for "corp" and wrong for nothing.
DisplayName string `koanf:"display_name"`
}
FederatedInstance is one identity provider an operator declared.
Name and Provider are different things on purpose. Name is the instance — what appears in the URL and what the operator writes their settings under — and Provider is the registered type that implements it. Two declarations with provider: oidc and different names are two identity providers, which is the ordinary case (a corporate tenant and a partner one) and the reason the registry is keyed by type.
type FederatedSet ¶
type FederatedSet struct {
// contains filtered or unexported fields
}
FederatedSet is the configured identity providers and the custody of the flows in progress.
The custody is the point. A provider never sees the anti-forgery state: this type issues it, holds it, and refuses a callback that does not carry it back — before the provider is asked anything. A redirect flow without that check works perfectly well until somebody attacks it, which is exactly the kind of omission that does not announce itself, so the framework does not offer the choice.
func NewFederatedSet ¶
func NewFederatedSet(cfg FederatedConfig) (*FederatedSet, error)
NewFederatedSet builds the configured providers, failing on the first declaration that cannot be honoured.
func (*FederatedSet) Begin ¶
func (s *FederatedSet) Begin(ctx context.Context, instance string) (redirectURL, stateToken string, err error)
Begin starts a sign-in and returns where to send the browser, plus the opaque state token the caller must give back on the callback.
The token is what a caller stores in a short-lived cookie. It is not the provider's state and the provider never sees it.
func (*FederatedSet) CallbackURL ¶
func (s *FederatedSet) CallbackURL(instance string) string
CallbackURL is the absolute URL an identity provider must send the browser back to for one instance. An operator registers this value with their identity provider, and `nucleus doctor` prints it, so it is derived in exactly one place.
func (*FederatedSet) Complete ¶
func (s *FederatedSet) Complete(ctx context.Context, instance, stateToken string, r *http.Request) (*federated.User, error)
Complete verifies the callback and returns the authenticated identity.
stateToken is what Begin returned. A callback that arrives without it, with one this set did not issue, or with one already spent, is refused HERE — the provider is not called, so a provider cannot forget to check.
func (*FederatedSet) Instances ¶
func (s *FederatedSet) Instances() []FederatedInstance
Instances returns the declarations, in order, for a sign-in page that needs to render a button per identity provider.
func (*FederatedSet) Names ¶
func (s *FederatedSet) Names() []string
Names returns the configured instance names, in declaration order.
func (*FederatedSet) PendingCount ¶
func (s *FederatedSet) PendingCount() int
PendingCount reports how many sign-ins are in flight. It exists for tests and for a metric; it is not part of the flow.
type JWK ¶
type JWK struct {
Kid string `json:"kid"`
Kty string `json:"kty"`
Alg string `json:"alg"`
Use string `json:"use"`
N string `json:"n,omitempty"`
E string `json:"e,omitempty"`
Crv string `json:"crv,omitempty"`
X string `json:"x,omitempty"`
Y string `json:"y,omitempty"`
}
JWK is the wire shape of an RFC 7517 JSON Web Key. The Use field is always "sig" for keys produced by this package; HMAC keys are not emitted, so the kty field is "RSA" (RS256) or "EC" (ES256).
RSA keys populate N and E; EC keys populate Crv, X and Y. The omitempty tags keep each emitted key minimal — an EC key carries no n/e and an RSA key carries no crv/x/y.
type JWKSet ¶
type JWKSet struct {
Keys []JWK `json:"keys"`
}
JWKSet is the wire shape of an RFC 7517 JSON Web Key Set.
type JWTManager ¶
type JWTManager struct {
// contains filtered or unexported fields
}
JWTManager handles JWT token generation and validation. It supports two modes that coexist:
- Legacy single-secret HS256: built via NewJWTManager. Tokens have no "kid" header; Validate falls back to the single secret when the incoming token lacks a kid.
- Multi-key with rotation: built via NewJWTManagerFromKeys or by calling RotateKey on an existing manager. Tokens carry a "kid" header; Validate looks up the key by kid. Adding a new key keeps existing tokens valid until they expire — the operator path for zero-downtime rotation.
JWKSHandler exposes the asymmetric portion of the keyset over an RFC 7517 / RFC 7518 JSON shape; HMAC keys are intentionally omitted so the published endpoint cannot leak shared secrets.
func NewJWTManager ¶
func NewJWTManager(secret string, expiry time.Duration, issuer ...string) *JWTManager
NewJWTManager creates a single-secret HS256 manager. Backwards compatible: tokens carry no "kid" header and Validate uses the single secret. Use NewJWTManagerFromKeys (or RotateKey on the returned manager) to opt into rotation.
NewJWTManager panics when secret is shorter than 32 bytes (256 bits, the HMAC-SHA256 output width): a short secret yields forgeable tokens, and a weak-key deployment must crash at startup rather than serve — the same regexp.MustCompile-style posture as CSRFMiddleware (ADR-006). The signature cannot grow an error return (frozen public surface); callers who want an error path instead of a panic should configure the manager through pkg/app (`jwt_secret` / `jwt_keys`), which validates with a returned error before ever reaching this constructor.
func NewJWTManagerFromKeys ¶
func NewJWTManagerFromKeys(keys []SigningKey, currentKID string, expiry time.Duration, issuer ...string) (*JWTManager, error)
NewJWTManagerFromKeys creates a manager that signs with the key identified by currentKID and accepts tokens signed by any key in keys. At least one key is required; currentKID must match one of them.
func (*JWTManager) Audience ¶
func (m *JWTManager) Audience() string
Audience returns the audience Generate stamps and Validate requires, or "" when none is configured.
func (*JWTManager) CurrentKID ¶
func (m *JWTManager) CurrentKID() string
CurrentKID returns the kid that Generate will stamp into new tokens, or empty when the manager is in legacy single-secret mode.
func (*JWTManager) Generate ¶
func (m *JWTManager) Generate(userID, username, role string) (string, error)
Generate creates a signed JWT token for the given user claims. In multi-key mode the current key's algorithm is used and its kid is stamped into the token header. In legacy mode HS256 is used and the header carries no kid.
func (*JWTManager) JWKS ¶
func (m *JWTManager) JWKS() JWKSet
JWKS returns the public key set in the canonical JWK Set shape. Use JWKSHandler for a ready-to-mount HTTP handler; JWKS itself is exposed for callers that need the raw structure (e.g. embedding in OIDC discovery responses).
func (*JWTManager) JWKSHandler ¶
func (m *JWTManager) JWKSHandler() http.HandlerFunc
JWKSHandler returns an HTTP handler that serves the manager's public key set in JWK Set format (RFC 7517 / RFC 7518). Only asymmetric keys are published — HMAC keys are intentionally excluded so the endpoint cannot leak shared secrets.
Mount the handler at the canonical path (`/.well-known/jwks.json`) or wherever your deployment expects relying parties to discover it.
func (*JWTManager) Middleware ¶
func (m *JWTManager) Middleware() func(http.Handler) http.Handler
Middleware returns an HTTP middleware that extracts and validates the JWT token from the Authorization header (Bearer scheme). On success, the claims are stored in the request context and can be retrieved via ClaimsFromContext.
func (*JWTManager) OptionalJWTMiddleware ¶
func (m *JWTManager) OptionalJWTMiddleware() func(http.Handler) http.Handler
OptionalJWTMiddleware is like Middleware but does not reject requests without a token. If a valid token is present, claims are added to the context. If no token or an invalid token is present, the request proceeds without claims.
func (*JWTManager) RemoveKey ¶
func (m *JWTManager) RemoveKey(kid string) error
RemoveKey drops a key from the verification set. Tokens signed with that kid will be rejected on the next Validate call. The current signing key cannot be removed; promote a different key with RotateKey first.
func (*JWTManager) RotateKey ¶
func (m *JWTManager) RotateKey(key SigningKey, makeCurrent bool) error
RotateKey adds a key to the verification set. When makeCurrent is true, future Generate calls use this key as the signing key. Existing tokens (signed with the previous current key) remain valid as long as that key stays in the set. Operators are responsible for removing keys with RemoveKey after the access-token lifetime expires.
func (*JWTManager) SetAudience ¶
func (m *JWTManager) SetAudience(aud string)
SetAudience makes Generate stamp aud into new tokens and Validate reject any token that does not carry it. Empty (the default) leaves aud unchecked. pkg/app sets it from `jwt_audience`.
func (*JWTManager) Validate ¶
func (m *JWTManager) Validate(tokenString string) (*Claims, error)
Validate parses and validates a JWT token string. Returns the claims if valid.
Resolution order:
- If the token header carries a "kid" matching a key in the verification set, that key is used.
- Otherwise, if the manager has a legacy single secret, it is used (HS256 only).
- Otherwise, the token is rejected — a multi-key manager will not accept a token without a kid.
type MemcachedSessionStore ¶
type MemcachedSessionStore struct {
// contains filtered or unexported fields
}
MemcachedSessionStore persists sessions in Memcached with key TTL.
func NewMemcachedSessionStore ¶
func NewMemcachedSessionStore(client *memcache.Client, keyPrefix string) (*MemcachedSessionStore, error)
NewMemcachedSessionStore creates a Memcached-backed session store from an existing client.
func NewMemcachedSessionStoreFromServers ¶
func NewMemcachedSessionStoreFromServers(servers []string, keyPrefix string) (*MemcachedSessionStore, *memcache.Client, error)
NewMemcachedSessionStoreFromServers creates a memcache.Client and a Memcached session store.
func (*MemcachedSessionStore) All ¶
func (s *MemcachedSessionStore) All() (map[string][]byte, error)
All returns all active sessions visible from the configured key prefix. Note: Memcached doesn't support key listing, so this returns an empty map. Use Redis or SQL if you need to list all sessions.
func (*MemcachedSessionStore) AllCtx ¶
AllCtx returns all active sessions. Note: Memcached doesn't support key listing, so this returns an empty map. Use Redis or SQL if you need to list all sessions.
func (*MemcachedSessionStore) Commit ¶
Commit stores the session payload for token with absolute expiry.
func (*MemcachedSessionStore) CommitCtx ¶
func (s *MemcachedSessionStore) CommitCtx(ctx context.Context, token string, b []byte, expiry time.Time) error
CommitCtx stores the session payload in Memcached with a TTL derived from expiry.
func (*MemcachedSessionStore) Delete ¶
func (s *MemcachedSessionStore) Delete(token string) error
Delete removes the session token from the store.
func (*MemcachedSessionStore) DeleteCtx ¶
func (s *MemcachedSessionStore) DeleteCtx(ctx context.Context, token string) error
DeleteCtx removes the session token from Memcached.
type RedisSessionStore ¶
type RedisSessionStore struct {
// contains filtered or unexported fields
}
RedisSessionStore persists sessions in Redis with key TTL.
func NewRedisSessionStore ¶
func NewRedisSessionStore(client redis.UniversalClient, keyPrefix string) (*RedisSessionStore, error)
NewRedisSessionStore creates a Redis-backed session store from an existing client.
func NewRedisSessionStoreFromURL ¶
func NewRedisSessionStoreFromURL(rawURL, keyPrefix string) (*RedisSessionStore, *redis.Client, error)
NewRedisSessionStoreFromURL creates a redis.Client and a Redis session store.
func (*RedisSessionStore) All ¶
func (s *RedisSessionStore) All() (map[string][]byte, error)
All returns all active sessions visible from the configured key prefix.
func (*RedisSessionStore) AllCtx ¶
AllCtx returns all active sessions visible from the configured key prefix.
func (*RedisSessionStore) Commit ¶
Commit stores the session payload for token with absolute expiry.
func (*RedisSessionStore) CommitCtx ¶
func (s *RedisSessionStore) CommitCtx(ctx context.Context, token string, b []byte, expiry time.Time) error
CommitCtx stores the session payload in Redis with a TTL derived from expiry.
func (*RedisSessionStore) Delete ¶
func (s *RedisSessionStore) Delete(token string) error
Delete removes the session token from the store.
func (*RedisSessionStore) DeleteCtx ¶
func (s *RedisSessionStore) DeleteCtx(ctx context.Context, token string) error
DeleteCtx removes the session token from Redis.
type SQLSessionStore ¶
type SQLSessionStore struct {
// contains filtered or unexported fields
}
SQLSessionStore persists sessions in a SQL table.
func NewSQLSessionStore ¶
func NewSQLSessionStore(db *sql.DB, cfg SQLSessionStoreConfig) (*SQLSessionStore, error)
NewSQLSessionStore builds a SQL-backed session store and ensures table schema.
func (*SQLSessionStore) All ¶
func (s *SQLSessionStore) All() (map[string][]byte, error)
All returns all non-expired sessions.
func (*SQLSessionStore) CommitCtx ¶
func (s *SQLSessionStore) CommitCtx(ctx context.Context, token string, b []byte, expiry time.Time) error
CommitCtx stores the session payload for token with absolute expiry.
func (*SQLSessionStore) Delete ¶
func (s *SQLSessionStore) Delete(token string) error
Delete removes the session token from the store.
func (*SQLSessionStore) DeleteCtx ¶
func (s *SQLSessionStore) DeleteCtx(ctx context.Context, token string) error
DeleteCtx removes the session token from the store.
type SQLSessionStoreConfig ¶
SQLSessionStoreConfig configures a SQL-backed SCS store.
type SessionCache ¶
type SessionCache struct {
// contains filtered or unexported fields
}
SessionCache provides a cache mechanism scoped to an individual user session. Unlike the global application cache, session cache data is automatically isolated per session and cleaned up when the session expires or is destroyed. This is perfect for storing temporary, user-specific data like form data, temporary calculations, API responses, or any other ephemeral data.
func NewSessionCache ¶
func NewSessionCache(sm *SessionManager) *SessionCache
NewSessionCache creates a new session cache instance.
func (*SessionCache) Flush ¶
func (c *SessionCache) Flush(ctx context.Context)
Flush removes all values from the session cache. Only cache-prefixed entries are touched; every other session key is left alone.
func (*SessionCache) Forget ¶
func (c *SessionCache) Forget(ctx context.Context, key string)
Forget removes a value from the session cache.
func (*SessionCache) Get ¶
Get retrieves a value from the session cache. Returns the value and a boolean indicating if the key exists and is not expired.
func (*SessionCache) Has ¶
func (c *SessionCache) Has(ctx context.Context, key string) bool
Has checks if a key exists in the session cache and is not expired.
func (*SessionCache) Put ¶
Put stores a value in the session cache with an optional TTL. If ttl is 0, the value will persist until the session expires.
func (*SessionCache) PutBool ¶
PutBool stores a bool value in the session cache with an optional TTL.
func (*SessionCache) Remember ¶
func (c *SessionCache) Remember(ctx context.Context, key string, ttl time.Duration, fn func() (string, error)) (string, error)
Remember retrieves a value from the session cache. If the key does not exist, it executes the provided function and stores the result with the given TTL.
func (*SessionCache) RememberBool ¶
func (c *SessionCache) RememberBool(ctx context.Context, key string, ttl time.Duration, fn func() (bool, error)) (bool, error)
RememberBool retrieves a bool value from the session cache. If the key does not exist, it executes the provided function and stores the result with the given TTL.
func (*SessionCache) RememberInt ¶
func (c *SessionCache) RememberInt(ctx context.Context, key string, ttl time.Duration, fn func() (int, error)) (int, error)
RememberInt retrieves an int value from the session cache. If the key does not exist, it executes the provided function and stores the result with the given TTL.
type SessionConfig ¶
type SessionConfig struct {
Lifetime time.Duration // Session lifetime (default: 72h)
IdleTimeout time.Duration // Optional inactivity timeout (default: disabled)
Secure bool // Cookie Secure flag (set true in production)
Path string // Cookie path (default: "/")
Domain string // Cookie domain (default: host-only)
CookieName string // Cookie name (default: "session")
SameSite string // Cookie SameSite: lax|strict|none (default: lax)
}
SessionConfig configures the session manager.
type SessionInfo ¶
type SessionInfo struct {
// Token is the raw session token (the store key) — a bearer credential.
Token string
// Deadline is the absolute expiry the codec recorded for the session.
Deadline time.Time
// Values are the decoded session key/value pairs (may be sensitive).
Values map[string]any
}
SessionInfo is a decoded snapshot of one stored session, returned by SessionManager.ActiveSessions. It is distinct from the lifecycle-event payload observability/hooks.SessionInfo — this is an enumeration/admin snapshot.
SECURITY: SessionInfo carries live secrets. Token is the raw session token, a bearer credential — anyone who obtains it can impersonate the session until it expires. Values is the full decoded session map and MAY hold user identifiers, CSRF tokens, and flash data. ActiveSessions is intended ONLY for a trusted, in-process operator/admin surface (orbit). NEVER serialize Token or Values to an untrusted response, an access log, or any sink that leaves the process without deliberate redaction. The raw Token is retained (not hashed) on purpose: an operator tool acts on a session — e.g. revokes it through the store — which needs the exact token.
type SessionManager ¶
type SessionManager struct {
// contains filtered or unexported fields
}
SessionManager wraps alexedwards/scs for server-side session management. Sessions can be backed by in-memory storage (default), SQL, or Redis.
func NewSessionManager ¶
func NewSessionManager(cfg SessionConfig) *SessionManager
NewSessionManager creates a session manager with the given configuration. By default it uses in-memory storage. Call SetStore to use Redis, SQL, or another backend.
func (*SessionManager) ActiveSessions ¶
func (s *SessionManager) ActiveSessions(ctx context.Context) ([]SessionInfo, error)
ActiveSessions returns a decoded snapshot of every session currently held by the store, for a session-management/observability surface (e.g. an admin "active sessions" view). It requires a store that supports enumeration — the memory, SQL, Redis and Memcached stores all do; a custom store implementing neither All nor AllCtx yields ErrSessionStoreNotIterable.
A payload that fails to decode is skipped rather than failing the whole call, so one corrupt entry cannot blind the operator to every other session. The returned slice is a point-in-time snapshot, unordered; callers sort/filter as needed.
Cost: O(N) in the number of stored sessions — it loads and decodes the whole store into memory in one pass (scs stores expose no server-side pagination), so cap what you render at the call site for a very large store.
This packages a capability already reachable through the SCS() escape hatch (Store + Codec) into one typed call; it exposes no scs type on the API. The returned Values/Token are sensitive — see SessionInfo (SECURITY).
func (*SessionManager) Destroy ¶
func (s *SessionManager) Destroy(ctx context.Context) error
Destroy deletes the entire session.
func (*SessionManager) Exists ¶
func (s *SessionManager) Exists(ctx context.Context, key string) bool
Exists checks if a key exists in the session.
func (*SessionManager) Flash ¶
func (s *SessionManager) Flash(ctx context.Context, key, value string)
Flash stores a key-value pair in the session that will be available in the next request only. After the next request, the data is automatically deleted. This is useful for status messages (e.g., "Task completed successfully").
func (*SessionManager) FlashBool ¶
func (s *SessionManager) FlashBool(ctx context.Context, key string, value bool)
FlashBool stores a bool value in flash data.
func (*SessionManager) FlashInt ¶
func (s *SessionManager) FlashInt(ctx context.Context, key string, value int)
FlashInt stores an int value in flash data.
func (*SessionManager) Forget ¶
func (s *SessionManager) Forget(ctx context.Context, keys []string)
Forget removes multiple keys from the session in one operation.
func (*SessionManager) GetBool ¶
func (s *SessionManager) GetBool(ctx context.Context, key string) bool
GetBool retrieves a bool value from the session.
func (*SessionManager) GetFlash ¶
func (s *SessionManager) GetFlash(ctx context.Context, key string) string
GetFlash retrieves a flash value for the current request.
func (*SessionManager) GetFlashBool ¶
func (s *SessionManager) GetFlashBool(ctx context.Context, key string) bool
GetFlashBool retrieves a flash bool value for the current request.
func (*SessionManager) GetFlashInt ¶
func (s *SessionManager) GetFlashInt(ctx context.Context, key string) int
GetFlashInt retrieves a flash int value for the current request.
func (*SessionManager) GetInt ¶
func (s *SessionManager) GetInt(ctx context.Context, key string) int
GetInt retrieves an int value from the session.
func (*SessionManager) GetString ¶
func (s *SessionManager) GetString(ctx context.Context, key string) string
GetString retrieves a string value from the session.
func (*SessionManager) Invalidate ¶
func (s *SessionManager) Invalidate(ctx context.Context) error
Invalidate regenerates the session ID and removes all data from the session. This is useful for logout or when you want to completely reset the session.
func (*SessionManager) Keep ¶
func (s *SessionManager) Keep(ctx context.Context, keys []string)
Keep keeps specific flash data keys for one more request.
func (*SessionManager) Middleware ¶
func (s *SessionManager) Middleware() func(http.Handler) http.Handler
Middleware returns the session middleware that must be applied to the router for session handling to work.
func (*SessionManager) Now ¶
func (s *SessionManager) Now(ctx context.Context, key, value string)
Now stores a key-value pair that is only available in the current request: the middleware sweeps it when the request ends.
func (*SessionManager) Pull ¶
func (s *SessionManager) Pull(ctx context.Context, key string) string
Pull retrieves a value from the session and deletes it in one operation.
func (*SessionManager) PullBool ¶
func (s *SessionManager) PullBool(ctx context.Context, key string) bool
PullBool retrieves a bool value from the session and deletes it in one operation.
func (*SessionManager) PullInt ¶
func (s *SessionManager) PullInt(ctx context.Context, key string) int
PullInt retrieves an int value from the session and deletes it in one operation.
func (*SessionManager) Put ¶
func (s *SessionManager) Put(ctx context.Context, key, value string)
Put stores a string value in the session.
func (*SessionManager) PutBool ¶
func (s *SessionManager) PutBool(ctx context.Context, key string, value bool)
PutBool stores a bool value in the session.
func (*SessionManager) PutInt ¶
func (s *SessionManager) PutInt(ctx context.Context, key string, value int)
PutInt stores an int value in the session.
func (*SessionManager) Reflash ¶
func (s *SessionManager) Reflash(ctx context.Context)
Reflash keeps all flash data readable now for one more request.
func (*SessionManager) Remove ¶
func (s *SessionManager) Remove(ctx context.Context, key string)
Remove deletes a key from the session.
func (*SessionManager) RenewToken ¶
func (s *SessionManager) RenewToken(ctx context.Context) error
RenewToken generates a new session ID while preserving data. Should be called after login to prevent session fixation.
func (*SessionManager) SCS ¶
func (s *SessionManager) SCS() *scs.SessionManager
SCS returns the underlying scs.SessionManager for advanced configuration (e.g. setting a custom store for Redis).
func (*SessionManager) SetSessionStore ¶
func (s *SessionManager) SetSessionStore(store SessionStore)
SetSessionStore installs a framework SessionStore on the manager, adapting it to the underlying library.
func (*SessionManager) SetStore ¶
func (s *SessionManager) SetStore(store scs.Store)
SetStore sets a custom SCS store implementation (Redis, SQL, etc).
type SessionRuntimeIdentity ¶
SessionRuntimeIdentity describes the runtime node that handled a session request.
func DetectSessionRuntimeIdentity ¶
func DetectSessionRuntimeIdentity() SessionRuntimeIdentity
DetectSessionRuntimeIdentity infers pod and host identifiers from common Kubernetes/runtime environment variables.
type SessionStore ¶
type SessionStore = sessionstore.Store
SessionStore is the interface a session backend implements. See sessionstore.Store.
func BuildSessionStore ¶
func BuildSessionStore(name string, params SessionStoreParams) (SessionStore, func(context.Context) error, error)
BuildSessionStore resolves a configured store name and builds it. It returns the store (nil means "keep the manager's in-memory default"), an optional shutdown hook, and an error naming the registered stores when the name is unknown.
type SessionStoreFactory ¶
type SessionStoreFactory = sessionstore.Factory
SessionStoreFactory builds a session store plus an optional shutdown hook.
type SessionStoreParams ¶
type SessionStoreParams = sessionstore.Params
SessionStoreParams carries what a store needs to build itself.
type SigningAlgorithm ¶
type SigningAlgorithm string
SigningAlgorithm enumerates the JWT signing algorithms this package supports. New algorithms (e.g. ES256) can be added without changing the public API — extend the switch in signingMethod / verifyMaterial.
const ( HS256 SigningAlgorithm = "HS256" RS256 SigningAlgorithm = "RS256" // ES256 is ECDSA with the NIST P-256 curve and SHA-256. Only P-256 // is supported — P-384 (ES384) and P-521 (ES512) are deliberately // out of scope until there is a concrete need; see ADR-005. ES256 SigningAlgorithm = "ES256" )
type SigningKey ¶
type SigningKey struct {
KID string
Algorithm SigningAlgorithm
HMACSecret []byte // HS256
RSAPrivate *rsa.PrivateKey // RS256
ECDSAPrivate *ecdsa.PrivateKey // ES256 (P-256 only)
}
SigningKey is one entry in a JWTManager's keyset. Exactly one of the material fields must be set, matching the Algorithm.
type UserProvider ¶
type UserProvider interface {
// FindByID retrieves a user by their unique identifier.
FindByID(ctx context.Context, id string) (*User, error)
// FindByUsername retrieves a user by username (used for login).
FindByUsername(ctx context.Context, username string) (*User, error)
// FindByEmail retrieves a user by email address.
FindByEmail(ctx context.Context, email string) (*User, error)
// ValidateCredentials checks if the username/password combination is valid.
// Returns the user if valid, an error otherwise.
ValidateCredentials(ctx context.Context, username, password string) (*User, error)
}
UserProvider is the interface that applications must implement to integrate their user model with Nucleus's authentication system.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package backend is the contract a third-party authentication backend implements — and nothing else.
|
Package backend is the contract a third-party authentication backend implements — and nothing else. |
|
backendtest
Package backendtest is a conformance suite for authentication backends.
|
Package backendtest is a conformance suite for authentication backends. |
|
Package federated is the contract a browser-redirect identity provider implements — OIDC, SAML, anything where the user leaves for an identity provider and comes back — and nothing else.
|
Package federated is the contract a browser-redirect identity provider implements — OIDC, SAML, anything where the user leaves for an identity provider and comes back — and nothing else. |
|
Package secrets resolves opaque reference strings into raw secret bytes for the auth layer — JWT signing keys, primarily.
|
Package secrets resolves opaque reference strings into raw secret bytes for the auth layer — JWT signing keys, primarily. |
|
Package sessionstore is the contract a third-party session store implements — and nothing else.
|
Package sessionstore is the contract a third-party session store implements — and nothing else. |