Stage 7 · Master
Authentication and Authorization
Refresh Token Rotation
Why short-lived access tokens need a second credential, how rotation turns refresh tokens from long-lived bearer bombs into trackable session families, and what identity-service should eventually implement without pretending the flow already exists.
Why Refresh Tokens Exist
Access tokens should be short-lived because bearer-token theft is a realistic threat. Short lifetimes limit damage. They also create a usability problem: users should not have to sign in again every few minutes. Refresh tokens exist to solve that tension. They let the client obtain a new short-lived access token without replaying the user's primary credential each time.
The mistake is to stop reasoning there. A refresh token is itself a sensitive bearer credential. If it is static, long-lived, and silently replayable, the system has simply moved the problem from one token type to another. That is why serious backends treat refresh tokens as stateful session artifacts rather than as immortal strings handed to the browser and forgotten.
| Pattern | What seems convenient | Security reality |
|---|---|---|
| Long-lived access token only | Few auth round trips | A stolen access token stays useful far too long |
| Static refresh token | Easy client code and easy server code | Replay remains valid until expiry, so theft becomes a shadow session |
| Rotating refresh token family | More server bookkeeping | Chosen because each successful refresh invalidates the previous credential |
Rotation and Token Families
Rotation means that every successful refresh consumes one current refresh token and returns a successor. The backend therefore needs server-side state: which token is current for this session family, when it expires, whether the family is revoked, and whether a previously spent token was presented again. That state is what makes replay detection possible. Without it, the server cannot distinguish a valid refresh from an old stolen token being reused.
Workflow
Present: The client sends the current refresh token to request a new access token and successor refresh token.
Step 1 / 4 — Present
create table refresh_sessions (
id uuid primary key,
family_id uuid not null,
user_id uuid not null,
tenant_id uuid not null,
current_token_id uuid not null unique,
current_token_hash text not null,
expires_at timestamptz not null,
last_rotated_at timestamptz not null default now(),
revoked_at timestamptz,
revoke_reason text,
created_at timestamptz not null default now()
);
create index idx_refresh_sessions_family on refresh_sessions (family_id);
create index idx_refresh_sessions_user_tenant on refresh_sessions (user_id, tenant_id);Only the token hash is stored, not the plaintext refresh secret. The family_id groups successive refresh credentials into one logical session lineage.
The table includes tenant_id because Meridian authorization is tenant-scoped. A session may need to know which tenant context its access token should refresh into, or which tenant-specific membership snapshot it represents. Other systems may model sessions at the user level and choose tenant context later. The important lesson is not that one design is universally correct, but that session scope should match authorization scope deliberately.
Transactional Refresh
Refresh rotation is a concurrency problem, not just a token-format problem. Browsers open multiple tabs, mobile apps retry, networks duplicate submissions, and attackers replay stolen credentials. Two requests can present the same refresh token nearly simultaneously. The system therefore needs a transactional winner. One request should advance the family. The loser should be rejected, and in many designs the entire family should be revoked because reuse is now evidence of credential escape.
Narrowing Breach Impact
Refresh token rotation is fundamentally about breach containment. If a current refresh token is stolen and reused after the legitimate client has already rotated it, the server learns something valuable: the credential left the safe path. The safest response is usually to revoke that session family and force re-authentication for that device or browser session. That is inconvenient, but it is much less inconvenient than silently allowing the attacker to keep refreshing indefinitely.
This conservative stance becomes easier to justify once access tokens are short-lived and refresh flows are infrequent relative to ordinary API calls. The hot path remains stateless enough for performance. The sensitive renewal path is allowed to be stateful because that is where revocation and replay detection actually matter.
Current Repository Status
identity-service currently has no auth endpoints, no session table, and no Postgres integration. This lesson describes the production-grade design Meridian should adopt when authentication is implemented; it is not a summary of current code.
- Keep access tokens short-lived and use refresh tokens for session continuity.
- Store refresh state server-side so reuse and revocation become possible.
- Rotate the token family transactionally because concurrent refresh attempts are normal.
- Treat refresh-token reuse as a security signal, not merely as a stale retry inconvenience.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.