Page cover

Tokens

A JWT (JSON Web Token) is a compact, URL-safe token format used to securely transmit information between parties as a JSON object. It is digitally signed, so it can be verified and trusted. JWTs are commonly used for authentication and authorization in web applications.

Structure of a JWT

A JWT consists of three parts separated by dots (.):

  1. Header: Contains metadata about the token, such as the signing algorithm (e.g., HMAC SHA256 or RSA).

    {
      "alg": "HS256",
      "typ": "JWT"
    }
  2. Payload: Contains the claims, which are statements about an entity (e.g., user) and additional data. Claims can be:

    • Registered claims: Standard claims like iss (issuer), exp (expiration time), sub (subject), etc.

    • Public claims: Custom claims defined by the application.

    • Private claims: Custom claims agreed upon between parties.

    {
      "sub": "1234567890",
      "name": "John Doe",
      "admin": true
    }
  3. Signature: Ensures the token's integrity. It is created by signing the encoded header and payload using a secret key or a public/private key pair.

    HMACSHA256(
      base64UrlEncode(header) + "." + base64UrlEncode(payload),
      secret
    )

Example of a JWT

Here’s an example of a JWT:

  • Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

  • Payload: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

  • Signature: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

How JWTs Work in Web Applications


Mastering MERN Auth: The "Silent Refresh" Dance of Access and Refresh Tokens

If you are building a modern web application like a Spotify clone in the MERN stack, you face a critical challenge: How do you keep a user logged in securely?

You want the music to keep playing for hours without interrupting the user to ask for their password again. But you also don't want a security nightmare where a stolen token gives a hacker permanent access to an account.

The industry-standard solution to this balancing act involves JSON Web Tokens (JWTs) and a "two-token strategy" separating short-lived Access Tokens from long-lived Refresh Tokens.


1. Why does a JWT change every time?

A common point of confusion is that if you sign the exact same user data with the exact same secret key, you get a different token string every second.

Why? Because JWT libraries automatically add temporal data to the payload:

  1. iat (Issued At): The exact second the token was created.

  2. exp (Expiration): The exact second the token will become invalid.

Because time never stops moving, the payload changes every second. Because the payload changes, the final token signature changes. This ensures tokens are "fresh" and helps prevent replay attacks.


2. The Two-Token Strategy: Speed vs. Security

If we just used one long-lasting JWT, and a hacker stole it (perhaps via an XSS attack), they would have access to that account forever until it expired.

To solve this, we split the responsibility into two different tokens with very different jobs.

Token A: The Access Token (The "Backstage Pass")

  • Role: The high-speed pass used for accessing resources. You send this when you want to "Like a Song," "Create a Playlist," or "Follow an Artist."

  • Lifespan: Short (e.g., 15 minutes to 1 hour).

  • Where it lives: In the React State (Memory) of your frontend application.

  • Security: Because it lives in memory, it is wiped clean if the user closes the tab or refreshes the page. It is generally safe from XSS attacks because JavaScript can't read other tabs' memory.

  • Backend Verification: The server verifies its signature quickly using middleware. It usually does not require a database lookup, making API calls fast.

Token B: The Refresh Token (The "Golden Ticket")

  • Role: A highly secure credential whose only job is to obtain a new Access Token when the old one dies.

  • Lifespan: Long (e.g., 7 days, 30 days, or longer).

  • Where it lives: In an httpOnly, Secure Cookie in the user's browser.

  • Security: Crucially, an httpOnly cookie cannot be read by JavaScript. Even if a hacker injects malicious code into your site, they cannot steal this token. The browser, however, automatically sends this cookie to your server when asking for a refresh.

  • Backend Verification: The server verifies its signature AND usually checks if it exists in the Database. This allows you to revoke access (e.g., "Log out of all devices" deletes the Refresh Token from the DB).


3. The "Silent Refresh" Workflow

So, how do we make it feel like the user is logged in forever if the Access Token dies every 15 minutes? We use a flow often called the "Silent Refresh."

Here is what happens when a user is listening to music on your Spotify clone and their access token expires halfway through a playlist:

  1. The Fail: The frontend tries to fetch the next song using the expired Access Token stored in memory.

  2. The Rejection: Your Express backend middleware sees the expired token and instantly returns a 401 Unauthorized error.

  3. The Interception: Your frontend (using something like an Axios Interceptor) notices the 401 error. It realizes, "Uh oh, my token died." It pauses the music request.

  4. The Handshake: The frontend sends a request to a special backend route: /api/auth/refresh. It sends no data in the body.

  5. Cookie Magic: The browser notices the request to your domain and automatically attaches the httpOnly Refresh Cookie that was set during initial login.

  6. The Renewal: The backend receives the cookie, verifies the Refresh Token against the database, and if everything is good, generates a brand new Access Token and sends it back.

  7. The Retry: The frontend receives the new Access Token, saves it into React State, and retries the original song request.

The music starts playing again. The user never saw a loading screen or a login box.


4. Backend Architecture: The Two Pillars

To implement this secure flow (if you aren't using a provider like Clerk), your Express backend needs two distinct pieces of architecture to handle these two tokens.

Pillar 1: The Verifier Middleware (The "Bouncer")

This protects your standard API routes (/api/songs, /api/albums). It only cares about the Access Token.

Pillar 2: The Refresh Endpoint (The "Ticket Office")

This is a dedicated route dedicated solely to handling the Refresh Token.


Backend Implementation (Node.js & Express)

In this manual setup, we use jsonwebtoken to sign tokens and cookie-parser to handle the Refresh Token cookie.

1. The Token Generation Utility

Create a helper to sign both tokens.

2. The Login Controller

This sends the Access Token in the JSON body and the Refresh Token in a secure cookie.

3. The Refresh Endpoint

This is the "emergency" route the frontend calls when the Access Token expires.


Frontend Implementation (React & Axios)

On the frontend, we use an Axios Interceptor to catch 401 errors and silently refresh the token.

1. The API Instance


JWT vs. Session-Based Auth — Which One Wins?

While we've focused on the JWT (Stateless) approach, the traditional Session (Stateful) approach is still widely used and, in some cases, superior for security.

1. The Core Difference: Where is the "Truth"?

  • Session Approach (Stateful): The "Source of Truth" lives on your Server. The server keeps a record of every logged-in user in its memory or a database (like Redis). The user just gets a "Session ID" to prove they have a record on the server.

  • JWT Approach (Stateless): The "Source of Truth" lives inside the Token itself. The server doesn't "remember" you; it just trusts the token you carry because it recognizes its own signature.


2. How the Session Approach Works

  1. Login: User sends credentials.

  2. Creation: The server verifies the user and creates a Session Record in the database.

  3. The Handover: The server sends back a Set-Cookie header containing a unique Session ID.1

  4. Storage: The browser automatically stores this cookie.

  5. Requests: For every future request, the browser automatically sends the Session ID cookie.2

  6. The Lookup: The server receives the ID, looks it up in the database to see if it’s still valid, and then responds.


3. Comparison Table: JWT vs. Sessions

Feature

JWT (Stateless)

Session (Stateful)

Scalability

High (No DB lookup needed for every request).

Lower (Requires a DB/Redis lookup every time).

Revocation

Difficult (Token is valid until it expires).

Easy (Just delete the session from the DB).

Data Size

Larger (Contains payload + signature).

Tiny (Just a small ID string).

Security

Susceptible to XSS if not handled right.

Highly secure; IDs are useless without the server record.


Last updated