Skip to main content

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
Analogy:
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

Decision Framework — Which Auth Strategy?

RBAC vs ABAC vs PBAC

For most NestJS applications, RBAC is sufficient. Start with roles, add permissions if you need finer granularity, and only move to ABAC when your access rules depend on resource attributes (like “owner can edit their own posts but not others”).

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.
Common Mistake: Using 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

  1. User logs in with credentials
  2. Server validates credentials
  3. Server creates and signs a JWT
  4. Client stores the JWT
  5. Client sends JWT in Authorization header
  6. Server verifies JWT and extracts user info
Diagram: JWT Flow

Installing JWT Package

JWT Module Setup

Production Pattern: Use 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 a tokenVersion 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.
Practical Tip: Apply different rate limits to different endpoints. Public endpoints can be generous (100/min), but authentication endpoints should be strict (5/min). Use @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
Best Practices:
  • 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
Next Chapter: Learn about testing strategies, including unit tests, integration tests, and E2E tests.