Part 4.4 — Testing Auth Guards, JWT Strategy & Protected Routes
Authentication is a seam in your backend where many things can go wrong: missing headers, expired tokens, malformed payloads, misconfigured guards, or incorrect authorization checks.
If you don’t test auth, you’re guessing.
In this post, you’ll learn how to test authentication and authorization end-to-end in a NestJS REST backend using Jest and Supertest—without weakening security or polluting production code.
1. The Goal: Test Behavior, Not Crypto
We are not testing JWT cryptography.
That’s a library problem.
We are testing:
guards block unauthenticated requests
valid tokens allow access
invalid tokens are rejected
user identity reaches controllers
authorization rules are enforced
To do this, we need test-friendly auth wiring.
2. Typical Auth Setup (Quick Context)
You likely have something like:
JwtAuthGuard(extendsAuthGuard('jwt'))JwtStrategy(Passport strategy)@UseGuards(JwtAuthGuard)on routesreq.userpopulated from JWT payload
We’ll assume that structure and focus on testing it safely.
3. Protect a Route (Example)
@UseGuards(JwtAuthGuard)
@Get('me')
getProfile(@Req() req) {
return req.user;
}This endpoint must:
reject unauthenticated requests
return user info when authenticated
Now we test that behavior.
4. Strategy 1: Use Real JWTs in Tests (Recommended)
This gives you the highest confidence with minimal complexity.
Configure a Test JWT Secret
In .env.test:
JWT_SECRET=test-secretYour JwtStrategy should already read from env:
secretOrKey: process.env.JWT_SECRET5. Create a Helper to Generate Test Tokens
// test/utils/jwt.ts
import jwt from 'jsonwebtoken';
export function signTestToken(payload: { sub: number; email: string }) {
return jwt.sign(payload, process.env.JWT_SECRET!, {
expiresIn: '1h',
});
}This mirrors production behavior—but with a test secret.
6. Test a Protected Endpoint (Unauthorized)
it('rejects requests without token', async () => {
await request(app.getHttpServer())
.get('/users/me')
.expect(401);
});If this ever passes incorrectly, your guard is broken.
7. Test a Protected Endpoint (Authorized)
First, insert a user into the test DB:
const user = await prisma.user.create({
data: { name: 'Alice', email: 'alice@example.com' },
});Then generate a token:
const token = signTestToken({
sub: user.id,
email: user.email,
});Make the request:
const res = await request(app.getHttpServer())
.get('/users/me')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(res.body.email).toBe('alice@example.com');This verifies:
JWT is parsed
strategy runs
guard allows access
req.useris populated correctly
8. Testing Invalid Tokens
it('rejects invalid token', async () => {
await request(app.getHttpServer())
.get('/users/me')
.set('Authorization', 'Bearer invalid-token')
.expect(401);
});This ensures your strategy does not silently accept garbage.
9. Testing Authorization Logic (Ownership Rules)
Example: only the author can delete a post.
Service logic:
if (post.authorId !== userId) {
throw new ForbiddenException();
}Test it:
it('forbids deleting another user’s post', async () => {
const alice = await prisma.user.create({
data: { name: 'Alice', email: 'a@example.com' },
});
const bob = await prisma.user.create({
data: { name: 'Bob', email: 'b@example.com' },
});
const post = await prisma.post.create({
data: {
title: 'Test',
body: 'Body',
authorId: alice.id,
},
});
const token = signTestToken({ sub: bob.id, email: bob.email });
await request(app.getHttpServer())
.delete(`/posts/${post.id}`)
.set('Authorization', `Bearer ${token}`)
.expect(403);
});This test guards real business rules—not just auth.
10. Strategy 2: Override Guards (When Needed)
Sometimes you want to test controller logic without auth.
NestJS allows overriding guards in tests:
.overrideGuard(JwtAuthGuard)
.useValue({ canActivate: () => true })Example:
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
})
.overrideGuard(JwtAuthGuard)
.useValue({
canActivate: (ctx) => {
const req = ctx.switchToHttp().getRequest();
req.user = { id: 1, email: 'test@example.com' };
return true;
},
})
.compile();Use this sparingly.
Prefer real JWT tests for auth correctness.
11. Common Auth Testing Pitfalls
Avoid these mistakes:
❌ mocking JWT everywhere (false confidence)
❌ testing only “happy path”
❌ skipping authorization tests
❌ sharing tokens across tests
❌ disabling guards globally in tests
Auth is a security boundary—treat it seriously.
12. What to Test (Checklist)
You should have tests for:
unauthenticated access → 401
authenticated access → 200
invalid token → 401
expired token → 401
forbidden action → 403
correct
req.userinjectionownership enforcement
If these pass, your auth layer is solid.
13. Summary
You now know how to:
test JWT-protected routes with Supertest
generate real test tokens safely
validate guards and strategies
test authorization rules
override guards when appropriate
avoid common auth testing traps
Related
Part 4.3 — Integration Testing with Supertest: REST Endpoints
Integration tests verify the entire request pipeline—controllers, DTOs, pipes, filters, interceptors, and database behavior. This post shows how to test your REST API like a professional.
Part 4.2 — Writing Unit Tests for Services, DTOs & Pipes
In this post, you’ll learn how to isolate and test NestJS services, validate DTOs programmatically, and verify custom pipes with clear testing strategies.
Part 4.1 — Setting Up Jest, Test Environment & Prisma Test DB
Before you write your first test, you need a stable foundation: Jest configuration, test modules, a sandboxed PostgreSQL database, and a clean Prisma workflow for reproducible tests.
Comments