Claude Fable 5.1 & GPT-6 Astra packages are live

Authentication

Free

Wiring authentication into a backend service — session versus token, the verification middleware, refresh and rotation, and multi-tenant identity.

196 lines8.5 KB Qwen Backend
targetModels
Qwen3.8-MaxQwen3.8-Flash-NextQwen3.8-27BQwen3.8 FamilyFuture Qwen Models
name
authentication
category
Backend
description
Wiring authentication into a backend service — session versus token, the verification middleware, refresh and rotation, and multi-tenant identity.
license
MIT
author
Agent.md maintainers
last-verified
reviewed-by
unreviewed
<!-- Generated from models/_canonical by scripts/build-model-variants.js. Edit the canonical source, not this file. Behavioural profile for Qwen: scripts/model-profiles.json -->

#Task boundary

  1. Implement only what the task names; no extra abstractions or files.
  2. English-only comments and identifiers.
  3. Stop when the checklist passes.

#Purpose

Rules for implementing authentication in a backend service: choosing a mechanism, verifying credentials on each request, and managing session lifetime.

Credential storage, password policy and MFA are Security/authentication. Token format specifics are Security/jwt. This package is the server-side plumbing.


#Choose the mechanism from the client

ClientMechanismWhy
First-party browser appOpaque session id in an HttpOnly cookieRevocable instantly; invisible to JavaScript
First-party mobile appRefresh token in the OS keystore + short access tokenNo cookie jar; needs explicit rotation
Third-party integrationOAuth 2.0, or a scoped API keyRevocable per integration, auditable
Service to servicemTLS, or a short-lived signed tokenNo long-lived shared secret

Default to server-side sessions. They can be revoked in one DELETE, they carry no claims that go stale, and they are a cookie the browser handles for you.

Reach for JWTs only when statelessness is a genuine requirement — and then accept that a JWT cannot be revoked before it expires, which is why access tokens must be short-lived (5–15 minutes) and paired with a revocable refresh token.

ini
Set-Cookie: sid=<128-bit random>; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=1209600

Never put a session or access token in localStorage. Any XSS then becomes full account takeover. → Security/xss


#The verification middleware

Authentication runs once, early, for every request, and establishes exactly one thing: who is calling.

ts
app.use(async (req, res, next) => {
  const sid = req.cookies.sid;
  if (!sid) return next();                       // anonymous; authorization decides
  const session = await sessions.get(sid);       // single lookup, cached briefly
  if (!session || session.expiresAt < Date.now()) return next();

  req.auth = { userId: session.userId, tenantId: session.tenantId,
               scopes: session.scopes, sessionId: sid };
  next();
});
  1. It establishes identity; it does not decide access. That is Security/authorization.
  2. Register it before any route. → Backend/middlewares
  3. Default-deny: apply requireAuth globally and mark public routes explicitly, so a new route is protected by default.
  4. Never read identity from the request body or a client-supplied header such as X-User-Id. Only the verified credential establishes it.
  5. Put userId, tenantId and sessionId into async context so logging and authorization reach them without threading parameters.

For bearer tokens, verify the signature against a cached JWKS with a refresh interval — a network fetch per request is a hard dependency on the identity provider for every single call.


#Sessions and refresh

  1. Rotate the identifier on login, on logout, and on any privilege change. Reusing the pre-login id is session fixation.
  2. Enforce both an idle timeout and an absolute lifetime. Idle alone lets a stolen token live indefinitely under automated use.
  3. Logout deletes server-side state. Clearing the cookie is not logout; a captured token remains valid until natural expiry.
  4. Rotate refresh tokens on use, and store them hashed. Detect reuse of an already-consumed refresh token: that means it was stolen, so revoke the entire token family and force re-authentication.
  5. Invalidate every session on password change, except optionally the one making the change.
  6. Keep a session list per user with device, IP and last-seen, and let users revoke individual sessions. This is both a security control and the feature users ask for.

#Multi-tenant identity

The tenant is part of the identity, resolved server-side, and never taken from the request.

ts
// Every query is scoped by the session's tenant, not by a parameter
const order = await db.order.findFirst({
  where: { id: req.params.id, tenantId: req.auth.tenantId },
});

Never accept tenantId from a header, body or query parameter. A client-supplied tenant is horizontal privilege escalation in one line.

When a user belongs to several tenants, the active tenant is part of the session, changed by an explicit endpoint that rotates the session id.

Impersonation ("log in as customer") must record the real actor alongside the impersonated one, be time-limited, and be audit-logged on every request. → Security/audit-log


#Failure behaviour

  1. 401 for missing or invalid credentials; 403 for authenticated but not permitted. Returning 403 to an anonymous caller confirms the resource exists.
  2. Identical responses for unknown user and wrong password, in body, status and timing. Any difference is a user-enumeration oracle.
  3. Rate limit login attempts on both account and IP. Per-IP alone does not stop distributed credential stuffing; per-account alone lets one host spray many accounts. → API/rate-limiting
  4. Never lock an account permanently on failed attempts — that is a denial-of-service primitive against your own users. Use temporary backoff.
  5. Log every authentication failure, success, logout and privilege change with actor, source IP and user agent.

#Anti-patterns

Anti-patternWhy it failsFix
Token in localStorageXSS becomes account takeoverHttpOnly cookie
JWT chosen by defaultCannot be revoked before expiryServer-side sessions
Long-lived access tokensA stolen token stays valid for hours5–15 minutes plus refresh
Identity from a request headerClient-controlledVerified credential only
tenantId from the requestHorizontal privilege escalationTenant from the session
Per-route opt-in authOne forgotten line is an open endpointDefault-deny globally
Session id reused after loginSession fixationRotate on privilege change
Idle timeout onlyStolen token lives forever under useAbsolute lifetime too
Logout clears only the cookieCaptured token still validDelete server-side state
Refresh tokens not rotatedA stolen refresh token is permanentRotate on use; detect reuse
Refresh tokens stored plaintextDB leak yields live sessionsStore hashed
Distinct errors for unknown userEnumeration oracleOne identical failure response
JWKS fetched per requestHard dependency on the IdP for every callCache with refresh
Permanent lockoutSelf-inflicted DoSTemporary backoff
No session inventoryUsers cannot revoke a stolen sessionPer-user session list

#Checklist

  • The mechanism is chosen per client type and written down
  • Browser sessions use HttpOnly; Secure; SameSite cookies
  • No token is stored in localStorage or sessionStorage
  • Access tokens, where used, are short-lived and paired with refresh tokens
  • Authentication middleware runs before all routes and only establishes identity
  • Routes are default-deny with explicitly marked public exceptions
  • Identity and tenant are never read from client-supplied fields
  • Identity is carried in async context, not threaded parameters
  • JWKS or key material is cached with a refresh interval
  • Session identifiers rotate on login, logout and privilege change
  • Both idle and absolute expiry are enforced
  • Logout deletes server-side session state
  • Refresh tokens rotate on use, are stored hashed, and reuse triggers revocation
  • All sessions are invalidated on password change
  • Users can list and revoke their own sessions
  • Impersonation records the real actor and is time-limited and audited
  • 401 and 403 are used correctly; failures are indistinguishable
  • Login is rate limited on both account and IP, with temporary backoff