Skip to content

JWT Authentication Service — Capstone Project for Token-Based Security

DodaTech Updated 2026-06-28 4 min read

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

Build a complete JWT authentication service supporting multiple signing algorithms, refresh token rotation, JWKS key distribution, blacklisting, and multi-service audience validation.

Project Overview

You will build a Flask-based JWT authentication service (Auth Hub) that issues tokens for three internal APIs. The service must support service-to-service auth (RS256 with JWKS) and user auth (HS256 with refresh rotation).

flowchart TD
    A["Auth Hub Service"] --> B["/api/v1/login\nIssue HS256 tokens"]
    A --> C["/api/v1/refresh\nRotate tokens"]
    A --> D["/api/v1/oauth/token\nIssue RS256 tokens\n(Client Credentials)"]
    A --> E["/.well-known/jwks.json\nPublish public keys"]
    A --> F["/api/v1/revoke\nBlacklist tokens"]
    B --> G["User API\naud: user-api"]
    C --> G
    D --> H["Threat API\naud: threat-api"]
    D --> I["Device API\naud: device-api"]
    style A fill:#dbeafe,stroke:#2563eb
    style G fill:#dcfce7,stroke:#16a34a
    style H fill:#dcfce7,stroke:#16a34a
    style I fill:#dcfce7,stroke:#16a34a

Requirements

1. User Authentication (HS256)

  • POST /api/v1/login — Accept username/password, return access token (15 min) + refresh token (7 days)
  • POST /api/v1/refresh — Accept refresh token, rotate both access and refresh tokens
  • POST /api/v1/logout — Blacklist the access token and invalidate the refresh token
  • GET /api/v1/me — Return user info from the access token claims

2. Service Authentication (RS256 + JWKS)

  • POST /api/v1/oauth/token — Client Credentials grant, return RS256 signed access token
  • GET /.well-known/jwks.json — Return public keys for RS256 verification
  • Support multiple service clients with different audience values

3. Token Blacklisting (Redis)

  • Blacklist access tokens on logout
  • Blacklist refresh tokens on rotation (old token no longer valid)
  • Automatic TTL cleanup (blacklist entries expire when the token would have expired)

4. Multi-Service Audience

  • Token aud claim specifies which API the token is for
  • Three APIs: user-api, threat-api, device-api
  • Tokens for one API cannot access another

Starter Code

from flask import Flask, request, jsonify
import jwt, redis, datetime, secrets, hashlib

app = Flask(__name__)
r = redis.Redis(decode_responses=True)

# Configuration
HS256_SECRET = secrets.token_hex(32)
API_AUDIENCES = ["user-api", "threat-api", "device-api"]

# In-memory user store (use database in production)
USERS = {"admin": {"password": "pass123", "role": "admin"}}

# RS256 key pair (generate on startup)
# See jwt-signing lesson for key generation

# === YOUR IMPLEMENTATION ===

# 1. POST /api/v1/login
# 2. POST /api/v1/refresh
# 3. POST /api/v1/logout
# 4. POST /api/v1/oauth/token
# 5. GET /.well-known/jwks.json
# 6. GET /api/v1/me
# 7. Token validation middleware

def require_jwt(allowed_audiences=None):
    """Middleware that validates JWT and checks audience."""
    def decorator(f):
        def wrapper(*args, **kwargs):
            auth = request.headers.get("Authorization", "")
            if not auth.startswith("Bearer "):
                return jsonify({"error": "Missing token"}), 401

            token = auth[7:]
            # Determine algorithm from header
            header = jwt.get_unverified_header(token)
            alg = header.get("alg")

            try:
                if alg == "HS256":
                    payload = jwt.decode(
                        token, HS256_SECRET,
                        algorithms=["HS256"]
                    )
                elif alg == "RS256":
                    # Fetch key from JWKS
                    key = get_key_from_jwks(header.get("kid"))
                    if not key:
                        return jsonify({"error": "Unknown key"}), 401
                    payload = jwt.decode(
                        token, key, algorithms=["RS256"]
                    )
                else:
                    return jsonify({"error": "Unsupported algorithm"}), 401

                # Check blacklist
                jti = payload.get("jti")
                if jti and r.exists(f"blacklist:{jti}"):
                    return jsonify({"error": "Token revoked"}), 401

                # Check audience
                if allowed_audiences:
                    aud = payload.get("aud")
                    if aud not in allowed_audiences:
                        return jsonify({"error": "Wrong audience"}), 403

                g.user = payload

            except jwt.ExpiredSignatureError:
                return jsonify({"error": "Token expired"}), 401
            except jwt.InvalidTokenError:
                return jsonify({"error": "Invalid token"}), 401

            return f(*args, **kwargs)
        return wrapper
    return decorator

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

Testing Your Project

# Test user login
curl -X POST -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"pass123","audience":"user-api"}' \
  http://localhost:5000/api/v1/login

# Test service auth
curl -X POST -H "Content-Type: application/json" \
  -d '{"client_id":"threat-service","client_secret":"secret","audience":"threat-api"}' \
  http://localhost:5000/api/v1/oauth/token

# Fetch JWKS
curl http://localhost:5000/.well-known/jwks.json

Common Mistakes

1. Using the Same Secret for HS256 and RS256

Never use the same key material for both algorithms. This enables algorithm confusion attacks.

2. Not Validating Token Type

Ensure access tokens cannot be used as refresh tokens and vice versa. Include token_type in claims.

3. Exposing Private Keys in JWKS

The JWKS endpoint must contain only public keys. Never expose private keys.

4. Not Handling Token Expiry in Middleware

The middleware must check exp and return a specific error so clients can refresh.

5. Missing Audience Validation

Without audience, a threat-api token could access the user-api. Always validate audience.

FAQ

Should I use two separate services for HS256 and RS256?

Not necessarily. The same auth service can issue both token types. Use different endpoints and validate algorithm-specific claims.

How do I handle key rotation for RS256?

Publish both the old and new keys in the JWKS. Use the kid header to indicate which key signed the token. Keep the old key until all tokens signed with it expire.

Can I use this project in production?

Yes, with modifications: add a real database, proper error handling, rate limiting, and HTTPS. Replace in-memory storage with Redis and PostgreSQL.

How do I test RS256 token verification?

Fetch the JWKS from your server, extract the public key, and use it to verify tokens. Python's jwt library handles this automatically.

What monitoring should I add?

Track token issuance rate, refresh rate, blacklist size, and failed validation attempts. Alert on unusual patterns (surge in 401s).

What's Next

Congratulations on completing the JWT learning path! Continue to the OAuth2 Deep Dive guide for delegated authorization frameworks.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro