1

Detection

Mapping Authentication Flows

πŸ”Ž

Detection

The first step in testing authentication is to enumerate every distinct authentication-related flow the application exposes, not just the obvious login form.

As covered in Lesson 3.1, this typically includes:

Registration β€” creating a new account.
Login β€” the primary credential-verification flow.
Password reset and recovery β€” regaining access without the original password.
MFA enrollment β€” adding or configuring a second factor.
MFA verification β€” completing the second-factor challenge.
Session establishment and logout β€” creating and invalidating authenticated sessions.
Remember me β€” establishing persistent authentication across browser sessions.
API authentication β€” separate login endpoints used by mobile applications, SPAs, or other clients.

The tester should also actively search for alternative authentication portals and legacy endpoints.

For example:

Main web login
      β”‚
      β”œβ”€β”€ API login
      β”œβ”€β”€ Mobile login
      β”œβ”€β”€ Admin login
      β”œβ”€β”€ Legacy login
      └── Forgotten / undocumented endpoint

Legacy or forgotten endpoints are equally important. An endpoint does not stop being an attack surface simply because the current interface no longer links to it. Older login forms, API versions, development routes, and deprecated authentication mechanisms may remain reachable and may not have received the same security improvements as the current login flow.

The key testing mistake is assuming:
β€œThere is one login page, therefore there is one authentication mechanism.”

A useful first deliverable is an authentication-flow map showing every entry point, the credentials or factors it accepts, the session it creates, and the recovery or alternative paths that can reach the same authenticated state.

2

Detection

Identifying Attack Surfaces

πŸ”Ž

Detection

Once every authentication flow has been mapped, the next step is to catalog every input and trust boundary involved in each flow.

For each endpoint, record inputs such as:

Visible form fields β€” usernames, passwords, OTPs, recovery codes, and other authentication data.
Hidden form fields β€” values that may look internal but are still controlled by the client.
Cookies β€” session identifiers, remember-me tokens, MFA state, and other authentication-related state.
URL parameters β€” authentication flags, reset tokens, redirect parameters, or other values that influence the flow.
HTTP headers β€” particularly headers such as Host and X-Forwarded-For when they influence reset-link construction,
rate limiting, or other security decisions.
Request bodies and API parameters β€” especially where web, mobile, and SPA clients use different authentication endpoints.

The important question is not simply β€œwhat inputs exist?” but:
Which of these inputs does the server trust when making an authentication or security decision?

For example, a request might contain:

username = alice@example.com
password = ...
role = user
authenticated = true
X-Forwarded-For = 203.0.113.10

Every one of those values is potentially attacker-controlled unless the application has an independent reason to trust it.

Client-side JavaScript deserves particular attention. If JavaScript appears to determine whether a user is authenticated or authorized, test whether the server independently enforces the same decision.

For example:

if (localStorage.getItem('isLoggedIn') === 'true') {
    showAdminPanel();
}

The visible UI isn't the important security boundary. The tester should follow the application's API requests and determine whether those endpoints independently verify the authenticated session.

A useful attack-surface inventory therefore looks like:

Authentication Flow β†’ Endpoint β†’ Inputs β†’ Authentication state β†’ Server-side trust decisions β†’ Protected resource / session

This approach also makes previously identified vulnerabilities easier to test systematically. A Host header becomes relevant when constructing reset links, X-Forwarded-For when determining rate-limit identity, and client-side state when determining whether authentication actually exists.

3

Detection

Testing Login

πŸ”Ž

Detection

Apply the concepts from Lessons 3.2, 3.3, 3.4 directly to the login flow. The goal is not simply to determine whether the correct password works, but to test how the application behaves when authentication fails and how consistently its security controls are enforced.

1. Test Username Enumeration

Compare authentication attempts using:

  • a known-valid username

  • a clearly invalid username

Compare both the response content and response timing.

Check for differences such as:

Invalid username  β†’ "User not found"
Valid username    β†’ "Invalid password"

Even when the messages are identical, measure response times across multiple requests. A valid account may trigger password-hash verification while an invalid account does not, potentially creating a measurable timing difference.

2. Test Rate Limiting and Lockout

Submit a controlled sequence of deliberately incorrect passwords against a test account and record:

how many attempts are accepted
when throttling begins
whether the account becomes locked
how long the restriction lasts
whether successful authentication resets the counter
whether the behavior changes across different clients or sessions

Then test whether the control is tied to the correct identity.

For example, if the application uses X-Forwarded-For to determine the client's IP, test whether changing that header changes the rate-limit identity. This is only meaningful if the application actually trusts that header without a trusted proxy establishing it.

3. Test Authentication Logic

Look for flaws in the actual condition that determines whether authentication succeeds.

For PHP applications, this can include testing whether security-sensitive comparisons use appropriate strict or constant-time comparison mechanisms rather than loose equality. The 0e type-juggling issue from Lesson 3.3 is a specific example of why general-purpose comparison operators can be dangerous when handling password hashes.

4. Test Login Inputs as Normal Attack Surfaces

The username and password fields are still untrusted application inputs. Test them for the relevant injection and input-validation vulnerabilities, including SQL injection where the application's database interaction makes that applicable.

Authentication flaw β†’ bypasses or weakens identity verification

SQL injection β†’ exploits unsafe database query construction

A login endpoint can therefore have both kinds of vulnerability independently.

5. Compare Every Login Path

Finally, compare the main web login against every alternative authentication endpoint discovered during mapping:

Web login β†’ Strong rate limiting βœ“ and Strong enumeration resistance βœ“
API login β†’ No equivalent rate limiting βœ— and Different error behavior βœ—

A security control implemented correctly on one authentication path does not automatically protect another.

This comparison is particularly important for:

  • API login endpoints

  • mobile authentication endpoints

  • SPA authentication APIs

  • admin portals

  • legacy login endpoints

A useful test record for each endpoint is:

Endpoint β†’ Inputs β†’ Success condition β†’ Failure behavior
         β†’ Rate limit β†’ Lockout β†’ Enumeration
         β†’ Authentication logic β†’ Alternative paths
4

Detection

Testing Registration

πŸ”Ž

Detection

Registration is an authentication-related attack surface in its own right. Test the registration endpoint directly rather than relying only on the behavior of the visible form.

1. Test Server-Side Password Policy Enforcement

First, determine what password policy the application claims to enforce. Then submit registration requests directly to the endpoint with passwords that violate those requirements.

For example, if the client-side form rejects:

1234

send the same value directly to the registration endpoint and observe whether the server accepts it.

Client-side JavaScript is useful for user experience, but it is not a security boundary:

Browser validation
      ↓
Can be bypassed
      ↓
Direct HTTP request
      ↓
Server-side validation
      ↓
Actual security decision

The server must independently enforce password length, breached-password checks, or other requirements defined by the application's security policy.

2. Test Username Enumeration

Registration commonly leaks whether an account already exists.

For example:

Existing email:
"This email is already registered."

Unknown email:
"Account created."

This allows an attacker to build a list of valid accounts without interacting with the login endpoint at all.

Compare the behavior for known-existing and clearly unused email addresses, including:

  • response messages

  • HTTP status codes

  • response structure

  • response timing

  • differences in redirects or UI behavior

The same enumeration principle from login applies here: the registration flow should not unnecessarily disclose whether a particular account exists.

3. Test for Mass Assignment

Registration endpoints sometimes accept more fields than the visible form exposes. Test whether security-sensitive attributes can be injected into the request.

For example, a normal request might contain:

{
    "name": "Alice",
    "email": "alice@example.com",
    "password": "..."
}

Test whether adding unexpected fields such as:

{
    "name": "Alice",
    "email": "alice@example.com",
    "password": "...",
    "role": "admin",
    "is_admin": true
}

causes those attributes to be accepted.

The important question is not simply whether the server accepts unknown parameters. It is whether client-controlled parameters can modify security-sensitive properties that should be assigned exclusively by the server.

A vulnerable implementation might effectively do:

$user = User::create($request->all());

whereas a safer approach explicitly selects the fields that registration is permitted to set:

$user = User::create([
    'name' => $request->name,
    'email' => $request->email,
    'password' => Hash::make($request->password),
]);

The same principle applies to other attributes such as account status, verification state, organization membership, or permissions.

3. Test the Complete Registration Flow

Finally, don't stop at account creation. Check what happens immediately afterward:

Registration
    ↓
Account created
    ↓
Email verification?
    ↓
Automatic authentication?
    ↓
MFA enrollment?
    ↓
Session established?

A registration endpoint may correctly validate its inputs while another step in the flow accidentally grants excessive privileges or establishes an authenticated session without the expected verification.

5

Detection

Testing Password Reset

πŸ”Ž

Detection

Apply the concepts from Lesson 3.6 directly to the password-reset flow. Treat the reset mechanism as an alternative authentication path, and test whether each security property is actually enforced rather than assuming the implementation matches its documentation.

1. Test Reset Token Generation

Request multiple password resets for a controlled test account and compare the resulting tokens.

Look for:

  • repeated or partially repeated tokens

  • sequential values

  • predictable structure

  • values correlated with timestamps, usernames, or other known inputs

  • unusually short token lengths

The goal is not to prove randomness merely by looking at a few tokens, but to identify obvious patterns suggesting that the token is derived from predictable information rather than generated using a cryptographically secure random source.

For example:

Reset 1 β†’ 9f82a1...
Reset 2 β†’ 4c17e9...
Reset 3 β†’ d03b72...

Random-looking output alone does not prove security; source-code review or stronger statistical analysis may be necessary to establish how the tokens are actually generated.

2. Test Token Reuse

Complete a password reset using a valid token, then attempt to use the same token again.

The expected behavior is:

Token issued
    ↓
Password reset succeeds
    ↓
Token consumed
    ↓
Same token rejected

If the token remains valid after successful use, an attacker who obtains it may be able to reset the password repeatedly.

Also test whether requesting a newer reset token invalidates older outstanding tokens, where the application's intended security model requires that behavior.

3. Test Token Expiration

Determine the token's actual validity period rather than relying solely on documentation or configuration claims.

Using a controlled account, record when the token is issued and test it at appropriate intervals until it is rejected.

Verify that:

  • expired tokens are rejected server-side

  • expiration cannot be extended through client-controlled values

  • a token does not remain valid indefinitely

  • expiration is enforced consistently across reset endpoints

The objective is to establish the real attack window available if a reset token is leaked.

4. Test Reset-Link Host Construction

Specifically test whether the application constructs reset links using attacker-influenced request headers.

Send a reset request with a controlled alternate value in:

Host: attacker.example

and, where the deployment/framework trusts forwarded host information:

X-Forwarded-Host: attacker.example

Then inspect the reset email or generated reset URL.

A vulnerable result might look like:

https://attacker.example/reset-password?token=VALID_TOKEN

The security issue is present if the application reflects an attacker-controlled host into a password-reset link that contains a valid reset token.

This is one of the most valuable reset-flow tests because it can identify password-reset poisoning before an actual user has to interact with a malicious link.

The expected behavior is that reset URLs are constructed from a trusted server-side application URL, regardless of client-supplied Host or forwarded-host values.

5. Test the Complete Reset Lifecycle

Finally, test the flow as a sequence rather than as isolated requests:

Request reset
    ↓
Token generated
    ↓
Token delivered
    ↓
Token accepted
    ↓
Password changed
    ↓
Token invalidated
    ↓
Old token rejected

Also compare behavior across any alternative reset endpoints discovered during application mapping, such as API-based recovery or mobile-specific flows.


The reset flow should be tested as an authentication mechanism: unpredictable token, secure delivery, limited lifetime, single use, and trusted URL construction.

6

Detection

Testing MFA

πŸ”Ž

Detection

Apply the concepts from Lesson 3.5 directly to the MFA flow. Test both the individual factor and the entire path from password authentication to the fully authenticated state.

1. Test OTP Rate Limiting

Check whether the OTP verification endpoint has its own attempt limit, independent of the rate limiting applied to the main login endpoint.

For example:

Password login
      ↓
Strong rate limiting βœ“
      ↓
OTP verification
      ↓
No equivalent limit βœ—

Submit controlled invalid OTPs and observe:

  • how many attempts are permitted

  • when throttling begins

  • whether failed attempts are tracked per account, session, or another identifier

  • whether the restriction can be bypassed through another authentication path

2. Test OTP Generation

If the application uses a custom OTP implementation rather than a well-established library or standardized mechanism, examine:

  • code length

  • character set

  • randomness

  • generation algorithm

  • expiration window

Request multiple codes for a controlled account and look for obvious patterns that could indicate predictable generation.

A 6-digit numeric OTP has 1,000,000 possible values, while a 4-digit OTP has only 10,000. However, the practical security of the code also depends heavily on verification attempt limits and expiration.

3. Test OTP Replay

After successfully completing MFA with a controlled OTP, test whether the same OTP can be accepted again.

OTP issued
    ↓
OTP accepted
    ↓
Authentication succeeds
    ↓
OTP consumed
    ↓
Same OTP rejected

4. Test MFA Recovery

Test each fallback mechanism independently:

  • backup codes

  • lost-authenticator recovery

  • trusted-device functionality

  • MFA replacement

  • recovery-code regeneration

Ask whether the recovery path provides equivalent assurance to the MFA factor it replaces.

For example, if an attacker cannot complete the normal MFA challenge but can register a new authenticator after providing substantially weaker proof, the recovery mechanism may effectively become an MFA bypass.

5. Test Push-MFA Controls

For push-based MFA, check whether repeated approval requests are appropriately restricted.

In a controlled test environment, verify whether the application:

  • limits repeated prompts

  • limits resend operations

  • provides useful context about the authentication attempt

  • uses stronger approval mechanisms such as number matching where appropriate

The goal is to identify MFA-fatigue exposure without generating abusive notification traffic against real users.

6. Test MFA State Confusion

This is one of the most important tests.

Map the expected authentication states:

Fresh session β†’ Password verified β†’ MFA pending β†’ MFA verified β†’ Fully authenticated

Then determine whether the server independently enforces each transition.

For example, identify the endpoint normally reached after successful MFA and call it directly from a fresh, unauthenticated or MFA-incomplete test session. It should reject the request rather than assuming that the previous MFA step must already have happened.

Also test whether:

  • the post-MFA endpoint rejects a fresh session

  • the MFA-pending state is tied to the correct session

  • MFA state cannot be supplied through client-controlled parameters

  • restarting authentication clears incomplete MFA state

  • alternative API endpoints enforce the same MFA requirement

6. Compare All MFA Paths

Finally, compare MFA enforcement across every authentication path discovered during mapping:

Web login     β†’ MFA βœ“
API login     β†’ MFA ?
Mobile login  β†’ MFA ?
Admin portal  β†’ MFA ?
Recovery      β†’ MFA ?

A strong MFA implementation on the primary web login does not protect an alternative endpoint that establishes the same authenticated identity without enforcing the same factor.

7

Detection

Chaining Authentication Flaws

πŸ”Ž

Detection

Individual authentication weaknesses that appear relatively low-severity in isolation can become a complete account-takeover path when chained together. This is one of the most important mindsets in authentication testing.

The Lesson 3.6 password-reset example illustrates this clearly:

Username enumeration β†’ Confirm target account exists

Host-header poisoning β†’ Expose a valid reset token

Token reuse / excessive lifetime β†’ Keep the stolen token usable

Reset password β†’ Complete account takeover

None of the individual findings necessarily provides complete account takeover on its own:

  • Username enumeration tells the attacker which accounts exist.

  • Host-header poisoning can expose a legitimate reset credential.

  • Token reuse or excessive lifetime increases the usefulness and attack window of a leaked credential.

Together, however, they can eliminate the need to know or crack the victim's original password entirely.

The same reasoning applies outside password reset:

Weak login rate limiting
        +
Username enumeration
        ↓
Practical credential attack

Weak MFA recovery
        +
Compromised account email
        ↓
MFA bypass

Missing authentication check
        +
Predictable / discoverable endpoint
        ↓
Unauthenticated access

Think in Attack Paths, Not Just Findings

When a vulnerability is identified, ask three questions:

1. What does this weakness give the attacker?

For example:

Enumeration β†’ valid account identifier
Token leakage β†’ authentication credential
Missing MFA check β†’ authenticated state

2. What prerequisite does the next step require?

Determine whether another finding supplies exactly what the attacker needs to continue.

3. Does the chain reach a meaningful security boundary?

The important endpoint may be:

  • authenticated session

  • password reset

  • MFA bypass

  • privileged account

  • sensitive data

  • administrative functionality

A tester therefore shouldn't stop after proving each vulnerability independently. Once multiple weaknesses have been identified, attempt to determine whether they can be combined into a practical attack path within the authorized test environment.

This also affects how findings should be reported. Individual vulnerabilities can still be documented separately for remediation, but the assessment should clearly explain when their combined effect is substantially more serious than any one finding alone.

8

Summary

Key Takeaways

Summary

Testing authentication means systematically mapping every distinct authentication flow the application exposes not just the primary login form and testing each flow against the specific weaknesses covered throughout this topic.

Registration, login, password reset, MFA, session handling, logout, remember-me functionality, API authentication, administrative portals, and legacy endpoints can each introduce their own security controls and failure modes. A control that is correctly implemented in one flow cannot be assumed to protect another.

The most valuable part of the assessment, however, is recognizing how individually modest weaknesses can combine into a practical attack path:

Map flows β†’ Identify weaknesses β†’ Understand what each weakness enables β†’ Test whether those capabilities can be chained β†’ Measure the resulting security impact

A username-enumeration issue may identify valid targets. A weak reset mechanism may expose an authentication credential. An MFA recovery weakness may remove a second-factor requirement. Individually, these findings may appear limited; together, they can produce complete account takeover.

The goal of a thorough authentication assessment is therefore not simply to produce a list of vulnerabilities. It is to understand how an attacker can move through the application's authentication system from an initial weakness to a meaningful security impact.