Skip to main content

JWT authentication test examples

  • Creating JWT tokens
  • Secured endpoints
  • Async tests
  • Token validation tests
  • Expired token tests
  • Invalid token tests

This example uses:

  • PyJWT
  • httpx.AsyncClient
  • pytest + pytest-asyncio

Example Project Structure

app/
├── auth.py
├── main.py
tests/
└── test_jwt_auth.py

1. JWT Utility File (auth.py)

from datetime import datetime, timedelta
from typing import Optional
import jwt

SECRET_KEY = "TEST_SECRET_KEY"
ALGORITHM = "HS256"


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

2. FastAPI App With JWT Auth (main.py)

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
import jwt

from app.auth import SECRET_KEY, ALGORITHM

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


def verify_token(token: str = Depends(oauth2_scheme)):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload # contains user_id or email
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")


@app.get("/secure")
async def secure_endpoint(payload: dict = Depends(verify_token)):
return {"message": "Access granted", "payload": payload}

3. Async JWT Tests (test_jwt_auth.py)

Install requirements:

pip install pytest pytest-asyncio httpx PyJWT

Test valid JWT

import pytest
from datetime import timedelta
from httpx import AsyncClient
from app.main import app
from app.auth import create_access_token


@pytest.mark.asyncio
async def test_valid_jwt():
token = create_access_token({"sub": "alice"}, expires_delta=timedelta(minutes=5))

async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/secure", headers={"Authorization": f"Bearer {token}"})

assert response.status_code == 200
assert response.json()["message"] == "Access granted"
assert response.json()["payload"]["sub"] == "alice"

Test expired JWT

@pytest.mark.asyncio
async def test_expired_jwt():
# Token expired 1 minute ago
token = create_access_token(
{"sub": "user"},
expires_delta=timedelta(minutes=-1)
)

async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/secure", headers={"Authorization": f"Bearer {token}"})

assert response.status_code == 401
assert response.json() == {"detail": "Token expired"}

Test missing token

@pytest.mark.asyncio
async def test_missing_token():
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/secure")

assert response.status_code == 401
assert response.json()["detail"] == "Not authenticated"

Test malformed/invalid token

@pytest.mark.asyncio
async def test_invalid_token():
invalid_token = "abc.def.ghi"

async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get(
"/secure",
headers={"Authorization": f"Bearer {invalid_token}"}
)

assert response.status_code == 401
assert response.json() == {"detail": "Invalid token"}

Test token with wrong secret key

import jwt

@pytest.mark.asyncio
async def test_wrong_secret_jwt():
# Token signed with a different key
fake_token = jwt.encode({"sub": "hacker"}, "WRONG_KEY", algorithm="HS256")

async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/secure", headers={"Authorization": f"Bearer {fake_token}"})

assert response.status_code == 401
assert response.json() == {"detail": "Invalid token"}

Summary of What You Get

These tests validate:

TestValidates
test_valid_jwtProper access to protected routes
test_expired_jwtToken expiration handling
test_missing_tokenOAuth2 missing header logic
test_invalid_tokenToken structure errors
test_wrong_secret_jwtSignature mismatch handling