Authentication & Authorization Testing
Every application's security stands or falls on its authentication and authorization logic. Authentication failures allow attackers to impersonate users; authorization failures allow users to access resources they shouldn't. These are the most critical security aspects for testers to verify on every feature.
Comprehensive Auth Testing
import requests
import pytest
BASE_URL = "https://staging.myapp.com"
# ══════════════════════════════════════════════════════════════
# AUTHENTICATION TESTING
# ══════════════════════════════════════════════════════════════
class TestAuthentication:
def setup_method(self):
"""Get fresh session for each test"""
self.session = requests.Session()
def test_brute_force_protection(self):
"""Account should lockout after 5 failed attempts"""
email = "alice@test.com"
for i in range(5):
r = self.session.post(f"{BASE_URL}/api/auth/login",
json={"email": email, "password": f"wrongpass{i}"})
assert r.status_code == 401, f"Attempt {i}: should be 401"
# 6th attempt — account should be locked
r = self.session.post(f"{BASE_URL}/api/auth/login",
json={"email": email, "password": "wrongpass6"})
assert r.status_code == 423 or ("locked" in r.text.lower()), "Account should be locked after 5 failed attempts"
def test_session_invalidated_after_logout(self):
"""JWT/session token must be invalid after logout"""
# Login
login_r = self.session.post(f"{BASE_URL}/api/auth/login",
json={"email": "alice@test.com", "password": "Test@1234"})
token = login_r.json()["token"]
# Logout
self.session.post(f"{BASE_URL}/api/auth/logout",
headers={"Authorization": f"Bearer {token}"})
# Try to use old token (should fail)
profile_r = requests.get(f"{BASE_URL}/api/user/profile",
headers={"Authorization": f"Bearer {token}"})
assert profile_r.status_code == 401, "Old token should be invalid after logout"
def test_jwt_signature_validation(self):
"""Tampered JWT token should be rejected"""
login_r = self.session.post(f"{BASE_URL}/api/auth/login",
json={"email": "alice@test.com", "password": "Test@1234"})
token = login_r.json()["token"]
# Tamper the payload (change userId in JWT body — base64 encoded)
import base64, json
parts = token.split(".")
payload = json.loads(base64.b64decode(parts[1] + "==").decode())
payload["userId"] = 999 # Change to a different user ID
payload["role"] = "admin" # Try to escalate to admin
tampered_payload = base64.b64encode(json.dumps(payload).encode()).decode().rstrip("=")
tampered_token = f"{parts[0]}.{tampered_payload}.{parts[2]}"
profile_r = requests.get(f"{BASE_URL}/api/user/profile",
headers={"Authorization": f"Bearer {tampered_token}"})
assert profile_r.status_code == 401, "Tampered JWT should be rejected"
# ══════════════════════════════════════════════════════════════
# AUTHORIZATION (ACCESS CONTROL) TESTING
# ══════════════════════════════════════════════════════════════
class TestAuthorization:
@pytest.fixture
def alice_token(self):
r = requests.post(f"{BASE_URL}/api/auth/login",
json={"email": "alice@test.com", "password": "Test@1234"})
return r.json()["token"]
@pytest.fixture
def bob_token(self):
r = requests.post(f"{BASE_URL}/api/auth/login",
json={"email": "bob@test.com", "password": "Test@1234"})
return r.json()["token"]
def test_user_cannot_read_another_users_data(self, alice_token, bob_token):
"""IDOR: Alice cannot read Bob's profile"""
# Alice reads HER OWN profile (OK)
alice_r = requests.get(f"{BASE_URL}/api/user/profile",
headers={"Authorization": f"Bearer {alice_token}"})
alice_id = alice_r.json()["id"]
# Alice tries to read BOB'S profile by ID (should fail)
bob_profile_id = requests.get(f"{BASE_URL}/api/users/{alice_id + 1}/profile",
headers={"Authorization": f"Bearer {alice_token}"})
assert bob_profile_id.status_code in [403, 404], "User should not access another user's profile"
def test_regular_user_cannot_call_admin_api(self, alice_token):
"""Vertical privilege escalation attempt"""
admin_endpoints = [
"/api/admin/users",
"/api/admin/reports",
"/api/admin/settings",
"/api/admin/roles",
]
for endpoint in admin_endpoints:
r = requests.get(f"{BASE_URL}{endpoint}",
headers={"Authorization": f"Bearer {alice_token}"})
assert r.status_code == 403, f"Regular user should not access {endpoint}"Common Mistakes
- Only testing authentication — testing that login works but not that session management, JWT validation, and logout are secure
- Testing authorization only for UI-blocked paths — attackers bypass the UI; always test authorization on direct API calls without going through the UI
- Not testing IDOR (Insecure Direct Object Reference) — changing /api/orders/1 to /api/orders/2 shouldn't show another user's order; test this explicitly
- Assuming admin features are secure because the link is hidden — 'security by obscurity' is not security; always test that admin endpoints enforce authorization on the server side
Tip
Tip
Practice Authentication Authorization Testing in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
Technical diagram.
Practice Task
Note
Practice Task — (1) Write a working example of Authentication Authorization Testing from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Quick Quiz
Common Mistake
Warning
A common mistake with Authentication Authorization Testing is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready software testing code.
Key Takeaways
- Every application's security stands or falls on its authentication and authorization logic.
- Only testing authentication — testing that login works but not that session management, JWT validation, and logout are secure
- Testing authorization only for UI-blocked paths — attackers bypass the UI; always test authorization on direct API calls without going through the UI
- Not testing IDOR (Insecure Direct Object Reference) — changing /api/orders/1 to /api/orders/2 shouldn't show another user's order; test this explicitly