API Authentication & Security Testing

Complete guide to testing authentication methods and securing your web APIs

Published: January 2025 • 14 min read

API authentication and security are critical for protecting your services and user data. A single vulnerability can expose sensitive information, allow unauthorized access, or compromise your entire system. Proper testing ensures your authentication mechanisms work correctly and your APIs are secure.

This guide covers the most common authentication methods - JWT, OAuth 2.0, and API keys - along with security best practices and testing strategies. You'll learn what to test, how to test it, and common vulnerabilities to watch for.

JWT (JSON Web Token) Authentication

JWT is one of the most popular authentication methods for REST APIs. It's a self-contained token that carries user information and can be verified without database lookups. But if not implemented correctly, JWTs can be vulnerable to attacks.

Understanding JWT Structure

A JWT consists of three parts: Header, Payload, and Signature, separated by dots.

// JWT Token Structure
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

// Decoded:
// Header
{
  "alg": "HS256",    // Algorithm
  "typ": "JWT"       // Token type
}

// Payload
{
  "sub": "1234567890",           // Subject (user ID)
  "name": "John Doe",            // Custom claims
  "role": "admin",
  "iat": 1516239022,             // Issued at (timestamp)
  "exp": 1516242622              // Expiration (timestamp)
}

// Signature = HMACSHA256(
//   base64(header) + "." + base64(payload),
//   secret_key
// )

✓ Testing JWT Authentication Flow

Test the complete authentication flow: login, token generation, token validation, and token expiration.

// 1. Login - Get JWT Token
POST /api/auth/login
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "SecurePass123!"
}

Response: 200 OK
{
  "token": "eyJhbGc...",
  "refresh_token": "eyJhbGc...",
  "expires_in": 3600,
  "token_type": "Bearer"
}

// 2. Use Token for Protected Endpoint
GET /api/users/me
Authorization: Bearer eyJhbGc...

Response: 200 OK
{
  "id": "123",
  "name": "John Doe",
  "email": "[email protected]"
}

// 3. Try Without Token
GET /api/users/me

Response: 401 Unauthorized
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authentication required"
  }
}

// 4. Try With Invalid Token
GET /api/users/me
Authorization: Bearer invalid_token

Response: 401 Unauthorized
{
  "error": {
    "code": "INVALID_TOKEN",
    "message": "Invalid or malformed token"
  }
}

JWT Test Cases:

  • ✓ Valid credentials return valid JWT
  • ✓ Invalid credentials are rejected
  • ✓ Token works for protected endpoints
  • ✓ Expired tokens are rejected
  • ✓ Tampered tokens are rejected
  • ✓ Missing tokens return 401
  • ✓ Tokens have reasonable expiration time
  • ✓ Refresh tokens work to get new access tokens

Critical JWT Security Tests

These vulnerabilities are common in JWT implementations. Always test for them.

Test #1: Algorithm Confusion Attack

Change algorithm from RS256 (asymmetric) to HS256 (symmetric) and sign with public key. Server should reject this.

// Original token uses RS256
{"alg": "RS256", "typ": "JWT"}

// Attacker changes to HS256
{"alg": "HS256", "typ": "JWT"}
// Server must reject this!

Test #2: None Algorithm Attack

Set algorithm to "none" (no signature). Server should reject unsigned tokens.

{"alg": "none", "typ": "JWT"}
// Server must require signature!

Test #3: Token Tampering

Modify payload (change role from user to admin) without valid signature. Server should reject.

// Original: {"role": "user"}
// Modified: {"role": "admin"}
// Without re-signing = Invalid!

Test #4: Expired Token Usage

Try using expired token. Server should check exp claim and reject.

{"exp": 1516239022}  // Past timestamp
// Server must check and reject!

✓ JWT Best Practices to Test

  • Use strong algorithms: RS256 or ES256, not HS256 with weak secrets
  • Set reasonable expiration: 15 minutes to 1 hour for access tokens
  • Use refresh tokens: Long-lived refresh tokens to get new access tokens
  • Don't store sensitive data: JWT payload is readable (base64, not encrypted)
  • Validate all claims: Check exp, iss (issuer), aud (audience)
  • Use HTTPS only: Never send JWT over unencrypted HTTP

OAuth 2.0 Authentication

OAuth 2.0 is the industry standard for authorization. It allows users to grant third-party applications limited access to their resources without sharing passwords. Testing OAuth flows is more complex due to multiple steps and redirect URLs.

Authorization Code Flow (Most Common)

The most secure OAuth 2.0 flow for web applications. Test each step of the flow.

// Step 1: Redirect user to authorization endpoint
GET /oauth/authorize?
  response_type=code&
  client_id=YOUR_CLIENT_ID&
  redirect_uri=https://yourapp.com/callback&
  scope=read:profile,write:posts&
  state=random_state_string

// User logs in and approves...

// Step 2: Authorization server redirects back with code
GET https://yourapp.com/callback?
  code=AUTH_CODE_HERE&
  state=random_state_string

// Step 3: Exchange code for access token
POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=AUTH_CODE_HERE&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
redirect_uri=https://yourapp.com/callback

Response: 200 OK
{
  "access_token": "ya29.a0AfH6...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "1//0gJw...",
  "scope": "read:profile write:posts"
}

// Step 4: Use access token
GET /api/user/profile
Authorization: Bearer ya29.a0AfH6...

Response: 200 OK
{
  "id": "123",
  "name": "John Doe",
  "email": "[email protected]"
}

OAuth 2.0 Test Cases:

  • ✓ Authorization endpoint validates client_id
  • ✓ Authorization endpoint validates redirect_uri
  • ✓ State parameter prevents CSRF attacks
  • ✓ Authorization code can only be used once
  • ✓ Authorization code expires (5-10 minutes)
  • ✓ Token endpoint validates client credentials
  • ✓ Access token works for API requests
  • ✓ Refresh token can get new access tokens
  • ✓ Scopes are properly enforced

OAuth 2.0 Security Tests

Test #1: CSRF Protection (State Parameter)

Try callback without state parameter or with mismatched state. Server should reject.

Test #2: Redirect URI Validation

Try authorization with attacker's redirect_uri. Server should only allow pre-registered URLs.

Test #3: Authorization Code Reuse

Try using the same authorization code twice. Second attempt should be rejected.

Test #4: Scope Enforcement

Try accessing resources beyond granted scopes. Server should return 403 Forbidden.

Other OAuth 2.0 Flows

Implicit Flow (Deprecated)

Directly returns access token in URL fragment. Less secure, avoid if possible. Use Authorization Code with PKCE instead.

Client Credentials Flow

Machine-to-machine authentication. Client directly exchanges credentials for access token. Test that client_id and client_secret are validated.

Password Grant (Not Recommended)

User provides username/password directly to app. Only use for trusted first-party apps. Prefer Authorization Code flow.

PKCE (Proof Key for Code Exchange)

Extension for Authorization Code flow. Prevents authorization code interception. Essential for mobile and SPA apps.

API Key Authentication

API keys are the simplest authentication method - just a secret string that identifies the caller. While easy to implement, they require careful handling to stay secure.

✓ Using API Keys Securely

API keys can be sent in headers, query parameters, or request body. Headers are most secure.

✓ RECOMMENDED - Header:

GET /api/data
X-API-Key: sk_live_51H...
// or
Authorization: ApiKey sk_live_51H...

// Pros:
// - Not logged in URLs
// - Not cached by browsers
// - Not visible in history

⚠LESS SECURE - Query:

GET /api/data?api_key=sk_live_51H...

// Cons:
// - Logged in server logs
// - Cached by browsers
// - Visible in history
// - Shared in URLs

API Key Test Cases:

  • ✓ Valid API key grants access
  • ✓ Invalid API key is rejected (401)
  • ✓ Missing API key is rejected (401)
  • ✓ Expired/revoked keys are rejected
  • ✓ Rate limits are enforced per key
  • ✓ Keys can be rotated/regenerated
  • ✓ Different keys have different permissions

✓ API Key Best Practices

Generate Long, Random Keys

Use at least 32 characters from cryptographically secure random source. Don't use predictable patterns.

✓ sk_live_51HxYz... (good)
✗ user123_key (predictable)

Use Prefix to Identify Key Type

Prefix makes it easy to identify leaked keys and rotate them. Common pattern: environment_purpose_random

sk_live_... (secret key, production)
pk_test_... (public key, test)

Store Keys Securely

Hash keys before storing in database (like passwords). Only show full key once during creation.

Implement Rate Limiting

Prevent abuse by limiting requests per key. Return 429 Too Many Requests when limit exceeded.

Provide Key Management

Allow users to create, list, revoke, and rotate keys. Never send keys via email or insecure channels.

Monitor for Leaks

Monitor GitHub, logs, and error messages for accidentally exposed keys. Automatically revoke if detected.

General API Security Testing

Always Use HTTPS

Never send authentication credentials over unencrypted HTTP. Test that your API rejects or redirects HTTP requests.

HTTPS Tests:

  • ✓ API only accepts HTTPS connections
  • ✓ HTTP requests redirect to HTTPS or are rejected
  • ✓ HSTS header is set (Strict-Transport-Security)
  • ✓ Valid SSL/TLS certificate
  • ✓ Strong cipher suites (TLS 1.2+)

✓ Configure CORS Properly

Cross-Origin Resource Sharing (CORS) controls which domains can access your API from browsers.

❌ DANGEROUS:

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

// Allows ANY domain with credentials!
// Major security risk!

✓ SECURE:

Access-Control-Allow-Origin: 
  https://yourapp.com
Access-Control-Allow-Credentials: true

// Only allows specific domains
// Much safer!

✓ Implement Rate Limiting

Prevent brute force attacks and abuse by limiting requests per IP, user, or API key.

// Rate limit exceeded response
Response: 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1641902400

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Retry after 60 seconds."
  }
}

Rate Limiting Tests:

  • ✓ Returns 429 when limit exceeded
  • ✓ Includes Retry-After header
  • ✓ Resets after time window
  • ✓ Different limits for authenticated/anonymous
  • ✓ Login endpoints have strict limits (prevent brute force)

✓ Validate and Sanitize All Input

Test that your API properly validates input to prevent injection attacks.

SQL Injection Test

Try: '; DROP TABLE users; --

Should be rejected or escaped properly

XSS (Cross-Site Scripting) Test

Try: <script>alert('XSS')</script>

Should be sanitized in output

Path Traversal Test

Try: ../../etc/passwd

Should not access files outside allowed paths

Command Injection Test

Try: ; rm -rf /

Should never execute system commands from user input

✓ Test Authorization (Not Just Authentication)

Authentication verifies who you are. Authorization verifies what you can do. Test both!

// User A (authenticated) tries to access User B's data
GET /api/users/456/profile
Authorization: Bearer [User A's token]

Response: 403 Forbidden
{
  "error": {
    "code": "FORBIDDEN",
    "message": "You don't have permission to access this resource"
  }
}

// Admin can access any user
GET /api/users/456/profile
Authorization: Bearer [Admin token]

Response: 200 OK
{ "id": 456, "name": "User B", ... }

// Test Cases:
// ✓ Users can only access their own data
// ✓ Admins can access all data
// ✓ Roles are properly enforced
// ✓ Can't escalate privileges
// ✓ Horizontal privilege escalation prevented
// ✓ Vertical privilege escalation prevented

API Security Testing Checklist

✓ Authentication Tests

  • Valid credentials grant access
  • Invalid credentials are rejected
  • Tokens expire properly
  • Tampered tokens are rejected
  • Refresh tokens work correctly
  • Logout invalidates tokens

✓ Authorization Tests

  • Users can't access other users' data
  • Role-based permissions enforced
  • Privilege escalation prevented
  • Admin-only endpoints protected
  • Scope/permissions are validated
  • 403 returned for forbidden actions

✓ Security Tests

  • HTTPS enforced
  • CORS configured properly
  • Rate limiting works
  • Input validation prevents injection
  • No sensitive data in URLs/logs
  • Security headers present

✗ Vulnerabilities to Check

  • SQL injection
  • XSS (Cross-Site Scripting)
  • CSRF attacks
  • Path traversal
  • Mass assignment
  • Exposed sensitive data

Related Tools & Resources

External References

Official Documentation & Standards

Conclusion

API authentication and security are non-negotiable. A single vulnerability can compromise your entire system and expose sensitive user data. By thoroughly testing your authentication mechanisms - whether JWT, OAuth 2.0, or API keys - you protect your API from common attacks and ensure only authorized users can access resources.

Remember to test not just the happy path, but all failure scenarios: expired tokens, tampered credentials, missing authentication, insufficient permissions, and common attack vectors like SQL injection and XSS. Always use HTTPS, implement rate limiting, validate all input, and enforce proper authorization. Security testing should be part of your regular development workflow, not an afterthought. Start testing today and build APIs that users can trust.