1

Remediation

Secure Password Storage

πŸ›‘οΈ

Remediation

The first defense against the password weaknesses covered in Lesson 3.2 is secure password storage. Passwords should never be stored in plaintext, encrypted reversibly, or processed with fast general-purpose hashing algorithms such as MD5 or SHA-1.

Instead, passwords should be processed using a slow, adaptive, salted password-hashing algorithm designed specifically to make offline cracking expensive. Modern applications should generally use Argon2id or bcrypt, with the work factor configured appropriately for the application's environment.

Conceptually:

User password
     ↓
Slow password-hashing algorithm
     ↓
Unique salt + computational work
     ↓
Stored password hash

When the user logs in, the submitted password is verified against the stored hash rather than being decrypted:

if (Hash::check($password, $user->password_hash)) {
    // Authentication succeeds
}

The purpose of a slow adaptive hash is to make offline password cracking expensive if an attacker obtains the application's password database. It does not make weak passwords safe; a predictable password can still eventually be cracked.

Password Strength Enforcement:

Secure storage is only one part of password defense. New passwords should also be checked against known-compromised password lists so that users cannot choose credentials that attackers are already likely to try.

The Have I Been Pwned Pwned Passwords service supports this using a k-anonymity model. The application hashes the password locally and sends only a small prefix of that hash to the service rather than sending the plaintext password itself.

The principle is:

Candidate password
        ↓
Hash locally
        ↓
Send only hash prefix
        ↓
Check against breached-password corpus
        ↓
Reject known-compromised password

This is generally more useful than relying exclusively on arbitrary composition requirements such as:

Must contain:
βœ“ uppercase
βœ“ lowercase
βœ“ number
βœ“ symbol
βœ“ 10 characters

A password can satisfy all of those rules while still being highly predictable. A better policy prioritizes length and resistance to known-compromised choices, while avoiding unnecessary requirements that encourage predictable password construction or reuse.

This aligns with the NIST SP 800-63B approach discussed in Lesson 3.2 password policies should focus on preventing commonly used or compromised passwords rather than treating superficial character complexity as a reliable measure of strength.

2

Remediation

Multi Factor Authentication

πŸ›‘οΈ

Remediation

MFA should be treated as a core authentication defense, not merely an optional convenience feature. It should be strongly encouraged for ordinary accounts and mandatory for privileged or administrative accounts, since those accounts provide attackers with significantly greater impact if compromised.

Where possible, applications should prefer phishing-resistant authentication methods such as passkeys/WebAuthn over methods that rely on codes transmitted through a separate channel.

SMS-based OTP can still provide meaningful protection over passwords alone, but it has additional weaknesses β€” particularly risks associated with SIM swapping, number takeover, and phishing. It should therefore generally be treated as a weaker option when stronger factors are practical.

Protect High-Risk Actions

MFA should not necessarily be considered satisfied forever simply because the user completed MFA during login.

Sensitive account changes should trigger step-up authentication where appropriate, particularly actions such as:

  • changing the password

  • changing the account email address

  • disabling MFA

  • replacing an authenticator

  • generating new recovery codes

  • changing security-sensitive account settings

  • performing high-impact administrative operations

Privileged Accounts:

Administrative accounts deserve the strongest requirements:

Standard account
β†’ MFA strongly encouraged

Privileged account
β†’ MFA mandatory

Sensitive administrative action
β†’ Step-up authentication where appropriate

The goal is defense in depth. A stolen password should not be sufficient to compromise a high-value administrative identity, and possession of an existing session should not automatically authorize the most sensitive account changes.

3

Remediation

Rate Limiting

πŸ›‘οΈ

Remediation

Rate limiting is the primary defense against the automated guessing attacks covered in Lesson 3.4. For authentication endpoints, limits should account for both the account being targeted and the source of the requests, because either dimension alone leaves an important attack path open.

For example:

Account-based limit only
        ↓
Attacker rotates source IPs
        ↓
Brute-force one account

IP-based limit only
        ↓
Attacker targets many accounts
        ↓
Password spraying

A robust design therefore considers both dimensions:

                Authentication attempt
                         β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              ↓                     ↓
       Target account           Source IP
              β”‚                     β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                         ↓
                  Rate-limit decision

Login, OTP verification, password reset, and other security-sensitive operations may require substantially stricter and more specialized controls.

Progressive Delay:

Rather than relying exclusively on a flat request count, authentication endpoints can use progressive backoff. Each successive failure increases the delay before another attempt is accepted:

Failure 1 β†’ minimal delay
Failure 2 β†’ slightly longer
Failure 3 β†’ longer
Failure 4 β†’ longer still
       ↓
Repeated failures become increasingly expensive

This makes automated guessing progressively slower while avoiding an unnecessarily aggressive permanent lockout after a small number of mistakes.

Account-based controls should also be designed carefully: a mechanism that permanently locks an account solely because someone else supplied its username can itself become a denial-of-service vector.

Source-IP Identification:

Applications should not blindly trust a client-supplied X-Forwarded-For header when determining the request's source IP.

An attacker can otherwise attempt:

Request 1 β†’ X-Forwarded-For: 10.0.0.1
Request 2 β†’ X-Forwarded-For: 10.0.0.2
Request 3 β†’ X-Forwarded-For: 10.0.0.3
...

If the application treats each value as a new client, the attacker may effectively reset the IP-based rate limit on every request.

X-Forwarded-For can be used safely when the application is behind a properly configured, trusted reverse proxy that establishes the client IP and the application is configured to trust only that proxy's forwarded headers.

Defence in Depth:

Rate limiting should not be treated as the only authentication-abuse defense. A mature implementation can combine:

Account-based limits
        +
IP / network-based limits
        +
Progressive backoff
        +
MFA
        +
Credential-compromise detection
        +
Monitoring / alerting

The exact thresholds should be based on the application's risk profile and legitimate usage patterns rather than applying a universal number to every endpoint.

4

Remediation

Account Lockout

πŸ›‘οΈ

Remediation

Account lockout is intended to stop repeated authentication failures, but a poorly designed implementation can be turned into a denial-of-service mechanism against legitimate users.

The Lesson 3.4 failure mode is straightforward:

Attacker
   ↓
Repeated failed login attempts
   ↓
Victim's username
   ↓
Account locked
   ↓
Victim cannot log in

A stronger design should avoid making the account itself the only condition that triggers a lockout. Combining account identity with request-source information can make it harder for an unrelated attacker to lock a victim's account simply by repeatedly submitting the victim's username.

Conceptually:

Target account
      +
Request source
      ↓
Abuse decision

However, account+IP should not be treated as a complete solution. Attackers can rotate IP addresses, while legitimate users can legitimately change networks. Lockout should therefore work alongside rate limiting, progressive backoff, and other abuse-detection mechanisms rather than serving as the only control.

Notify the Account Owner:

When a significant lockout or authentication-abuse threshold is reached, notifying the account owner can provide an important detection mechanism.

For example:

Repeated failed authentication
        ↓
Temporary restriction
        ↓
Notify account owner
        ↓
User recognizes unexpected activity
        ↓
Investigate / secure account

The notification should avoid exposing sensitive information and should not itself become an account-enumeration mechanism.

Apply Lockout Consistently:

Lockout and equivalent abuse controls must be applied across every authentication entry point discovered during the mapping stage in Lesson 3.7.

For example:

Web login
    ↓
Lockout βœ“

API login
    ↓
No equivalent protection βœ—

Mobile login
    ↓
No equivalent protection βœ—

If an attacker can switch from the protected web login to an unprotected API login, the lockout mechanism provides little meaningful protection for that account.

The same principle applies to:

  • administrative login portals

  • mobile authentication endpoints

  • SPA/API authentication

  • legacy login mechanisms

  • alternative authentication flows

Layer the Controls

A robust authentication defense typically combines several mechanisms:

Rate limiting
      +
Progressive backoff
      +
Account-abuse detection
      +
Temporary restrictions
      +
Account notification
      +
MFA

This avoids relying on a single permanent lockout threshold that can either be too weak against attackers or too disruptive to legitimate users.

5

Remediation

Secure Reset Mechanisms

πŸ›‘οΈ

Remediation

Password-reset mechanisms must be secured as an alternative authentication path, directly addressing the weaknesses covered in Lesson 3.6.

Reset tokens should be:

  • Cryptographically random β€” generated with a CSPRNG such as random_bytes(), never derived from usernames, email addresses, timestamps, or other predictable values.

  • Single-use β€” permanently invalidated immediately after a successful reset.

  • Short-lived β€” commonly measured in minutes rather than days; a window around 15–60 minutes can be appropriate depending on the application's risk profile and user experience.

  • Protected during delivery β€” the token should not be unnecessarily exposed through logs, URLs sent to third parties, API responses, or other unintended channels.

For example:

// Secure β€” cryptographically random
$token = bin2hex(random_bytes(32));

The reset URL itself should always be constructed from a trusted, server-configured application domain, never from the incoming request's Host header:

// Vulnerable
$resetUrl = "https://" . $_SERVER['HTTP_HOST'] .
            "/reset-password?token=" . $token;

// Correct
$resetUrl = config('app.url') .
            "/reset-password?token=" . $token;

This prevents the password-reset poisoning vulnerability covered in Lesson 3.6, where an attacker-controlled host could cause a legitimate reset token to be placed into an attacker-controlled URL.

Invalidate Existing Sessions

Successfully changing a password should also trigger session invalidation appropriate to the application's authentication model.

For example:

Password reset completed
        ↓
Invalidate existing sessions
        ↓
Invalidate / rotate persistent authentication tokens
        ↓
Require authentication again

This is particularly important when the password was changed because an attacker may already have obtained an authenticated session. Changing the password should not leave that previously compromised session permanently valid.

The application can provide an explicit β€œsign out all other devices” option as well, but a security-sensitive password reset should have a clear policy for invalidating existing authentication state.

Notify the Account Owner

The application should also notify the account owner when a password reset is successfully completed.

A notification such as:

Your password was changed.

If you did not make this change,
secure your account immediately.

provides an important detection mechanism when the reset was unauthorized.

The notification should be sent through a trusted channel already associated with the account and should avoid including the new password or unnecessary sensitive reset information.

The complete defensive flow is therefore:

Secure random token
        ↓
Short expiration
        ↓
Trusted reset URL
        ↓
Single-use enforcement
        ↓
Password changed
        ↓
Existing authentication state invalidated
        ↓
Account owner notified
6

Remediation

Username Enumeration Prevention

πŸ›‘οΈ

Remediation

Login, registration, and password-reset endpoints should avoid revealing whether a particular account exists through:

  • response messages

  • HTTP status codes

  • response structure

  • redirects

  • response timing

  • other observable behavioral differences

The goal is to make valid and invalid account requests indistinguishable enough that an attacker cannot reliably determine which accounts exist.

Consistent Authentication Timing

A common timing leak occurs during password authentication:

$user = User::where('email', $request->email)->first();

if ($user && Hash::check($request->password, $user->password_hash)) {
    // authenticated
}

When the username exists, Hash::check() performs an intentionally expensive password-hash verification. When it does not, that operation is skipped.

A defensive implementation performs an equivalent dummy hash verification when no user is found:

$user = User::where('email', $request->email)->first();

$hash = $user
    ? $user->password_hash
    : config('auth.dummy_password_hash');

if (Hash::check($request->password, $hash) && $user) {
    // authenticated
}

The dummy hash should be a fixed, valid password hash generated ahead of time, not a newly generated hash for every request. The objective is to ensure that both the valid-user and invalid-user paths perform comparable password-hashing work.

The broader principle is:

Account exists
    ↓
Password-hash work
    ↓
Generic failure

Account doesn't exist
    ↓
Dummy password-hash work
    ↓
Same generic failure

Timing can never be made mathematically identical in a real network, so the objective is to eliminate application-level differences large enough to produce reliable statistical enumeration.

Generic Responses

The same principle applies to registration and password recovery.

Instead of:

"This email is already registered."

or:

"No account exists for this email."

use a response that does not disclose the account's existence.

For password reset, for example:

"If an account exists for this email,
a reset link has been sent."

The same response should be returned whether the address belongs to an account or not.

The application should also avoid leaking the distinction through status codes, response sizes, redirects, or other observable differences.

Apply the Principle Everywhere

Enumeration prevention should therefore cover the entire authentication surface:

Login
  ↓
Generic failure + comparable processing

Registration
  ↓
No unnecessary account-existence disclosure

Password reset
  ↓
Generic confirmation + comparable processing
7

Remediation

Secure Authentication Architecture

πŸ›‘οΈ

Remediation

The structural principle tying the authentication defenses together is centralized, consistently enforced security logic.

Authentication should not be independently reimplemented across the web login form, API endpoints, mobile clients, and administrative portals. Instead, applications should centralize shared authentication and authorization decisions through well-tested services, middleware, and common security components.

This directly prevents the pattern seen in Lessons 3.3 and 3.4:

Web login
    ↓
Security controls βœ“

API login
    ↓
Different implementation
    ↓
Security controls βœ—

Admin portal
    ↓
Forgotten protection
    ↓
Security controls βœ—

A centralized design makes it much harder for one authentication path to quietly diverge from the others.

Server-Side Enforcement:

Every protected request must be independently verified by the server, regardless of what the client claims.

Client-side checks can improve usability, but they cannot establish authentication:

Client says:
"I'm authenticated"
        ↓
Untrusted claim
        ↓
Server verifies session / credential
        ↓
Security decision

The server should derive authentication state from trusted server-side information such as a validated session, credential, or authentication token β€” never from hidden fields, JavaScript variables, URL parameters, or other client-controlled state.

Defense in Depth:

Centralization does not mean relying on one giant security control. Authentication should use independent layers of defense, so that failure of one layer does not automatically result in compromise.

For example:

Secure password hashing
        +
MFA
        +
Rate limiting
        +
Abuse detection / lockout
        +
Secure password reset
        +
Session protection
        +
Logging and monitoring

Each layer addresses a different failure mode.

For example, if a password is compromised:

Compromised password
        ↓
MFA
        ↓
Attacker still blocked

If MFA is bypassed through a recovery weakness:

MFA bypass
        ↓
Session monitoring
        ↓
Suspicious activity detected

The purpose of defense-in-depth is not to make every individual control perfect. It is to ensure that one failure does not automatically become total compromise.

Authentication Monitoring:

Preventive controls should be complemented by detection.

Authentication events worth monitoring include:

  • repeated failed login attempts

  • unusual authentication volumes

  • repeated MFA failures

  • unexpected password-reset activity

  • new or unusual authentication devices

  • suspicious geographic or network changes

  • authentication at unusual times relative to the user's normal behavior

  • sudden changes to account security settings

For example:

Repeated failures
       +
New location/device
       +
Password reset
       ↓
Higher-risk authentication event
       ↓
Alert / additional verification

"Impossible travel" or unusual geographic patterns can be useful signals, but they should generally be treated as risk indicators rather than definitive proof of compromise. VPNs, mobile networks, proxies, and shared infrastructure can make location-based signals noisy.

Monitoring also provides an important final layer: it can detect attacks while preventive controls are functioning exactly as designed. An attacker repeatedly triggering rate limits may never successfully authenticate, but the behavior itself can still reveal an ongoing attack.

The Architectural Principle:

A mature authentication architecture therefore follows four principles:

Centralize
    ↓
Consistent enforcement

Verify server-side
    ↓
Never trust client claims

Layer defenses
    ↓
One failure β‰  full compromise

Monitor continuously
    ↓
Detect attacks that prevention misses

The goal is not simply to build a secure login form. It is to create an authentication system in which every path to an authenticated state is governed by the same security principles and protected by multiple independent layers.

8

Remediation

Key Takeaways

πŸ›‘οΈ

Remediation

Every defense in this lesson maps directly to a vulnerability class covered earlier in the topic:

Weak passwords
      ↓
Secure password hashing + breached-password checks

Brute force / password spraying
      ↓
Rate limiting + progressive backoff + abuse controls

Account lockout abuse
      ↓
Carefully scoped temporary restrictions + monitoring

MFA bypass
      ↓
Strong MFA + secure recovery + step-up authentication

Reset-token attacks
      ↓
Random, short-lived, single-use tokens + trusted reset URLs

Username enumeration
      ↓
Consistent responses + comparable processing time

No individual control is sufficient on its own. Strong password hashing cannot prevent credential stuffing. MFA cannot compensate for an insecure recovery flow. Rate limiting cannot fix a compromised password. A secure reset token is meaningless if the application sends it to an attacker-controlled destination.

Effective authentication security therefore comes from multiple independent layers reinforcing one another, all built on a centralized architecture where security decisions are consistently enforced on the server.

When secure credential storage, MFA, rate limiting, reset protection, enumeration resistance, session security, and monitoring work together, an attacker who defeats one layer encounters another rather than reaching the account immediately.

You've completed Authentication Vulnerabilities

Great work β€” explore other topics to keep learning.