Chapter 6: Authentication & Authorization
Securing your application is critical. This chapter covers authentication (verifying identity), authorization (controlling access), JWT, OAuth, RBAC, password hashing, guards, and best practices for building secure APIs in NestJS. We’ll walk through practical examples and explain the “why” behind each step.
6.1 Authentication vs Authorization
Understanding the difference between authentication and authorization is fundamental to building secure applications.Authentication
Authentication answers: “Who are you?”- Verifies user identity
- Confirms credentials (username/password, token, etc.)
- Establishes user session
- Examples: Login, token validation
Authorization
Authorization answers: “What are you allowed to do?”- Controls access to resources
- Enforces permissions and roles
- Determines what actions are allowed
- Examples: Admin-only routes, resource ownership checks
Think of authentication as showing your employee badge at the office entrance (proving who you are). Authorization is the keycard system on individual floors — just because you work at the company does not mean you can enter the server room. In NestJS, JWT guards handle authentication (validating the badge), while Roles guards handle authorization (checking which rooms your badge unlocks). A common mistake is conflating the two: returning 401 (Unauthorized) when you mean 403 (Forbidden). 401 means “I do not know who you are,” 403 means “I know who you are, but you are not allowed here.”
The Relationship
Authentication Strategy Comparison
| Strategy | Stateless? | Best For | Token Storage | Revocation | Scalability |
|---|---|---|---|---|---|
| JWT (Access Token) | Yes | APIs, SPAs, mobile | Client (memory/cookie) | Hard (need blocklist) | Excellent (no server state) |
| JWT + Refresh Token | Hybrid | Production APIs | Access in memory, refresh in httpOnly cookie | Moderate (revoke refresh) | Excellent |
| Session (Cookie) | No | Server-rendered apps | Server (Redis/DB) | Easy (delete session) | Moderate (session store is bottleneck) |
| OAuth2 (Social) | Depends | ”Login with Google/GitHub” | Delegated to provider | Moderate (revoke at provider) | Excellent |
| API Key | Yes | Service-to-service, public APIs | Client config | Easy (delete key from DB) | Excellent |
| mTLS | Yes | Service mesh, zero-trust | Certificate | Moderate (CRL/OCSP) | Excellent |
RBAC vs ABAC vs PBAC
| Model | How Access Is Decided | Complexity | Best For |
|---|---|---|---|
| RBAC (Role-Based) | User’s role matches required role | Low | Most applications (“admin can delete”) |
| ABAC (Attribute-Based) | User/resource/environment attributes evaluated | High | Complex policies (“EU users can only access EU data”) |
| PBAC (Policy-Based) | Policies evaluated at runtime (like ABAC but externalized) | Very High | Enterprises with dynamic compliance rules |
6.2 Password Hashing
Before implementing authentication, you must understand password security. Never store passwords in plain text.Why Hash Passwords?
- Security: Even if database is compromised, passwords are protected
- Privacy: Developers can’t see user passwords
- Compliance: Required by security standards (GDPR, PCI-DSS)
Using bcrypt
bcrypt is the industry standard for password hashing. It is intentionally slow — that is a feature, not a bug. Each hash takes ~100ms, which makes brute-force attacks impractical (an attacker trying 1 billion passwords would need ~3 years). The “salt rounds” parameter controls this slowness: each increment roughly doubles the time.bcryptjs (pure JavaScript) instead of bcrypt (native C++ bindings). The pure JS version is 3-5x slower and can block the Node.js event loop under high load. Use the native bcrypt package unless you cannot compile native modules in your environment.
Password Validation
6.3 JWT Authentication
JSON Web Tokens (JWT) are a popular way to implement stateless authentication in APIs. JWTs are signed tokens that clients send with each request to prove their identity.How JWT Works
- User logs in with credentials
- Server validates credentials
- Server creates and signs a JWT
- Client stores the JWT
- Client sends JWT in
Authorizationheader - Server verifies JWT and extracts user info
Installing JWT Package
JWT Module Setup
JwtModule.registerAsync() with ConfigService to load the secret from environment variables safely, and validate that it is set before the app starts:
Auth Service
JWT Strategy
Local Strategy (Username/Password)
Auth Controller
JWT Auth Guard
Local Auth Guard
Using JWT Guard
6.4 Authorization: Roles & Permissions
Role-Based Access Control (RBAC) restricts access based on user roles. This lets you control who can do what in your app.Roles Decorator
Roles Guard
Using Roles
Permission-Based Authorization
For more granular control, use permissions:6.5 OAuth2 & Social Login
NestJS supports OAuth2 via Passport strategies. This lets users log in with Google, Facebook, GitHub, etc.Installing Passport OAuth
Google Strategy
Google Auth Controller
6.6 Refresh Tokens
Refresh tokens allow users to get new access tokens without re-authenticating.Implementing Refresh Tokens
6.7 Multi-Factor Authentication (MFA)
Enhance security by requiring a second factor (e.g., OTP, email code) after password login.MFA Service
6.8 Auth Edge Cases
Edge Case 1: JWT token rotation and race conditions When a client’s access token expires and it sends a refresh request, what happens if two simultaneous requests both try to refresh at the same time? Both carry the same refresh token, but after the first request succeeds and rotates the token, the second request has a stale refresh token. Without handling this, the second request fails and the user gets logged out. Solutions: (1) Allow a short grace period where the old refresh token is still valid; (2) Use a token family approach where any reuse of an old token invalidates the entire family (detecting token theft). Edge Case 2: The “log out everywhere” problem with JWT JWTs are stateless — once issued, they are valid until expiration. If a user changes their password and wants to invalidate all existing sessions, you cannot “revoke” a JWT. Solutions: (1) Keep a Redis blocklist of revoked JWTs (checked on every request via a guard); (2) Store atokenVersion on the user record and increment it on password change — the JWT strategy’s validate() method checks if the token’s version matches the database; (3) Use short-lived access tokens (5-15 minutes) so revocation is eventually consistent.
Edge Case 3: OAuth provider returns inconsistent email
Some OAuth providers (notably Apple Sign In) can return a “private relay” email that is different every time the user signs in from a different app. You cannot rely solely on email for user matching. Store the provider’s unique user ID (sub claim) alongside the email, and match on the provider ID first.
Edge Case 4: Guard execution with GraphQL
GraphQL resolvers do not have the same ExecutionContext as REST controllers. When using @UseGuards(JwtAuthGuard) on a resolver, the guard receives a GraphQL context, not an HTTP request. The JwtStrategy expects ExtractJwt.fromAuthHeaderAsBearerToken(), which reads from the HTTP request. You need to configure the GraphQL module to pass the HTTP request through: GraphQLModule.forRoot({ context: ({ req }) => ({ req }) }).
Edge Case 5: Rate limiting with distributed instances
The @nestjs/throttler module stores rate limit counts in memory by default. If you run 3 instances behind a load balancer, each instance has its own counter — an attacker can make 30 requests (10 per instance) before any instance blocks them. Use the ThrottlerStorageRedisService to share counts across instances.
6.8 Security Best Practices
Following security best practices protects your application and users.Password Security
- Always hash passwords (use bcrypt with salt rounds >= 10)
- Enforce strong password policies
- Never log or return passwords
- Use password reset tokens (time-limited)
JWT Security
- Use strong, random secrets
- Set appropriate expiration times
- Use HTTPS in production
- Store secrets in environment variables
- Consider refresh tokens for long sessions
HTTP Security Headers
Rate Limiting
Rate limiting is your first defense against brute-force login attacks and API abuse. Without it, an attacker can try thousands of passwords per second against your login endpoint.@Throttle() decorator on individual routes to override the global limit:
CORS Configuration
Input Validation
Always validate and sanitize input:Security Checklist
- Always hash passwords
- Use HTTPS in production
- Store secrets in environment variables
- Set secure HTTP headers (helmet)
- Limit login attempts (prevent brute force)
- Validate and sanitize all input
- Keep dependencies up to date
- Use CORS to restrict allowed origins
- Log authentication events for auditing
- Implement rate limiting
- Use refresh tokens for long sessions
- Consider MFA for sensitive applications
6.9 Real-World Example: Complete Auth Module
Here’s a complete authentication module structure:6.10 Summary
You’ve learned how to secure your NestJS APIs: Key Concepts:- Authentication: Verifying user identity
- Authorization: Controlling access to resources
- JWT: Stateless token-based authentication
- OAuth2: Social login integration
- RBAC: Role-based access control
- Password Hashing: Secure password storage
- MFA: Multi-factor authentication
- Always hash passwords
- Use HTTPS in production
- Store secrets in environment variables
- Set secure HTTP headers
- Implement rate limiting
- Validate all input
- Use refresh tokens
- Log authentication events