HOW TO TEST

How to Test a Login Page.

Authentication A complete testing guide for login and authentication flows: functional, security, accessibility, session management, MFA, OAuth, rate limiting, and API-level auth checks.

14
scenarios
12
test cases
15 min
read
intermediate4–6 hours (full suite); 1–2 hours (smoke pass covering happy path + 3 critical security checks) testingQA engineersSDETsSecurity engineersAutomation engineers

The login page is the highest-risk surface in any application. A single flaw here — a subtle session bug, an account enumeration vulnerability, or a broken logout — can expose every user account at scale. This guide covers the full authentication surface: functional login flows, invalid credentials, session management, rate limiting and account lockout, MFA, social OAuth, accessibility for keyboard and screen-reader users, security hardening, and API-level auth verification. Every test case includes concrete steps, preconditions, and expected results written for a senior QA or SDET audience — not checklists, but executable tests.

Risks

Unauthorized access via credential bypass

Attackers may exploit weak session handling, insecure cookies, or unprotected API endpoints to gain access without valid credentials. Programmatic login against an unprotected API endpoint is the most common vector.

Account enumeration through error messages

Differentiating 'email not found' from 'wrong password' — or returning different HTTP status codes, different response times, or different response body lengths — allows attackers to harvest valid email addresses at scale without needing a password.

Broken session management

Failure to invalidate sessions server-side on logout, or improper token rotation after privilege escalation, leaves users exposed to session hijacking. A captured token replayed after logout is the clearest signal this is broken.

Absent or UI-only brute-force protection

Without rate limiting or account lockout at the API layer, an attacker can programmatically cycle through password lists even if the UI throttles. Rate limiting applied only to the frontend provides zero security.

Broken logout

Logout that only clears the client-side cookie without server-side session invalidation leaves the old session token valid for replay. This is especially dangerous on shared devices.

Login bypass via direct URL or API manipulation

Protected routes that rely solely on frontend routing (not server-enforced auth checks) can be accessed by navigating directly to the URL. API endpoints with missing auth middleware are a silent companion risk.

Accessibility barriers excluding disabled users

Login forms that depend on mouse-only interaction, lack proper ARIA labels, fail colour contrast requirements, or don't announce errors to screen readers exclude users with disabilities and may create legal liability under WCAG 2.1 AA.

Test Scenarios

Login with valid credentials redirects to the authenticated destination

CriticalfunctionalFully automated

Submit correct email and password. Assert the final URL is the post-login destination (e.g. /dashboard), not a 200 back on /login itself. A 200 response on the login page is the most common sign of a broken redirect.

Invalid password shows a generic error without revealing account existence

CriticalsecurityFully automated

Submit a registered email with a wrong password. The error message must be identical to the one shown when the email does not exist — 'Invalid email or password', not 'Wrong password for this account'.

Non-existent email returns the same error text and response time as wrong password

CriticalsecurityFully automated

The application must not differentiate between 'email not found' and 'incorrect password'. Error text must match exactly, and HTTP response time must be within ~100ms of the invalid-password case to prevent timing-based enumeration.

Empty username and/or password fields trigger accessible inline validation

HighfunctionalFully automated

Submitting with one or both fields empty must show field-level validation messages programmatically associated with their inputs, prevent form submission, and make no network request.

Account is locked or rate-limited after N consecutive failed login attempts

CriticalsecurityFully automated

Submitting N invalid passwords for the same account triggers lockout or a CAPTCHA challenge. This must apply at the API layer, not just the UI — test by sending requests directly to POST /api/auth/login.

MFA challenge is required after valid first-factor and cannot be bypassed

HighsecurityManual only

Entering valid credentials must redirect to the MFA challenge, not to the dashboard. Calling a protected API endpoint with the intermediate (pre-MFA) session token must return 401 — MFA must be enforced at the API layer, not only the UI.

Remember-me persists the session across browser restarts with correct cookie attributes

MediumfunctionalFully automated

When 'Remember me' is checked, closing and reopening the browser restores the authenticated session. The persistent cookie must have Secure, HttpOnly, SameSite, and a future Max-Age/Expires. Without the checkbox, the session is discarded on restart.

Logout invalidates the session server-side and blocks token replay

CriticalsecurityFully automated

After logout, replaying the captured session token against any protected API endpoint (e.g. GET /api/me) must return 401. Client-side cookie deletion alone is not sufficient.

Accessing a protected route without authentication redirects to login

CriticalfunctionalFully automated

Navigating directly to /dashboard, /settings, or any protected URL while unauthenticated must redirect to /login, not render any protected content — even partially via a loading state.

Session expires after the configured inactivity period

HighfunctionalManual only

After the session TTL elapses, the next user action must redirect to login. Expiry must be enforced server-side — a client-side timer alone can be bypassed by manipulating JavaScript.

Social login (OAuth) completes the flow and lands on the correct destination

HighfunctionalManual only

Clicking 'Login with Google/GitHub' redirects to the provider; returning with a valid code completes auth and redirects to the post-login page. A second social login with the same account links to the same user record rather than creating a duplicate.

Keyboard-only navigation completes the full login flow

HighaccessibilitySend automated · human eval

Tab through email → password → submit without a mouse. Focus indicators must be visible on every interactive element. Pressing Enter from the password field submits the form.

Login form fields and errors are announced correctly by screen readers

HighaccessibilitySend automated · human eval

All fields have labels associated via for/id or aria-labelledby. Error messages are announced via aria-live or aria-describedby. The submit button has an accessible name. Verify with VoiceOver (macOS) or NVDA (Windows).

Protected API endpoints reject requests with invalid or missing tokens

CriticalsecurityFully automated

Direct calls to protected endpoints — with no token, a tampered token, or an expired JWT — must return 401 with no user data in the body. A 200 with partial data or a 403 for the no-token case are both bugs.

Detailed Test Cases

Preconditions

  • A registered user account exists with a known email and password
  • The user has no active session (cookies cleared)
  • The application login page is reachable at /login

Steps

  1. 1.Navigate to /login
  2. 2.Enter the registered email address in the email field
  3. 3.Enter the correct password in the password field
  4. 4.Click 'Log in' (or press Enter from the password field)
  5. 5.Observe the resulting URL, HTTP status code, and page content
  6. 6.Open DevTools → Application → Cookies and inspect the session cookie attributes

Expected result

The browser redirects to the post-login destination (e.g. /dashboard). The final URL is NOT /login with a 200 status. A session cookie is set with Secure, HttpOnly, and SameSite attributes. The dashboard renders the authenticated user's data without an extra page reload.

Test data

  • email: testuser@example.com
  • password: ValidPass123!
  • Expected landing URL: /dashboard

Edge Cases

Email with leading or trailing whitespace

Enter ' user@example.com ' (with spaces). The app should trim whitespace and authenticate successfully. Failing to trim causes valid users to be rejected with a confusing 'invalid credentials' error — especially common with autofill.

Email in a different case (UPPERCASE, mixed case)

Login with USER@EXAMPLE.COM when the account was registered as user@example.com. Authentication should succeed — email matching must be case-insensitive. A case-sensitive comparison creates support burden and UX confusion.

Concurrent logins from multiple browsers or devices

Log in from two browsers simultaneously with the same credentials. Unless the app enforces a single-session policy, both sessions should be valid. Check whether invalidating one session (via logout) affects the other — intentional per-session invalidation is fine; accidental global logout is a bug.

Back button after logout

After logout, press the browser back button to navigate to the previously-authenticated page. The page must not render protected content from a browser cache. The server must verify auth on every page load — not just when the route is first visited.

Pasted password containing hidden characters

Paste a password copied from a password manager — it may contain zero-width spaces (U+200B), BOM characters, or trailing newlines. The app should either strip these silently (with documented behaviour) or surface a clear error rather than silently failing authentication.

Very long input values

Enter an email of 300+ characters or a password of 1000+ characters. The app should return a clear validation error. Acceptable failure modes: field-level validation rejection. Unacceptable: 500 error, silent truncation that stores a different hash from the one used at login, or a database error message surfaced to the user.

Expired JWT token mid-session

If the app uses short-lived JWTs with refresh tokens, let the access token expire while the user is actively browsing. The next API call should silently refresh the token and succeed, or redirect to login with a clear 'session expired' message — not produce a confusing 401 error on a UI action.

Browser autofill with stale credentials

The browser autofills an old password (after a password reset). The user submits without reviewing. The app should return the generic 'Invalid email or password' error — no special handling that would reveal the password was recently changed.

Login attempt during account suspension

A suspended or deactivated account attempts to log in with correct credentials. The app must reject with an appropriate message ('Your account has been suspended — contact support') while not revealing whether the rejection is credential-based or account-state-based if that distinction is security-sensitive.

Double-submit (clicking Login twice in rapid succession)

Click 'Log in' twice before the first response arrives. The app must not create two sessions, send two login events to analytics, or enter a race condition. The submit button should disable on the first click to prevent this at the UI layer — and the API should be idempotent for the login request.

Password with Unicode characters (emoji, CJK, Arabic script)

Use a password containing emoji, CJK characters, or Arabic script. The app must handle Unicode passwords correctly — both at registration and login — ensuring the hashing is applied to the full Unicode string and not a corrupted or truncated representation.

Login redirect loop (OAuth misconfiguration)

If the OAuth callback handler receives an invalid or expired state parameter, it must display an error page rather than retrying the redirect indefinitely. A redirect loop is silent to the user and creates the impression of a broken application.

Automation Ideas

Happy-path login via API (programmatic auth setup for test suites)

Use POST /api/auth/login directly to obtain a session token and inject it as a cookie in Playwright's or Cypress's browser context. This is 10–20× faster than UI login and should be used in beforeEach hooks to establish authenticated state for all tests that aren't explicitly testing the login UI.

Tools: playwright, cypress

Account enumeration regression suite

Automate two API requests: valid email + wrong password, and non-existent email + any password. Assert that HTTP status codes, error message bodies, and response times are equivalent. Run on every CI build — error message changes are a common enumeration regression introduced by well-meaning UX improvements.

Tools: playwright, postman

Rate limiting and lockout verification

Script N+1 rapid POST /api/auth/login requests with wrong credentials using Playwright's APIRequestContext or a Postman Collection Runner. Assert that the (N+1)th request returns 429 or the expected lockout response. Run against a dedicated test environment with a disposable account — never against production.

Tools: playwright, postman, bruno

Logout session invalidation regression

After calling the logout endpoint, replay the captured session token against GET /api/me and assert 401. Fully automatable with Playwright's request API: log in programmatically, capture the token, log out, replay. This catches server-side session invalidation regressions without any UI interaction.

Tools: playwright, postman

Protected route redirect sweep

Maintain a fixture list of all protected routes. In a Playwright spec, clear cookies, navigate to each, and assert the final URL matches /login or /login?redirect=... New routes added to the router are automatically covered if they appear in the fixture. Fail the test if any route returns 200 without authentication.

Tools: playwright, cypress

Accessibility audit on login form states

Use axe-core (via @axe-core/playwright or cypress-axe) to audit the login form in three states: initial load, field validation errors (empty submit), and authentication failure (wrong credentials). Assert zero critical or serious violations. Run on every CI build — login accessibility regressions are common after form library upgrades.

Tools: axe-core, playwright, cypress

Open-redirect fuzzing with a redirect bypass wordlist

Parameterise the ?redirect= value with a list of known bypass patterns: absolute URLs, protocol-relative URLs (//evil.com), backslash variants (/\evil.com), and JavaScript URLs. Assert every case results in a safe post-login destination. Automatable as an API-level test — no browser needed.

Tools: playwright, owasp-zap

Visual regression on login form states

Capture screenshots of the login page in default, loading, validation-error, and auth-failure states. Compare to baseline images on every PR. Keep this in a separate slow suite, not the main CI gate — use it to catch unintended UI regressions after design-system or component library updates.

Tools: playwright, chromatic, backstopjs

Common Bugs

Session remains active after logout

The logout endpoint clears the client-side cookie but does not invalidate the server-side session or revoke the JWT. A captured token replayed immediately after logout returns 200.

Impact: Any user whose token was captured — via XSS, a shared device, network interception, or a logged request — retains indefinite access until the token expires naturally.

Error message reveals whether an account exists

The app returns 'Wrong password' for a valid email and 'Email not found' for an unknown email, or uses different HTTP status codes (401 vs 404), different response times, or different response body lengths for the two cases.

Impact: Enables automated account enumeration — an attacker can verify whether any email is registered without knowing the password, enabling targeted phishing or credential stuffing.

Remember-me cookie does not persist after browser restart

The 'Remember me' checkbox is present but has no effect — the session cookie lacks Max-Age/Expires, so the browser discards it on restart regardless of the checkbox state.

Impact: Users must re-authenticate on every browser launch, the feature is broken, and users lose trust in the product without understanding why.

Protected routes remain accessible after logout via browser back button

Client-side routing removes the auth token from memory on logout, but when the user presses the browser back button, the SPA renders the previously-visited protected page from its in-memory or disk cache before the auth check fires.

Impact: The previous user's data is briefly visible to anyone who shares the device, even after a correct logout — a GDPR and privacy risk.

Double-submit creates duplicate login events or session confusion

Clicking 'Log in' twice before the first response arrives sends two requests. The server creates two sessions, or one request succeeds and one produces an error, leaving the client in an unexpected partially-authenticated state.

Impact: Session confusion, duplicate analytics events, audit log pollution, and rare but possible crashes depending on the server's session handling.

Validation errors are absent, generic, or not associated with their fields

Submitting an empty form shows no error, a page-level 'Something went wrong' toast, or an error message not associated with its input field in the accessibility tree.

Impact: Poor UX for all users; WCAG 3.3.1 (Error Identification) and 4.1.3 (Status Messages) failures for screen-reader users who cannot determine which field has the error.

MFA bypassed by calling an authenticated API endpoint directly

After the first factor succeeds, a partially-authenticated session token is issued. Calling GET /api/me or POST /api/orders with this intermediate token returns 200 — the second factor is not enforced at the API layer.

Impact: MFA provides no protection against any attacker who can observe or steal the intermediate session token, which is present in cookies at this point.

Social login causes a redirect loop

The OAuth callback handler fails to exchange the code for a token (expired state parameter, misconfigured redirect URI, or CORS error on the token exchange) and retries the redirect indefinitely rather than surfacing an error page.

Impact: Social login users are completely locked out. The error is silent and confusing — users see 'Loading...' or redirects forever with no recoverable error message.

Login redirect URL parameter accepts external domains (open redirect)

The ?redirect= query parameter accepts absolute URLs or protocol-relative URLs. After successful login on the legitimate site, the user is silently redirected to an attacker-controlled page.

Impact: High-credibility phishing vector: the attacker sends a link to the real login page; the user authenticates legitimately but then lands on the attacker's site, which can harvest session tokens or credentials.

Rate limiting applied only to the UI, not the API

The UI throttles login attempts after N failures, but POST /api/auth/login has no server-side rate limiting — an attacker can make unlimited attempts by bypassing the UI with curl or a script.

Impact: Brute-force protection is completely ineffective against any attacker with basic HTTP tooling. The UI rate limit provides only an illusion of security.

Password field value exposed in server logs or error reports

The login form submits as a GET request (password visible in URL and browser history), or the server-side error handler logs the complete request body including the password field, or the error monitoring tool (Sentry, Datadog) captures it.

Impact: Passwords end up in server logs, CDN access logs, browser history, and error-monitoring dashboards — a severe credential exposure risk affecting every user whose login generates an error.

Useful Tools

Playwright

End-to-end login flow testing, programmatic API request interception for auth header assertions, and cookie manipulation for session replay tests.

Cypress

Login flow automation with cy.session() for fast programmatic auth setup in test beforeEach hooks, and cypress-axe for accessibility assertions in CI.

Postman

API-level auth testing: send requests with no token, expired tokens, and tampered tokens to assert the correct 401 responses without a browser.

Bruno

Git-based alternative to Postman for scripting and running auth API test collections in CI pipelines without a cloud dependency.

OWASP ZAP

Automated security scanning for the login surface: detects missing rate limiting, insecure cookies, open-redirect vulnerabilities, and common authentication misconfigurations.

Burp Suite

Manual security testing proxy for intercepting and replaying login requests, fuzzing rate-limit thresholds, and inspecting full cookie and header details.

axe-core

Automated accessibility auditing integrated into Playwright or Cypress CI runs — detects missing labels, insufficient colour contrast, and ARIA association issues on the login form.

jwt.io

Decode and inspect JWT tokens during manual testing to verify claims (exp, iat, sub, iss) and identify misconfigured or overly-permissive token payloads.

Browser DevTools

Inspect cookie flags (Secure, HttpOnly, SameSite, Max-Age) in the Application tab, replay auth requests in the Network tab, audit the accessibility tree for form label associations, and check for password values leaking into request URLs.

Practice this → Try it hands-on in the Buggy Web App.