Authentication and Session Management

Implement secure authentication and session management in backend services. Covers JWT vs. sessions, OAuth2 flows, token lifecycle, session storage, and security hardening.

Authentication and Session Management is the foundation of secure, stateful interactions between clients and backend services. It determines how users prove their identity, how systems track their activity across requests, and how access to resources is controlled and scaled. This page covers the core mechanics of authenticating users and managing their sessions: from initial login through token renewal, session invalidation, and handling failures. It assumes a distributed system with multiple services, mobile clients, and API endpoints.

User Login and Token Acquisition

A user begins authentication by submitting credentials—typically username and password—to a /login endpoint. The server validates these, creates a session, and returns a token.

POST /login HTTP/1.1
Host: api.example.com
Content-Type: application/json

{
  "username": "[email protected]",
  "password": "s3cr3t!"
}

On success, the server responds with an Authorization header containing a Bearer token:

HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: session_id=abc123xyz; Path=/; HttpOnly; Secure; SameSite=Lax
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZUBleGFtcGxlLmNvbSIsImV4cCI6MTcwMDAwMDAwMH0.4N1vS9m2iX5YJ1TgkRnKZ5n1f4uXp5n3d2bKs1q5s3c

{
  "user": {
    "id": 42,
    "name": "Alice",
    "email": "[email protected]"
  },
  "expires_in": 3600
}

The client stores the Authorization header value and includes it in all subsequent requests:

GET /profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZUBleGFtcGxlLmNvbSIsImV4cCI6MTcwMDAwMDAwMH0.4N1vS9m2iX5YJ1TgkRnKZ5n1f4uXp5n3d2bKs1q5s3c

Token Formats and Structure

Tokens are typically JSON Web Tokens (JWT), but can be opaque or signed. A JWT consists of three parts: header, payload, and signature, separated by dots.

{
  "alg": "HS256",
  "typ": "JWT"
}
{
  "sub": "[email protected]",
  "exp": 1700000000,
  "iat": 1699996400,
  "scope": "read write"
}

The signature is computed over the base64-encoded header and payload, using a secret key. The client verifies the signature locally or delegates it to the server.

Token Renewal

Tokens have a finite lifespan. When exp (expiration time) is near, clients must refresh the token. The most common method is the refresh_token flow.

The server returns a refresh_token alongside the initial access_token:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZUBleGFtcGxlLmNvbSIsImV4cCI6MTcwMDAwMDAwMH0.4N1vS9m2iX5YJ1TgkRnKZ5n1f4uXp5n3d2bKs1q5s3c",
  "refresh_token": "rft_abc123xyz",
  "expires_in": 3600,
  "refresh_expires_in": 604800
}

To renew the token:

POST /refresh HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZUBleGFtcGxlLmNvbSIsImV4cCI6MTcwMDAwMDAwMH0.4N1vS9m2iX5YJ1TgkRnKZ5n1f4uXp5n3d2bKs1q5s3c

{
  "refresh_token": "rft_abc123xyz"
}

The server validates the refresh_token, issues a new access_token, and optionally updates the refresh_token:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZUBleGFtcGxlLmNvbSIsImV4cCI6MTcwMDA0MDAwMH0.8K3qNwDz1m8vJ7yH1lX1cDv5XpKs2h3iF1nK4c1v4p1",
  "refresh_token": "rft_def456uvw",
  "expires_in": 3600
}

The client uses the new access_token and stores the new refresh_token for future renewals.

Silent Token Renewal

For single-page applications (SPAs) and mobile apps, clients can renew tokens in the background without blocking UI. This is achieved via a refresh_token stored in memory or local storage.

On every request, the client checks the access_token's exp value. If it is within 5 minutes of expiry, the client automatically sends a refresh request before dispatching the next API call.

Failure modes:

Session Management and State Tracking

Sessions link a user’s identity across multiple requests and services. The session state is stored either in memory, in a database, or in a distributed cache.

Session Storage Options

Sessions can be stored in:

CREATE TABLE sessions (
  id VARCHAR(255) PRIMARY KEY,
  user_id INT NOT NULL,
  expires_at TIMESTAMPTZ NOT NULL,
  data JSONB,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

Each session has a unique id, often used as a session_id cookie.

Set-Cookie: session_id=abc123xyz; Path=/; HttpOnly; Secure; SameSite=Lax

Session Invalidation

When a user logs out, changes password, or a security event occurs, sessions must be invalidated.

Immediate Invalidation

On logout, the server removes the session from storage and sets the Expires header on the session_id cookie:

Set-Cookie: session_id=abc123xyz; Path=/; HttpOnly; Secure; SameSite=Lax; Expires=Thu, 01 Jan 1970 00:00:00 GMT

The client sends the session_id cookie with every request. The server checks the session ID in storage. If the session is missing or expired, it returns a 401 error:

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "error": "session_expired",
  "message": "The session has expired or was invalidated."
}

Graceful Session Expiration

The server can track session expiration via expires_at in the sessions table. On each request, it checks if now() > expires_at. If so, it sends a 410 Gone:

HTTP/1.1 410 Gone
Content-Type: application/json

{
  "error": "session_expired",
  "message": "The session has expired. Please log in again.",
  "retry_after": 300
}

Clients can use retry_after to prompt users to log in or refresh the session.

Token-Based Sessions

Instead of session_id cookies, systems can use JWTs as session tokens. The JWT contains the user’s identity and session metadata.

{
  "sub": "[email protected]",
  "session_id": "abc123xyz",
  "exp": 1700000000,
  "iat": 1699996400,
  "revoked": false
}

The server validates the JWT on every request. On logout or revocation, it sets revoked: true in the JWT and updates it in storage (e.g., Redis). Clients check revoked on each request and refresh the token if needed.

Multi-Service Session Consistency

In a microservices architecture, a user’s session must be consistent across services. Each service validates the access_token independently.

Services can validate JWTs locally using a public key (e.g., from a jwks.json endpoint):

GET /.well-known/jwks.json HTTP/1.1
Host: auth.example.com
{
  "keys": [
    {
      "kty": "RSA",
      "kid": "abc123",
      "use": "sig",
      "n": "1234567890...",
      "e": "AQAB"
    }
  ]
}

Services can verify the JWT signature using the n and e values from the key.

Failure modes:

Session Invalidation Strategies

A TRL entry:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZUBleGFtcGxlLmNvbSIsImV4cCI6MTcwMDAwMDAwMH0.4N1vS9m2iX5YJ1TgkRnKZ5n1f4uXp5n3d2bKs1q5s3c",
  "revoked_at": "2023-11-15T10:00:00Z",
  "reason": "user_logout"
}

Services can check the TRL via a GET /revoked?token={token} endpoint.

Failure Modes and Edge Cases

Configuration and Best Practices

A typical auth service configuration includes:

auth:
  token:
    secret: "super-secret-key-for-jwt-signing"
    ttl: 3600
    refresh_ttl: 604800
    issuer: "api.example.com"
    audience: "api.example.com"
    jwks:
      url: "https://auth.example.com/.well-known/jwks.json"
      cache_ttl: 300
  session:
    storage: "redis"
    redis:
      host: "redis.example.com"
      port: 6379
      database: 0
    cookie:
      name: "session_id"
      path: "/"
      http_only: true
      secure: true
      same_site: "Lax"
      max_age: 3600
    timeout: 1800
    cleanup_interval: 300

The server validates the Authorization header using the following steps:

  1. Parse the Authorization header.
  2. Extract the Bearer token.
  3. Validate the JWT signature using the secret or jwks.json.
  4. Check exp, nbf, iss, and aud.
  5. Retrieve the session from storage using session_id.
  6. Return 401 if any validation step fails.

Clients should:

Session management is not just authentication. It is the glue that binds user identity to service state across time and services.

This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.