JWT Deep Dive:
Structure, Security & Example Flow

Last updated: April 27, 2025

1. Introduction: What is a JWT?

JSON Web Token, commonly known as JWT (pronounced "jot"), is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information, called "claims," can be verified and trusted because it is digitally signed.

Think of a JWT like a digitally signed passport: it contains verifiable information about the holder (claims), and the signature confirms its authenticity and that it hasn't been tampered with. Because JWTs are compact (small size) and self-contained (include all necessary info within them), they are widely used in web applications and APIs, especially for authentication and authorization scenarios.

2. JWT Structure: The Three Parts

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


Header.Payload.Signature
        

Each part (Header and Payload) is a JSON object that is Base64Url encoded. The Signature is created based on the encoded Header, encoded Payload, a secret or private key, and the algorithm specified in the header.

An example JWT might look like this (line breaks added for readability):


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
        

Let's break down each part.

The header typically consists of two parts: the type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 (HS256) or RSA (RS256).

Example Header JSON:


{
  "alg": "HS256",
  "typ": "JWT"
}
        
  • alg: Specifies the cryptographic algorithm used to sign the token. Common values include HS256 (HMAC using SHA-256, symmetric), RS256 (RSA signature using SHA-256, asymmetric), and ES256 (ECDSA signature using SHA-256, asymmetric).
  • typ: Declares the media type as "JWT".
  • kid (Key ID - Optional): Sometimes included, especially with asymmetric keys, to indicate which key was used to sign the token, helping the recipient select the correct public key for verification.

This JSON object is then Base64Url encoded to form the first part of the JWT. For the example above, this becomes eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.

4. The Payload: Claims Information

The second part of the token is the payload, which contains the claims. Claims are statements about an entity (typically the user) and additional data.

There are three types of claims:

  • Registered Claims: A set of predefined claims recommended by the JWT specification (RFC 7519). These are not mandatory but provide a standard set of useful, interoperable claims.
  • Public Claims: Custom claims defined by those using JWTs, but registered in the IANA JSON Web Token Registry or defined as URIs with collision-resistant namespaces to prevent conflicts.
  • Private Claims: Custom claims created for specific use cases between parties that agree on their meaning. These should be used with caution to avoid naming collisions.

Common Registered Claims include:

  • iss (Issuer): Identifies the principal that issued the JWT.
  • sub (Subject): Identifies the principal that is the subject of the JWT (often the user ID).
  • aud (Audience): Identifies the recipients that the JWT is intended for (often the API or service that should accept the token).
  • exp (Expiration Time): Identifies the expiration time on or after which the JWT MUST NOT be accepted for processing (Unix timestamp).
  • nbf (Not Before): Identifies the time before which the JWT MUST NOT be accepted for processing (Unix timestamp).
  • iat (Issued At): Identifies the time at which the JWT was issued (Unix timestamp).
  • jti (JWT ID): Provides a unique identifier for the JWT, which can help prevent replay attacks.

Example Payload JSON:


{
  "sub": "user-12345",
  "iss": "https://auth.example.com",
  "aud": "https://api.example.com",
  "exp": 1678886400,
  "iat": 1678882800,
  "jti": "unique-token-id-abc",
  "roles": ["user", "reader"],
  "name": "Alice Wonderland"
}
        

This JSON payload is then Base64Url encoded to form the second part of the JWT. For the example above, this becomes (approximately, actual encoding depends on whitespace): eyJzdWIiOiJ1c2VyLTEyMzQ1IiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tIiwiYXVkIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20iLCJleHAiOjE2Nzg4ODY0MDAsImlhdCI6MTY3ODg4MjgwMCwianRpIjoidW5pcXVlLXRva2VuLWlkLWFiYyIsInJvbGVzIjpbInVzZXIiLCJyZWFkZXIiXSwibmFtZSI6IkFsaWNlIFdvbmRlcmxhbmQifQ.

Important Note: The payload is Base64Url encoded, *not* encrypted by default. Anyone can decode it. Therefore, never put sensitive information (like passwords) directly into the JWT payload unless the entire JWT is encrypted (using JWE - JSON Web Encryption), which is a separate standard.

5. The Signature: Ensuring Integrity

The third part is the signature. It's used to verify that the sender of the JWT is who it says it is and that the message wasn't changed along the way.

The signature is created by taking:

  1. The encoded Header
  2. A dot (.)
  3. The encoded Payload
  4. A secret (for symmetric algorithms like HS256) or a private key (for asymmetric algorithms like RS256/ES256)
  5. Applying the signing algorithm specified in the header (alg).

Example using pseudocode for HS256:


Signature = HMACSHA256(
  base64UrlEncode(Header) + "." + base64UrlEncode(Payload),
  YOUR_SECRET_KEY
)
         

Common Signing Algorithms:

  • HS256 (HMAC with SHA-256): A symmetric algorithm. Uses a single shared secret key for both signing and verification. Faster but requires secure sharing and storage of the secret.
  • RS256 (RSA Signature with SHA-256): An asymmetric algorithm. Uses a private key to sign and a corresponding public key to verify. More secure for distributed systems as the private key never needs to be shared, but computationally more expensive than HS256.
  • ES256 (ECDSA with SHA-256): An asymmetric algorithm using Elliptic Curve Cryptography. Offers similar security to RSA but with smaller key sizes and generally better performance than RS256.

The resulting signature is then Base64Url encoded to form the final part of the JWT.

6. How JWTs are Used

JWTs are commonly used in several scenarios:

  • Authentication & Session Management: After a user logs in, the server creates a JWT containing user identity claims and sends it to the client. The client stores this token (e.g., in an HttpOnly cookie or memory) and includes it in subsequent requests to prove authentication. This enables stateless authentication, as the server doesn't need to store session state.
  • Authorization: JWTs can contain claims representing user roles or permissions (scopes). When the client sends the JWT to access a protected API endpoint, the server verifies the signature and checks these claims to determine if the user is authorized to perform the requested action.
  • Information Exchange: JWTs provide a secure way to transmit information between parties because their signature ensures integrity and authenticity.
  • OpenID Connect (OIDC): The ID Token used in OIDC to convey user identity information is typically a JWT.

JWTs are usually sent in the Authorization HTTP header using the Bearer scheme:


Authorization: Bearer <token>
        

6.1 Example Authentication Flow

  1. Login Request (Client -> Server):
    
    POST /login HTTP/1.1
    Host: api.example.com
    Content-Type: application/json
    
    {
      "username": "alice",
      "password": "password123"
    }
                    
  2. Server Verifies & Issues JWT:
    • Server validates credentials ("alice", "password123").
    • Creates Header: {"alg": "HS256", "typ": "JWT"} (Base64Url: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9)
    • Creates Payload: {"sub": "alice123", "name": "Alice", "roles": ["user"], "iss": "myApp", "exp": 1678886400, "iat": 1678882800} (Base64Url: eyJzdWIiOiJhbGljZTEyMyIsIm5hbWUiOiJBbGljZSIsInJvbGVzIjpbInVzZXIiXSwiaXNzIjoibXlBcHAiLCJleHAiOjE2Nzg4ODY0MDAsImlhdCI6MTY3ODg4MjgwMH0
      )
    • Generates Signature using a secret key: HMACSHA256(encodedHeader + "." + encodedPayload, 'your-256-bit-secret') -> (e.g., Base64Url: AbcDeFgHiJkLmNoPQrStUvWxYz12345EXAMPLE)
    • Server Sends JWT in Response:
      
      {
        "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZTEyMyIsIm5hbWUiOiJBbGljZSIsInJvbGVzIjpbInVzZXIiXSwiaXNzIjoibXlBcHAiLCJleHAiOjE2Nzg4ODY0MDAsImlhdCI6MTY3ODg4MjgwMH0.AbcDeFgHiJkLmNoPQrStUvWxYz12345EXAMPLE"
      }
                               
  3. Client Stores JWT: The client application receives the accessToken and stores it securely (e.g., in memory or an HttpOnly cookie).
  4. Client Accesses Protected Resource: Client makes a request to a protected endpoint, including the JWT.
    
    GET /profile HTTP/1.1
    Host: api.example.com
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZTEyMyIsIm5hbWUiOiJBbGljZSIsInJvbGVzIjpbInVzZXIiXSwiaXNzIjoibXlBcHAiLCJleHAiOjE2Nzg4ODY0MDAsImlhdCI6MTY3ODg4MjgwMH0.AbcDeFgHiJkLmNoPQrStUvWxYz12345EXAMPLE
                     
  5. Server Verifies JWT & Responds:
    • Server receives the request and extracts the token from the Authorization header.
    • Decodes the Header and Payload (without verifying yet).
    • Checks the alg in the header (HS256).
    • Re-calculates the signature using the received encoded header, encoded payload, and its *own* copy of the secret key: HMACSHA256(encodedHeader + "." + encodedPayload, 'your-256-bit-secret')
    • Compares the recalculated signature with the signature received in the token. If they match, the token is authentic and unmodified.
    • Validates claims: Checks if exp is in the future, iss is trusted, aud matches (if present), etc.
    • If all checks pass, the server trusts the claims (e.g., knows the request is from user "alice123" with role "user") and processes the request, returning the profile data.
    • If any check fails (signature mismatch, expired token, etc.), the server returns a 401 Unauthorized error.

7. Security Considerations

While JWTs provide significant benefits, they must be implemented correctly to be secure:

  • Use HTTPS: Always transmit JWTs over HTTPS to prevent interception.
  • Validate Signatures Always: The most critical step. The receiving party MUST validate the signature using the correct secret or public key before trusting any claims. Failure to do so makes the token meaningless.
  • Validate Claims: Always validate registered claims like exp (check if expired), nbf (check if active yet), aud (check if intended for this recipient), and iss (check if from a trusted issuer).
  • Algorithm Security:
    • Verify the alg header claim matches the expected algorithm.
    • Crucially, reject tokens with alg: none unless explicitly required and understood. Accepting none bypasses signature validation entirely.
    • Choose strong algorithms (e.g., RS256/ES256 over HS256 if the secret cannot be perfectly secured or needs sharing).
  • Sensitive Data: Remember the payload is only encoded, not encrypted. Do not store sensitive data (passwords, PII) in the payload unless using JWE (JSON Web Encryption).
  • Prevent Replay Attacks: Use the jti (JWT ID) claim combined with server-side tracking to ensure a token cannot be reused, or keep token lifetimes very short.
  • Token Storage (Client-Side): Storing JWTs securely in the browser is challenging.
    • localStorage/sessionStorage: Convenient but vulnerable to Cross-Site Scripting (XSS) attacks, allowing attackers to steal the token. Generally discouraged for sensitive tokens.
    • HttpOnly Cookies: More secure against XSS as they aren't accessible via JavaScript. However, they can be vulnerable to Cross-Site Request Forgery (CSRF) if not protected (e.g., using SameSite attribute).
    • In-Memory Storage: Storing the token only in JavaScript variables is most secure against XSS/CSRF but doesn't persist across page reloads/tabs.
    The best approach often involves a combination (e.g., Refresh Tokens in secure HttpOnly cookies, short-lived Access Tokens in memory).
  • Token Revocation: JWTs are inherently stateless, making immediate revocation difficult. Strategies include maintaining server-side blocklists (using the jti claim) or keeping token lifetimes very short and relying on refresh token revocation.

8. Conclusion

JSON Web Tokens provide a standardized, compact, and self-contained method for securely transmitting information as claims between parties. Understanding their structure (Header, Payload, Signature) and the different types of claims is essential for using them effectively. While powerful for authentication, authorization, and information exchange, JWT security relies heavily on correct implementation. Developers must prioritize HTTPS, rigorously validate signatures and claims, choose appropriate algorithms, protect against common vulnerabilities like replay attacks, and carefully consider secure token storage strategies.

9. Additional Resources

Related Articles

External Resources