Skip to content

JWT Token Authentication — Signed Claims for Stateless API Security

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about JWT Token Authentication. We cover key concepts, practical examples, and best practices to help you master this topic.

JWT (JSON Web Token) authentication uses signed tokens containing user identity and claims, enabling stateless API security without server-side session storage.

What You'll Learn

How JWT tokens work for API authentication, the structure of a JWT, how signing prevents tampering, and implementation patterns for login and protected endpoints.

Why It Matters

Unlike opaque tokens that require server-side lookup, a JWT is self-contained. The server verifies the signature and reads user claims directly from the token — no database query needed. This makes JWT ideal for distributed systems and Microservices.

Real-World Use

Auth0, Firebase, and Google APIs all use JWT for authentication. Durga Antivirus Pro uses JWT for its dashboard API — each request carries user role and permissions in the token itself.

flowchart LR
    A["Client"] -->|"POST /login"| B["Auth Server"]
    B -->|"JWT: header.payload.signature"| A
    A -->|"GET /data\nBearer JWT"| C["API Server"]
    C -->|"Verify signature"| D["Valid JWT?"]
    D -->|"Yes — extract claims"| E["200 OK"]
    D -->|"No"| F["401 Unauthorized"]
    style A fill:#dbeafe,stroke:#2563eb
    style B fill:#fef3c7,stroke:#d97706
    style C fill:#fef3c7,stroke:#d97706
    style E fill:#dcfce7,stroke:#16a34a
    style F fill:#fecaca,stroke:#dc2626

JWT Structure

A JWT has three base64url-encoded parts separated by dots:

header.payload.signature

The header specifies the signing algorithm. The payload contains claims (user ID, role, expiry). The signature verifies the token has not been tampered with.

Code Example: JWT Authentication with PyJWT

import jwt
import datetime
from flask import Flask, request, jsonify

app = Flask(__name__)
SECRET_KEY = "your-secret-key-change-in-production"
USERS = {"admin": "password123"}

@app.route("/api/login", methods=["POST"])
def login():
    data = request.get_json()
    password = USERS.get(data.get("username"))
    if not password or password != data.get("password"):
        return jsonify({"error": "Invalid credentials"}), 401

    token = jwt.encode({
        "sub": data["username"],
        "role": "admin",
        "iat": datetime.datetime.utcnow(),
        "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
    }, SECRET_KEY, algorithm="HS256")

    return jsonify({"token": token, "expires_in": 3600})

def require_auth(f):
    def wrapper(*args, **kwargs):
        auth = request.headers.get("Authorization", "")
        if not auth.startswith("Bearer "):
            return jsonify({"error": "Missing token"}), 401
        try:
            token = auth[7:]
            payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
            request.user = payload
        except jwt.ExpiredSignatureError:
            return jsonify({"error": "Token expired"}), 401
        except jwt.InvalidTokenError:
            return jsonify({"error": "Invalid token"}), 401
        return f(*args, **kwargs)
    wrapper.__name__ = f.__name__
    return wrapper

@app.route("/api/profile")
@require_auth
def profile():
    return jsonify({
        "user": request.user["sub"],
        "role": request.user["role"]
    })

if __name__ == "__main__":
    app.run()

Expected output:

$ curl -X POST -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"password123"}' \
  http://localhost:5000/api/login
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","expires_in":3600}

$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  http://localhost:5000/api/profile
{"user":"admin","role":"admin"}

Common Mistakes

1. Storing Secrets in Code

Hardcoding JWT signing secrets in source code exposes them to anyone with Repository access. Use environment variables or a secrets manager.

2. Not Validating the Expiration

Without exp validation, an attacker can use an old JWT forever. PyJWT validates exp automatically when present.

3. Using Weak Secret Keys

Short or dictionary-word secrets can be brute-forced. Use at least 256 bits of entropy (32 bytes from secrets.token_bytes).

4. Including Passwords in JWT Payload

The payload is base64-encoded, not encrypted. Anyone with the token can decode the payload. Never include passwords or secrets.

5. Not Using Access + Refresh Token Pattern

Short-lived access tokens with long-lived refresh tokens balance security and usability. Without refresh tokens, users must log in every hour.

Practice Questions

  1. What three parts make up a JWT?
  2. How does the server verify a JWT without a database lookup?
  3. What happens when a JWT expires?
  4. Why should the JWT payload not contain passwords?
  5. What is the purpose of the sub claim?

Answers:

  1. Header (algorithm, type), Payload (claims), Signature (verification hash).
  2. The server recomputes the signature using the shared secret or public key. If the computed signature matches the token's signature, the token is valid.
  3. The server returns 401. The client should use a refresh token to obtain a new access token.
  4. The payload is only base64-encoded — anyone can decode it. Secrets must never be in the payload.
  5. The sub (subject) claim identifies the user. It is typically the user ID or username.

Challenge: Implement JWT authentication with access tokens (15 min expiry) and refresh tokens (7 day expiry). Include token refresh and logout (add refresh token to blocklist).

FAQ

Is JWT secure?

Yes when implemented correctly: use strong secrets, short expiry, HTTPS, and validate all claims (exp, iss, aud). JWT is only as secure as its implementation.

Can a JWT be decrypted without the secret?

The payload is base64-encoded, not encrypted. Anyone can decode it. The signature proves the token hasn't been tampered with. For sensitive data, use JWE (JSON Web Encryption).

How do I log out a JWT?

Since JWTs are stateless, you cannot invalidate them server-side. Use a blocklist (Redis) to track revoked tokens until expiration, or use short expiry.

What happens if the JWT secret is leaked?

Anyone with the secret can forge valid tokens. Rotate the secret immediately, which invalidates all existing tokens. All users must re-login.

Can I use multiple signing algorithms?

Yes, but use separate keys per algorithm and validate the algorithm in the header against an allowed list to prevent algorithm confusion attacks.

Mini Project

Create a Flask application with JWT authentication: login endpoint that issues a signed JWT, a @require_auth decorator for protected routes, and a profile endpoint that reads user claims from the token.

What's Next

JWT is covered in depth in the JWT Complete Guide. Next, learn about Session Cookie Authentication for traditional web app auth.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro