Testing Authentication
Testing protected endpoints
Many endpoints require authentication. Tests for these endpoints must send valid tokens and verify that unauthenticated requests are rejected.
Testing 401 (not authenticated)
describe("protected endpoints", () => {
test("returns 401 without Authorization header", async () => {
const response = await app.fetch(new Request("http://localhost/v2/admin/books"));
expect(response.status).toBe(401);
});
test("returns 401 with invalid token", async () => {
const response = await app.fetch(
new Request("http://localhost/v2/admin/books", {
headers: { Authorization: "Bearer invalid-token" },
}),
);
expect(response.status).toBe(401);
});
test("returns 401 with expired token", async () => {
const expiredToken = createTestToken({ userId: "u1", role: "admin" }, { expiresIn: -1 });
const response = await app.fetch(
new Request("http://localhost/v2/admin/books", {
headers: { Authorization: `Bearer ${expiredToken}` },
}),
);
expect(response.status).toBe(401);
});
}); Creating test tokens
Build a helper that creates tokens for testing:
// tests/helpers/auth.ts
import jwt from "jsonwebtoken";
const TEST_SECRET = "test-secret-key";
export function createTestToken(
payload: { userId: string; role?: string },
options: { expiresIn?: number } = {},
): string {
return jwt.sign(payload, TEST_SECRET, {
expiresIn: options.expiresIn ?? 3600,
});
}
export function authHeader(userId: string, role: string = "user"): Record<string, string> {
return { Authorization: `Bearer ${createTestToken({ userId, role })}` };
} [!NOTE] The Authentication course built JWT-based auth. The Testing Auth and Security course tested auth flows specifically. This lesson shows how to test protected endpoints in general — any route that requires authentication.
Testing with authentication
describe("POST /v2/books (authenticated)", () => {
test("creates book when authenticated", async () => {
const response = await app.fetch(
jsonRequest(
"POST",
"/v2/books",
{
title: "Kindred",
authorId: "a1",
genre: "science-fiction",
},
authHeader("u1"),
),
);
expect(response.status).toBe(201);
});
test("returns 401 without authentication", async () => {
const response = await app.fetch(
jsonRequest("POST", "/v2/books", {
title: "Kindred",
authorId: "a1",
genre: "science-fiction",
}),
);
expect(response.status).toBe(401);
});
}); The jsonRequest helper from earlier accepts an optional headers argument. authHeader("u1") adds the Authorization header with a valid test token.
Testing 403 (not authorized)
describe("DELETE /v2/books/:id (admin only)", () => {
test("allows admin to delete", async () => {
const response = await app.fetch(
new Request("http://localhost/v2/books/b1", {
method: "DELETE",
headers: authHeader("u1", "admin"),
}),
);
expect(response.status).toBe(204);
});
test("returns 403 for non-admin user", async () => {
const response = await app.fetch(
new Request("http://localhost/v2/books/b1", {
method: "DELETE",
headers: authHeader("u2", "user"),
}),
);
expect(response.status).toBe(403);
});
test("returns 401 without authentication", async () => {
const response = await app.fetch(
new Request("http://localhost/v2/books/b1", {
method: "DELETE",
}),
);
expect(response.status).toBe(401);
});
}); Three states: no auth (401), wrong role (403), correct role (success). Each has its own test.
Exercises
Exercise 1: Create the authHeader helper. Test a protected endpoint with and without the header.
Exercise 2: Test the three states: no token (401), valid token wrong role (403), valid token correct role (success).
Exercise 3: Test with an expired token. Verify 401.
Why test both 401 and 403 separately?