Testing Authentication Flows
Part of the Full Stack Developer Roadmap
In short
Auth bugs are the scary kind: a logged-out user sees someone's dashboard, or nobody can log in after a refactor. A few focused tests catch both before your users do.
Specifically testing login, signup, protected routes and token expiry — since auth bugs are high-impact and easy to accidentally break while refactoring either side of the stack.

Think of it like this
Auditing the society gate
A good security audit doesn't just check that residents get in. It tries an expired pass, no pass at all, and a visitor pass trying to reach the terrace. Testing auth is the same: test the happy path, then every way someone should be stopped.
Watch it happen
Three auth tests, three outcomes
goto('/dashboard')
1/7Test 1: a logged-out visitor opens a protected page.
The big ideas
What you need to know
Test the stops, not just the doors
For each protected route, test logged out (401 or redirect), wrong role (403) and the right user (200).
Expire sessions on purpose
Use a short expiry in the test environment or fake timers to check refresh works and expired sessions are rejected.
Log in once, reuse it
Playwright's storageState saves cookies after one login, so other tests start signed in without retyping.
Seed test users
Create known users and roles in a test database before the run. Never test against real accounts.
See it in code
Walk through the code
1import { test, expect } from "@playwright/test";2 3test("logged-out visitors are sent to login", async ({ page }) => {4 await page.goto("/dashboard");5 await expect(page).toHaveURL(/\/login/);6});7 8test("a user can log in and reach the dashboard", async ({ page }) => {9 await page.goto("/login");10 await page.getByLabel("Email").fill("asha@test.local");11 await page.getByLabel("Password").fill(process.env.TEST_USER_PASSWORD!);12 await page.getByRole("button", { name: "Log in" }).click();13 await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();14});15 16// API check, as a signed-in normal user: expect((await request.get("/api/admin/users")).status()).toBe(403);1/3The 'stopped at the gate' test: no cookie, so the protected page must redirect to login.
Set baseURL in playwright.config.ts so goto('/login') hits your local app. The test password comes from an env variable, never from the code.
Save yourself the pain
Common mistakes
Oops
Only testing that login works.
Do this instead
Also test the failures: wrong password, logged out, expired session and wrong role. That's where data leaks hide.
Oops
Logging in through the UI at the start of every single test.
Do this instead
Log in once in a setup step, save storageState, and reuse it. Keep one UI test for the login form itself.
Your mission
Guard the gate with tests
Quick check
A signed-in normal user calls DELETE /api/users/5, which is admin-only. What should your test expect?
Go deeper
- 1Write an E2E test that logs in and confirms access to a protected page.
- 2Write a test confirming an expired or invalid token correctly redirects to login.
- 3Test that a logged-out user genuinely cannot reach protected API endpoints, not just protected pages.
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.