Skip to content

Secure JWT Implementation for APIs — Complete Guide

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about Secure JWT Implementation for APIs. We cover key concepts, practical examples, and best practices to help you master this topic.

JSON Web Tokens (JWT) are a compact, URL-safe way to represent claims between parties. A JWT is signed, not encrypted, meaning anyone can read its contents but only the holder of the private key can forge it.

What You'll Learn

You'll learn secure JWT implementation including proper signing algorithms, claim validation, token storage, and common attacks to avoid.

Why It Matters

Improper JWT implementation leads to token forgery, algorithm confusion attacks, and sensitive data exposure. JWT vulnerabilities are among OWASP's top API risks.

Real-World Use

A payment processing API uses signed JWTs to authorize transactions. Each token contains the user ID, merchant ID, and amount. The signature ensures no one can tamper with these values.

flowchart LR
    A[User Login] --> B[Server Creates JWT]
    B --> C{Header: alg & typ}
    B --> D{Payload: claims & exp}
    B --> E{Signature: verify integrity}
    C --> F[Encode JWT]
    D --> F
    E --> F
    F --> G[Client Stores Token]
    G --> H[API Request with JWT]
    H --> I[Server Verifies Signature]
    I -->|Valid| J[Process Request]
    I -->|Invalid| K[401 Unauthorized]

Teacher's Mindset

A JWT is like a stamped passport. The passport's information is visible to anyone who reads it, but the government's stamp (signature) proves it's authentic and hasn't been altered.

Implementing Secure JWT

import jwt
from datetime import datetime, timedelta

SECRET_KEY = "your-256-bit-secret"
ALGORITHM = "HS256"

def create_access_token(user_id: str) -> str:
    payload = {
        "sub": user_id,
        "iat": datetime.utcnow(),
        "exp": datetime.utcnow() + timedelta(minutes=15),
        "type": "access",
        "scope": ["read", "write"]
    }
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

def verify_token(token: str) -> dict:
    try:
        payload = jwt.decode(
            token,
            SECRET_KEY,
            algorithms=[ALGORITHM],
            options={"require": ["exp", "iat"]}
        )
        return payload
    except jwt.ExpiredSignatureError:
        raise ValueError("Token expired")
    except jwt.InvalidTokenError:
        raise ValueError("Invalid token")
# Secure validation with audience claim
def create_token(user_id, audience="api.example.com"):
    payload = {
        "sub": user_id,
        "aud": audience,
        "iss": "auth.example.com",
        "exp": datetime.utcnow() + timedelta(hours=1),
        "iat": datetime.utcnow(),
        "jti": secrets.token_hex(16)
    }
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

def validate_token(token, expected_audience):
    try:
        payload = jwt.decode(
            token,
            SECRET_KEY,
            algorithms=[ALGORITHM],
            audience=expected_audience,
            issuer="auth.example.com"
        )
        return payload
    except jwt.InvalidAudienceError:
        raise ValueError("Invalid audience")
    except jwt.InvalidIssuerError:
        raise ValueError("Invalid issuer")
# Preventing algorithm confusion
def verify_token_safe(token, public_key):
    try:
        header = jwt.get_unverified_header(token)
        if header["alg"] == "HS256":
            raise ValueError("HMAC algorithm not allowed")
        payload = jwt.decode(
            token,
            public_key,
            algorithms=["RS256", "ES256"]
        )
        return payload
    except jwt.InvalidKeyError:
        raise ValueError("Invalid key")

Common Mistakes

Mistake Why It's Wrong Fix
Using "none" algorithm Removes signature verification Explicitly reject "none" algorithm
Algorithm confusion attack Attacker changes RS256 to HS256 using public key as secret Always specify allowed algorithms server-side
Storing sensitive data in payload JWT payload is base64-encoded, not encrypted Never store secrets or PII in JWT payload
No token expiration Stolen tokens work forever Set short expiration (15-60 minutes)
Not validating all claims Missing aud, iss, or sub checks Validate every claim that matters to your app

Practice Questions

  1. What three parts make up a JWT?
  2. How do you prevent algorithm confusion attacks?
  3. Why should you never store passwords in a JWT payload?
  4. What is the purpose of the jti claim?
  5. How does RS256 differ from HS256?

Challenge

Implement a JWT system with access tokens (15 min) and refresh tokens (7 days). Include audience and issuer validation. Test token replay by using an expired token.

FAQ

Should I encrypt JWT payloads?

JWTs are signed, not encrypted. If you need confidentiality, use JWE (JSON Web Encryption) or pass sensitive data through other channels.

What is the best signing algorithm?

RS256 (RSA with SHA-256) or ES256 (ECDSA) for asymmetric signing. HS256 (HMAC) works for single-server setups but requires shared secret management.

How do I revoke a JWT?

Maintain a blocklist of revoked jti values, use short-lived tokens, or check against a revocation endpoint. JWT revocation is inherently harder than sessions.

Can JWT be used for sessions?

Yes, but stateless JWTs cannot be revoked server-side. For session management, consider opaque tokens stored server-side instead.

What is JWT header injection?

Attacker manipulates the JWT header to change algorithm or add unexpected fields. Always validate the header matches expected values.

Mini Project

Build a Flask API with secure JWT authentication using RS256. Generate an RSA key pair, sign tokens with the private key, and verify with the public key. Include token blacklisting.

What's Next

Explore OAuth 2.0 flow to understand delegated authorization for third-party applications.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro