A monolith’s authentication system can feel like a single, monolithic entity itself, but adding session and JWT authentication reveals it’s actually a surprisingly flexible beast once you understand how its components interact.

Let’s see it in action. Imagine a user logs in.

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

{
  "username": "alice",
  "password": "securepassword123"
}

On the server-side, this triggers a process. The username and password are validated against a user store. If they match, the system needs to remember that "alice" is logged in. This is where session and JWT come in.

Session Authentication:

  1. Server Generates Session ID: The server creates a unique, random string (e.g., a3f7b9d1e8c2a0f5).

  2. Stores Session Data: It stores this session ID in a server-side store (like Redis or in-memory) along with user-specific data (e.g., {"user_id": 123, "username": "alice", "role": "admin"}).

  3. Sends Session ID to Client: The session ID is sent back to the client via a Set-Cookie header.

    HTTP/1.1 200 OK
    Set-Cookie: session_id=a3f7b9d1e8c2a0f5; HttpOnly; Secure; SameSite=Strict
    Content-Type: application/json
    
    {
      "message": "Login successful"
    }
    
  4. Client Stores Cookie: The browser automatically stores this cookie.

  5. Subsequent Requests: On every subsequent request to the same domain, the browser sends the session_id cookie back to the server.

    GET /profile HTTP/1.1
    Host: example.com
    Cookie: session_id=a3f7b9d1e8c2a0f5
    
  6. Server Validates Session: The server looks up the session_id in its store. If found and not expired, it knows who the user is and can authorize the request.

JWT (JSON Web Token) Authentication:

  1. Server Generates JWT: The server creates a token containing user claims and signs it with a secret key. A JWT typically looks like this: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik1pY2hhZWwgQ2FsbGFoYW4iLCJpYXQiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c.

    • Header: {"alg": "HS256", "typ": "JWT"} (algorithm and token type)
    • Payload: {"sub": "1234567890", "name": "Michael Callahan", "iat": 1516239022} (user ID, name, issued at timestamp)
    • Signature: Created by signing the header and payload with a secret key (e.g., your-super-secret-key).
  2. Sends JWT to Client: The JWT is sent back to the client, usually in the response body or as a Set-Cookie.

    HTTP/1.1 200 OK
    Content-Type: application/json
    
    {
      "message": "Login successful",
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik1pY2hhZWwgQ2FsbGFoYW4iLCJpYXQiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
    }
    
  3. Client Stores Token: The client stores this token (e.g., in localStorage or sessionStorage).

  4. Subsequent Requests: The client includes the JWT in the Authorization header of subsequent requests.

    GET /profile HTTP/1.1
    Host: example.com
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik1pY2hhZWwgQ2FsbGFoYW4iLCJpYXQiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
    
  5. Server Validates JWT: The server receives the token, splits it into its parts, verifies the signature using its secret key, and checks any expiration claims. If valid, it trusts the claims within the token (like sub for user ID) and authorizes the request.

The Mental Model:

At its core, authentication is about proving identity and then maintaining that proof across multiple interactions.

  • Session Authentication is like a bouncer at a club giving you a wristband. The wristband (session ID) doesn’t contain any information about you, but the bouncer (server) has a list of who has wristbands and can look you up. The wristband is stored by the club (browser cookie), and you show it to the bouncer every time you want in.
  • JWT Authentication is more like a passport. The passport (JWT) itself contains verifiable information about you (your claims). The passport is signed by an authority (your secret key) so anyone can check if it’s legitimate without needing to call the authority directly. You carry this passport and present it for verification.

The choice between them often hinges on state management. Sessions require server-side storage, which can be a bottleneck or complexity in distributed systems. JWTs are stateless on the server once issued; the server only needs the secret key to verify. This makes JWTs excellent for microservices or APIs that need to be consumed by many different clients. However, managing JWT expiration and revocation can be more complex than simply deleting a session.

The surprising truth is that both methods are fundamentally about establishing a trusted assertion of identity. Sessions establish trust by deferring verification to a server-side lookup, while JWTs establish trust through cryptographic proof embedded within the token itself.

When you use JWTs, the payload is base64 encoded, not encrypted. This means anyone can decode the payload and read the claims. For sensitive information, you should use encrypted JWTs (JWE) or ensure sensitive data is only referenced by an ID and retrieved from a secure, server-side store.

Want structured learning?

Take the full Monolith course →