How to Test Authentication and Authorization with Mock APIs
Authentication and authorization flows are some of the hardest frontend paths to verify. Real identity services are often incomplete, rate-limited, expensive to reset, or awkward to force into expired-token and permission-denied states on demand.
When you test authentication with mock APIs, you can reproduce login success, invalid credentials, missing tokens, expired sessions, and role-based denials without waiting on a production identity provider. That lets you prove the UI behaves correctly before the real backend is ready.
Why Auth Flows Are Hard to Test
Auth is not one endpoint. It is a chain of UI states tied to identity, session lifetime, and permissions. Staging accounts get locked. Tokens expire at inconvenient times. Role changes require admin tooling. Multi-factor challenges may be unavailable in local environments. Mock APIs give you deterministic control over those states.
Keep the two concepts separate while you design scenarios:
- Authentication proves who the user is — credentials, tokens, sessions, refresh flows.
- Authorization decides what that identity may access — roles, ownership, tenant boundaries, admin-only actions.
For a broader error-testing workflow that includes auth failures, see how to test API error responses like a real QA engineer.
Authentication Scenarios Every Frontend Should Test
Cover the full login and session lifecycle, not only the happy path:
- Successful login: tokens stored, user profile loaded, redirect to the intended page.
- Invalid username or password: field-level or form-level messaging without leaking whether the email exists.
- Missing required fields: client validation and server validation both produce recoverable UI states.
- Locked or disabled account: clear copy and no silent retry loop.
- Multi-factor authentication required: challenge step renders and can be cancelled safely.
- Expired session: protected calls fail and the user is guided back to login.
- Invalid access token: malformed or revoked tokens are treated as unauthenticated.
- Failed token refresh: session clears and the user is not stuck mid-flow.
- Logout and revoked session: local state is wiped and subsequent protected requests fail cleanly.
Understanding 401 Versus 403
Mixing these codes leads to the wrong UX. Treat them as different product states:
401 Unauthorizedusually means authentication is missing or invalid.403 Forbiddenmeans the identity may be known, but access is not allowed.
| Signal | 401 Unauthorized | 403 Forbidden |
|---|---|---|
| Meaning | Not authenticated, or credentials invalid | Authenticated, but not permitted |
| Typical cause | Missing token, expired session, bad password | Wrong role, ownership mismatch, policy deny |
| Frontend action | Prompt login or refresh; clear bad session | Show access denied; do not treat as logout by default |
| Retry strategy | Refresh once if supported; otherwise stop | Do not retry with the same credentials |
Build Mock Authentication Response Profiles
Create one mock endpoint for login or session checks, then define multiple response profiles you can switch during testing. Useful profiles include:
- Login success:
200 - Invalid credentials:
401 - Forbidden role:
403 - Validation failure:
422 - Rate limited login:
429 - Identity-provider failure:
500or503
Example success response:
{
"user": {
"id": "usr_123",
"name": "Maya Cohen",
"email": "maya@example.com",
"role": "editor"
},
"accessToken": "mock_access_token",
"expiresIn": 3600
}Example invalid-credentials response:
{
"error": {
"code": "INVALID_CREDENTIALS",
"message": "The email or password is incorrect."
}
}For identity-provider outages, also rehearse delays and retries. See how to test API timeouts, retries, and network failures.
Frontend Implementation Example
The UI should handle loading, success, validation, invalid credentials, forbidden access, and generic server failure. This TypeScript example uses fetch against a mock login endpoint:
type LoginResult =
| { status: "success"; user: { id: string; role: string } }
| { status: "invalid_credentials"; message: string }
| { status: "forbidden"; message: string }
| { status: "validation"; message: string }
| { status: "server_error"; message: string };
export async function loginWithPassword(
email: string,
password: string
): Promise<LoginResult> {
if (!email || !password) {
return { status: "validation", message: "Email and password are required." };
}
const response = await fetch("https://mock.example.com/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
if (response.status === 401) {
const body = await response.json();
return {
status: "invalid_credentials",
message: body.error?.message ?? "Invalid credentials.",
};
}
if (response.status === 403) {
return { status: "forbidden", message: "You do not have access." };
}
if (response.status === 422) {
return { status: "validation", message: "Check the submitted fields." };
}
if (!response.ok) {
return { status: "server_error", message: "Login is temporarily unavailable." };
}
const data = await response.json();
return { status: "success", user: data.user };
}Testing Protected Routes
Protected routes should cover loading while the session is checked, redirect for anonymous users, access-denied UI for insufficient permissions, and the correct page for authorized users. A React-style sketch:
function ProtectedPage({
session,
requiredRole,
}: {
session: { status: "loading" | "anonymous" | "authenticated"; role?: string };
requiredRole: string;
}) {
if (session.status === "loading") {
return <p>Checking your session…</p>;
}
if (session.status === "anonymous") {
// In Next.js App Router, redirect("/login") from a server component
// or use router.replace("/login") in a client effect.
return <p>Redirecting to login…</p>;
}
if (session.role !== requiredRole) {
return <p role="alert">You do not have permission to view this page.</p>;
}
return <p>Welcome to the editor workspace.</p>;
}For React and Next.js mock wiring patterns, see how to use mock APIs in React and Next.js applications.
Testing Token Expiration and Refresh Failure
Expired access tokens are common in long-lived SPAs. A realistic sequence to mock:
- An API call returns
401. - The client calls a refresh endpoint.
- The refresh endpoint also fails.
- The frontend clears the session.
- The user is redirected to login with a useful message.
async function fetchWithSession(input: RequestInfo, init: RequestInit = {}) {
let response = await fetch(input, { ...init, credentials: "include" });
if (response.status !== 401) {
return response;
}
const refreshed = await fetch("/auth/refresh", { method: "POST", credentials: "include" });
if (!refreshed.ok) {
clearLocalSession();
window.location.assign("/login?reason=session_expired");
throw new Error("Session expired");
}
response = await fetch(input, { ...init, credentials: "include" });
return response;
}Mock the first endpoint as 401 and the refresh endpoint as 401 or 503 so you can verify the redirect and messaging without waiting for a real token clock to expire.
Security Caveat
Mock APIs test client behavior. They do not prove the security of a production authentication system. Mocks cannot demonstrate that:
- Password storage is secure
- Tokens are correctly signed
- Backend authorization rules are enforced
- Sessions cannot be hijacked
- Real identity-provider configuration is correct
Use mocks to harden UX and client state machines. Use security reviews, penetration testing, and backend tests for the actual trust boundary.
Practical Checklist
Verify these authentication and authorization states before production:
- Successful login stores session data and lands on the intended route
- Invalid credentials show safe, actionable feedback
- Missing fields fail locally and via server validation
- Locked or disabled accounts are explained clearly
- MFA challenge and cancel paths work
- Expired and invalid tokens trigger login recovery
- Refresh failure clears state and redirects once
- Logout removes tokens and blocks protected APIs
- Anonymous users cannot stay on protected pages
- Insufficient roles show access denied, not empty layouts
- Admin-only and ownership rules are visible in the UI and enforced by mocked APIs
- Tenant isolation prevents cross-workspace data display
For a wider release checklist, use the complete API testing checklist for frontend teams.
Using MockFlow for Auth Scenarios
MockFlow response profiles help you switch quickly between status codes, headers, bodies, and delays for the same auth endpoint. That makes it practical to rehearse login success, invalid credentials, forbidden roles, validation failures, rate limits, and identity outages in one session.
MockFlow does not validate real JWT signatures or act as a complete identity provider. Use it to exercise frontend handling of authentication and authorization responses while the real auth service is still evolving. Guest mode works locally without an account; logged-in users can use shareable mock API URLs when teammates need the same scenarios.
Related reading: how to simulate API responses for frontend development and how to mock API responses without building a backend.
FAQ
What is the difference between authentication and authorization?
Authentication proves identity. Authorization decides access. Your UI needs distinct handling for each.
How do you test 401 and 403 with mock APIs?
Define separate response profiles and switch them while running the same login or protected-route flow. Confirm redirects for 401 and access-denied UI for 403.
Can mock APIs validate real JWT security?
No. They help verify client behavior only. Production trust still depends on backend crypto, policy enforcement, and identity-provider configuration.
Should frontend apps retry 401 responses?
Not blindly. Attempt a single refresh when your protocol supports it. If refresh fails, clear the session and send the user to login.
What auth scenarios should every frontend test?
Login success and failure, missing fields, locked accounts, MFA, expired tokens, refresh failure, logout, anonymous access, role denial, ownership, and admin-only actions.
Create realistic auth response scenarios in MockFlow
Switch between login success, 401, 403, validation errors, and identity outages so your frontend handles authentication and authorization before the real backend is ready.