APIs have become the backbone of modern applications. Mobile apps, single-page applications, microservices architectures, and third-party integrations all communicate through APIs. This ubiquity makes API security one of the most critical—and frequently overlooked—aspects of application security.
The OWASP API Security Top 10 provides a framework for understanding API-specific risks. In this post, we'll examine the authentication and authorization vulnerabilities that dominate API penetration testing findings: Broken Object Level Authorization (BOLA), Broken Function Level Authorization, and the constellation of JWT and OAuth misconfigurations that enable account takeover.
Broken Object Level Authorization (BOLA)
OWASP API #1 Broken Object Level Authorization
APIs expose endpoints that handle object identifiers, creating a wide attack surface for object-level access control issues. Object-level authorization checks should be implemented in every function that accesses a data source using input from the user.
BOLA—also known as Insecure Direct Object Reference (IDOR)—occurs when an API exposes object identifiers and fails to verify that the requesting user has permission to access the referenced object. It remains the most prevalent API vulnerability we encounter during penetration tests.
The Pattern
Consider a typical API endpoint for retrieving user orders:
GET /api/v1/orders/12345
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
The API returns order details for order ID 12345. But what happens when an authenticated user changes that ID?
GET /api/v1/orders/12346
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
If the API returns details for order 12346—which belongs to a different user—the application has a BOLA vulnerability. The API verified that the requester is authenticated but failed to verify they're authorized to access that specific resource.
Why It's So Common
BOLA persists because:
- Sequential IDs: Auto-incrementing database IDs make enumeration trivial. An attacker can iterate through IDs to discover and access all resources.
- Framework defaults: ORMs and API frameworks often generate CRUD endpoints automatically. Developers must explicitly add authorization checks—they're not built in.
- Inconsistent implementation: Authorization logic scattered across multiple endpoints leads to gaps. One endpoint checks ownership; another forgets.
- Testing blind spots: Automated scanners struggle to detect BOLA because exploitation requires understanding application-specific object relationships.
Real-World Impact
BOLA vulnerabilities have enabled some of the largest API-related breaches:
- Exposing millions of users' personal data by enumerating user profile endpoints
- Accessing other customers' financial records, invoices, and payment details
- Downloading confidential documents by manipulating file reference IDs
- Modifying or deleting other users' data through PUT/DELETE operations on enumerable endpoints
Defense Strategies
Preventing BOLA requires authorization checks at every data access point:
- Centralized authorization: Implement authorization logic in a single layer that all data access flows through, rather than in individual endpoints
- Use indirect references: Map user-specific identifiers to actual database IDs server-side, so users can only reference their own objects
- Validate ownership: Every query should include a user context constraint (e.g.,
WHERE user_id = :current_user) - UUIDs over sequential IDs: While not a security control itself, UUIDs make enumeration impractical, reducing opportunistic exploitation
Broken Function Level Authorization
OWASP API #5 Broken Function Level Authorization
Complex access control policies with different hierarchies, groups, and roles, and an unclear separation between administrative and regular functions, tend to lead to authorization flaws.
While BOLA concerns access to specific objects, Broken Function Level Authorization concerns access to specific operations. The question isn't "can this user access this order?" but "can this user perform administrative actions?"
Common Manifestations
Hidden Administrative Endpoints
APIs often expose administrative functionality through predictable URL patterns:
# User endpoint
GET /api/v1/users/me
# Administrative endpoints (same API, different paths)
GET /api/v1/admin/users
POST /api/v1/admin/users
DELETE /api/v1/admin/users/12345
If the API relies on obscurity—assuming attackers won't discover the /admin/ path—rather than enforcing role-based access, any authenticated user can access administrative functions.
HTTP Method Confusion
Some APIs check authorization for certain HTTP methods but not others:
# GET is protected - returns 403 Forbidden
GET /api/v1/users/12345
# But PUT is not - allows modification
PUT /api/v1/users/12345
{"role": "admin"}
Parameter-Based Privilege Escalation
APIs that accept role or permission parameters without validation enable direct privilege escalation:
POST /api/v1/users
{
"email": "attacker@example.com",
"password": "...",
"role": "admin" // Should be ignored for non-admin requesters
}
Defense Strategies
- Deny by default: All endpoints should require explicit authorization configuration. Unannotated endpoints should be inaccessible.
- Separate administrative APIs: Administrative functions should run on a separate API with distinct authentication, ideally on a separate network segment.
- Consistent authorization middleware: Role checks should happen in middleware that processes every request, not in individual endpoint handlers.
- Audit all HTTP methods: Authorization testing must cover GET, POST, PUT, PATCH, DELETE, and any custom methods the API supports.
JWT Vulnerabilities
JSON Web Tokens have become the dominant API authentication mechanism. Their stateless nature suits distributed architectures, but implementation mistakes create serious vulnerabilities.
Algorithm Confusion Attacks
JWTs specify their signing algorithm in the header:
{
"alg": "HS256",
"typ": "JWT"
}
The classic "alg: none" attack exploits libraries that accept unsigned tokens when the algorithm is set to "none":
{
"alg": "none",
"typ": "JWT"
}
// No signature required
Most modern libraries reject this, but a more subtle variant persists: RS256 to HS256 confusion.
When an API uses RS256 (asymmetric RSA signing), it verifies tokens with a public key. If an attacker can trick the API into treating that public key as an HS256 (symmetric HMAC) secret, they can:
- Obtain the public key (often exposed at
/.well-known/jwks.json) - Create a new token with
"alg": "HS256" - Sign it using the public key as the HMAC secret
- Submit the token—the API verifies the signature using the public key, which now "matches"
Weak Secrets
HS256 tokens signed with weak secrets can be cracked offline. Tools like hashcat can test millions of candidate secrets per second against a captured token:
hashcat -a 0 -m 16500 jwt.txt wordlist.txt
Common weak secrets include:
- Default values from tutorials ("secret", "key", "password")
- Short or low-entropy strings
- Application names or environment names
- Secrets committed to version control
Missing Expiration Enforcement
JWTs should include an exp (expiration) claim, but applications must actually check it:
{
"sub": "user123",
"role": "admin",
"exp": 1609459200 // January 1, 2021
}
If the server doesn't validate expiration, tokens remain valid indefinitely. Stolen tokens become permanent credentials.
Insufficient Claim Validation
Beyond expiration, JWTs contain claims that must be validated:
- iss (issuer): Is this token from our authentication server?
- aud (audience): Is this token intended for our API?
- nbf (not before): Is this token active yet?
Accepting tokens without validating these claims allows cross-service token reuse and tokens from unrelated issuers.
JWT Best Practices
- Explicitly configure allowed algorithms; never accept algorithms from the token header without validation
- Use RS256 or ES256 (asymmetric) to avoid shared secret distribution
- If using HS256, generate secrets with at least 256 bits of cryptographic randomness
- Validate all relevant claims: exp, iss, aud, nbf
- Keep token lifetimes short (minutes to hours, not days)
- Implement token revocation for sensitive operations
OAuth Implementation Flaws
OAuth 2.0 provides a framework for delegated authorization, but its flexibility creates numerous opportunities for misconfiguration.
Redirect URI Manipulation
OAuth flows redirect users back to the client application with authorization codes or tokens. If redirect URI validation is weak, attackers can steal these credentials:
Open Redirects
# Registered redirect URI
https://app.example.com/callback
# Attacker-controlled redirect (if validation is substring-based)
https://app.example.com.attacker.com/callback
https://app.example.com/callback/../../../attacker.com
Localhost Bypasses
Mobile and desktop applications often register localhost redirect URIs. If the authorization server allows any localhost port, attackers running local services can intercept tokens.
State Parameter Omission
The state parameter prevents CSRF attacks against the OAuth flow. Without it, attackers can:
- Initiate an OAuth flow with their own account
- Capture the authorization URL before completion
- Trick a victim into completing the flow
- The victim's session is now linked to the attacker's identity provider account
Token Leakage via Referrer
When authorization codes or tokens appear in URLs (implicit flow, authorization code flow before PKCE), they may leak via:
- Referrer headers when the page loads external resources
- Browser history
- Server logs
- Shared or cached pages
OAuth Best Practices
- Use Authorization Code flow with PKCE, never Implicit flow
- Validate redirect URIs with exact string matching
- Always require and validate the state parameter
- Use short-lived authorization codes (single use, expire in minutes)
- Bind tokens to specific clients using client authentication
API Key Security
Many APIs still use API keys for authentication. While simpler than OAuth, API keys have significant security limitations that developers must understand.
Common API Key Mistakes
Keys in URLs
GET /api/data?api_key=sk_live_abc123def456
API keys in query strings appear in logs, browser history, and referrer headers. They're also visible to anyone with network access.
Client-Side Exposure
API keys embedded in JavaScript, mobile apps, or client-side code can be extracted by anyone with access to the application. These keys should be considered public.
Overprivileged Keys
Keys that grant full API access when the use case only requires read access to specific endpoints violate least privilege. A compromised key's blast radius should be minimized.
API Key Best Practices
- Transmit keys in headers, never URLs
- Scope keys to specific operations and resources
- Implement key rotation mechanisms
- Monitor and alert on anomalous key usage
- For client-side applications, proxy through a backend that holds the real credentials
Rate Limiting and Brute Force
Authentication endpoints are natural targets for brute force attacks. Without rate limiting, attackers can attempt millions of credentials.
What to Rate Limit
- Login attempts: Per username, per IP, and globally
- Password reset requests: Per email/phone and per IP
- OTP/MFA attempts: Per session and per user
- API key generation: Per user account
- Token refresh: Per refresh token
Effective Rate Limiting
Naive rate limiting by IP alone is insufficient. Attackers distribute attacks across botnets. Effective strategies combine multiple signals:
- IP address reputation and geolocation
- Device fingerprinting
- Account-level tracking (failed attempts per username)
- Behavioral analysis (request patterns, timing)
- Progressive delays and lockouts
Testing API Authentication
API authentication testing should be systematic. Here's a condensed checklist from our penetration testing methodology:
BOLA Testing
- Identify all endpoints that accept object IDs
- Create two test accounts with known resources
- Attempt cross-account access for each endpoint
- Test both read (GET) and write (PUT/DELETE) operations
- Check for ID enumeration (sequential, predictable patterns)
Function Level Authorization
- Map all API endpoints, including administrative paths
- Test each endpoint with tokens of different privilege levels
- Attempt privilege escalation via parameter manipulation
- Test all HTTP methods, not just those documented
JWT Testing
- Attempt algorithm confusion (none, HS256/RS256 swap)
- Test signature verification (modify payload, keep signature)
- Check expiration enforcement with expired tokens
- Verify claim validation (wrong issuer, wrong audience)
- Attempt secret cracking if HS256
OAuth Testing
- Test redirect URI validation with variations
- Verify state parameter requirement
- Check for token leakage in URLs and logs
- Test authorization code reuse
Conclusion
API authentication and authorization vulnerabilities remain the most impactful findings in modern application security assessments. The shift to API-centric architectures has expanded the attack surface while the complexity of OAuth, JWT, and microservices authorization has increased implementation difficulty.
Key principles for secure API authentication:
- Defense in depth: Combine multiple security controls; don't rely on a single mechanism
- Centralized authorization: Implement authorization logic in one place, enforce it everywhere
- Least privilege: Tokens, keys, and sessions should have minimal necessary permissions
- Assume breach: Design for token theft with short lifetimes, revocation, and monitoring
- Test thoroughly: Automated scanners miss most authorization flaws; manual testing is essential
The good news: these vulnerabilities are preventable with disciplined implementation. The bad news: they require constant vigilance as APIs evolve and new endpoints are added.