APIs are under constant attack. According to Gartner, APIs became the most frequent attack vector for enterprise web applications in 2023, and the trend has only accelerated. The OWASP API Security Top 10, updated in 2023, places Broken Object Level Authorization (BOLA) at the number one position for the second consecutive edition. Not injection. Not XSS. Authorization flaws in APIs.[1]
This ranking reflects what we see in the field. In our API penetration testing engagements, authentication and authorization flaws account for more critical-severity findings than all other vulnerability categories combined. These are not esoteric edge cases. They are systematic failures in how APIs verify who is making a request and what that requester is allowed to access.
The consequences are severe. A single BOLA vulnerability can expose every user record in your database. A broken JWT implementation can give an attacker persistent admin access. An API key leaked in a mobile app can provide unrestricted access to your backend services. This article covers the most dangerous API authentication flaws we encounter, how attackers exploit them, and what you need to do to prevent them.
Broken Object Level Authorization (BOLA): OWASP API #1
BOLA, also known as Insecure Direct Object Reference (IDOR) in the traditional OWASP Top 10, occurs when an API endpoint accepts an object identifier from the client and returns or modifies the corresponding object without verifying that the requesting user has permission to access it. It is the simplest vulnerability to understand, the easiest to exploit, and by far the most common API flaw in production systems.[2]
The Basic BOLA Pattern
Consider an API endpoint that returns user profile information:
GET /api/v1/users/1001/profile
Authorization: Bearer eyJhbGci...
Response:
{
"id": 1001,
"name": "Alice Smith",
"email": "[email protected]",
"phone": "+1-555-0101",
"ssn": "***-**-1234",
"address": "123 Main St, Springfield, IL"
}
The user authenticates with a valid token (they are who they claim to be), and the server returns user 1001's profile. Now the attacker changes the ID:
GET /api/v1/users/1002/profile
Authorization: Bearer eyJhbGci... (same token, still Alice's)
Response:
{
"id": 1002,
"name": "Bob Johnson",
"email": "[email protected]",
"phone": "+1-555-0102",
"ssn": "***-**-5678",
"address": "456 Oak Ave, Springfield, IL"
}
Alice's token was validated (authentication passed), but the server did not check whether Alice has permission to access Bob's profile (authorization failed). This is BOLA. The API authenticated the user but did not authorize the specific resource access.
Why BOLA Is So Prevalent
BOLA is common because of how modern APIs are built. Frameworks like Express.js, Django REST Framework, Spring Boot, and Laravel make it easy to create CRUD endpoints that accept an ID parameter and return the corresponding database record. The framework handles routing, serialization, and often authentication. But authorization, the check that asks "does this specific user have permission to access this specific object," must be implemented by the developer for every endpoint.
In a REST API with 50 endpoints, that is 50 places where the developer must remember to add authorization checks. Miss one, and you have a BOLA vulnerability. Add a new endpoint six months later when the original developer has left, and the chance of missing the check increases. This is why BOLA is not just common but persistent. It recurs in applications even after remediation because every new endpoint is a new opportunity to forget.[3]
BOLA Beyond Simple ID Substitution
The basic ID-in-URL pattern is just the starting point. BOLA manifests in several more subtle forms.
BOLA in request bodies: APIs that accept the object ID in the POST or PUT body rather than the URL are equally vulnerable, but they are harder to test with simple URL manipulation. The attacker needs to intercept and modify the request body:
PUT /api/v1/profile/update
Body: { "user_id": 1002, "phone": "+1-555-9999" }
BOLA in query parameters: Filter and search endpoints often accept object references as query parameters: /api/v1/documents?owner_id=1002. If the server filters documents by the provided owner_id without checking authorization, the attacker can access any user's documents.
BOLA via UUID guessing: Many teams switch from sequential integers to UUIDs, believing that unpredictability provides security. It does not. UUIDs are exposed in API responses, emails, URLs, and logs. A BOLA vulnerability with UUID identifiers is still a BOLA vulnerability. The exploitation just requires finding a valid UUID first, which is usually trivial through other API endpoints, shared links, or enumeration of publicly visible resources.
Critical point: Unpredictable identifiers (UUIDs, random strings) are a defense-in-depth measure, not a security control. Authorization must be enforced server-side regardless of the identifier format. An API that relies on UUID unpredictability for access control is one leaked UUID away from a data breach.
Broken Function Level Authorization: OWASP API #5
While BOLA is about accessing objects you should not see, Broken Function Level Authorization (BFLA) is about performing actions you should not be allowed to perform. It occurs when an API does not properly restrict which users can call which endpoints based on their role or privilege level.[1]
Horizontal vs. Vertical BFLA
Vertical BFLA is the more dangerous variant. A regular user discovers and calls an admin-only endpoint:
# Regular user calls admin endpoint
DELETE /api/v1/admin/users/1002
Authorization: Bearer eyJhbGci... (regular user token)
Response: 200 OK
{ "message": "User 1002 deleted successfully" }
The API authenticated the user and confirmed they have a valid session, but it did not check whether their role includes admin privileges before executing the delete operation. We find this vulnerability most often when admin and user APIs share the same codebase and the admin endpoints are "hidden" by not being linked in the user-facing UI, but are still accessible to anyone who knows (or guesses) the URL.
Horizontal BFLA involves a user performing actions on another user's resources. Unlike BOLA (which is about reading data), horizontal BFLA involves write operations: updating another user's settings, canceling another user's order, or sending a message as another user.
HTTP Method-Based BFLA
A pattern we see frequently is authorization that varies by HTTP method. A developer protects the DELETE /api/v1/users/1002 endpoint with admin-only authorization, but the PUT /api/v1/users/1002 endpoint (which can modify all user fields, including role) only requires basic authentication. Or the GET method is properly authorized, but sending the same URL with a PATCH method bypasses the authorization middleware because the route was only configured for GET.
Testing for this involves systematically trying every HTTP method (GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD) on every endpoint with different privilege levels. Burp Suite's "Change request method" feature and Postman's request duplication make this efficient. OWASP ZAP's active scanner includes tests for HTTP method tampering, though it cannot assess business-level authorization rules.[4]
Mass Assignment: The Silent Privilege Escalation
Mass assignment occurs when an API binds client-supplied data directly to internal data models without filtering which fields the client is allowed to set. This is OWASP API Security Top 10 #6 (Unrestricted Access to Sensitive Business Flows was added, pushing this to a shared position in the 2023 edition).[1]
How Mass Assignment Works
Consider a user profile update endpoint. The frontend sends:
PUT /api/v1/users/me
Body: { "name": "Alice", "email": "[email protected]" }
The server-side code does something like this (Node.js/Express example):
app.put('/api/v1/users/me', async (req, res) => {
await User.findByIdAndUpdate(req.user.id, req.body);
res.json({ success: true });
});
The req.body is passed directly to the database update function. If the User model has fields like role, isAdmin, emailVerified, creditBalance, or subscriptionTier, the attacker can set any of them:
PUT /api/v1/users/me
Body: {
"name": "Alice",
"role": "admin",
"isAdmin": true,
"creditBalance": 99999,
"subscriptionTier": "enterprise"
}
This vulnerability was famously exploited in the 2012 GitHub Rails vulnerability, where a researcher used mass assignment on the public key update endpoint to add his SSH key to any repository, including the Rails organization. It led to the introduction of Strong Parameters in Ruby on Rails.[5]
Finding Mass Assignment Vulnerabilities
The testing methodology is straightforward. For every API endpoint that accepts user input and modifies a resource, add extra fields to the request body that map to sensitive model attributes. The challenge is knowing which fields to try. Effective approaches include:
- Read API documentation or OpenAPI/Swagger specs for the full model schema
- Examine GET responses for the same resource. Fields returned in GET but not expected in PUT/PATCH are candidates
- Try common privilege-related fields:
role,isAdmin,admin,permissions,group,tier,plan,verified,active,approved - Check the frontend JavaScript for data models or TypeScript interfaces that reveal all fields
- Use Param Miner (a Burp Suite extension) to brute-force parameter names
JWT Implementation Flaws
JSON Web Tokens are the dominant API authentication mechanism, and they are implemented incorrectly more often than they are implemented correctly. The JWT specification (RFC 7519) is flexible enough to allow several dangerous configurations that undermine the token's security guarantees.[6]
The "none" Algorithm Attack
The JWT header includes an alg field that specifies the signing algorithm. The specification includes "alg": "none" as a valid value, intended for situations where the token integrity is guaranteed by other means (such as transport-layer security). If the server's JWT library accepts the "none" algorithm, an attacker can forge arbitrary tokens.
The attack is simple. Take a valid JWT, decode the header, change "alg": "HS256" to "alg": "none", modify the payload (change the user ID, add admin claims, extend the expiration), remove the signature, and send it. If the server accepts it, the attacker has complete control over their session claims.
# Original token header (base64 decoded)
{"alg": "HS256", "typ": "JWT"}
# Modified header
{"alg": "none", "typ": "JWT"}
# Forged token (note: no signature segment, or empty signature)
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyX2lkIjoxLCJyb2xlIjoiYWRtaW4ifQ.
Modern JWT libraries have mostly patched this by default, but we still find it in applications using older library versions, custom JWT implementations, or libraries where the developer must explicitly reject the "none" algorithm. The tool jwt_tool by ticarpi automates testing for this and several other JWT attacks.[7]
Algorithm Confusion (RS256 to HS256)
This is a more sophisticated attack that exploits the difference between asymmetric and symmetric signing. When an API uses RS256 (asymmetric), it signs tokens with a private key and verifies them with the corresponding public key. If the server can be tricked into treating the token as HS256 (symmetric), it will use the public key as the HMAC secret.
Since the public key is, by definition, public (often available at /.well-known/jwks.json), the attacker can:
- Obtain the server's RSA public key
- Craft a new JWT with
"alg": "HS256" - Sign the token using the RSA public key as the HMAC secret
- Send the forged token to the server
If the server's JWT verification logic accepts any algorithm specified in the header (rather than enforcing the expected algorithm), it will verify the HS256 signature using the public key and accept the forged token. This attack (CVE-2016-10555 for the Node.js jsonwebtoken library) affected multiple JWT libraries and remains exploitable in applications using vulnerable versions.[8]
Weak Signing Secrets
HS256 tokens are signed with a shared secret. If that secret is weak (a dictionary word, a short string, or a default value like "secret" or "changeme"), it can be cracked offline. The tool hashcat supports JWT cracking (mode 16500), and purpose-built tools like jwt-cracker can test billions of candidates per second on modern GPUs.
In our assessments, we run every HS256 JWT through a wordlist attack using a curated list of common secrets. The success rate is disturbingly high. We have cracked production JWT secrets that were set to values like secret123, myapp-jwt-key, company names, and even empty strings.
Missing Token Expiration and Revocation
JWTs are stateless by design, which means the server does not need to look up session state to validate them. The downside is that there is no built-in mechanism for token revocation. If a JWT has no expiration (exp claim), or if the expiration is set to an unreasonably long period (years), a compromised token provides persistent access.
We test for this by examining the exp claim in decoded tokens. We also test whether the server actually enforces expiration by modifying the exp claim to a past timestamp and checking if the token is still accepted. On several occasions, we have found that the server decodes the JWT for the user identity but does not validate the expiration, making every token effectively permanent.
JWT security checklist: Enforce a specific algorithm server-side (never trust the header's alg value). Use strong signing secrets (256+ bits of entropy for HS256). Set reasonable expiration times (15-60 minutes for access tokens). Implement a token blocklist or use short-lived tokens with refresh token rotation for revocation capability.
API Key Leakage in Client Applications
API keys embedded in client-side applications, whether mobile apps, single-page applications, or desktop software, are not secrets. They are public information that anyone can extract with minimal effort. Despite this, we regularly find production APIs where the only authentication mechanism is an API key that ships with the client application.
Mobile App API Key Extraction
Extracting API keys from mobile applications requires no special expertise. For Android, decompiling the APK with jadx or apktool and searching for strings like "api_key", "apiKey", "authorization", or "Bearer" typically reveals the key within minutes. For iOS, the process involves extracting the IPA (using tools like frida-ios-dump), then searching the binary and embedded plist files for key-value pairs.
Even applications that use "advanced" obfuscation are vulnerable. The key must eventually be used in an HTTP request, so intercepting traffic with a proxy (Burp Suite, mitmproxy) reveals the key regardless of how it is stored or obfuscated in the binary. Certificate pinning adds friction but is bypassable with tools like Frida and Objection.[9]
JavaScript SPA Key Exposure
Single-page applications are even simpler. API keys, service account tokens, and third-party credentials are embedded in the JavaScript bundle that the browser downloads and executes. Opening the browser's developer tools and searching through the Sources tab (or running grep -r "key\|token\|secret\|api" bundle.js) exposes them immediately.
We have found AWS access keys, Stripe secret keys (not publishable keys, actual secret keys), database connection strings, and internal service tokens in production JavaScript bundles. In one engagement, we extracted a Firebase Admin SDK key from the frontend JavaScript that provided full read/write access to the production database, including user passwords stored in plaintext.
Version Control Exposure
API keys committed to version control history persist even after deletion. Tools like truffleHog, git-secrets, and gitleaks scan repository history for high-entropy strings and known secret patterns. GitHub's own secret scanning feature detects keys from dozens of service providers and sends automated alerts, but only for public repositories in the free tier.
Rate Limiting Bypass Techniques
Rate limiting is the primary defense against brute-force attacks on authentication endpoints. When it is implemented poorly, attackers can bypass it through several techniques.
Header-Based Bypass
Many rate limiting implementations use the client's IP address as the rate limit key. When the application sits behind a load balancer or CDN, the client IP comes from headers like X-Forwarded-For, X-Real-IP, or CF-Connecting-IP. If the application trusts these headers without validation, the attacker can spoof a different IP with each request:
POST /api/auth/login
X-Forwarded-For: 1.2.3.4
Body: { "email": "[email protected]", "password": "attempt1" }
POST /api/auth/login
X-Forwarded-For: 1.2.3.5
Body: { "email": "[email protected]", "password": "attempt2" }
Each request appears to come from a different IP, so the rate limiter treats them as separate clients. We test this on every engagement by adding, modifying, and rotating IP-related headers. The success rate is surprisingly high, particularly on applications that use application-level rate limiting (Express rate-limit, Django Ratelimit) rather than infrastructure-level controls (Cloudflare, AWS WAF).
Endpoint Variation
Rate limits applied per-endpoint can be bypassed by hitting equivalent endpoints. If /api/v1/login is rate limited, try /api/v2/login, /api/Login (case variation), /api/v1/login/ (trailing slash), /api/v1/login?dummy=1 (added parameter), or /api/v1/auth/signin (alternative path). Some applications have multiple authentication endpoints that all validate against the same user database but have independent rate limits.
Account Lockout Bypass
Account lockout mechanisms that lock the account after N failed attempts can be bypassed by distributing attempts across accounts (password spraying) rather than concentrating on one account. Instead of trying 1,000 passwords against one account, the attacker tries one common password (like "Password1!") against 1,000 accounts. This stays below per-account lockout thresholds while still compromising accounts with weak passwords.[10]
Token Replay and Session Attacks
Even when tokens are properly generated and validated, the way they are transmitted, stored, and invalidated creates additional attack surface.
Token Replay Attacks
If an attacker captures a valid authentication token (through network interception, log file access, URL leakage via Referer headers, or browser history), they can replay it to gain the victim's access. Defenses include:
- Short token lifetimes: Access tokens that expire in 15 minutes limit the replay window
- Token binding: Binding tokens to the client's IP address or TLS session prevents replay from different network locations
- One-time tokens: For sensitive operations, issue single-use tokens that are invalidated after first use
- Refresh token rotation: Each time a refresh token is used, issue a new one and invalidate the old one. If the old token is replayed, it signals compromise and all tokens for that session can be revoked
Insecure Token Storage
Where tokens are stored on the client determines their vulnerability to theft. Tokens in localStorage are accessible to any JavaScript running on the page, making them vulnerable to XSS. Tokens in cookies without the HttpOnly flag are equally exposed. Tokens in cookies with HttpOnly and Secure flags are protected from JavaScript access and only sent over HTTPS, but are vulnerable to CSRF if the SameSite attribute is not set.
The most secure pattern for browser-based applications is to use HttpOnly, Secure, SameSite=Strict cookies for session tokens, combined with CSRF protection for state-changing requests. For API-only scenarios (mobile apps, service-to-service), short-lived bearer tokens with refresh token rotation provide the best balance of security and usability.
Real-World API Authentication Breaches
These vulnerabilities are not theoretical. They have been exploited at scale in major breaches.
- Optus (2022): An unauthenticated API endpoint exposed the personal data of 9.8 million customers. The endpoint did not require any authentication, and customer records could be accessed by incrementing a sequential identifier. This was a BOLA vulnerability combined with broken authentication (or rather, absent authentication).[3]
- T-Mobile (2023): An API vulnerability exposed the personal data of 37 million customers. The attacker accessed the API starting in November 2022 and was not detected until January 2023, demonstrating the difficulty of detecting BOLA exploitation when each individual request looks legitimate.
- Peloton (2021): A researcher discovered that Peloton's API returned user data including age, gender, city, weight, and workout history for any user ID, even when the requesting user had a private profile. The API checked authentication (valid token required) but did not enforce authorization (any valid token could access any profile).
- Coinbase (2019): A bug bounty researcher found a mass assignment vulnerability in Coinbase's API that could have allowed transferring cryptocurrency from any user's account. The vulnerability was in a trading endpoint that accepted a
source_account_idparameter without verifying ownership. The payout was $25,000. - Facebook (2018): The "View As" feature contained an API vulnerability that generated access tokens for the user being viewed, rather than the viewing user. This allowed attackers to steal access tokens for approximately 50 million users. While not a pure BOLA, the root cause was an authorization flaw in how API tokens were scoped.
Testing API Authentication: A Practical Approach
Here is the methodology we follow at Lorikeet Security for API authentication testing.
Phase 1: Enumerate and Map
- Collect all API endpoints from documentation, OpenAPI/Swagger specs, JavaScript bundles, and traffic interception
- Map authentication mechanisms: which endpoints require auth, what type (JWT, API key, session cookie, OAuth), and how tokens are obtained
- Create accounts at every privilege level available (unauthenticated, free user, paid user, admin if possible)
- Document the identifier format for all resources (sequential integers, UUIDs, slugs)
Phase 2: BOLA Testing
- For every endpoint that accepts a resource identifier, substitute identifiers belonging to other users
- Test with IDs from the same tenant (horizontal) and from different tenants (cross-tenant)
- Test all HTTP methods (GET for read access, PUT/PATCH/DELETE for write access)
- Test indirect references: filter parameters, relationship IDs, nested resource paths
- Use Burp Suite's Autorize extension to automate this across all captured requests[4]
Phase 3: BFLA Testing
- Call every admin endpoint with a regular user token
- Call every endpoint with no authentication token
- Test HTTP method variations on every endpoint
- Look for admin functionality exposed through the same API path structure (e.g.,
/api/admin/)
Phase 4: Token and Session Testing
- Decode JWTs and analyze claims, algorithm, and expiration
- Test for "none" algorithm, algorithm confusion, and weak secrets
- Test token expiration enforcement
- Test token revocation (does logging out actually invalidate the token?)
- Test for token leakage in URLs, logs, and error messages
Phase 5: Rate Limiting and Brute-Force
- Test rate limits on login, registration, password reset, and OTP endpoints
- Attempt header-based bypass (X-Forwarded-For spoofing)
- Test endpoint variations
- Assess account lockout mechanisms and bypass potential
Tool recommendation: For systematic API auth testing, we use Burp Suite Professional with the Autorize extension (automated authorization testing), AuthMatrix (role-based access testing), and JSON Web Token Attacker (JWT-specific attacks). For API-first testing, Postman collections with environment variables allow rapid switching between user contexts.[4]
Building Secure API Authentication
Fixing these vulnerabilities requires a combination of architectural decisions and implementation discipline.
- Implement authorization at the data layer. Instead of checking authorization in each route handler (which developers will forget), implement it as a middleware or data access layer that automatically scopes all queries to the authenticated user's permissions. In SQL, this means every query includes a
WHERE tenant_id = ?clause enforced by the ORM, not by the developer. - Use an authorization framework. Tools like Open Policy Agent (OPA), Casbin, and OWASP's authorization libraries provide policy-as-code authorization that can be tested, versioned, and audited independently of the application code.
- Enforce JWT best practices. Pin the expected algorithm server-side. Use strong signing secrets (generated by a CSPRNG, at least 256 bits). Set short expiration times. Implement refresh token rotation with reuse detection.
- Never put secrets in client code. Use the Backend for Frontend (BFF) pattern to keep API keys on the server. For third-party services that require client-side access (like Stripe), use the publishable keys designed for client exposure, never the secret keys.
- Implement defense-in-depth rate limiting. Apply rate limits at the infrastructure level (WAF/CDN), the application level (per-user, per-IP), and the business logic level (per-account lockout). Do not trust client-supplied IP headers without validation against a known proxy allowlist.
- Log and monitor authorization failures. A spike in 403 responses for a single user, or a pattern of sequential ID access attempts, is a strong signal of BOLA exploitation. These patterns should trigger alerts, not just log entries.
- Include API auth testing in every penetration test. Specifically request BOLA, BFLA, and JWT testing as part of your pentest scope. Provide your testers with API documentation, multiple user accounts at different privilege levels, and information about your authorization model.
API authentication flaws are not decreasing. As applications move toward API-first architectures, more business logic and more sensitive data flow through API endpoints. The OWASP API Security Top 10 exists because the traditional web application Top 10 did not adequately address the unique risks of API-driven applications.
The path from a single BOLA vulnerability to full account takeover, mass data exfiltration, or financial fraud is short. In many cases, it is a single HTTP request with a modified ID parameter. The organizations that avoid these outcomes are the ones that treat API authorization as a first-class architectural concern, test it rigorously, and continue testing it as the API evolves.
If your API handles sensitive data and you have not had a dedicated API security assessment, the vulnerabilities described in this article are likely present. The question is not whether they exist, but how many, and who finds them first.
Sources
- OWASP, "OWASP API Security Top 10 - 2023," OWASP Foundation, https://owasp.org/API-Security/editions/2023/en/0x11-t10/
- OWASP, "API1:2023 - Broken Object Level Authorization," OWASP API Security Project, https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/
- Australian Information Commissioner, "Optus Data Breach Investigation Report," OAIC, 2023, https://www.oaic.gov.au/
- PortSwigger, "Testing for API Authorization Issues," Burp Suite Documentation, https://portswigger.net/burp/documentation
- Egor Homakov, "How I Hacked GitHub Again," 2012, https://homakov.blogspot.com/2012/03/how-to.html
- IETF, "RFC 7519 - JSON Web Token (JWT)," Internet Engineering Task Force, https://datatracker.ietf.org/doc/html/rfc7519
- ticarpi, "jwt_tool - A Toolkit for Testing, Tweaking and Cracking JSON Web Tokens," https://github.com/ticarpi/jwt_tool
- Auth0, "Critical Vulnerabilities in JSON Web Token Libraries," 2015, https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
- OWASP, "Mobile Security Testing Guide - Network Communication," OWASP MASTG, https://mas.owasp.org/MASTG/
- OWASP, "Credential Stuffing Prevention Cheat Sheet," OWASP Cheat Sheet Series, https://cheatsheetseries.owasp.org/cheatsheets/Credential_Stuffing_Prevention_Cheat_Sheet.html
How Secure Are Your API Endpoints?
BOLA, JWT flaws, and broken authorization are the most exploited API vulnerabilities in the wild. Our penetration testers specialize in API-first security assessments that cover the full OWASP API Security Top 10.
Book a Consultation Our Services