Skip to main content

Module browser_token

Module browser_token 

Source
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; SameSite cookie that JavaScript can never read. The browser silently mints a fresh access JWT from it at the /auth/browser/refresh endpoint.

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§

BrowserIdentity
The identity carried by a validated browser token.
BrowserTokenAuthority
Mints and verifies the hybrid-token pair for the browser credential layer. Cheap to clone the Arc the runtime holds; the keys inside are derived once at construction.
BrowserTokenConfig
Configuration for the hybrid-token authority. Secure by default: Secure cookies, SameSite=Strict, a short access TTL, and an HttpOnly refresh cookie.
IssuedTokens
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§

BrowserTokenError
Reasons a token is refused. Kept distinct so the WS handshake and the refresh endpoint can log why without leaking detail to the client.
SameSite
SameSite cookie attribute for the refresh cookie. Strict is 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/None exist for deployments that serve the SPA from a different site; None requires Secure (enforced in BrowserTokenConfig::sanitised).
TokenType
Which leg of the hybrid pair a token is. Stamped into the typ claim 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 + .).