JWTs are a surprisingly simple way to transmit signed, self-contained pieces of information between parties, and their core mechanism for security is something most developers fundamentally misunderstand.

Let’s see one in action. Imagine a user logs into your application. Your backend authenticates them, then issues a JWT. This token might look like:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

This is actually three parts, Base64Url encoded and separated by dots:

  1. eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  2. eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
  3. SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Let’s decode the first two parts using base64url (it’s slightly different from standard Base64):

Part 1 (Header):

{
  "alg": "HS256",
  "typ": "JWT"
}

This tells us the algorithm used for signing (HS256, HMAC using SHA-256) and that this is a JWT.

Part 2 (Payload/Claims):

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

This contains the actual information (claims) about the subject (sub), their name, and when the token was issued (iat - issued at). You can put almost any JSON data here, but be mindful of what you expose.

Part 3 (Signature): This is the crucial part. It’s generated by taking the encoded header and payload, concatenating them with a dot, and signing them using the secret key and the algorithm specified in the header. The signature is then Base64Url encoded.

The server that issued the token knows the secret key. When a client sends this token back (e.g., in an Authorization: Bearer <token> header), the server can:

  1. Decode the header and payload.
  2. Verify the signature. It does this by taking the decoded header and payload, re-signing them with its own secret key using the specified algorithm, and comparing the newly generated signature with the one that came with the token. If they match, the token is valid and hasn’t been tampered with.

The core problem JWTs solve is stateless authentication. Instead of the server needing to query a database for every request to check if a user is logged in, the token itself contains enough information (or a reference to it) and proof of its authenticity.

The Mental Model:

Think of a JWT like a sealed envelope with a tamper-evident seal.

  • Header: The "label" on the envelope, stating its contents type and how it was sealed.
  • Payload: The "letter" inside, containing the actual information.
  • Signature: The "tamper-evident seal." It’s unique to the contents and the sealer.

If someone tries to open the envelope and change the letter, the seal will break. If someone tries to forge a new seal without knowing the original sealer’s tools (the secret key), it won’t match.

The key takeaway is that the signature is not encryption. It’s a cryptographic proof of integrity and authenticity. Anyone can decode the payload. The signature only tells you that the payload hasn’t been altered since it was signed by the party holding the secret key.

This is why, when using HMAC (like HS256), the secret key must be kept absolutely secret on the server. If the secret key is compromised, an attacker can forge any token they want, containing any payload they desire, and your server will blindly trust it. For scenarios where the server issuing the token doesn’t need to verify the token itself (e.g., when a separate authorization server issues tokens that your API just needs to trust), you’d use asymmetric algorithms like RS256, where the server signs with a private key and others verify with a publicly available public key.

One of the most common pitfalls is misunderstanding what goes into the payload. While you can put sensitive user data there, remember that the payload is only Base64Url encoded, not encrypted. Anyone who gets the token can read it. Therefore, sensitive information like passwords or credit card numbers should never be placed in the JWT payload. Instead, include identifiers (like a user ID) and let your application fetch the necessary sensitive details from a secure backend store using that ID.

A common follow-up problem after implementing JWTs is managing token expiration and revocation.

Want structured learning?

Take the full Http course →