diff --git a/docs/api.md b/docs/api.md index 5dbabe692..7e6e1b430 100644 --- a/docs/api.md +++ b/docs/api.md @@ -169,8 +169,12 @@ session cookie that admits it exactly as a credential would. That cookie is host, and `Secure` whenever the instance URL is `https`. It carries its own expiry and the signature that proves this instance minted it, so no session is kept in memory and every replica of an instance accepts the sessions the others -mint. A browser holds one session per instance, whichever policy established -it. +mint. + +A signed-in browser also holds a short-lived transaction cookie during a login, +and a renewal marker naming the policy it signed in under, which is what lets an +expired session be renewed against the provider without asking the person again. +The marker carries no credential. ### Login @@ -186,6 +190,11 @@ discovered from the policy's issuer, and sets a short-lived transaction cookie that binds the login to this browser. That cookie is what the callback checks before it acts on anything the provider says. +`to` names where to land once the login completes, and is honoured only when it +is a path on this instance, so the endpoint cannot be turned into an open +redirect by whoever composes the link. Anything else falls back to the referring +page, and then to the first path the policy declares. + === "303" The provider's authorization URL in `Location`, with the transaction @@ -267,12 +276,13 @@ behalf. POST /self/v1/auth/logout ``` -Both cookies are expired before anything else is decided, and whether or not -the request carried them, so no outcome leaves a session behind. Where the -session names a policy whose provider offers to end its own session, the -browser is then sent on to do so, carrying the identity token as proof of -whose session it is asking to end. That is what stops the next sign-in from -silently admitting whoever used the browser last. +Every cookie this endpoint mints is expired before anything else is decided, +and whether or not the request carried it, so no outcome leaves a session +behind or a browser eligible to renew one. Where the session names a policy +whose provider offers to end its own session, the browser is then sent on to do +so, carrying the identity token as proof of whose session it is asking to end. +That is what stops the next sign-in from silently admitting whoever used the +browser last. This endpoint answers `POST` rather than `GET` because signing out changes state at the provider, which [RFC 9110 @@ -281,15 +291,10 @@ outside what a safe method may do. A link to it would be followed by anything that prefetches or unfurls URLs, signing the person out unasked, so the control that reaches it is a form submission. -A session is a sealed value this instance keeps no record of, which is what -lets it stay stateless. Signing out therefore takes the session from the -browser rather than revoking it, and a copy taken beforehand stays usable -until it expires. Sessions are short-lived for that reason. - === "303" - Both cookies expired in `Set-Cookie`, and either the provider's end-session - URL or the instance root in `Location`. + The session, transaction and renewal cookies expired in `Set-Cookie`, and + either the provider's end-session URL or the instance root in `Location`. === "405" diff --git a/docs/configuration.md b/docs/configuration.md index b7ae40eb6..f2392910e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -336,6 +336,17 @@ never what to expose. When a path is governed by more than one policy, the policies are unioned, so a single collection can admit both a machine presenting a credential and a user carrying a session. +None of it is stored. Every credential is checked as it arrives, against the +environment or against the issuer's published key set, and a session is a sealed +value the instance keeps no record of, so any replica verifies any of them on +its own, with no session or state to look up and no coordination with the +others. Public key material is still fetched from an issuer and cached, which +is a lookup of what an issuer publishes rather than of who is signed in. That +is what lets an instance scale horizontally, and it is why taking access away +works differently for each policy type, described under each below. Sessions +belong to `oidc` alone: a `jwt` policy has no session and no cookie, whether or +not it names the same provider. + **The UNIX model**: Visibility and access are kept separate, following the UNIX filesystem model. A policy that governs a directory does not erase it from its parent's listing. Just as `ls` reveals a directory you cannot `cd` into, a @@ -371,7 +382,9 @@ regardless of type: ### API Key Consumers present a key through the `Authorization` header using the `Bearer` -scheme ([RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750)). +scheme ([RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750)). Revoking one +means dropping its variable from `keys` and restarting, which leaves every other +key in the policy working. An `apiKey` policy declares the following additional properties: @@ -467,7 +480,12 @@ A token is admitted only when its signature verifies against the issuer's key set, its `iss` claim matches the policy's `issuer`, its `aud` claim includes the policy's `audience`, its signature algorithm is one the policy allows, and it is within its validity period. A token that fails any of these is denied, with the -same response as any other unauthenticated request. +same response as any other unauthenticated request. Nothing here asks the +issuer whether a subject is still welcome, and no token is ever recorded as +withdrawn, so a valid token is accepted until it expires. The issuer can cut +that short by retiring the key its tokens were signed with, which reaches every +token signed under it once the cached key set refreshes. Short token lifetimes +are what bound exposure otherwise. | Property | Type | Required | Default | Description | |-----------------|------|----------|---------|-------------| @@ -557,19 +575,24 @@ follows is signed with a secret of the instance's own, unrelated to the provider policy declares. The order of `paths` therefore decides where signing in leaves somebody, though it never changes what the policy gates. -!!! note - - A browser holds one session per instance, whichever interactive policy - established it, so signing in with a second one ends the first. Separate - browser profiles are the way to hold both at once. - - Signing out ends the provider's session too, where the provider offers to - end it, so signing in again asks who you are rather than admitting whoever - used the browser last. It takes the session from the browser rather than - revoking it, since a session is a sealed value this instance keeps no - record of, which is what lets it stay stateless and scale horizontally. A - copy taken beforehand therefore stays usable until it expires, which is why - sessions are short-lived. +A browser holds one session per instance, whichever interactive policy +established it, so signing in with a second one ends the first. A session lasts +an hour and renews without anybody noticing, by sending the browser back to the +provider, which answers without displaying anything where the sign-in still +stands. Only a navigation renews: a script calling the API with an expired +session is denied plainly rather than redirected, since a redirect chain to an +identity provider is not something it can follow. + +Because a session is a sealed value rather than a record, nothing can be struck +out. Signing out takes the session from the browser, so a copy taken beforehand +stays usable until it expires. Ending every session at once means leaving a +single new secret in `sessionSecrets`, dropping the ones sessions were signed +under, and restarting. That is the only immediate lever there is, and note that +merely adding a secret does the opposite: existing sessions keep working, which +is what makes rotation invisible. There is deliberately no OpenID Connect +Back-Channel Logout, since acting on one means keeping a record of which +sessions are dead and consulting it on every request, which is the round trip +statelessness avoids. !!! warning @@ -622,14 +645,6 @@ sign in through their identity provider to reach it: admits both a machine that presents a credential and a user who signs in, so one endpoint can serve continuous integration and users at once. -!!! note - - The session signing secret is the instance's own, unrelated to the provider. - To rotate it without invalidating live sessions, the environment variable may - hold several secrets, one per line, newest first. New session cookies are - signed with the first, while existing cookies are accepted against any, so an - old secret can be retired once the sessions signed with it have expired. - ## Extends The `extends` property enables configuration inheritance, allowing you to build