Understanding JWT

What's a jwt

Last updated: 3/14/2025

1 hour
Medium

JSON Web Tokens (JWTs): Overview and Construction

What Are JWTs?

JSON Web Tokens (JWTs) are a compact, URL-safe means of representing claims to be transferred between two parties. They are widely used for authentication and authorization in web applications because they enable a stateless, self-contained method of securely transmitting information. JWTs follow the RFC 7519 standard.

Structure of a JWT

A JWT is composed of three parts, separated by periods:

  1. Header
  2. Payload
  3. Signature

1. Header

The header typically consists of two parts:

  • Type: The token type, which is JWT.
  • Algorithm: The signing algorithm used, such as HMAC SHA256 (HS256) or RSA (RS256).

Example Header:

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

This header is then Base64Url-encoded.

2. Payload

The payload contains the "claims" or statements about an entity (typically, the user) and additional metadata. There are three types of claims:

  • Registered Claims: Predefined claims like iss (issuer), exp (expiration time), sub (subject), and aud (audience).
  • Public Claims: Custom claims that can be defined by those using JWTs, such as role or permissions.
  • Private Claims: Custom claims agreed upon between parties, which are neither registered nor public.

Example Payload:

{ "userId": "123e4567-e89b-12d3-a456-426614174000", "iat": 1618239022, "exp": 1618242622, "role": "admin" }

The payload is also Base64Url-encoded.

3. Signature

To create the signature, you take the encoded header, the encoded payload, a secret key (or a private key in case of asymmetric signing), and the algorithm specified in the header. For instance, using the HMAC SHA256 algorithm:

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

The resulting signature is then Base64Url-encoded. The final JWT is constructed by concatenating the three parts with periods:

<encoded header>.<encoded payload>.<encoded signature>

How to Construct a JWT

Here is a step-by-step process:

  1. Create the Header:
    Define the header as a JSON object and Base64Url-encode it.

    const header = { alg: 'HS256', typ: 'JWT' }; const encodedHeader = base64UrlEncode(JSON.stringify(header));
  2. Create the Payload:
    Define the payload with the necessary claims and Base64Url-encode it.

    const payload = { userId: '123e4567-e89b-12d3-a456-426614174000', iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 3600, // Token expires in 1 hour role: 'admin' }; const encodedPayload = base64UrlEncode(JSON.stringify(payload));
  3. Generate the Signature:
    Use a secret key to sign the concatenated string of the encoded header and payload.

    const secretKey = 'your-very-secure-secret'; const data = `${encodedHeader}.${encodedPayload}`; const signature = HMACSHA256(data, secretKey); // Replace with your chosen crypto function const encodedSignature = base64UrlEncode(signature);
  4. Construct the JWT:
    Concatenate the encoded header, payload, and signature with periods.

    const jwt = `${encodedHeader}.${encodedPayload}.${encodedSignature}`;

Usage in Authentication

  • Issuing Tokens:
    Upon successful login, the authentication service API generates a JWT containing essential user claims. The token is returned to the client, which includes it in the Authorization header (using the "Bearer" schema) for subsequent requests.

  • Token Validation:
    Protected endpoints decode the token, verify its signature with the secret key, check that it hasnโ€™t expired (using the exp claim), and ensure that the necessary claims (e.g., userId) are present. If the token is invalid or expired, the service returns an appropriate error response (e.g., 401 Unauthorized).

Including JWTs in Client Requests

Once a client successfully logs in and receives a JWT, it must include the token in all subsequent requests to protected endpoints. This is typically done via the Authorization header using the Bearer scheme. Here's how clients can include JWTs in their requests:

Header Format

The JWT should be included in the Authorization header in the following format:

Authorization: Bearer <JWT>

Example Usage

Using the Fetch API (JavaScript)

fetch('https://api.example.com/protected-resource', { method: 'GET', headers: { 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error));

Using Axios (JavaScript)

axios.get('https://api.example.com/protected-resource', { headers: { 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' } }) .then(response => console.log(response.data)) .catch(error => console.error('Error:', error));

Best Practices for Clients

  • Secure Storage:
    Store the JWT securely on the client side. For web applications, prefer using HTTP-only cookies over local storage to mitigate XSS vulnerabilities.

  • Token Renewal:
    Implement a refresh mechanism if tokens have a short lifetime, so that clients can obtain a new token without requiring the user to re-authenticate.

  • Error Handling:
    Monitor for responses with 401 Unauthorized status. If encountered, prompt the user to log in again or attempt a token refresh if a refresh endpoint is available.

Conclusion

JWTs provide a secure, stateless, and scalable method for authenticating users in distributed systems. By following the standard construction processโ€”encoding a header and payload, signing the resulting dataโ€”and by including the JWT in client requests via the Authorization header, developers can create a robust authentication flow that underpins modern web applications and microservices architectures.