Authentication vs. Authorization
Before diving into mechanisms, understand the two concepts:
- Authentication (AuthN) — “Who are you?” Verifying the identity of the client making the request.
- Authorization (AuthZ) — “What can you do?” Determining what resources and actions the authenticated client is allowed to access.
A user might be authenticated (logged in) but not authorized (lacks permission) to delete another user’s account. Testing both aspects is critical.
API Key Authentication
The simplest form of API authentication. The server issues a unique key that the client includes with every request.
How It Works
GET /api/weather?city=London HTTP/1.1
Host: api.weather.com
X-API-Key: sk_live_abc123def456
Or as a query parameter (less secure):
GET /api/weather?city=London&api_key=sk_live_abc123def456
Testing API Keys
| Test Scenario | Expected Result |
|---|---|
| Valid API key | 200 OK with data |
| Missing API key | 401 Unauthorized |
| Invalid/expired API key | 401 or 403 |
| Revoked API key | 401 Unauthorized |
| API key from wrong environment | 401/403 |
| Rate limit exceeded for key | 429 Too Many Requests |
| API key in URL vs header | Both should work (or URL should be rejected) |
Security Concerns
- API keys should never be in URLs (they appear in server logs and browser history)
- Keys should be transmitted over HTTPS only
- Keys should have scoped permissions (read-only vs read-write)
Basic Authentication
Sends username and password encoded in Base64 with every request. Simple but not very secure.
How It Works
GET /api/users HTTP/1.1
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
The value after “Basic” is username:password encoded in Base64. It is not encrypted — anyone intercepting the request can decode it.
Testing Basic Auth
# Encode credentials
echo -n "admin:secret123" | base64
# Result: YWRtaW46c2VjcmV0MTIz
# Use in request
curl -H "Authorization: Basic YWRtaW46c2VjcmV0MTIz" https://api.example.com/users
Test scenarios: valid credentials, wrong password, non-existent user, empty credentials, malformed Base64.
OAuth 2.0
The industry standard for authorization. OAuth 2.0 allows third-party applications to access user resources without sharing passwords.
OAuth 2.0 Flows
Authorization Code Flow (most common for web apps):
- User clicks “Login with Google”
- App redirects to Google’s authorization server
- User logs in and grants permission
- Google redirects back with an authorization code
- App exchanges code for access token (server-to-server)
- App uses access token to call APIs
Client Credentials Flow (server-to-server, no user):
- App sends client_id and client_secret to auth server
- Auth server returns access token
- App uses token to call APIs
Key tokens in OAuth 2.0:
- Access Token — short-lived, used to access APIs (15 min to 1 hour)
- Refresh Token — long-lived, used to get new access tokens (days to months)
Testing OAuth 2.0
| Test Scenario | Expected Result |
|---|---|
| Valid access token | 200 OK |
| Expired access token | 401 Unauthorized |
| Refresh token flow | New access token issued |
| Expired refresh token | Force re-authentication |
| Invalid scope | 403 Forbidden |
| Token with insufficient scope | 403 with scope error |
| Revoked token | 401 Unauthorized |
| Using access token after logout | 401 Unauthorized |
JSON Web Tokens (JWT)
JWT is a compact, self-contained token format commonly used for authentication. It allows the server to verify the token without querying a database.
JWT Structure
A JWT has three parts separated by dots: header.payload.signature
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0.signature
Header (algorithm and type):
{
"alg": "HS256",
"typ": "JWT"
}
Payload (claims — user data):
{
"sub": "1234567890",
"name": "Alice",
"role": "admin",
"iat": 1716239022,
"exp": 1716242622
}
Signature (verification):
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
Important JWT Claims
| Claim | Name | Purpose |
|---|---|---|
sub | Subject | User identifier |
iat | Issued At | When the token was created |
exp | Expiration | When the token expires |
iss | Issuer | Who created the token |
aud | Audience | Who the token is intended for |
role | Role | User’s permissions (custom) |
Testing JWT Authentication
Token validation tests:
- Valid token — 200 OK
- Expired token — 401 Unauthorized
- Tampered payload (modified claims) — 401 (signature invalid)
- Missing signature — 401
- Token signed with wrong secret — 401
- Token with
alg: none— 401 (critical security test)
Token lifecycle tests:
- Verify token contains expected claims after login
- Verify expired tokens are rejected
- Test refresh flow generates new valid token
- Verify old tokens are invalid after password change
- Test token works across different API endpoints
Decode JWT for testing (use jwt.io or command line):
# Decode payload (middle part)
echo "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0" | base64 -d
Common Authentication Vulnerabilities to Test
1. Broken Authentication
- Can you access protected endpoints without any token?
- Does the API accept tokens after they should be invalidated?
- Are passwords transmitted in plain text?
2. Token Manipulation
- Can you modify JWT claims and still access resources? (weak/missing signature verification)
- Does
alg: nonebypass signature verification? - Can you use a token from one environment in another?
3. Brute Force Protection
- Is there a lockout after multiple failed login attempts?
- Are rate limits applied to authentication endpoints?
- Do error messages reveal whether a username exists?
4. Session Management
- Are tokens invalidated on logout?
- Does password change invalidate existing tokens?
- Can multiple active sessions exist simultaneously?
Hands-On Exercise
- Set up JWT testing: Use jwt.io to create and decode JWTs. Modify claims and observe what happens when you send tampered tokens.
- Test authentication flows: Using a public API that requires authentication (GitHub API with personal access tokens), test valid auth, invalid auth, and missing auth scenarios.
- Create a test matrix: For an imaginary API with admin and user roles, create a matrix of endpoints x roles x expected status codes.
- Test token expiration: If available, get a short-lived token and verify it’s rejected after expiration.
Key Takeaways
- Authentication verifies identity (who you are); authorization verifies permissions (what you can do)
- API Keys are simple but limited — best for server-to-server and public API rate limiting
- OAuth 2.0 is the standard for third-party access with multiple flows for different use cases
- JWTs are self-contained tokens with header, payload, and signature — the payload is readable by anyone
- Always test the complete lifecycle: valid tokens, expired tokens, revoked tokens, tampered tokens, and missing tokens