Authentication is the front door of every web application. If it fails, nothing else matters. Access controls, encryption, logging, input validation - all of it becomes irrelevant when an attacker can log in as any user they choose or bypass the login flow entirely.
We test authentication mechanisms in every web application engagement we perform. Not with a checklist and a scanner, but with targeted manual techniques that probe the specific ways authentication implementations break. Over the years, we have developed a reliable playbook of attack techniques that consistently produce results.
This article covers the authentication bypass methods we use most frequently: JWT manipulation, session management attacks, OAuth misconfigurations, password reset flaws, MFA bypass, and race conditions. These are not theoretical attacks. They are techniques we have successfully used against production applications.
JWT Manipulation: When Tokens Become Weapons
JSON Web Tokens have become the default authentication mechanism for modern APIs and single-page applications. They are stateless, portable, and well-supported by every major framework. They are also consistently misconfigured in ways that allow complete authentication bypass.
The "none" Algorithm Attack
The JWT specification (RFC 7519) defines an "alg" field in the token header that specifies which signing algorithm was used.[1] One valid value for this field is "none", which indicates an unsigned token. If the server accepts tokens with "alg": "none" and does not verify a signature, an attacker can forge arbitrary tokens.
The attack is straightforward. Take a valid JWT, decode the header (it is just base64), change the algorithm to "none", modify the payload to impersonate any user, re-encode the header and payload, and strip the signature. The resulting token looks like eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTcwOTgzMjAwMH0. - note the trailing period with no signature after it.
Modern JWT libraries have patched this by default, but we still find it in applications using older library versions or custom token validation logic. The jwt_tool from ticarpi tests for this automatically among dozens of other JWT attacks.[2]
Algorithm Confusion (RS256 to HS256)
This is the most elegant JWT attack and one we find more often than the "none" algorithm bypass. It works when the server uses RSA (RS256) for signing but the JWT library also accepts HMAC (HS256) signatures.
Here is how it works. With RS256, the server signs tokens with a private key and verifies them with a public key. With HS256, the same key is used for both signing and verification. If the server's public key is exposed (and it often is, through JWKS endpoints, /.well-known/openid-configuration, or even hardcoded in client-side JavaScript), an attacker can:
- Obtain the server's RSA public key.
- Change the JWT header's algorithm from RS256 to HS256.
- Modify the payload to impersonate any user.
- Sign the modified token using the RSA public key as the HMAC secret.
When the server receives this token, a vulnerable library sees HS256 in the header, fetches the "verification key" (which is the RSA public key), and uses it as the HMAC secret. Since the attacker signed the token with that same key, the signature validates. Authentication bypassed.
We demonstrated this attack against a financial services API in 2025. The JWKS endpoint was publicly accessible, and the backend used an older version of PyJWT that did not enforce algorithm whitelisting. We forged admin tokens and accessed every account in the system.
Defense: Always whitelist allowed algorithms on the server side. Never let the token's header dictate which algorithm to use for verification. Use algorithms=["RS256"] explicitly in your JWT library configuration.
JWK Header Injection
Some JWT libraries support embedding a JSON Web Key directly in the token header via the "jwk" parameter. If the server trusts the key provided in the token itself rather than a preconfigured key, an attacker can generate their own key pair, embed the public key in the JWT header, sign the token with their private key, and the server will validate the signature using the attacker-supplied key.
We test this by generating an RSA key pair, embedding the public key in the JWT header's "jwk" field, setting the payload to whatever claims we want, and signing with our private key. If the application accepts the token, the authentication system is completely broken.
Session Management Attacks
Even with the rise of JWTs, traditional session-based authentication remains common, especially in server-rendered applications. Session management has its own class of vulnerabilities that we test for systematically.
Session Fixation
Session fixation occurs when the application does not issue a new session identifier after successful authentication. The attack works like this: an attacker obtains a valid session ID (by visiting the login page), forces the victim to use that session ID (via a crafted link, XSS, or meta tag injection), and waits for the victim to log in. Once the victim authenticates, the attacker's pre-set session ID is now tied to the victim's authenticated session.[3]
We test this by recording the session cookie before login, completing the login flow, and checking whether the session cookie value changed. If it stayed the same, session fixation is possible. This is a simple test, but it catches a surprising number of applications, particularly those built on custom session management rather than framework defaults.
Session Token Predictability
We collect a batch of session tokens (usually 500 or more) using Burp Suite's Sequencer tool and analyze them for entropy, patterns, and predictability.[4] Burp Sequencer performs statistical tests including FIPS 140-2 monobit, poker, and runs tests, as well as chi-squared analysis on the token values.
Weak session tokens are rare in applications using modern frameworks, but we still encounter them in custom-built applications, particularly those that use timestamps, sequential counters, or weak PRNGs (like PHP's rand() instead of random_bytes()) to generate session identifiers.
Cookie Attribute Failures
Every session cookie should have the Secure flag (only sent over HTTPS), the HttpOnly flag (inaccessible to JavaScript, preventing theft via XSS), and the SameSite attribute (preventing CSRF-style attacks). We check these attributes for every session cookie in every engagement. Missing HttpOnly on a session cookie combined with a stored XSS vulnerability is a direct path to account takeover.
OAuth and OpenID Connect Misconfigurations
OAuth 2.0 and OpenID Connect (OIDC) are the backbone of "Sign in with Google/GitHub/Facebook" flows, and they are complex protocols with numerous implementation pitfalls. The specification itself is over 70 pages, and most developers implement it using libraries that abstract away the details, sometimes incorrectly.[5]
Redirect URI Manipulation
The OAuth redirect_uri parameter tells the authorization server where to send the authorization code after the user approves access. If the redirect URI validation is weak, an attacker can redirect the code to their own server and exchange it for an access token.
We test for several variants of this attack:
- Subdomain matching: If the application registers
https://app.example.com/callback, does the server also accepthttps://evil.app.example.com/callback? - Path traversal: Does
https://app.example.com/callback/../evilbypass validation? - Open redirects chained with OAuth: If
https://app.example.com/redirect?url=https://evil.comis an open redirect, and the OAuth server allowshttps://app.example.com/redirectas a valid redirect_uri, the authorization code can be exfiltrated through the open redirect. - Localhost variations: Development redirect URIs like
http://localhost:3000/callbackleft in production configurations.
State Parameter Missing or Predictable
The state parameter in OAuth flows prevents CSRF attacks by tying the authorization request to the user's session. If the application does not send a state parameter, or if it sends a predictable one (like a static string or sequential integer), an attacker can craft a malicious OAuth authorization URL, trick a victim into clicking it, and have the victim's browser complete the OAuth flow with the attacker's authorization code, linking the attacker's identity provider account to the victim's application account.[6]
Token Leakage via Implicit Flow
The OAuth implicit flow (response_type=token) returns the access token directly in the URL fragment. This is inherently less secure than the authorization code flow because the token appears in browser history, can be leaked via the Referer header, and is accessible to any JavaScript running on the page (including third-party scripts). The OAuth 2.0 Security Best Current Practice RFC explicitly recommends against using the implicit flow.[7] We flag any application still using it.
Password Reset Flow Exploitation
"Forgot password" flows are consistently one of the weakest points in authentication implementations. They are often built as an afterthought, tested less rigorously than the primary login flow, and contain logic flaws that allow account takeover.
Token Predictability and Reuse
We request password reset tokens for multiple accounts and analyze the tokens for patterns. Are they sequential? Do they contain timestamps that could be predicted? Are they short enough to brute force? We have seen reset tokens as short as 6 numeric digits, which can be brute-forced in under a minute.
We also check whether tokens are single-use. If a reset token can be used multiple times, an attacker who intercepts the token (via email compromise, network sniffing, or Referer header leakage) has a wider window for exploitation.
Host Header Injection in Reset Emails
This is one of our favorite techniques because it works more often than it should. When an application generates a password reset link, it needs to construct the URL. Many applications use the HTTP Host header to determine the domain for the reset link. If the application trusts the Host header without validation, an attacker can:
- Submit a password reset request for the victim's email.
- Intercept the request with Burp Suite and change the Host header to
attacker.com. - The application generates a reset link like
https://attacker.com/reset?token=abc123and emails it to the victim. - If the victim clicks the link, the reset token is sent to the attacker's server.
This relies on social engineering (the victim must click the link), but in a targeted attack, a convincing phishing email combined with a legitimate-looking reset email from the real application makes this highly effective.
Account Enumeration via Reset Flow
Password reset forms often respond differently to valid and invalid email addresses. "If an account with that email exists, we have sent a reset link" versus "No account found with that email." The first response is correct. The second leaks information about which accounts exist. We test both the HTTP response body and response timing, because even if the message is identical, a measurable time difference (caused by the server actually sending an email versus returning immediately) reveals the answer.
Multi-Factor Authentication Bypass
MFA is supposed to be the safety net that catches authentication failures. In practice, it is frequently implemented with gaps that allow bypass.
Missing MFA Enforcement on All Endpoints
The most common MFA bypass is the simplest: the application enforces MFA on the login page but not on API endpoints, mobile app authentication, or alternative login flows. We test by authenticating through the primary login (which prompts for MFA), then checking whether the API accepts the session token or JWT that was issued before MFA completion.
In a recent engagement, a SaaS application required MFA for web login but issued a valid API token after the first factor (password) was verified. The MFA prompt appeared in the browser, but the API token was already functional. We used the token to access every API endpoint without ever completing the MFA challenge.
Response Manipulation
Some applications perform MFA verification on the client side by checking the server's response. We intercept the MFA verification response with Burp Suite and change the result. If the server returns {"success": false, "error": "Invalid code"} and we change it to {"success": true}, does the client proceed to the authenticated area? If the application logic trusts the response without server-side session state validation, the bypass works.
Brute-Forcing TOTP Codes
Time-based One-Time Passwords (TOTP) generate 6-digit codes that are valid for 30 seconds. That is one million possible values with a 30-second window. If the application does not implement rate limiting on the MFA verification endpoint, we can submit all one million values in under 30 seconds using Burp Suite's Intruder or a custom script with parallel requests. Even with slight timing tolerances (most implementations accept codes from the previous and next 30-second window), the brute force space is manageable.[8]
Backup Code Weaknesses
MFA backup codes (used when the primary MFA device is unavailable) are often shorter, simpler, and subject to less scrutiny than TOTP codes. We check whether backup codes are rate-limited, whether they expire, whether they are single-use, and whether they are stored in hashed form. We have encountered applications that stored backup codes in plaintext in the user profile API response, visible to any authenticated request.
The MFA paradox: Organizations implement MFA specifically because passwords are unreliable. Then they implement MFA with the same shortcuts and assumptions that made passwords unreliable in the first place. MFA is only as strong as the weakest path through the authentication flow.
Race Conditions in Authentication Flows
Race conditions are among the most underrated vulnerability classes in web applications, and authentication flows are particularly susceptible because they involve multiple state transitions (unauthenticated to pending to authenticated) that must happen atomically.
Parallel Login Requests
Consider an application that locks an account after five failed login attempts. If we send 20 login requests simultaneously (using Burp Suite's "Send group in parallel" feature or Turbo Intruder), does the lock counter handle concurrent requests correctly?[9] In many implementations, the counter reads the current value before incrementing it, creating a time-of-check to time-of-use (TOCTOU) gap. All 20 requests read the counter as "0 failed attempts" before any of them increment it, effectively giving us 20 free attempts instead of 5.
Token Refresh Race Conditions
When a JWT access token expires, the client uses a refresh token to obtain a new access token. If we send multiple refresh requests simultaneously, some applications issue multiple new access tokens and invalidate the refresh token only once. This can result in extra valid access tokens, or in some cases, refresh tokens that never get invalidated because the invalidation logic has its own race condition.
Registration and Password Reset Races
If a user can register with an email address that already has a pending password reset, sending the registration and reset requests simultaneously can produce interesting results. We have seen cases where the race condition creates two valid sessions for the same email address, or where the password reset token remains valid even after the account password is changed through the registration flow.
Putting It All Together: Our Testing Methodology
When we begin testing authentication on a web application, we follow a structured approach that covers all of these attack surfaces systematically.
Phase 1: Reconnaissance
- Identify all authentication mechanisms (session cookies, JWTs, API keys, OAuth providers).
- Map every endpoint that handles authentication state (login, logout, register, reset, MFA, token refresh, OAuth callbacks).
- Check for exposed JWKS endpoints, OAuth configuration endpoints (/.well-known/openid-configuration), and API documentation.
- Collect session tokens and JWTs for analysis.
Phase 2: Token Analysis
- Decode JWTs and inspect header and payload claims using jwt.io or jwt_tool.
- Test for algorithm confusion, "none" algorithm, JWK injection, and kid parameter manipulation.
- Analyze session token entropy with Burp Sequencer.
- Check cookie attributes (Secure, HttpOnly, SameSite, expiration).
Phase 3: Flow Manipulation
- Test OAuth redirect URI validation with various bypass techniques.
- Submit password reset requests and analyze token predictability, reuse, and Host header injection.
- Test MFA enforcement across all authentication paths.
- Attempt MFA brute force and response manipulation.
- Run race condition tests against rate-limited endpoints and token operations.
Phase 4: Credential Attacks
- Test password policy enforcement (minimum length, complexity, breach list checking).
- Check for username enumeration via login, registration, and reset responses.
- Verify account lockout behavior and test for lockout bypass via race conditions.
- Test default credentials on administrative interfaces.
Recommendations for Developers
Based on the patterns we see repeatedly, here are the highest-impact defensive measures for authentication:
- Whitelist JWT algorithms server-side. Never let the token header dictate which algorithm to use. Specify the exact algorithm in your verification code.
- Rotate session identifiers on every privilege change. New session ID after login, after MFA completion, after role changes. Every time.
- Validate OAuth redirect URIs with exact string matching. Not prefix matching, not regex, not substring. Exact match against a whitelist of registered URIs.
- Generate password reset tokens with at least 128 bits of entropy using a cryptographically secure random number generator. Make them single-use with a short expiration (15-30 minutes).
- Enforce MFA at the session level, not the page level. The session should not be considered fully authenticated until MFA is complete. No API access, no data access, no partial access.
- Implement rate limiting on every authentication endpoint with exponential backoff. Use server-side counters with atomic operations to prevent race condition bypasses.
- Use the authorization code flow with PKCE for OAuth. Never the implicit flow. PKCE (Proof Key for Code Exchange) protects against code interception even if the redirect URI is compromised.[10]
Sources
- RFC 7519 - JSON Web Token (JWT) Specification - datatracker.ietf.org
- jwt_tool - JWT Attack Toolkit by ticarpi - github.com/ticarpi/jwt_tool
- OWASP Session Fixation - owasp.org
- Burp Suite Sequencer Documentation - portswigger.net
- RFC 6749 - OAuth 2.0 Authorization Framework - datatracker.ietf.org
- OWASP Testing Guide - Testing for OAuth Weaknesses - owasp.org
- OAuth 2.0 Security Best Current Practice - datatracker.ietf.org
- RFC 6238 - TOTP: Time-Based One-Time Password Algorithm - datatracker.ietf.org
- PortSwigger Research - Race Conditions in Web Applications - portswigger.net
- RFC 7636 - Proof Key for Code Exchange (PKCE) - datatracker.ietf.org
Is Your Authentication Actually Secure?
We test every authentication flow manually, using the same techniques attackers use. Find out where your login, MFA, and session management can be bypassed.
Book a Consultation Our Services