ADR 0011: Anonymous State Token Policy
ADR 0011: Anonymous State Token Policy
Status: Accepted for architectural direction.
Date: 2026-05-11.
Context
The project should let students preserve planning state without requiring email accounts.
The user proposed a one-time secret string that students keep and use to return to the site.
That idea fits the project scope.
It also creates a password-equivalent secret.
Anyone with the secret can access the state.
OWASP session guidance emphasizes unpredictable token values, meaningless token contents, avoiding URL token transport, and avoiding sensitive token logging: OWASP Session Management Cheat Sheet.
Go implementations can generate token randomness with crypto/rand: Go crypto/rand.
Go implementations can derive keyed verifiers with crypto/hmac: Go crypto/hmac.
The project should be honest about this tradeoff.
The design should minimize account friction without being careless with state access.
Decision
The backend will support anonymous state records.
Creating a state record returns a generated state_token once.
The state_token is a secret access key.
The token must contain at least 256 bits of randomness.
The v1 recommended generation shape is 32 random bytes encoded as base64url without padding.
The token must be generated by the server using a cryptographically secure random source.
The token must be encoded as base64url without padding.
The token must not contain user data.
The token must not encode the state id in reversible form.
The token must not be called a hash in user-facing text.
The backend stores only a state_token_verifier.
The recommended verifier is a keyed digest of the token.
The state database stores the verifier, key version, state id, creation time, and optional last-used time.
The backend accepts the token through an Authorization header for API requests.
The backend must not accept the token in URL query parameters.
The backend must not log the raw token.
The backend must redact authorization headers from logs.
The backend should rate limit failed token checks.
The backend should allow state export.
The backend should allow state deletion with explicit confirmation.
Browser Flow
The simplest version can ask the user to paste or provide the token when restoring state.
The frontend may keep the token in memory for the active browser session.
If persistent same-browser access is needed, the preferred later flow is:
- User enters the state token.
- Backend verifies the token.
- Backend creates a browser session handle.
- Backend sets the handle in an HttpOnly, Secure, SameSite cookie.
- Frontend uses same-origin requests without reading the cookie.
In that flow, the long-term state token remains the user’s recovery secret.
The cookie is a session handle.
The cookie should not contain the raw long-term token.
Consequences
Students can use the app without email.
The backend stores less personal information.
The user must keep the token safely.
Losing the token can mean losing access unless export or local backup exists.
Sharing the token shares access.
There is no account recovery in version 1.
The system must communicate the token’s importance plainly.
The backend can rotate verifier keys by storing verifier key versions.
The project can later add optional accounts without changing the basic state model.
Alternatives Considered
Alternative 1: Email Accounts
The app could require email login.
This would enable recovery and device sync.
However, it adds account infrastructure, privacy burden, email deliverability, and user friction.
This alternative is rejected for version 1.
Alternative 2: User-Chosen Passwords
The app could let users create usernames and passwords.
This would avoid one-time token copying.
However, it creates password reset, credential stuffing, password storage, and support burden.
This alternative is rejected for version 1.
Alternative 3: Token in URL
The app could put the state token in a bookmarkable URL.
This is convenient.
However, URLs leak through browser history, logs, analytics, screenshots, referrers, and sharing.
This alternative is rejected.
Alternative 4: Local-Only Browser Storage
The app could store all state in the browser.
This avoids server-side secrets.
However, state is harder to access across devices.
It is easier to lose by clearing site data.
It also limits backend-owned migration and query workflows.
This alternative may be useful as an export or offline mode later.
It is not the main version 1 persistence design.
Follow-Up
Define token creation and restore API responses.
State deletion confirmation is defined in ADR 0019.
Define token verifier key rotation.
Define frontend copy, warning, and recovery text.
Same-origin cookie sessions are not in the first implementation.