August 12, 2026
Testing the Authentication Boundary: A Case Study in Session and Identity Security
The security properties I need to establish, the attack categories I test against, and the harness that makes it repeatable.
By Mdporschaa
15 min read
Authentication and session security are the layer everything else in an application depends on. Authorization can be perfectly designed and still fail if the identity it authorizes is fake. Data protection can be architecturally sound and still leak if the session that reads it is stolen. Every other control assumes that the caller is who the system thinks they are, and that assumption is worth testing directly.
What follows is the methodology I use for that layer. The engagement type it applies to is an authenticated application security assessment, scoped to authentication mechanics and session management. Authorization boundaries (cross-tenant, cross-region, cross-role) are the sister methodology. This piece focuses on establishing the caller's legitimacy before authorization is even requested.
The checklist and a runnable Python harness scaffold live in a companion repo: github.com/MDPorsch/auth-boundary-methodology.
This article carries the reasoning behind each test category.
The repo carries the operational artifacts.
The Layer Being Tested
Modern web applications typically split authentication into three concerns, and the methodology tests each.
Credentials and Enrollment. The endpoints where a user proves who they are: login, password reset, registration, MFA enrollment where applicable. These are the entry points to the authenticated surface.
Session Establishment. Once credentials are verified, the application issues a token or session identifier and binds subsequent requests to that identity. Modern architectures use JWTs, opaque bearer tokens, or session cookies. The mechanics differ. The properties they need to hold do not.
Session Lifecycle. Tokens have to expire, be refreshed, be invalidated on logout, and survive service restart. Every one of these transitions is a state change, and state changes are where the class of bugs lives.
The methodology tests all three, against a specific set of security properties, using a specific set of attack categories.
The Security Properties I Need To Establish
Seven properties, each with a specific failure mode the tests below are designed to elicit. Every attack category maps to at least one property, and every finding maps back to a failed property.
- A forged or tampered token cannot authenticate a request. Signature verification is enforced. Algorithm confusion is prevented. Payload modification is detected.
- A stolen token has a bounded useful lifetime. Expiry is enforced. Refresh does not extend the useful lifetime of the token being refreshed. Concurrent session policy matches design.
- A user can invalidate an active session before its natural expiry. Logout invalidates the token on the server. The invalidation survives service restart. Server-side invalidation (password change, admin action, suspicious activity response) is defended by the same mechanism.
- A session cookie cannot be attached to a request the user did not intend. CSRF protection is present, its mechanism is understood, and the cookie flags and cross-origin policy support its assumptions.
- Registration cannot escalate privileges. Fields the server should ignore are ignored. Extra fields do not override server-derived state. Role-related fields cannot be set through the endpoint.
- Authentication endpoints resist brute-force and credential-stuffing attacks. Rate limiting is present, its scope is defined, and lockout behavior matches the design.
- The authentication flow does not leak information. Response differences between "user exists" and "user does not exist" are absent or bounded. Timing side channels are measured. Password reset does not confirm account existence.
Scoping Considerations
Two conversations with the product team are worth having before testing starts.
What Authentication Mechanisms are in Scope. Applications routinely support several: primary username and password, social login, SSO, MFA, service-to-service tokens. Each has its own methodology. This article covers the primary flow, username and password with JWT sessions, plus its supporting endpoints. MFA and OAuth flows are adjacent methodologies, each with their own attack categories. Both are worth scoping explicitly rather than assuming coverage.
What Behaviors are Intentional. Some applications intentionally use long-lived tokens or client-side logout. The assessment should distinguish those documented design choices from deviations that create unacceptable risk. Intentional is not the same as acceptable, and the report needs to make that distinction clearly.
Test Setup
One authenticated account minimum. Two accounts if session isolation between accounts is in scope. A third pre-auth account marker, used for tests that require unauthenticated requests against authenticated endpoints.
For each account:
- Complete the full authentication flow once and observe. What does the token look like: JWT, opaque bearer, session cookie? What headers and cookies are set? If a JWT, what is the algorithm, what claims does it carry, what is the expiry, what is the refresh mechanism?
- Capture the token from a legitimate authentication flow. Both access and refresh tokens if the design uses both.
- Identify every state-changing endpoint the account can reach. These are the CSRF-relevant surface.
The pre-auth marker is used to test that unauthenticated requests to authenticated endpoints return 401 uniformly, and 401 rather than 500. A 500 signals that an application code path is being reached before the authentication check fires, which is its own class of finding.
The authenticated endpoint surface for authentication and session testing is smaller than for authorization testing, typically 5 to 15 endpoints in scope, but each endpoint takes a wider variety of test payloads.
Authentication As a State Machine
Authentication is not a single request. It is a state machine with transitions between states that each carry security-relevant behavior. The states worth naming:
- Unauthenticated. Before any credentials have been presented, or after the session has been fully invalidated.
- Authenticated. Credentials verified, session established, access token issued.
- Refreshed. Access token expired or nearing expiry, refresh token exchanged for a new access token. The prior refresh token is invalidated in the process if the design uses rotation.
- Logged out. User initiated invalidation. Session tokens invalidated on the server.
- Reauthenticated. A new authentication flow completed after logout or expiry. Depending on design, this may invalidate any prior session or coexist with it.
The methodology tests the transitions between these states. A forged token test verifies that unauthenticated cannot become authenticated through forgery. A logout test verifies that authenticated becomes logged out and stays there. A refresh test verifies that refreshed does not extend the useful life of the token being refreshed. Every attack category below is a test of one or more of these transitions, and every transition maps to at least one of the security properties above.
Algorithm Handling and Signature Verification
Tests in this section defend property 1: a forged or tampered token cannot authenticate a request. For JWT-based session tokens, algorithm handling is tested first because a failure here can undermine the entire token-verification boundary. The methodological question underlying every test in this category is: does the verifier enforce the expected algorithm independently of attacker-controlled token metadata?
alg=none. A token with the algorithm header set to none and no signature. If accepted, the application has skipped signature verification entirely. Verified by forging a token with the target payload, setting the algorithm to none, and submitting it. Expected: 401 with a clear rejection reason.
Algorithm Header Stripping. A token with the algorithm header omitted. Similar failure mode to alg=none in libraries that treat "no algorithm specified" as permission to skip verification. Verified by forging a token with the header field removed.
Algorithm Downgrade, RS256 to HS256. If the application uses asymmetric signing, test whether a token signed with HS256 using the public key as the HMAC secret is accepted. This class of attack succeeds where the verifier reads the algorithm from the token header and then uses that algorithm with a key that was intended for a different algorithm. The methodological check underneath the test: is the algorithm the verifier uses fixed by application configuration, or influenced by the incoming token?
Payload Tampering with Attacker Signature. A token where the payload has been modified (for example, changing the user identifier claim) and re-signed with a key the tester controls. Tests whether the application verifies the signature against its own key or accepts any valid format signature. Expected: 401.
kid Header Manipulation. If the token includes a kid (key ID) header pointing to which key should be used for verification, test manipulation of that header: path traversal, injection into any downstream lookup (SQL, LDAP), and pointing at attacker-controlled keys. This class of attack surfaces where the kid value is used as a filesystem path, a database key, or a URL without validation.
Each test is scripted as a forged token generator, run against a representative sample of authenticated endpoints. The algorithm handling logic is centralized, so a small sample across categories is enough to establish behavior. Running every forged token against every endpoint is wasted work.
Token Lifecycle
Tests in this section defend properties 2 and 3: a stolen token has a bounded useful lifetime, and a user can invalidate an active session before its natural expiry. The two properties are defended by different mechanisms and the tests are grouped accordingly.
Bounded Useful Lifetime (property 2)
Access Token Expiry. Wait for the token to expire, or advance the system clock in a test environment, then submit an authenticated request. Expected: 401 with a clear expiry signal.
Refresh Token Rotation. Submit a refresh request. Confirm the response contains a new access token and a new refresh token. Confirm the old refresh token is invalidated by replaying it. Expected: 401. A response of HTTP 500 on used refresh replay is a correctness issue rather than a security defect (the token is still rejected) but it belongs in the report under a separate category because it signals that an application error path is being reached rather than the authentication rejection path.
Cross-session Refresh Token Use. If refresh tokens are issued per session, test whether a refresh token from session A can be used from session B. Expected: rejection.
Concurrent Session Behavior. If the application supports multiple concurrent sessions per user, test whether concurrent session count is bounded and matches the design. If the application does not support concurrent sessions, test whether a new login invalidates the prior session's token. The design decision matters less than whether the implementation matches it.
User-initiated Invalidation (property 3)
Logout invalidation. Log out. Attempt to use the token that was valid immediately before logout. Expected: 401. This tests whether the application maintains a server-side blocklist of invalidated token identifiers, or whether logout is client-side only. Whether client-side-only logout constitutes a finding depends on the threat model. Where the application's design requires server-side invalidation (for token theft, shared devices, or regulatory obligations to terminate access), client-side-only logout fails to defend property 3 and is a finding. Where the design intentionally relies on natural token expiry and accepts that a leaked token remains valid until it expires, the design should be documented rather than flagged as a defect. Intentional is not the same as acceptable, and the report needs to make that distinction.
Blocklist Persistence. If a blocklist is maintained, does it survive a service restart? A blocklist held only in memory, or in a cache with a short TTL, fails on any restart and effectively resets all invalidations. Verified by logging out, restarting the relevant service if a test environment allows it, and reattempting the invalidated token.
Server-initiated Invalidation. Test whether the application supports session termination triggered by events other than user logout: password change, admin action, suspicious activity response. Where the design contemplates any of these, verify that the mechanism actually terminates active sessions rather than relying on natural expiry.
CSRF and Session Binding
Tests in this section defend property 4: a session cookie cannot be attached to a request the user did not intend. CSRF protection is tested against the specific model the application uses. Custom header, double submit cookie, and encrypted state approaches each have different failure modes.
Custom Header Pattern. The common pattern for JSON APIs with cookie-based session tokens: the frontend sends a specific header on every state-changing request, and the backend rejects requests without it. Test: submit state-changing requests without the header. Expected: rejection.
The safety of this pattern depends on the combined behavior of the browser and the server-side cross-origin policy. An attacker cannot cause the browser to make an authenticated state-changing request with the required custom header unless the server's cross-origin configuration permits it. This is why the CORS check below is not an adjacent concern. It is the same concern viewed from the other side.
Double Submit Cookie Pattern. A CSRF token is set both as a cookie and required as a form field or header. The backend compares the two. Test: submit requests where the cookie and the field disagree. Expected: rejection.
Cookie Flags Supporting the CSRF Assumption. The CSRF pattern's safety relies on cookie behavior. Verify: HttpOnly set on session cookies (preventing client-side JavaScript from directly reading the session credential), Secure set (HTTPS only), SameSite at minimum Lax (blocks the most common CSRF submission paths), __Host- prefix where possible (binds the cookie to the origin, prevents subdomain-based override).
CORS Interaction. The CSRF safety argument above collapses if the server's cross-origin policy permits attacker origins to make the state-changing request. Verify the CORS configuration is a strict allowlist: no wildcard origins, credentials permitted only from named origins, methods and headers explicitly listed rather than reflected off the request. A permissive CORS policy on a custom header CSRF pattern reopens the CSRF surface the pattern was designed to close.
Registration and Account Creation
Tests in this section defend property 5 (registration cannot escalate privileges) and touch property 7 (the flow does not leak information). The registration endpoint accepts arbitrary user input and creates persistent server-side state. It is a specific test surface.
Mass Assignment (property 5). Submit a registration payload with additional fields the server should ignore: role, is_admin, is_verified, permissions, tenant_id, email_verified. Expected: the extra fields are silently discarded, not accepted, and not error-responded. Silent discard is the safer failure mode. An error response confirms the field exists in the server model, which leaks the taxonomy of privileged fields to any attacker who submits a probing registration.
Duplicate Account Handling (property 7). Submit a registration payload with an email that already exists. Expected: a response that does not distinguish "email already registered" from "account created successfully". An explicit "email already registered" response is a user enumeration vector, exposing whether any given email has an account. Well-designed flows send an email to the address either way (a "you tried to register but you already have an account" email to the existing user, a standard verification email to a new one) and return an identical response to the caller.
Registration Rate Limiting (property 6). Submit registration attempts rapidly. Expected: rate limiting fires, either at IP scope or endpoint scope. Absence of registration rate limiting enables mass account creation for spam or infrastructure abuse.
Email Verification Enforcement (property 5). If the design requires email verification before privileged actions, test whether privileged actions are actually blocked pre-verification. A design that requires verification but does not enforce it reads secure and behaves insecure.
Authentication Endpoint
Tests in this section defend property 6 (resistance to brute force and credential stuffing) and property 7 (the flow does not leak information). The login endpoint is the primary target of brute force, credential stuffing, and user enumeration.
Rate Limiting Scope and Enforcement Point (property 6). Test both the scope of the rate limit (IP scoped, account scoped, or both) and the point at which it fires. IP-scoped rate limiting fires when many attempts come from one source and is effective against brute force on a single account. Account-scoped rate limiting fires when many attempts hit one account regardless of source and is effective against credential stuffing. Both matter, and each defends a different attack. The methodology establishes which the application uses by running scripted attempts and observing where the lockout triggers. The report describes what is present rather than prescribing a specific threshold.
Lockout Behavior (property 6). Once rate limiting fires, what happens? Time-bounded lockout, captcha challenge, notification email? Different designs have different tradeoffs, and the report describes the behavior rather than prescribes a specific mechanism. What the report flags is absence, not choice.
Timing Side Channel (property 7). Measure response time for logins against known existing and known non-existing accounts. A significant difference, typically because the application performs the expensive password hash comparison only for existing accounts, is a user enumeration vector. Measured with scripted requests, response time recorded, statistical comparison across at least 50 attempts per bucket to filter network noise.
Password Reset Flow (property 7). Submit a password reset request for a known existing email and a known non-existing email. Expected: response identical in both cases, and reset email behavior matched to the pattern above. Send a message either way; never confirm account existence in the HTTP response.
Password Reset Token Properties (properties 2 and 3). If a reset token is issued: is it single-use (using it once invalidates it), time-bounded (expires within a short window), invalidated on password change (so a compromise of an old reset link does not persist across the password change), and unpredictable (cryptographically random, not sequential or timestamp-based)?
Test Automation Architecture
The harness architecture is what makes the assessment repeatable across engagements and across regression cycles.
The harness:
- Token Forging Library. For JWT testing, a small library that takes a target payload and produces forged tokens across the algorithm handling categories: alg=none, header stripped, RS256 to HS256 downgrade, tampered payload, kid manipulation. Uses PyJWT as the base with custom generators for the pathological cases the library refuses to produce.
- Credential Loader. Authenticated account credentials from environment variables at runtime, never committed to source. Session tokens obtained via the standard authentication flow at the start of each test run so tokens are fresh.
- Endpoint Targets. A YAML file listing endpoint categories: authenticated (any endpoint that requires auth), state changing (any endpoint that modifies server state, for CSRF testing), auth flow (login, refresh, logout, register, password reset).
- Payload Generators per Category. One generator per attack class: algorithm handling, token lifecycle, CSRF, mass assignment, rate limiting, timing.
- Response Comparison. Simpler than the authorization harness. Mostly status code checks against expected responses (401 for forged tokens, 429 for rate-limited requests, identical responses for enumeration tests). Timing tests use statistical comparison across many runs to control for network noise.
- Rate Limit Awareness. Every test involving multiple attempts (rate limit testing itself, timing measurements, brute force resistance) needs to know when it has tripped the application's rate limiting and back off. The harness logs rate limit events as data points. Hitting rate limits is expected, not an error condition.
The harness produces candidates. The tester establishes the finding. Every item the harness flags is manually verified before it becomes a finding, and every verified finding is mapped back to the specific property it violates. Automation reduces the search space. It does not produce findings directly, and it is not a scanner. This distinction is what separates the methodology from tool driven approaches that generate long reports full of noise.
The harness runs the full suite in under an hour for a typical application. The output is a triage queue.
The runnable scaffold, checklist, and evidence template live in the companion repo.
Regression Testing After Remediation
Fixes for authentication issues fall into two patterns: library updates (a JWT library patched for algorithm handling) and application code changes (a new server side blocklist for logout).
Library updates are easier to regression test. The same forged token suite that surfaced the original finding runs against the updated library. Tests are written to be library agnostic where possible, so dependency updates do not break the suite.
Application code changes are harder because the fix may live in a code path that was not previously exercised. The regression test covers the class of issue rather than the specific reported case, which in this methodology means covering the property the fix is defending. A fix for logout invalidation is regression tested by running the full property 3 suite, not just the specific test that surfaced the finding. Fixes that address the reported case without addressing the property are the most common source of near term regressions.
Tools
- Burp Suite. Interactive testing, particularly for the session lifecycle tests where a state change has to happen between two requests.
- Python with requests and PyJWT. Language and libraries for the automation harness and token forging.
- jwt_tool or equivalent. Purpose-built JWT attack tool for quick reproduction of algorithm handling attacks. Useful for initial exploration. The harness supersedes it once endpoint targets are defined.
- Firefox with Multi-Account Containers. Sessions kept clean during interactive testing across multiple accounts.
What Constitutes a Finding
An authentication or session security finding requires three conditions:
- Reproducibility. The behavior is stable across repeated runs, not a transient artifact of caching, race conditions, or session state.
- Failure of a named security property. The test shows an outcome that violates one of the seven security properties above. The finding is documented against the property it violates, not against the attack technique that surfaced it. A forged token authenticating a request violates property 1. A session surviving logout in an application whose design requires server-side invalidation violates property 3. An authentication response distinguishing existing accounts from non-existent ones violates property 7. Every finding is written with the failed property named explicitly.
- Not intentional design, or intentional design that creates unacceptable risk. The behavior is not one the pre-assessment conversation identified as intentional. Where it is intentional, the design choice creates unacceptable risk that the report should surface. Intentional is not the same as acceptable.
A test where the expected rejection happens is not a finding. It is the control working, and the property it defends is confirmed. A test where the response format differs but the property still holds is a report item to flag, not a finding.
Report Framing
Two framing distinctions matter for this class of testing:
Correctness issues are not security defects, but are worth reporting. The HTTP 500 on used refresh token replay is the standard example: the token is still rejected, so the property holds, but the response code is wrong. This belongs in the report under a separate category so the engineering team can address it without triggering an emergency remediation process aimed at security defects. Conflating correctness and security in the report structure damages the credibility of both the report and the response.
Library updates that patch known CVEs are risk reductions, not zero findings. If the application uses a JWT library at a version with known algorithm handling issues, upgrading is a legitimate finding under property 1 even where the specific attack is not exploitable against the current configuration. The alternative, waiting for the specific attack to become exploitable through a config change or a downstream library update, is the wrong side of the risk reduction curve.
Close
Authentication and session security is the layer everything else stands on. The methodology exists to establish, for each of a small set of security properties, whether the layer holds. Not to enumerate every JWT attack that exists. Not to run every tool. To answer, for each of the seven properties, whether it can fail under any of the attack categories that could plausibly reach it.
The checklist and runnable scaffold live in the companion repo.