Toolypet
Back to Blog
Dev

JWT Complete Guide 2026 - Structure, Security, and Best Practices

From JSON Web Token structure and how it works to security vulnerabilities and best practices. Master JWT authentication with practical code examples.

Toolypet Team

Toolypet Team

Development Team

8 min read

JWT Complete Guide 2026

"JWT? Isn't it just a token?"

Yes, it is. But when that "just a token" isn't implemented correctly, it becomes a serious security issue. As of 2026, research shows that 48% of AI-generated authentication code contains security vulnerabilities.

This guide covers everything from JWT structure to practical security best practices.


What is JWT?

JWT (JSON Web Token) is a compact and self-contained method for securely transmitting information between two parties.

Key Features

FeatureDescription
StatelessNo session storage needed on server
Self-containedAll necessary information included in token
CompactUsable in URLs, HTTP headers
SignedTampering can be detected

Use Cases

  • API Authentication: API requests with Bearer token
  • SSO (Single Sign-On): Sharing authentication across multiple services
  • Information Exchange: Transmitting signed data
  • Authorization: Including permissions/role information

JWT Structure

JWT consists of three Base64URL-encoded parts separated by . (dots).

xxxxx.yyyyy.zzzzz
  |      |      |
Header  Payload  Signature

1. Header

{
  "alg": "HS256",
  "typ": "JWT"
}
FieldDescription
algSignature algorithm (HS256, RS256, etc.)
typToken type (JWT)

2. Payload (Claims)

{
  "sub": "user123",
  "name": "John Doe",
  "email": "john@example.com",
  "role": "admin",
  "iat": 1708502400,
  "exp": 1708588800
}

Registered Claims

ClaimDescriptionExample
issIssuer"https://auth.example.com"
subSubject"user123"
audAudience"https://api.example.com"
expExpiration Time1708588800
nbfNot Before1708502400
iatIssued At1708502400
jtiJWT ID"abc123"

Public/Private Claims

{
  "role": "admin",           // Private claim
  "email": "user@example.com",
  "permissions": ["read", "write"]
}

3. Signature

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

Signature Algorithms

Symmetric Key

AlgorithmDescriptionUse Case
HS256HMAC + SHA-256Single server, simple implementation
HS384HMAC + SHA-384Stronger security
HS512HMAC + SHA-512Highest level security
// Node.js - HS256
const jwt = require('jsonwebtoken');

const token = jwt.sign(
  { userId: 123, role: 'admin' },
  'your-256-bit-secret',  // Verified with same secret
  { algorithm: 'HS256', expiresIn: '1h' }
);

Asymmetric Key

AlgorithmDescriptionUse Case
RS256RSA + SHA-256Microservices, public verification
RS384RSA + SHA-384High security requirements
RS512RSA + SHA-512Highest level security
ES256ECDSA + P-256Short keys, mobile
// RS256 - Asymmetric
const privateKey = fs.readFileSync('private.pem');
const publicKey = fs.readFileSync('public.pem');

// Issue (Private Key)
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256' });

// Verify (Public Key)
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });

Algorithm Selection Guide

ScenarioRecommended Algorithm
Single serverHS256
MicroservicesRS256
Public verification neededRS256 / ES256
Mobile/IoTES256 (short keys)

Practical Implementation

Node.js (Express)

const express = require('express');
const jwt = require('jsonwebtoken');

const app = express();
const SECRET = process.env.JWT_SECRET; // Load from environment variable

// Token issuance
app.post('/login', async (req, res) => {
  const { email, password } = req.body;

  // User verification (example)
  const user = await verifyCredentials(email, password);
  if (!user) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const token = jwt.sign(
    {
      sub: user.id,
      email: user.email,
      role: user.role,
    },
    SECRET,
    {
      expiresIn: '1h',
      issuer: 'your-app-name',
    }
  );

  res.json({ token });
});

// Middleware verification
const authMiddleware = (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing token' });
  }

  const token = authHeader.split(' ')[1];

  try {
    const decoded = jwt.verify(token, SECRET, {
      issuer: 'your-app-name',
      algorithms: ['HS256'],
    });
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
};

// Protected route
app.get('/protected', authMiddleware, (req, res) => {
  res.json({ message: `Hello, ${req.user.email}` });
});

Python (FastAPI)

from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from datetime import datetime, timedelta
import os

app = FastAPI()
security = HTTPBearer()
SECRET = os.environ.get("JWT_SECRET")

# Token issuance
def create_token(user_id: str, role: str) -> str:
    payload = {
        "sub": user_id,
        "role": role,
        "iat": datetime.utcnow(),
        "exp": datetime.utcnow() + timedelta(hours=1),
    }
    return jwt.encode(payload, SECRET, algorithm="HS256")

# Token verification
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
    token = credentials.credentials
    try:
        payload = jwt.decode(token, SECRET, algorithms=["HS256"])
        return payload
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expired")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid token")

# Protected endpoint
@app.get("/protected")
def protected_route(user: dict = Depends(verify_token)):
    return {"message": f"Hello, {user['sub']}"}

Security Vulnerabilities and Countermeasures

1. Algorithm None Attack

// Malicious header
{
  "alg": "none",
  "typ": "JWT"
}

Countermeasure:

// Specify allowed algorithms
jwt.verify(token, secret, { algorithms: ['HS256'] }); // OK
jwt.verify(token, secret); // Dangerous

2. Weak Secret Key

// Dangerous
const SECRET = 'secret123';

// Safe (minimum 256 bits)
const SECRET = crypto.randomBytes(32).toString('hex');

3. Sensitive Information Exposure

// Dangerous: Payload can be Base64 decoded
{
  "password": "user_password",  // Never do this!
  "creditCard": "1234-5678-9012-3456"
}

// Safe
{
  "sub": "user123",
  "role": "admin"
}

4. Token Theft

Countermeasure strategies:

  • Short expiration time (15 minutes ~ 1 hour)
  • Use Refresh Token
  • HTTPS required
  • HttpOnly cookies (XSS prevention)

5. JWT Replay Attack

// Use jti (JWT ID)
const token = jwt.sign({
  sub: user.id,
  jti: crypto.randomUUID(), // Unique ID
}, SECRET);

// Manage jti blacklist on server

Access Token + Refresh Token

Why is it needed?

TokenLifespanPurpose
Access Token15 min ~ 1 hourAPI authentication
Refresh Token7 ~ 30 daysRenew Access Token

Implementation Example

// Issue both tokens on login
app.post('/login', async (req, res) => {
  const user = await verifyCredentials(req.body);

  const accessToken = jwt.sign(
    { sub: user.id, type: 'access' },
    ACCESS_SECRET,
    { expiresIn: '15m' }
  );

  const refreshToken = jwt.sign(
    { sub: user.id, type: 'refresh' },
    REFRESH_SECRET,
    { expiresIn: '7d' }
  );

  // Refresh Token as HttpOnly cookie
  res.cookie('refreshToken', refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60 * 1000,
  });

  res.json({ accessToken });
});

// Token renewal
app.post('/refresh', (req, res) => {
  const refreshToken = req.cookies.refreshToken;

  try {
    const decoded = jwt.verify(refreshToken, REFRESH_SECRET);

    const newAccessToken = jwt.sign(
      { sub: decoded.sub, type: 'access' },
      ACCESS_SECRET,
      { expiresIn: '15m' }
    );

    res.json({ accessToken: newAccessToken });
  } catch (err) {
    res.status(401).json({ error: 'Invalid refresh token' });
  }
});

JWT Debugging

Token Decoding

Paste your token in JWT Decoder to:

  • Check Header
  • Check Payload
  • Check expiration time
  • Verify signature (when secret is provided)

CLI Decoding

# Decode Header
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" | base64 -d

# Decode Payload
echo "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ" | base64 -d

Best Practices Checklist

Issuance

  • Strong secret key (256 bits or more)
  • Specify algorithm (HS256, RS256)
  • Short expiration time (15 min ~ 1 hour)
  • Include required claims (sub, iat, exp, iss)
  • No sensitive information

Verification

  • Algorithm whitelist (algorithms: ['HS256'])
  • Expiration time validation
  • Issuer (iss) validation
  • Audience (aud) validation

Storage and Transmission

  • HTTPS required
  • HttpOnly cookie (XSS prevention)
  • SameSite attribute (CSRF prevention)
  • Avoid localStorage (XSS vulnerable)

FAQ

Q1: JWT vs Session, when to use which?

A:

  • JWT: Stateless API, microservices, mobile apps
  • Session: Traditional web apps, server-side rendering, immediate logout required

Q2: What if JWT is stolen?

A: Access Token remains valid until expiration. Therefore:

  • Set short expiration time (15 min)
  • Refresh Token Rotation
  • Invalidate all tokens when suspicious

Q3: Why shouldn't I store in localStorage?

A: It's vulnerable to XSS attacks. Since JavaScript can access it, malicious scripts can steal tokens. HttpOnly cookies are safer.

Q4: Is RS256 safer than HS256?

A: It's not "safer" but "different purpose".

  • HS256: Same secret shared, single server
  • RS256: Verify with public key, multiple services

Q5: Do I need a token blacklist?

A: You need it if immediate logout is required. But it reduces the advantage of stateless. Short expiration + Refresh Token is an alternative.


Conclusion

Key points of JWT security:

  1. Strong secret: Random 256 bits or more
  2. Algorithm verification: Whitelist approach
  3. Short lifespan: Access 15 min, Refresh 7 days
  4. Safe storage: HttpOnly + Secure cookies
  5. No sensitive info: Payload is decodable by anyone

Related Tools

ToolPurpose
JWT DecoderJWT decoding and verification
Base64 EncoderBase64 encoding/decoding
Hash GeneratorHash generation
JWTAuthenticationSecurityAPIWeb DevelopmentToken

About the Author

Toolypet Team

Toolypet Team

Development Team

The Toolypet Team creates free, privacy-focused web tools for developers and designers. All tools run entirely in your browser with no data sent to servers.

Web DevelopmentCSS ToolsDeveloper ToolsSEOSecurity