27 / 65 · 14 API Testing Fundamentals · Authentication Testing← prev⊞ allnext →☰ Read as one page
4.3Testing Each Method
API Key Testing
def test_valid_api_key(base_url):
r = requests.get(f"{base_url}/data",
headers={"X-API-Key": os.environ["VALID_API_KEY"]})
assert r.status_code == 200
def test_missing_api_key(base_url):
r = requests.get(f"{base_url}/data")
assert r.status_code == 401
def test_invalid_api_key(base_url):
r = requests.get(f"{base_url}/data",
headers={"X-API-Key": "invalid-key-12345"})
assert r.status_code == 401
# Should not reveal whether the key format is wrong vs not found
assert "valid key" not in r.text.lower()
def test_revoked_api_key(base_url, revoked_key):
r = requests.get(f"{base_url}/data",
headers={"X-API-Key": revoked_key})
assert r.status_code == 401
JWT Testing
def test_valid_jwt(base_url, valid_token):
r = requests.get(f"{base_url}/users/me",
headers={"Authorization": f"Bearer {valid_token}"})
assert r.status_code == 200
def test_expired_jwt(base_url, expired_token):
r = requests.get(f"{base_url}/users/me",
headers={"Authorization": f"Bearer {expired_token}"})
assert r.status_code == 401
def test_tampered_jwt(base_url, valid_token):
"""Modify the payload of a valid JWT — should be rejected."""
import base64
parts = valid_token.split(".")
# Decode payload, modify it, re-encode (signature will be invalid)
payload = base64.urlsafe_b64decode(parts[1] + "==")
tampered = valid_token.replace(parts[1], base64.urlsafe_b64encode(
payload.replace(b'"role":"viewer"', b'"role":"admin"')
).decode().rstrip("="))
r = requests.get(f"{base_url}/users/me",
headers={"Authorization": f"Bearer {tampered}"})
assert r.status_code == 401
def test_missing_auth_returns_401(base_url):
r = requests.get(f"{base_url}/users/me")
assert r.status_code == 401
assert "password" not in r.text.lower() # No info leakage
def test_malformed_auth_header(base_url):
r = requests.get(f"{base_url}/users/me",
headers={"Authorization": "NotBearer token123"})
assert r.status_code == 401
Token Refresh Flow
def test_token_refresh(base_url, refresh_token):
"""Refresh token should return a new access token."""
r = requests.post(f"{base_url}/auth/refresh",
json={"refresh_token": refresh_token})
assert r.status_code == 200
new_token = r.json()["access_token"]
assert new_token != ""
# New token should work
r = requests.get(f"{base_url}/users/me",
headers={"Authorization": f"Bearer {new_token}"})
assert r.status_code == 200
def test_used_refresh_token_invalidated(base_url, refresh_token):
"""After using a refresh token, it should be invalidated (one-time use)."""
r1 = requests.post(f"{base_url}/auth/refresh",
json={"refresh_token": refresh_token})
assert r1.status_code == 200
r2 = requests.post(f"{base_url}/auth/refresh",
json={"refresh_token": refresh_token})
assert r2.status_code == 401 # Reuse should fail
Session Cookie Testing
def test_session_cookie_attributes(base_url):
r = requests.post(f"{base_url}/auth/login", json={
"email": "test@example.com", "password": "pass123"
})
assert r.status_code == 200
# Verify cookie security attributes
session_cookie = r.cookies.get("session_id")
assert session_cookie is not None
# Check Set-Cookie header for attributes
set_cookie = r.headers.get("Set-Cookie", "")
assert "HttpOnly" in set_cookie # Not accessible via JavaScript
assert "Secure" in set_cookie # Only sent over HTTPS
assert "SameSite" in set_cookie # CSRF protection