CWE-287: Improper Authentication
Overview
Improper authentication occurs when an application fails to correctly verify the identity of users, services, or systems. Weak passwords and credential stuffing exploit the credentials themselves; improper authentication is a flaw in the authentication logic, the mechanism that checks "you are who you claim to be". An attacker who finds such a flaw passes as a legitimate user or service without ever presenting valid credentials, and reaches whatever that identity is permitted to do.
Relationship to Other CWEs
CWE-287 is a MITRE Class, one level below the CWE-284 (Improper Access Control) Pillar, and MITRE marks it Discouraged for direct mapping since a more specific descendant usually applies better.
Its direct children in MITRE's research view:
- CWE-306 (Missing Authentication for Critical Function) - where no identity check exists on the path at all
- CWE-295 (Improper Certificate Validation)
- CWE-645 (Overly Restrictive Account Lockout Mechanism) - no page here
- CWE-1390 (Weak Authentication) - the class MITRE's own mapping note names alongside CWE-306 as the better target. No page here
A second group appears under CWE-287 only in MITRE's simplified mapping view (view-1003), which MITRE publishes for categorising public vulnerability data such as NVD's - so a finding routed through a CVE record may arrive here carrying one of these. In the research view they sit elsewhere:
- CWE-798 (Use of Hard-coded Credentials) and CWE-521 (Weak Password Requirements) - under CWE-1391 in the research view
- CWE-522 (Insufficiently Protected Credentials) - under CWE-668 and CWE-1390
- CWE-640 (Weak Password Recovery Mechanism) - under CWE-1390
- CWE-307 (Improper Restriction of Excessive Authentication Attempts) - under CWE-799 and CWE-1390, and the formal home of the brute-force and rate-limiting guidance below
CWE-307, CWE-521 and CWE-640 have no dedicated page here yet. If a finding names one of these specifically, prefer its own remediation; use this page when the finding is reported generically as an authentication weakness.
OWASP Classification
A07:2025 - Authentication Failures
Risk
High to Critical: The impact depends on which identity an attacker can assume and what it is permitted to do:
- Unauthorized access: Attackers access accounts, data, or functions without valid credentials
- Account takeover: Bypass authentication to fully control user accounts
- Unauthorized identity establishment: Impersonate another user or service before authorization checks run
- Data breaches: Access sensitive information belonging to other users
- Compliance violations: Can fail applicable SOX, PCI DSS, HIPAA or GDPR authentication requirements, depending on the organisation, system and data involved
- Business logic bypass: Access payment, ordering, or workflow functions without authenticating
- API abuse: Exploit unauthenticated or weakly authenticated API endpoints
In regulated industries or applications handling sensitive data, authentication failures can trigger mandatory breach disclosure and significant penalties.
Remediation Steps
Core Principle: Never trust client-supplied identity; every request must be authenticated by the server using a complete, validated authentication mechanism before any identity or session is accepted.
Trace authentication flows and identify weaknesses
- Identify every authentication entry point: login forms, API authentication, SSO, OAuth, password reset
- Examine how credentials are validated and look for bypass conditions
- Review how session tokens are generated, stored, timed out, and invalidated
- Identify which endpoints and functions require authentication
- Find resources with no authentication check, especially admin functions and APIs
Implement complete server-side authentication checks (Primary Defense)
- Validate credentials or tokens on the server: Verify passwords, API keys, session IDs, JWT signatures, issuer/audience, expiration, and revocation status as appropriate for the mechanism.
- Bind identity to a server-validated principal: Do not trust client-supplied user IDs, roles, or login flags.
- Fail closed: Missing, malformed, expired, or invalid credentials must result in
401 Unauthorized. - Regenerate sessions after login: Prevent session fixation by issuing a fresh session identifier after successful authentication.
- Protect all authenticated entry points: Apply authentication middleware/guards consistently to APIs, admin interfaces, and file endpoints.
Add multi-factor authentication for higher assurance
- Use multiple independent factors to verify identity:
- Something you know (password, PIN)
- Something you have (hardware token, phone, authenticator app)
- Something you are (biometric: fingerprint, face recognition)
- Require MFA: Mandatory for administrative, privileged, and other sensitive accounts, and offered to all other users
- Use standards: TOTP (RFC 6238), WebAuthn, FIDO2
- Support app-based authenticators: Google Authenticator, Authy, Microsoft Authenticator
- Provide backup codes: Users can regain access if they lose their device
- Treat SMS/voice as restricted or lower assurance: Prefer phishing-resistant authenticators such as WebAuthn/FIDO2 where possible; SMS is vulnerable to SIM swapping and number reassignment.
- Why this works: MFA reduces the chance that password compromise alone leads to account compromise. It strengthens authentication but does not replace server-side credential/session validation and can still be bypassed by phishing or weak recovery flows.
Use proven authentication frameworks (Defense in Depth)
- Don't build authentication from scratch: Use an established, maintained framework
- Modern frameworks provide:
- Secure password hashing (bcrypt, Argon2)
- Session management with secure defaults
- CSRF protection
- Rate limiting
- Account lockout mechanisms
- Recommended options:
- Java: Spring Security, Apache Shiro, Keycloak
- .NET: ASP.NET Core Identity, OpenIddict, Duende IdentityServer
- Python: Django auth, Flask-Security, Authlib
- Node.js: Passport.js, Auth0, Auth.js/NextAuth
- Standards: OAuth 2.0, OpenID Connect providers
Enforce authentication on all protected resources
- Default to "deny all": Require explicit authentication grants
- Check at application layer: Don't rely solely on web server authentication
- Verify every request: Don't assume session validity
- Protect all endpoints: API endpoints, admin interfaces, file access
- Hand off to authorization: After authentication, use authorization controls such as RBAC/ABAC to decide what the authenticated principal may access.
- Review indirect access: Direct object references, file paths
Implement account security controls
- Rate limiting:
- Limit failed login attempts (e.g., 5 attempts per 15 minutes)
- Implement progressive delays after failed attempts
- Use CAPTCHA or other risk-based challenges after multiple failures, weighing the accessibility and usability cost
- Monitor for distributed brute force attacks
- Account lockout:
- Temporary lockout or progressive throttling after repeated failures
- Avoid permanent or easily-triggered lockouts that let attackers deny service to users
- Require account recovery process or admin unlock only for high-risk cases
- Alert users to suspicious login attempts
- Monitoring:
- Log all authentication events (success and failure)
- Alert on unusual patterns (impossible travel, new devices)
- Track failed attempts by IP and username
Test authentication controls
- Test authentication bypass attempts: Missing credentials, empty passwords, SQL injection in login
- Test session management: Session fixation, session hijacking, concurrent sessions
- Test account lockout: Verify lockout after failed attempts
- Test MFA: Attempt bypass, test backup codes, verify enforcement
- Test password reset: Check that reset tokens are generated and validated securely
- Verify all protected resources require authentication
- Use automated scanners and penetration testing
- Re-scan with the security scanner to confirm the issue is resolved
Access Control Testing
Test scenarios:
- Access protected browser page without authentication → Should redirect to login; API requests should return 401 Unauthorized
- Access protected resource with invalid token → Should return 401 Unauthorized
- Access admin function as regular user → Should return 403 Forbidden from authorization controls
- Logout and attempt to reuse session token → Should require re-authentication
Brute Force Protection Testing
# Attempt multiple failed logins
for i in {1..10}; do
curl -X POST https://app.com/login \
-d "username=admin&password=wrong$i"
done
Expected result: After threshold (e.g., 5 attempts), receive rate limit error or account lockout.
MFA Verification Testing
- Attempt login with valid password but no MFA code → Should require MFA
- Attempt login with valid password and invalid MFA code → Should reject
- Attempt to reuse MFA code → Should reject (prevent replay attacks)
- Verify backup codes work when primary MFA unavailable
Session Security Testing
- Verify session tokens are cryptographically random
- Check session cookies have Secure, HttpOnly, SameSite attributes
- Test session timeout (idle sessions should expire)
- Verify concurrent session limits work (if implemented)
Authentication Bypass Testing
Common bypass attempts:
- Remove authentication headers
- Use different HTTP methods (POST vs GET)
- Add parameters like debug=true, admin=1
- Access resources via direct URL/path manipulation
- Test API endpoints separately from UI
Common Vulnerable Patterns
Authentication weaknesses commonly take these forms:
- Missing authentication on critical functions: Admin panels, APIs, or sensitive operations accessible without login
- Weak or predictable authentication tokens: Session IDs, JWT secrets, or API keys easily guessed or brute-forced
- Authentication bypass through trusted client state: Accepting user IDs, roles, or login flags from request parameters, cookies, or headers without server-side validation
- Insecure "remember me" functionality: Persistent tokens that never expire or can be stolen
- Session fixation vulnerabilities: Attacker sets known session ID before user authenticates
- Inadequate protection against automated attacks: No rate limiting, CAPTCHA, or account lockout mechanisms
- A login that returns before verifying the password when the account does not exist: The failure message is identical either way, but the two branches cost different amounts - a real account pays for a password hash, a missing one pays for nothing - so response time answers "does this username exist". Measured across this CWE's language guidance the gap ran from 4,000x to a million to one. The fix is to verify the submitted password against a fixed, genuine hash and discard the result, so both branches do the same work; see CWE-385 for the general shape and the language pages below for the concrete call. It generalises past login to any lookup-then-verify flow: password reset token redemption, API key checks, and TOTP validation.
Common Pitfalls
- Trusting a client-supplied identity claim: Accepting a "logged in" cookie flag, an unsigned token, or a JWT whose signature/issuer/audience/expiration is never actually verified server-side - an attacker who can set or forge that value is then treated as authenticated without ever proving it.
- Treating MFA as optional or client-enforced: Prompting for a second factor in the UI but still issuing a valid session if the client simply skips that step or calls the login API directly - MFA must be enforced as a required part of the server-side authentication flow, not a UI-only gate.
- Rolling a custom authentication scheme instead of using a maintained framework: Hand-written session/token generation, password comparison, or password-reset flows tend to miss subtle issues (timing attacks, weak randomness, missing rate limiting) that established libraries usually handle correctly.
- Assuming the framework closes every gap, without checking which ones: "use the framework" is the right default but not a complete answer. On the username-enumeration timing channel above, Django's
ModelBackendand Spring Security'sDaoAuthenticationProviderdo hash on the unknown-user branch, while ASP.NET Core Identity'sSignInManager.PasswordSignInAsync(string userName, ...)and Laravel'sAuth::attempt()return without hashing at all - measured at 22,000x on the first of those. Check the specific control on the specific framework rather than treating the whole class of issue as handled. - Authenticating at the perimeter only: Enforcing login at a gateway, load balancer, or UI layer while backend APIs and internal service-to-service calls trust the request implicitly - any endpoint reachable directly (mobile app, script, another internal service) bypasses perimeter-only checks entirely.
Language-Specific Guidance
- C# - ASP.NET Core Identity, cookie/JWT authentication handlers, TokenValidationParameters
- Go - net/http middleware, golang-jwt signing-method validation, bcrypt password checks
- Java - Spring Security AuthenticationProvider, JWT validation with jjwt/NimbusJwtDecoder, session fixation
- JavaScript/Node.js - Passport.js strategies, jsonwebtoken algorithm allowlisting, session regeneration
- PHP - password_verify vs loose comparison, firebase/php-jwt Key binding, session_regenerate_id
- Python - Django authentication backends, Flask-Login, PyJWT algorithm allowlisting