Skip to content
    universaldevtools.in
    Back to Blog
    Security
    9 min readJan 12, 2025

    JWT Tokens Explained: Authentication Made Simple

    That long string of gibberish in your Authorization header? That's a JWT—and understanding how it works is essential for modern web development. This guide breaks down JWT structure, validation, and the security pitfalls that trip up even experienced developers.

    What's Actually Inside a JWT?

    A JSON Web Token looks like random characters, but it's actually three Base64-encoded JSON objects separated by dots. Here's what each part contains:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

    Header

    Contains the token type (JWT) and signing algorithm (HS256, RS256). This tells the server how to verify the signature.

    Payload

    Contains "claims"—data like user ID, email, roles, and expiration time. Anyone can decode and read this, so never include secrets!

    Signature

    Created by hashing the header + payload with a secret key. This proves the token wasn't tampered with.

    Critical Understanding

    JWTs are signed, not encrypted. Anyone can decode and read the payload—they just can't modify it without invalidating the signature. Never put sensitive data like passwords or API keys in JWT payloads.

    How JWT Authentication Actually Works

    Understanding the flow helps you implement JWT correctly and debug issues:

    1

    User Logs In

    User sends credentials to your auth endpoint. Server verifies against database.

    2

    Server Creates JWT

    Server creates a JWT with user claims (ID, roles) and signs it with a secret key.

    3

    Client Stores Token

    Client stores the JWT (in memory, localStorage, or HttpOnly cookie).

    4

    Client Sends Token with Requests

    Every API request includes the JWT in the Authorization header: Bearer eyJhbG...

    5

    Server Validates Token

    Server verifies signature, checks expiration, and extracts user info from payload. No database lookup needed!

    Practical Code Examples

    Decoding a JWT Without Verification (Client-Side)

    // Decode JWT payload without verification (for client-side use only!)
    function decodeJwtPayload(token) {
      try {
        const base64Payload = token.split('.')[1];
        const payload = atob(base64Payload);
        return JSON.parse(payload);
      } catch (error) {
        console.error('Invalid JWT format');
        return null;
      }
    }
    
    // Usage
    const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
    
    const payload = decodeJwtPayload(token);
    console.log(payload);
    // { sub: "1234567890", name: "John", iat: 1516239022 }
    
    // Check if token is expired
    function isTokenExpired(token) {
      const payload = decodeJwtPayload(token);
      if (!payload || !payload.exp) return true;
      return Date.now() >= payload.exp * 1000;
    }

    Attaching JWT to API Requests

    // Axios interceptor for automatic JWT attachment
    import axios from 'axios';
    
    const api = axios.create({ baseURL: 'https://api.example.com' });
    
    api.interceptors.request.use((config) => {
      const token = localStorage.getItem('accessToken');
      if (token) {
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    });
    
    // Handle token expiration
    api.interceptors.response.use(
      (response) => response,
      async (error) => {
        if (error.response?.status === 401) {
          // Token expired - try refresh or redirect to login
          const refreshToken = localStorage.getItem('refreshToken');
          if (refreshToken) {
            try {
              const { data } = await axios.post('/auth/refresh', { refreshToken });
              localStorage.setItem('accessToken', data.accessToken);
              error.config.headers.Authorization = `Bearer ${data.accessToken}`;
              return api.request(error.config);
            } catch {
              localStorage.clear();
              window.location.href = '/login';
            }
          }
        }
        return Promise.reject(error);
      }
    );

    Common JWT Security Mistakes

    Not Validating the Signature

    Never trust a JWT without verifying its signature server-side. Attackers can modify the payload and create their own "valid" tokens if you only decode without verification.

    Using "none" Algorithm

    Some JWT libraries accept alg: "none" which means no signature required. Always explicitly specify and validate the expected algorithm. Reject tokens with unexpected algorithms.

    Storing Sensitive Data in Payload

    JWTs are encoded, not encrypted. Anyone with the token can read the payload. Store user ID and roles, but never passwords, SSNs, or other sensitive information.

    No Token Expiration

    Always set reasonable expiration times (exp claim). Long-lived tokens that get stolen give attackers extended access. Use short access tokens with refresh tokens for better security.

    Frequently Asked Questions

    Should I store JWTs in localStorage or cookies?

    HttpOnly cookies are generally safer because JavaScript can't access them, protecting against XSS attacks. localStorage is more convenient but vulnerable to XSS. For high-security applications, use HttpOnly cookies with SameSite=Strict and CSRF tokens. For SPAs with trusted content, localStorage with short expiration times can work.

    What's the difference between JWT signing algorithms HS256 and RS256?

    HS256 uses a shared secret key for both signing and verification—simpler but requires sharing the secret with every service. RS256 uses asymmetric keys: a private key for signing and a public key for verification. RS256 is better for distributed systems where you don't want to share secrets across services.

    How long should JWT access tokens last?

    Access tokens should be short-lived: 15-60 minutes for most applications. Use refresh tokens (stored securely) to obtain new access tokens without requiring users to re-authenticate. This limits damage if a token is stolen while maintaining good user experience.

    Wrapping Up

    JWTs are powerful for stateless authentication, but they require careful implementation. Always verify signatures server-side, use short expiration times, and never store sensitive data in payloads. Remember: JWTs are signed (tamper-proof) but not encrypted (not secret).

    Next steps: Decode your application's JWT using our tool to understand its structure, then review your token storage strategy based on the security best practices above.

    Related Resources