Expand description
Browser credential layer — the hybrid token model (issue #936, PRD #930, ADR 0036 §“Connection security”, ADR 0029 §Authorization).
A browser SPA cannot safely hold a long-lived bearer credential: any token reachable from JavaScript is exfiltrable by an XSS payload. The hybrid model splits the credential in two:
- a short-lived access JWT held only in memory (a JS variable), presented in the RedWire-over-WSS handshake (ADR 0036) exactly where native drivers present a bearer/OAuth-JWT, and
- a long-lived refresh token delivered as an
HttpOnly; Secure; SameSitecookie that JavaScript can never read. The browser silently mints a fresh access JWT from it at the/auth/browser/refreshendpoint.
Both tokens are HS256 JWTs minted and verified by this server with
a single symmetric secret — RedDB is both issuer and verifier, so the
asymmetric RS256/JWKS machinery of super::oauth (which exists to
trust a foreign IdP) is unnecessary weight here. The vetted
jsonwebtoken crate owns signature construction and verification;
this module owns the issuer/audience/type/expiry policy on top.
§Why access-token rotation does not tear down in-flight streams
ADR 0029 §Authorization makes the bearer token authenticate only the
open of a stream; an internal, unforwarded stream lease bound to
the MVCC snapshot pin is the credential consulted for every subsequent
chunk. So when a browser’s access JWT expires and it mints a new one
at /auth/browser/refresh, the new token is used for the next
handshake — the streams already accepted on the live RedWire
connection keep flowing under their leases, untouched. The refresh
cadence is decoupled from result-set delivery time. This module mints
the tokens; that decoupling lives in the stream lease (see
crate::server::output_stream) and is exercised end-to-end by the
issue-#936 integration test.
§What this module deliberately does not do
mTLS stays native-only (ADR 0036): browser client certificates are hostile UX, so there is no browser mTLS path here. The access JWT / refresh cookie pair is the browser’s sole credential.
Structs§
- Browser
Identity - The identity carried by a validated browser token.
- Browser
Token Authority - Mints and verifies the hybrid-token pair for the browser credential
layer. Cheap to clone the
Arcthe runtime holds; the keys inside are derived once at construction. - Browser
Token Config - Configuration for the hybrid-token authority. Secure by default:
Securecookies,SameSite=Strict, a short access TTL, and anHttpOnlyrefresh cookie. - Issued
Tokens - The pair returned to the browser by a successful login / refresh: the access JWT (held in memory) and the refresh JWT (set as a cookie).
Enums§
- Browser
Token Error - Reasons a token is refused. Kept distinct so the WS handshake and the refresh endpoint can log why without leaking detail to the client.
- Same
Site SameSitecookie attribute for the refresh cookie.Strictis the secure default — the refresh cookie is never attached to a cross-site navigation, which is the cleanest CSRF posture for a same-origin SPA.Lax/Noneexist for deployments that serve the SPA from a different site;NonerequiresSecure(enforced inBrowserTokenConfig::sanitised).- Token
Type - Which leg of the hybrid pair a token is. Stamped into the
typclaim and checked on verify so a refresh token can never be replayed as a session credential (and an access token can never be replayed at the refresh endpoint).
Constants§
- MIN_
SECRET_ BYTES - Minimum HS256 secret length. RFC 7518 §3.2 requires a key at least as long as the HMAC output (256 bits / 32 bytes); a shorter key is a silent downgrade of the signature’s security, so we reject it at construction rather than mint weakly-keyed tokens.
Functions§
- cookie_
value - Extract a named cookie’s value from a raw
Cookie:request header. Returns the first match. Cookie values are not URL-decoded — JWT compact serialization is already cookie-safe (base64url +.).