Documentation
¶
Overview ¶
Package middleware provides HTTP middleware for authentication and context resolution.
Index ¶
- Constants
- Variables
- func ActorsFrom(ctx context.Context) (principal.Chain, bool)
- func AppFrom(ctx context.Context) (*app.App, bool)
- func AppIDFrom(ctx context.Context) (id.AppID, bool)
- func AuthMethodFrom(ctx context.Context) (string, bool)
- func AuthMiddleware(resolveSession SessionResolver, resolveUser UserResolver, logger log.Logger, ...) forge.Middleware
- func AuthMiddlewareWithJWT(resolveSession SessionResolver, resolveUser UserResolver, ...) forge.Middleware
- func AuthMiddlewareWithStrategies(resolveSession SessionResolver, resolveUser UserResolver, ...) forge.Middleware
- func AuthzActorsFrom(ctx context.Context) principal.Chain
- func AutoRefreshMiddleware(refresher SessionRefresher, configResolver AutoRefreshConfigResolver, ...) forge.Middleware
- func CSRF(cfg CSRFConfig) forge.Middleware
- func CallerSubjectFrom(ctx context.Context) (principal.Ref, bool)
- func CaptchaMiddleware(opts CaptchaOptions) forge.Middleware
- func ClientAuthMiddleware(client *authclient.Client, logger log.Logger, bind ...SessionBindingConfig) forge.Middleware
- func ClientAutoRefreshMiddleware(refresher ClientSessionRefresher, cfg AutoRefreshConfig, logger log.Logger) forge.Middleware
- func ClientIP(r *http.Request) string
- func CredentialSchemeFrom(ctx context.Context) (string, bool)
- func EnforceDPoPForRequest(ctx context.Context, cfg SessionBindingConfig, r *http.Request, ...) error
- func EnvIDFrom(ctx context.Context) (id.EnvironmentID, bool)
- func EnvironmentFrom(ctx context.Context) (*environment.Environment, bool)
- func EnvironmentMiddleware(cfg EnvironmentMiddlewareConfig) forge.Middleware
- func EnvironmentSettingsFrom(ctx context.Context) (*environment.Settings, bool)
- func ExtractCredential(r *http.Request, cookieName string) (scheme, token string)
- func ExtractCredentialFromContext(ctx context.Context, r *http.Request, cookieName string) (scheme, token string)
- func HasScopes(sess *session.Session, scopes ...string) bool
- func IPAccessMiddleware(cfg IPAccessConfig) forge.Middleware
- func ImpersonatorFrom(ctx context.Context) (id.UserID, bool)
- func InternalError(ctx forge.Context, err error) error
- func InternalErrorCtx(ctx context.Context, err error) error
- func IsHTTPS(r *http.Request) bool
- func OrgIDFrom(ctx context.Context) (id.OrgID, bool)
- func PrincipalFrom(ctx context.Context) (*principal.Principal, bool)
- func PrincipalRefFrom(ctx context.Context) (principal.Ref, bool)
- func PublishableKeyMiddleware(resolver AppResolver, logger log.Logger) forge.Middleware
- func RateLimit(limiter ratelimit.Limiter, cfg RateLimitConfig) forge.Middleware
- func RawTokenFrom(ctx context.Context) (string, bool)
- func RequestURL(r *http.Request) string
- func RequireAnyRole(checker RoleChecker, roles ...string) forge.Middleware
- func RequireAuth() forge.Middleware
- func RequireChainPermission(checker ChainPermissionChecker, action, resource string) forge.Middleware
- func RequireEnvironment() forge.Middleware
- func RequirePermission(checker PermissionChecker, action, resource string) forge.Middleware
- func RequireScope(scopes ...string) forge.Middleware
- func ResourceMetadataChallenge(metadataURL string) forge.Middleware
- func SecurityHeaders(opts SecurityHeadersOptions) forge.Middleware
- func SecurityHeadersForAPI(opts SecurityHeadersOptions) forge.Middleware
- func SessionActivityMiddleware(toucher SessionToucher, configResolver SessionActivityConfigResolver, ...) forge.Middleware
- func SessionFrom(ctx context.Context) (*session.Session, bool)
- func SessionIDFrom(ctx context.Context) (id.SessionID, bool)
- func SessionIsFresh(ctx context.Context, now time.Time, window time.Duration, lookup SessionLookup) bool
- func SetInternalErrorLogger(l log.Logger)
- func SetTrustedProxies(nets []*net.IPNet)
- func TrustedProxiesAreDefault() bool
- func UserFrom(ctx context.Context) (*user.User, bool)
- func UserIDFrom(ctx context.Context) (id.UserID, bool)
- func WithActors(ctx context.Context, c principal.Chain) context.Context
- func WithApp(ctx context.Context, a *app.App) context.Context
- func WithAppID(ctx context.Context, appID id.AppID) context.Context
- func WithAuthMethod(ctx context.Context, method string) context.Context
- func WithCookieBridgedToken(ctx context.Context, token string) context.Context
- func WithCredentialScheme(ctx context.Context, scheme string) context.Context
- func WithEnvID(ctx context.Context, envID id.EnvironmentID) context.Context
- func WithEnvironment(ctx context.Context, env *environment.Environment) context.Context
- func WithEnvironmentSettings(ctx context.Context, s *environment.Settings) context.Context
- func WithImpersonator(ctx context.Context, adminID id.UserID) context.Context
- func WithOrgID(ctx context.Context, orgID id.OrgID) context.Context
- func WithPendingOAuthSession(ctx context.Context, p *PendingOAuthSession) context.Context
- func WithPrincipal(ctx context.Context, p *principal.Principal) context.Context
- func WithRawToken(ctx context.Context, token string) context.Context
- func WithRequestInfo(ctx context.Context, info RequestInfo) context.Context
- func WithSession(ctx context.Context, s *session.Session) context.Context
- func WithSessionID(ctx context.Context, sessionID id.SessionID) context.Context
- func WithUser(ctx context.Context, u *user.User) context.Context
- func WithUserID(ctx context.Context, userID id.UserID) context.Context
- type AppResolver
- type AutoRefreshConfig
- type AutoRefreshConfigResolver
- type CSRFConfig
- type CaptchaOptions
- type CaptchaResult
- type ChainPermissionChecker
- type ClientSessionRefresher
- type CookieNameResolver
- type CookieSetter
- type DefaultEnvironmentResolver
- type EnvironmentDetector
- type EnvironmentMiddlewareConfig
- type EnvironmentResolver
- type ExpectedAudienceResolver
- type IPAccessConfig
- type InternalHTTPError
- type JWTValidator
- type PendingOAuthSession
- type PermissionChecker
- type PrincipalResolver
- type RateLimitConfig
- type RefreshRequest
- type RequestInfo
- type RoleChecker
- type SecurityHeadersOptions
- type SessionActivityConfig
- type SessionActivityConfigResolver
- type SessionBindingConfig
- type SessionExistsChecker
- type SessionExistsContextChecker
- type SessionLookup
- type SessionRefresher
- type SessionResolver
- type SessionToucher
- type StrategyAuthenticator
- type UserResolver
Constants ¶
const DefaultStepUpWindow = 5 * time.Minute
DefaultStepUpWindow is how recently a caller must have signed in for a sensitive change (enrolling a second factor, registering a passkey) to proceed on session auth alone.
const PublishableKeyHeader = "X-Publishable-Key"
PublishableKeyHeader is the canonical request header carrying a `pk_*` publishable key. Frontends set this once on every request so the server can route signup, signin, and other public-auth calls to the correct app without an explicit `app_id` field in the body.
const PublishableKeyQuery = "publishable_key"
PublishableKeyQuery is the query-string fallback for GET endpoints where setting a custom header is awkward (mirrors the existing /v1/client-config?key=… convention).
const RequestIDHeader = "X-Request-ID"
RequestIDHeader carries the request id back to the client on a 500, so a support ticket can quote it and the log line can be found.
Variables ¶
var ErrDPoPBindingFailed = errors.New("authsome: token requires proof of possession")
ErrDPoPBindingFailed reports a bound token presented without an acceptable proof, for callers outside the middleware chain that only get to answer yes or no and cannot write an RFC 9449 challenge.
Functions ¶
func ActorsFrom ¶
ActorsFrom returns the actor chain previously stored with WithActors.
func AuthMethodFrom ¶
AuthMethodFrom retrieves the authentication method from the context.
func AuthMiddleware ¶
func AuthMiddleware(resolveSession SessionResolver, resolveUser UserResolver, logger log.Logger, binding ...SessionBindingConfig) forge.Middleware
AuthMiddleware extracts the session token from the Authorization header, resolves the session and user, and stores them in context. This middleware is the forge.Scope producer — it resolves the authenticated identity and sets AppID/OrgID on context for all downstream extensions.
func AuthMiddlewareWithJWT ¶
func AuthMiddlewareWithJWT( resolveSession SessionResolver, resolveUser UserResolver, strategies StrategyAuthenticator, jwtValidator JWTValidator, logger log.Logger, binding ...SessionBindingConfig, ) forge.Middleware
AuthMiddlewareWithJWT creates middleware that supports JWT token validation in addition to opaque session tokens and strategy auth. JWT tokens are validated stateless (no DB lookup for the access token itself).
func AuthMiddlewareWithStrategies ¶
func AuthMiddlewareWithStrategies( resolveSession SessionResolver, resolveUser UserResolver, strategies StrategyAuthenticator, logger log.Logger, binding ...SessionBindingConfig, ) forge.Middleware
AuthMiddlewareWithStrategies creates middleware that tries bearer-session resolution first, then falls back to the strategy registry for alternative auth methods (API keys, etc.). Bearer tokens with the "ask_" prefix skip session resolution and go directly to the strategy chain.
When a JWTValidator is provided, JWT tokens (detected by containing two dots) are validated stateless — no DB lookup needed for the access token.
Flow:
- Extract bearer token from Authorization header
- If token is JWT and jwtValidator is set → validate stateless
- If token exists and is NOT an API key prefix → resolve as session
- If no session resolved → try strategies.Authenticate(ctx, r)
- If strategy succeeds → set context with User, Session, AppID, Scope
- If all fail → continue unauthenticated (RequireAuth enforces)
func AuthzActorsFrom ¶
AuthzActorsFrom returns the actor chain Warden must independently authorize for this request.
The session is asked first, because Session.AuthzActors is where the impersonation exception lives: an admin acting as a user must be evaluated as that user alone, so the chain it returns is deliberately empty. Reading sess.Actors here instead would intersect the admin's own permissions into every check and invert impersonation, letting the admin do LESS than the person they are standing in for.
The context chain is the fallback, for callers that arrive without a session row behind them.
func AutoRefreshMiddleware ¶
func AutoRefreshMiddleware( refresher SessionRefresher, configResolver AutoRefreshConfigResolver, logger log.Logger, cookieSetter ...CookieSetter, ) forge.Middleware
AutoRefreshMiddleware checks if the authenticated session's access token is near expiry and, if so, transparently refreshes it. New tokens are returned in response headers:
- X-Auth-Token: the new access token
- X-Auth-Refresh-Token: the new refresh token (only if ExposeRefreshToken is true)
- X-Auth-Token-Expires-At: RFC 3339 expiration timestamp
When a CookieSetter is provided, the session cookie is also updated with the new access token so the browser cookie stays in sync.
This middleware MUST run after AuthMiddleware so the session is on context. On refresh failure, the original response is returned unchanged.
func CSRF ¶
func CSRF(cfg CSRFConfig) forge.Middleware
CSRF refuses an unsafe request that was authenticated by the session cookie unless the browser says it came from the same site, or its Origin is one the operator listed. A cookie travels with any request a browser makes, including one another site's page provoked; the Sec-Fetch-Site header (every current browser sends it) and the Origin header (sent on cross-site unsafe requests) are what tell those apart. A request with neither header is refused: a browser that sends none is older than this check, and a non-browser client does not use cookies.
Requests authenticated by bearer token, DPoP or API key are untouched: a credential the script had to attach itself cannot be attached by another site's page.
func CallerSubjectFrom ¶
CallerSubjectFrom returns the principal a permission check must evaluate.
The subject is who the request is FOR, never who is doing the acting: on a delegated session that is the human, and the agent is in the actor chain. Resolution order matches PrincipalRefFrom (a resolved principal, then a user, then a non-human session subject) with one addition at the end. A human session whose user row failed to resolve still has its user ID on the context, and falling back to it keeps this exactly as permissive as the UserIDFrom gate RequirePermission used before chains existed: no more, no less.
func CaptchaMiddleware ¶
func CaptchaMiddleware(opts CaptchaOptions) forge.Middleware
CaptchaMiddleware gates a request on a successful captcha verification.
The token is read from header X-Captcha-Token first, falling back to the form/query field "captcha_token" so the same middleware works for JSON APIs and HTML form posts.
Behaviour:
- If auth.captcha_required is false for the resolved app: pass through.
- Token missing: 403 with type="captcha_required".
- Token rejected by verifier: 403 with type="captcha_invalid".
- Verifier transient failure: 503 with type="captcha_unavailable".
The verifier is constructed lazily and cached per (appID, provider, secret) so secret rotation in the dashboard takes effect on the next request without a process restart.
Order: this middleware must run BEFORE any CPU-bound work (password hashing, etc.) so a captcha-failed probe doesn't pay argon2 cost server-side.
func ClientAuthMiddleware ¶
func ClientAuthMiddleware(client *authclient.Client, logger log.Logger, bind ...SessionBindingConfig) forge.Middleware
ClientAuthMiddleware validates tokens by calling the remote authsome server. Opaque tokens are validated via the /v1/introspect endpoint. Results are cached briefly to avoid per-request HTTP calls.
This middleware sets the same context keys as the engine-based AuthMiddleware (WithUser, WithUserID, WithAppID, WithOrgID, WithSessionID, WithAuthMethod) so downstream code works identically regardless of which middleware is used.
bind is optional and at most one is read. It carries the DPoP validator this service enforces RFC 9449 with. Enforcement is a property of the token, not of the service holding it, so a token whose introspection response carries a cnf claim needs a proof here exactly as it would at the identity server. Pass no config and a bound token is refused outright, because a service that cannot check a binding must not admit it unchecked.
func ClientAutoRefreshMiddleware ¶
func ClientAutoRefreshMiddleware( refresher ClientSessionRefresher, cfg AutoRefreshConfig, logger log.Logger, ) forge.Middleware
ClientAutoRefreshMiddleware is the client-mode counterpart to AutoRefreshMiddleware: it keeps a browser's cookie session alive by rotating it shortly before the access token expires.
Why client mode needs its own:
AutoRefreshMiddleware and SessionActivityMiddleware both reach into the session store — one to read sess.ExpiresAt and call Engine.Refresh, the other to extend the row — so neither can run in a service that has no engine. Extension.Middlewares() therefore returned only ClientAuthMiddleware in client mode, and the comment there said session refresh was "Portal's responsibility". Nothing took up that responsibility, so a cookie session died at the access token's TTL (one hour by default) no matter how actively it was being used: the cookie's own Max-Age is that same TTL, and ResolveSessionByToken refuses the session the moment it lapses.
How it works:
POST /v1/refresh is cookie-first, rotating from the session's server-side refresh token when the request carries a valid session cookie. So this middleware never needs the refresh token — which is just as well, since a client-mode service is only ever shown the access token. It forwards the browser's Cookie header, then replays the identity server's Set-Cookie response onto its own response so cookie attributes stay resolved in exactly one place.
Why the work happens BEFORE the handler:
Forge writes the status line and headers straight through to the connection — it does not buffer the response. A header set after next() returns has already missed the wire, however healthy it looks on an httptest recorder (whose Header() map keeps accepting writes long after WriteHeader snapshotted it). Rotating first is what makes the new cookie actually reach the browser.
The cost is a round-trip in front of the handler, paid only inside the refresh window rather than on every request.
Ordering: this MUST run after ClientAuthMiddleware, which is what records the expiry it reads. A refresh failure is non-fatal — the handler runs either way and the session keeps its original expiry.
func ClientIP ¶
ClientIP resolves the real client IP for a request. X-Forwarded-For and X-Real-IP are trusted ONLY when the immediate peer is a trusted proxy; otherwise the peer address is returned, so a direct client cannot forge its IP. When the peer is trusted, the X-Forwarded-For chain is walked right-to-left and the first non-proxy address (the real client) is returned.
func CredentialSchemeFrom ¶
CredentialSchemeFrom returns how the request presented its credential.
func EnforceDPoPForRequest ¶
func EnforceDPoPForRequest( ctx context.Context, cfg SessionBindingConfig, r *http.Request, boundJKT, scheme, token, appID, sessionID string, logger log.Logger, ) error
EnforceDPoPForRequest applies the rule enforceDPoP applies, for a credential resolved outside the HTTP middleware chain. The forge auth providers are the case that needs it: they return an auth context rather than a response, so they cannot answer with a challenge, but the binding still has to hold. A caller that can write a response should go through the middleware instead, so the client learns which retry would work.
func EnvIDFrom ¶
func EnvIDFrom(ctx context.Context) (id.EnvironmentID, bool)
EnvIDFrom retrieves the environment ID from the context.
func EnvironmentFrom ¶
func EnvironmentFrom(ctx context.Context) (*environment.Environment, bool)
EnvironmentFrom retrieves the full environment from the context.
func EnvironmentMiddleware ¶
func EnvironmentMiddleware(cfg EnvironmentMiddlewareConfig) forge.Middleware
EnvironmentMiddleware resolves the active environment for each request.
Resolution order:
- X-Environment-ID header (explicit override)
- API key prefix-based detection (if DetectFromKey is configured)
- Fall back to the app's default environment
After resolution, it stores the environment, its ID, and its resolved settings on the request context for downstream handlers.
func EnvironmentSettingsFrom ¶
func EnvironmentSettingsFrom(ctx context.Context) (*environment.Settings, bool)
EnvironmentSettingsFrom retrieves the resolved environment settings from the context.
func ExtractCredential ¶
ExtractCredential pulls the token out of a request and reports which scheme carried it, for auth paths outside this package that have to read a credential the way the middleware reads it. cookieName may be empty for the default session cookie.
func ExtractCredentialFromContext ¶
func ExtractCredentialFromContext(ctx context.Context, r *http.Request, cookieName string) (scheme, token string)
ExtractCredentialFromContext pulls the token out of a request and reports which scheme carried it, accounting for a cookie the engine bridged into the Authorization header earlier in this request. Auth paths outside this package that hold a request context should prefer it over ExtractCredential, which cannot tell a bridged cookie from a real Bearer header.
func IPAccessMiddleware ¶
func IPAccessMiddleware(cfg IPAccessConfig) forge.Middleware
IPAccessMiddleware restricts access based on client IP address ranges. Block list is evaluated first; if the IP matches a blocked range, the request is rejected. If an allow list is configured, only IPs in the allow list are permitted.
func ImpersonatorFrom ¶
ImpersonatorFrom retrieves the impersonator admin user ID from the context. Returns the zero value and false if the session is not impersonated.
func InternalError ¶
InternalError logs err with the request id, puts that id on the response, and returns the generic 500. Handlers use it wherever they used to hand forge the raw error, which put the cause (a store's error text, a path, a driver message) in the log without the id that finds it.
func InternalErrorCtx ¶
InternalErrorCtx is InternalError for code that holds a context.Context.
func IsHTTPS ¶
IsHTTPS reports whether the client reached the service over TLS, judged the way RequestURL judges it: the socket, or a trusted proxy's word.
func PrincipalFrom ¶
PrincipalFrom returns the resolved caller previously stored with WithPrincipal.
func PrincipalRefFrom ¶
PrincipalRefFrom returns the authenticated caller as a principal ref, whatever kind of caller it is.
Three sources, in order, which is what lets this work now and keep working once the principal package is wired all the way through the middleware. First a principal already resolved onto the context. Then a user, which is every human request today. Then a session whose subject is not a person, which is how service accounts, agents and workloads arrive before that wiring exists.
A session with no resolved user behind it is deliberately not a caller. An empty PrincipalKind means user, so such a session is a human one whose user could not be loaded, and treating it as authenticated would turn a failed lookup into an authorization bypass.
func PublishableKeyMiddleware ¶
func PublishableKeyMiddleware(resolver AppResolver, logger log.Logger) forge.Middleware
PublishableKeyMiddleware extracts a publishable key from the request, resolves it to an *app.App via the supplied resolver, and stashes both the App and its AppID on the request context (WithApp + WithAppID).
On success: handlers can read the App via AppFrom or the ID via AppIDFrom. On a missing key: the middleware is a no-op — handlers decide whether the absence is an error (signup/signin reject; some endpoints don't need an app context). On an unknown key: the request still proceeds with no context AppID set, so the handler's resolver returns its own well-shaped 400. The middleware itself never aborts — that keeps composition with rate-limit / captcha / auth middlewares predictable.
resolver must be non-nil; passing nil panics at construction time so a misconfigured server fails loudly during startup rather than silently admitting every request to the platform app.
func RateLimit ¶
func RateLimit(limiter ratelimit.Limiter, cfg RateLimitConfig) forge.Middleware
RateLimit returns a middleware that enforces rate limits using the given limiter.
func RawTokenFrom ¶
RawTokenFrom extracts the raw bearer token from context.
func RequestURL ¶
RequestURL reconstructs the absolute URL the client called, for comparison against a DPoP proof's htu claim.
Behind a load balancer r.Host and r.TLS describe the hop, not the client's URL, so X-Forwarded-Proto and X-Forwarded-Host are honoured only when the immediate peer is a trusted proxy. That is the same rule ClientIP applies and it reuses the same configuration, so there is one notion of trusted proxy in this codebase rather than two.
Query and fragment are excluded: RFC 9449 compares htu without them.
func RequireAnyRole ¶
func RequireAnyRole(checker RoleChecker, roles ...string) forge.Middleware
RequireAnyRole returns middleware that checks the user has at least one of the given roles. It first checks for a direct role slug match (fast path), then falls back to Warden's full RBAC+ReBAC+ABAC evaluation via PermissionChecker. This allows platform roles with wildcard permissions (e.g. platform_owner with *:*) to satisfy any role check without explicit slug matching.
func RequireAuth ¶
func RequireAuth() forge.Middleware
RequireAuth returns a forge middleware that rejects unauthenticated requests.
Authenticated means a resolved principal of any kind, not a user specifically. A machine caller carries no *user.User, so gating on one turned away every service account, agent and workload credential before it reached a handler.
func RequireChainPermission ¶
func RequireChainPermission(checker ChainPermissionChecker, action, resource string) forge.Middleware
RequireChainPermission returns a forge.Middleware that checks the caller's subject AND every actor standing between that subject and this request.
This is the guard that makes "delegation can only narrow" true on the request path rather than only inside the engine. Without it, an agent that exchanged a grant for a session whose subject is the human it acts for would be evaluated as that human alone, with its own permissions never intersected and the grant's narrowing thrown away.
func RequireEnvironment ¶
func RequireEnvironment() forge.Middleware
RequireEnvironment returns middleware that rejects requests without a resolved environment context.
func RequirePermission ¶
func RequirePermission(checker PermissionChecker, action, resource string) forge.Middleware
RequirePermission returns a forge.Middleware that checks the authenticated caller has the given permission before continuing.
The route is chosen per request, on whether the caller actually carries an actor chain, and not once at construction on whether the checker happens to have a Can method.
no actors -> requireUserPermission, unchanged actors + chain-aware checker -> RequireChainPermission actors + checker without Can -> refuse
Reading the actors first looks like a weakening of the chain fix and is not. It narrows WHEN the new path runs without narrowing WHAT it enforces. With an empty chain the two paths are semantically identical: HasPermission is literally Can with a nil chain (see Engine.HasPermission), so a chainless request gets the same answer either way. Every request that carries a chain still goes through Can, which is the whole point of the fix.
What it buys is that upgrading does not silently depend on every existing PermissionChecker's Can being correct. Any implementation that satisfies ChainPermissionChecker with a weak or unimplemented Can, in this repository or out of tree, would otherwise start denying requests it used to allow, with no compile error and no obvious symptom. That happened here: a plugin test double answered HasPermission with real logic and Can with a bare `return false, nil`, and every mutating route it guarded turned 403.
The last case does not fall back to evaluating the subject alone. A caller with a chain and no way to check it is exactly the Critical this fix exists to close, so it is refused and logged rather than quietly granted the human's full authority.
func RequireScope ¶
func RequireScope(scopes ...string) forge.Middleware
RequireScope admits a parked OAuth2 session to the route when it holds every named scope, and answers 403 insufficient_scope when it does not (RFC 6750 section 3.1). With no scopes named, any OAuth2 session is admitted. An ordinary session, or no session, passes through untouched: the route's own authentication rules still apply to those.
func ResourceMetadataChallenge ¶
func ResourceMetadataChallenge(metadataURL string) forge.Middleware
ResourceMetadataChallenge adds the RFC 9728 section 5.1 resource_metadata parameter to the WWW-Authenticate header on a 401.
It is how a client bootstraps discovery from a failed call instead of from a URL somebody handed it, which is the path the MCP spec uses. An empty metadataURL makes the middleware inert, so a deployment that has not configured discovery emits nothing rather than a broken hint.
A handler that already set its own challenge keeps it. The RFC 7592 routes set a realm of their own, and clobbering it would lose information the client can use.
Detection reads the error the inner handler returned rather than the response status: forge's Context does not expose a way to swap or inspect the underlying http.ResponseWriter from middleware, and forge's own error handler (which runs at the top of the middleware chain, after this middleware returns) is what actually turns a 401 error into a written response. forge.Unauthorized and friends implement an interface with StatusCode() int and ResponseBody() any; errors.As is used instead of a bare type assertion so a wrapped 401 still matches.
This only sees errors returned by other MIDDLEWARE further down the chain, such as middleware/rbac.go and middleware/auth.go. A route handler's returned error is converted to a written response by forge inside its own handler-conversion wrapper before it is ever handed back to enclosing middleware, so this middleware cannot observe a 401 minted that way at all, whether or not the handler set its own header first.
A route handler that wants the hint on its own 401s has to set the header itself before returning. plugins/oauth2provider/register.go's authenticateRegistration already did this, for a different reason (it sets its own realm on ctx before returning).
plugins/oauth2provider registers three route-handler 401s in the same group; each is handled differently, and the difference is deliberate:
- handleUserInfo (GET /v1/oauth/userinfo) sets its own hint the same way authenticateRegistration does. AuthMiddleware only soft-resolves the bearer token (middleware/auth.go) rather than rejecting, so handleUserInfo's own forge.Unauthorized is the actual point of enforcement on this endpoint, and this middleware never sees it. userinfo is the one protected-resource endpoint in the group, which is exactly what RFC 9728 discovery is for, so it gets the hint.
- handleToken's client-authentication 401s (POST /v1/oauth/token) are left without the hint on purpose: a client at the token endpoint already holds the authorization server's URL, since it just called it, so the hint would tell it nothing new. See the comment on handleToken.
- handleAuthorize's 401 (GET /v1/oauth/authorize) is left without the hint for a different reason again: it fires because the end user is not signed in, not because a client failed to authenticate, and the response goes to a browser mid-redirect, not to a machine parsing a challenge header. The correct response there is a login redirect; resource_metadata would be read by nobody. See the comment on handleAuthorize.
Known limit: this only sees a 401 that comes back as a Go error at all. plugin.SessionGuard (plugin/authz.go) gates /v1/oauth/device/complete in this plugin, and routes in plugins/subscription and plugins/apikey elsewhere. It is backed by forge's auth registry middleware (forge/extensions/auth registry.go), which on failure calls ctx.String(http.StatusUnauthorized, "Unauthorized") directly and returns that call's result, not a typed error. A route behind SessionGuard gets no hint either, for the same underlying reason a route handler's 401 does not: nothing reaches this middleware as an error for it to inspect.
func SecurityHeaders ¶
func SecurityHeaders(opts SecurityHeadersOptions) forge.Middleware
SecurityHeaders is a forge.Middleware that emits a baseline set of security response headers on every served request.
Headers (set unconditionally unless an option overrides them):
- Content-Security-Policy
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- Referrer-Policy: strict-origin-when-cross-origin
- Permissions-Policy: deny-list of sensor/payment APIs
- Strict-Transport-Security (only when HSTSMaxAgeSeconds > 0)
The middleware is conservative by design: callers can widen the CSP (e.g. to allow a CDN) but the default closes XSS and clickjacking surfaces that the dashboard cannot exercise legitimately.
func SecurityHeadersForAPI ¶
func SecurityHeadersForAPI(opts SecurityHeadersOptions) forge.Middleware
SecurityHeadersForAPI is a convenience preset that drops the CSP (irrelevant for JSON responses) but keeps the rest. Use this on routers that serve only JSON.
func SessionActivityMiddleware ¶
func SessionActivityMiddleware( toucher SessionToucher, configResolver SessionActivityConfigResolver, logger log.Logger, cookieSetter ...CookieSetter, ) forge.Middleware
SessionActivityMiddleware extends session expiry on each authenticated request, implementing a sliding session window. It updates LastActivityAt and extends ExpiresAt to now + InactivityTimeout.
To avoid a database write on every single request, the session is only touched if LastActivityAt is older than minTouchInterval (60 seconds).
When a CookieSetter is provided, the session cookie is re-set with the extended MaxAge so the browser cookie stays in sync with the server-side session lifetime.
This middleware MUST run after AuthMiddleware so the session is on context.
func SessionFrom ¶
SessionFrom retrieves the session from the context.
func SessionIDFrom ¶
SessionIDFrom retrieves the session ID from the context.
func SessionIsFresh ¶
func SessionIsFresh(ctx context.Context, now time.Time, window time.Duration, lookup SessionLookup) bool
SessionIsFresh reports whether the caller signed in within window: the session on the context, or failing that the one lookup returns for the context's session id, was created no longer than window ago. A missing session is never fresh, so a caller that cannot show when they signed in is asked to sign in again.
func SetInternalErrorLogger ¶
SetInternalErrorLogger installs the logger InternalError reports causes to.
func SetTrustedProxies ¶
SetTrustedProxies replaces the trusted-proxy CIDR set. Passing nil restores the built-in defaults. Primarily used for configuration and tests.
func TrustedProxiesAreDefault ¶
func TrustedProxiesAreDefault() bool
TrustedProxiesAreDefault reports whether the built-in private-range set is in use because nobody configured one. A deployment behind a load balancer on a public address, or with untrusted workloads on the same private network, should set AUTHSOME_TRUSTED_PROXIES.
func UserIDFrom ¶
UserIDFrom retrieves the user ID from the context.
func WithActors ¶
WithActors returns ctx carrying the actor chain acting on the subject's behalf. Delegates to the principal package's own context functions for the same shared-key reason as WithPrincipal.
func WithApp ¶
WithApp stores the resolved app entity in the context. Set by the publishable-key middleware so handlers can validate against the full app without re-querying the store.
func WithCookieBridgedToken ¶
WithCookieBridgedToken records that the Authorization header on this request was synthesized from a session cookie rather than sent by the client.
The engine bridges the cookie into "Authorization: Bearer <token>" so every downstream reader sees one credential shape. That is convenient and it also erased the one fact RFC 9449 section 7.1 turns on, namely whether the credential arrived in an Authorization header at all. Recording the bridged value here puts the fact back without changing what downstream readers see.
func WithCredentialScheme ¶
WithAuthMethod stores the authentication method used (e.g. "session", "strategy"). WithCredentialScheme records how the request presented its credential (bearer, dpop or cookie), for the checks that only apply to one of them.
func WithEnvironment ¶
func WithEnvironment(ctx context.Context, env *environment.Environment) context.Context
WithEnvironment stores a full environment entity in the context.
func WithEnvironmentSettings ¶
WithEnvironmentSettings stores the resolved environment settings in the context.
func WithImpersonator ¶
WithImpersonator stores the ID of the admin who initiated the impersonation.
func WithPendingOAuthSession ¶
func WithPendingOAuthSession(ctx context.Context, p *PendingOAuthSession) context.Context
WithPendingOAuthSession parks p on ctx. The auth middleware calls it; tests call it to stand in for the middleware.
func WithPrincipal ¶
WithPrincipal returns ctx carrying the resolved caller.
This delegates to the principal package's own context functions rather than defining a separate key, so a plugin reading through principal.FromContext sees the exact value this middleware wrote, and PrincipalRefFrom's first branch (above) resolves against it.
func WithRawToken ¶
WithRawToken stores the raw bearer token in context so downstream services can forward it to other services (e.g. TwinOS → Portal for user-scoped ops).
func WithRequestInfo ¶
func WithRequestInfo(ctx context.Context, info RequestInfo) context.Context
WithRequestInfo stores the request's correlation data on the context.
func WithSession ¶
WithSession stores a session in the context.
func WithSessionID ¶
WithSessionID stores a session ID in the context.
Types ¶
type AppResolver ¶
type AppResolver interface {
ResolveAppByPublicKey(ctx context.Context, publicKey string) (*app.App, error)
}
AppResolver resolves a publishable key to its owning app. The engine satisfies this interface via Engine.ResolveAppByPublicKey; defining it locally keeps the middleware package free of an import cycle on the authsome root package.
type AutoRefreshConfig ¶
type AutoRefreshConfig struct {
// Enabled turns on auto-refresh.
Enabled bool
// Threshold is the time before access token expiry to trigger refresh.
// Default: 5 minutes.
Threshold time.Duration
// ExposeRefreshToken controls whether the refresh token is included in
// the X-Auth-Refresh-Token response header. When false (recommended),
// only the access token is returned in headers. The refresh token should
// only be obtained via the /v1/refresh endpoint.
ExposeRefreshToken bool
}
AutoRefreshConfig controls automatic token refresh behavior.
type AutoRefreshConfigResolver ¶
type AutoRefreshConfigResolver func(ctx context.Context) AutoRefreshConfig
AutoRefreshConfigResolver returns the auto-refresh configuration for the current request context (may vary per app).
type CSRFConfig ¶
type CSRFConfig struct {
// Enabled turns the check on.
Enabled bool
// AllowedOrigins are origins (scheme://host[:port], compared case
// insensitively) whose cross-site cookie requests are accepted.
AllowedOrigins []string
}
CSRFConfig governs the cross-site check on cookie sessions.
type CaptchaOptions ¶
type CaptchaOptions struct {
// Settings is the settings manager used to resolve per-app captcha
// configuration at request time. Required.
Settings *settings.Manager
// ResolveAppID returns the app id for a request. Defaults to a
// resolver that reads forge.AppIDFrom(ctx). Returning ok=false skips
// app-scoped settings (global cascade only).
ResolveAppID func(forge.Context) (id.AppID, bool)
// VerifierFor builds a captcha.Verifier from a (provider, secret) pair.
// Defaults to defaultCaptchaVerifierFor which knows about Turnstile.
VerifierFor func(provider, secret string) (captcha.Verifier, error)
// Action is the optional captcha action binding (e.g. "signup",
// "signin"). Empty disables the action check.
Action string
// Chronicle, if non-nil, records audit events with Action="captcha.verify".
Chronicle bridge.Chronicle
// Logger for transient verifier failures. May be nil.
Logger log.Logger
}
CaptchaOptions configures CaptchaMiddleware.
type CaptchaResult ¶
CaptchaResult is the outcome of an out-of-middleware captcha check. Allowed reports whether the request may proceed; when false, RejectCode is the same stable type code the middleware would have written ("captcha_required", "captcha_invalid", "captcha_unavailable", "captcha_misconfigured"). RejectStatus is the HTTP status code that matches.
func VerifyCaptchaForRequest ¶
func VerifyCaptchaForRequest(ctx context.Context, opts CaptchaOptions, r *http.Request, appID id.AppID, action string) CaptchaResult
VerifyCaptchaForRequest is a non-forge variant of CaptchaMiddleware for callers that aren't part of forge's standard middleware chain (e.g. dashboard auth-pages handlers, which receive a *router.PageContext rather than a forge.Context). The semantics match the middleware exactly:
- If auth.captcha_required resolves to false: returns Allowed=true.
- Token missing: Allowed=false, RejectCode="captcha_required".
- Token rejected: Allowed=false, RejectCode="captcha_invalid".
- Verifier transient failure: Allowed=false, RejectCode="captcha_unavailable".
- Verifier construction failure: Allowed=false, RejectCode="captcha_misconfigured".
The optional appID scopes setting resolution; pass id.AppID{} for global only. Audit recording is the caller's responsibility (the middleware records it internally; out-of-middleware callers may want different fields).
type ChainPermissionChecker ¶
type ChainPermissionChecker interface {
Can(ctx context.Context, subject principal.Ref, actors principal.Chain, action, resource string) (bool, error)
}
ChainPermissionChecker is the chain-aware counterpart to PermissionChecker: it evaluates a permission for a subject given the actors acting on that subject's behalf.
It sits beside PermissionChecker rather than replacing it, following the same convention as PermissionChecker and UserResolver: this package must not import the engine, so the capability arrives as a narrow interface the engine happens to satisfy. *authsome.Engine implements it via Can, and plugin.Engine carries Can on its core interface, so every real engine reaching RequirePermission is chain-aware without any call site changing.
Why this matters: with an actor chain present, every party must allow the action. A session minted by a token exchange carries the human as its subject and the agent in its chain, and checking the human alone would hand the agent the human's entire authority. Delegation may only narrow, and this interface is how the request path gets to say so.
type ClientSessionRefresher ¶
type ClientSessionRefresher func(ctx context.Context, cookieHeader string) (*authclient.RefreshedSession, error)
ClientSessionRefresher rotates the session behind a browser's Cookie header and reports the new tokens plus the Set-Cookie headers the identity server issued. Client.RefreshTokensWithCookies fulfills it.
func NewClientSessionRefresher ¶
func NewClientSessionRefresher(client *authclient.Client) ClientSessionRefresher
NewClientSessionRefresher adapts an authclient to ClientSessionRefresher.
type CookieNameResolver ¶
CookieNameResolver returns the session cookie name for the current context. When nil, the default "authsome_session_token" is used.
type CookieSetter ¶
CookieSetter re-sets the session cookie with the given token and max-age. Used by activity and auto-refresh middleware to keep the browser cookie in sync with the server-side session lifetime.
type DefaultEnvironmentResolver ¶
type DefaultEnvironmentResolver func(appID id.AppID) (*environment.Environment, error)
DefaultEnvironmentResolver loads the default environment for an app.
type EnvironmentDetector ¶
type EnvironmentDetector func(rawKey string) (environment.Type, bool)
EnvironmentDetector detects an environment type from a raw API key string. Returns the detected type and true, or empty and false if unknown.
type EnvironmentMiddlewareConfig ¶
type EnvironmentMiddlewareConfig struct {
// ResolveEnvironment loads an environment by ID.
ResolveEnvironment EnvironmentResolver
// ResolveDefault loads the default environment for an app.
ResolveDefault DefaultEnvironmentResolver
// DetectFromKey detects environment type from an API key prefix (optional).
DetectFromKey EnvironmentDetector
// Logger for debug/warn messages.
Logger log.Logger
}
EnvironmentMiddlewareConfig configures the environment resolution middleware.
type EnvironmentResolver ¶
type EnvironmentResolver func(envID id.EnvironmentID) (*environment.Environment, error)
EnvironmentResolver loads an environment by ID.
type ExpectedAudienceResolver ¶
ExpectedAudienceResolver returns the resource identifiers this deployment answers to for the app the presented token was minted under. Nil, or an empty result, disables the audience check.
The app id handed to the resolver is the TOKEN's, never the request's. A resource identifier is configured per app, and the request context only carries an app id when the caller sent a publishable key, which is optional. Resolving from the request would let a caller switch the check off by omitting a header, and would leave a resource server that never installs PublishableKeyMiddleware with no app-scoped enforcement at all.
type IPAccessConfig ¶
type IPAccessConfig struct {
// AllowList contains CIDR ranges that are explicitly allowed.
// If non-empty, only requests from these ranges are permitted.
AllowList []string `json:"allow_list"`
// BlockList contains CIDR ranges that are always blocked.
// Evaluated before AllowList — a blocked IP is rejected even if
// it also appears in the allow list.
BlockList []string `json:"block_list"`
}
IPAccessConfig configures IP-based access control.
type InternalHTTPError ¶
type InternalHTTPError struct {
RequestID string
// contains filtered or unexported fields
}
InternalHTTPError is the 500 a client sees for a failure that is ours: a fixed message, never the cause. Forge masks every 5xx body to its status text, so the request id that ties the response to the log line travels on the X-Request-ID response header instead. The cause stays on the error for errors.Is and errors.As, and in the log line written when the error was made, keyed by the same request id.
func (*InternalHTTPError) Error ¶
func (e *InternalHTTPError) Error() string
func (*InternalHTTPError) ResponseBody ¶
func (e *InternalHTTPError) ResponseBody() any
ResponseBody is what forge serialises (and then masks, as for any 5xx).
func (*InternalHTTPError) StatusCode ¶
func (e *InternalHTTPError) StatusCode() int
func (*InternalHTTPError) Unwrap ¶
func (e *InternalHTTPError) Unwrap() error
type JWTValidator ¶
type JWTValidator interface {
ValidateJWT(token string) (*tokenformat.TokenClaims, error)
}
JWTValidator validates JWT access tokens and returns claims. The engine implements this via its TokenFormatForApp method.
type PendingOAuthSession ¶
type PendingOAuthSession struct {
Session *session.Session
// contains filtered or unexported fields
}
PendingOAuthSession is an OAuth2-issued session the auth middleware resolved but has not admitted to the request.
func PendingOAuthSessionFrom ¶
func PendingOAuthSessionFrom(ctx context.Context) (*PendingOAuthSession, bool)
PendingOAuthSessionFrom returns the parked OAuth2 session, if any.
type PermissionChecker ¶
type PermissionChecker interface {
HasPermission(ctx context.Context, userID id.UserID, action, resource string) (bool, error)
}
PermissionChecker can verify if a user has a specific permission.
type PrincipalResolver ¶
PrincipalResolver resolves a caller by ref. Middleware takes this as a function rather than an engine so it does not import the engine package.
type RateLimitConfig ¶
type RateLimitConfig struct {
// Limit is the maximum number of requests per window.
Limit int
// Window is the sliding window duration.
Window time.Duration
// KeyFunc extracts the rate limit key from the request (default: client IP).
KeyFunc func(ctx forge.Context) string
// FailOpen lets the request through when the limiter returns an error.
// Off by default: a limiter that cannot answer refuses with 503, so an
// outage of the shared store does not become an unlimited window.
FailOpen bool
// OnError is called with the limiter's error, for a metric or a log.
OnError func(err error)
// OnReject is called when a request is refused for exceeding the limit,
// and OnFailOpen when a limiter error let one through. Both are for
// metrics.
OnReject func()
OnFailOpen func()
}
RateLimitConfig configures the rate limit middleware for a specific endpoint.
type RefreshRequest ¶
type RefreshRequest struct {
// RefreshToken is the session's current refresh token when the caller
// holds it. A session loaded from the store carries none: the store keeps
// refresh tokens as hashes, so the middleware sends SessionToken instead
// and the refresher rotates the session behind that access token.
RefreshToken string
// SessionToken is the access token the request authenticated with.
SessionToken string
// IPAddress and UserAgent are validated against the session's stored
// values when session binding is enabled.
IPAddress string
UserAgent string
// DPoPProof is the raw DPoP header from this request. It is the same proof
// the auth middleware already accepted, and re-checking it is safe: the
// request-scoped record of accepted proofs keeps the replay cache from
// reading it as a replay of itself.
DPoPProof string
// Method and RequestURL are this request's, for the proof's htm and htu.
Method string
RequestURL string
// KeepRefreshToken asks for the access token alone to be rotated. Set
// when the refresh token is not exposed in the response: the client
// keeps the one it has, and it stays valid.
KeepRefreshToken bool
}
RefreshRequest carries everything the engine needs to re-check a session's bindings while rotating it. Auto-refresh happens on an ordinary request rather than a call to /v1/refresh, so the request's own details are what the checks run against: the DPoP proof is bound to this method and URL, and the IP and User-Agent are this request's.
type RequestInfo ¶
type RequestInfo = hook.RequestInfo
RequestInfo re-exports hook.RequestInfo so handlers and plugins can read the request correlation data without importing the hook package.
func RequestInfoFrom ¶
func RequestInfoFrom(ctx context.Context) (RequestInfo, bool)
RequestInfoFrom returns the request's correlation data.
type RoleChecker ¶
type RoleChecker interface {
ListUserRoleSlugs(ctx context.Context, userID id.UserID) ([]string, error)
}
RoleChecker can list a user's role slugs.
type SecurityHeadersOptions ¶
type SecurityHeadersOptions struct {
// HSTSMaxAgeSeconds is the max-age value emitted in the
// Strict-Transport-Security header. Zero (the default) means
// "do not emit HSTS" — appropriate for local development. Set
// to 31536000 (1 year) for production deployments behind TLS.
HSTSMaxAgeSeconds int
// HSTSIncludeSubdomains adds includeSubDomains to the HSTS
// directive. Only meaningful when HSTSMaxAgeSeconds > 0.
HSTSIncludeSubdomains bool
// HSTSPreload adds the preload directive (the operator must
// also submit the domain to hstspreload.org). Only meaningful
// when HSTSMaxAgeSeconds > 0 and HSTSIncludeSubdomains is true.
HSTSPreload bool
// CSP overrides the default Content-Security-Policy. When
// empty, a conservative default is used: own-origin scripts
// and styles, no framing, no inline scripts, no plugins.
// Pass an empty value via DisableCSP to opt out entirely
// (e.g. for JSON-only APIs where CSP has no effect).
CSP string
// DisableCSP omits the Content-Security-Policy header.
// Useful for pure-JSON API routes where CSP is irrelevant
// and a misconfigured policy can confuse responders.
DisableCSP bool
// FrameOptions is the value of X-Frame-Options. Defaults to
// "DENY". Set to "SAMEORIGIN" if the dashboard ever needs to
// embed itself; never set to ALLOW-FROM (deprecated).
FrameOptions string
// ReferrerPolicy is the value of Referrer-Policy. Defaults to
// "strict-origin-when-cross-origin" — the OWASP-recommended
// default that preserves analytics on same-origin while
// stripping query strings on cross-origin navigations.
ReferrerPolicy string
// PermissionsPolicy is the value of Permissions-Policy.
// Defaults to a conservative deny-list that disables sensors,
// payment, and serial APIs the dashboard never uses.
PermissionsPolicy string
}
SecurityHeadersOptions configures SecurityHeaders.
Defaults are tuned for the dashboard's HTML routes. JSON-only API surfaces should pass APIRoutes:true to drop the HTML-shaped CSP.
type SessionActivityConfig ¶
type SessionActivityConfig struct {
// Enabled turns on session extension on activity.
Enabled bool
// InactivityTimeout is how long a session lives without activity.
// On each authenticated request, ExpiresAt is reset to now + InactivityTimeout.
// Default: 30 minutes.
InactivityTimeout time.Duration
// AbsoluteLifetime caps the sliding window: ExpiresAt is never moved
// past CreatedAt + AbsoluteLifetime. Zero means no cap.
AbsoluteLifetime time.Duration
}
SessionActivityConfig controls sliding session extension behavior.
type SessionActivityConfigResolver ¶
type SessionActivityConfigResolver func(ctx context.Context) SessionActivityConfig
SessionActivityConfigResolver returns the activity extension configuration for the current request context (may vary per app).
type SessionBindingConfig ¶
type SessionBindingConfig struct {
// BindToIP rejects requests when the client IP differs from the
// IP recorded at session creation.
BindToIP bool
// BindToDevice rejects requests when the User-Agent differs from
// the one recorded at session creation.
BindToDevice bool
// CookieNameResolver returns the session cookie name for the current
// request context. Falls back to "authsome_session_token" if nil or empty.
CookieNameResolver CookieNameResolver
// JWTSessionChecker, when set, cross-checks JWT tokens against the session
// store. This enables JWT revocation — revoked sessions are rejected even
// if the JWT signature is valid. Also enables IP/device binding for JWTs.
JWTSessionChecker SessionExistsChecker
// ExpectedAudienceResolver returns the resource identifiers this
// deployment answers to for the app that minted the presented token.
// Nil, or an empty result, disables the check.
//
// Per app rather than per process: two apps in one deployment are two
// different resources, and a token minted for one must not authenticate at
// the other. This runs per request on the same path as
// CookieNameResolver, so it should read a cached setting rather than hit
// the database.
ExpectedAudienceResolver ExpectedAudienceResolver
// PrincipalResolver, when set, resolves the session's subject onto the
// context as a *principal.Principal after every auth path that produces a
// session. Threaded through this config (rather than as a standalone
// parameter on each middleware constructor) so it defaults to nil and
// every existing caller keeps working unchanged.
PrincipalResolver PrincipalResolver
// DPoPValidator enforces RFC 9449 proof-of-possession. When nil, DPoP is
// not enforced at all, which is only correct in tests: the engine always
// supplies one.
DPoPValidator *dpop.Validator
// DPoPNonceSigner mints the nonces served with a use_dpop_nonce challenge.
// Nil disables nonce challenges.
DPoPNonceSigner *dpop.NonceSigner
// DPoPNonceRequired reports whether the app owning this session demands a
// nonce. Nil means never.
DPoPNonceRequired func(ctx context.Context, appID string) bool
// DPoPAudit records a DPoP failure. Nil disables recording.
DPoPAudit func(ctx context.Context, action string, md map[string]string)
// JWTSessionContextChecker is JWTSessionChecker taking the request's
// context. When both are set this one is used.
JWTSessionContextChecker SessionExistsContextChecker
}
SessionBindingConfig controls session binding validation.
type SessionExistsChecker ¶
SessionExistsChecker checks whether a session ID still exists in the store. Used for JWT revocation support — when enabled, JWT tokens are cross-checked against the session store so revoked sessions are rejected immediately. appID is the token's own app, so the check honours that app's setting rather than the global one.
type SessionExistsContextChecker ¶
type SessionExistsContextChecker func(ctx context.Context, appID, sessionID string) (*session.Session, error)
SessionExistsContextChecker is SessionExistsChecker with the request's context, so the store lookup carries the request's values (its trace span among them).
type SessionLookup ¶
SessionLookup loads a session by id when the request carries only the id (a JWT, for instance) and not the row itself.
type SessionRefresher ¶
SessionRefresher refreshes a session and returns the updated session with new tokens. The extension adapts the engine's Refresh and RefreshBySessionToken to it, picking by which credential the request holds.
type SessionResolver ¶
SessionResolver loads a session from a token.
type SessionToucher ¶
type SessionToucher func(ctx context.Context, sessionID id.SessionID, lastActivityAt, expiresAt time.Time) error
SessionToucher updates a session's last activity time and expiry.