NestJS Authentication with JWT, Refresh Tokens, and Role-Based Access Control (RBAC): The Complete Guide (2026)

Learn how to build a secure authentication and authorization system in NestJS using JWT access tokens, refresh tokens, and Role-Based Access Control (RBAC). This guide covers architecture, best practices, common mistakes, and production-ready examples.


Introduction

Authentication and authorization are two of the most critical aspects of any backend application. Whether you're building a SaaS platform, an e-commerce system, or an internal API, securing user access is essential.

In this guide, you'll learn how to implement a production-ready authentication system in NestJS using:

  • JWT Access Tokens
  • Refresh Tokens
  • Role-Based Access Control (RBAC)
  • Password hashing with bcrypt
  • Guards
  • Passport strategies
  • Custom decorators
  • Secure token storage
  • Token rotation
  • Best practices

By the end of this article, you'll have a clear understanding of how to design a secure authentication system that scales.


Authentication vs Authorization

These two concepts are often confused.

Authentication

Authentication answers the question:

Who are you?

Examples:

  • Logging in with email and password
  • Signing in with Google
  • Logging in with GitHub
  • Using an API key

Authentication verifies the user's identity.


Authorization

Authorization answers the question:

What are you allowed to do?

Examples:

  • Can the user create a product?
  • Can the user delete another user?
  • Can the user access the admin dashboard?

Authorization determines permissions after the user has been authenticated.


System Architecture

A production-ready authentication flow typically looks like this:

Client
   │
   ▼
POST /auth/login
   │
   ▼
Validate Credentials
   │
   ▼
Generate JWT Access Token
Generate Refresh Token
   │
   ▼
Return Tokens
   │
   ▼
Client Stores Tokens
   │
   ▼
Protected Request
Authorization: Bearer ACCESS_TOKEN
   │
   ▼
JwtAuthGuard
   │
   ▼
Controller

Recommended Project Structure

src/
│
├── auth/
│   ├── controllers/
│   ├── services/
│   ├── dto/
│   ├── guards/
│   ├── strategies/
│   ├── decorators/
│   ├── interfaces/
│   ├── auth.module.ts
│   └── auth.service.ts
│
├── users/
│
├── common/
│   ├── decorators/
│   ├── guards/
│   ├── filters/
│   └── interceptors/
│
├── config/
│
└── main.ts

Keeping authentication concerns isolated makes the application easier to maintain and extend.


Step 1: User Login

A user submits:

{
  "email": "john@example.com",
  "password": "password123"
}

Your service should:

  1. Find the user by email.
  2. Compare the password with the stored hash using bcrypt.
  3. Generate an access token.
  4. Generate a refresh token.
  5. Return both tokens.

Never store or compare passwords in plain text.


Password Hashing

Always hash passwords before storing them.

Example:

const hashedPassword = await bcrypt.hash(password, 12);

To verify:

await bcrypt.compare(password, hashedPassword);

A cost factor of 10–12 is a common balance between security and performance.


JWT Access Token

The access token contains lightweight user information.

Example payload:

{
  "sub": 12,
  "email": "john@example.com",
  "role": "ADMIN"
}

A short expiration time (e.g., 15 minutes) limits the impact if a token is compromised.


Refresh Token

Unlike the access token, the refresh token is used only to obtain a new access token after it expires.

A typical flow:

  1. User logs in.
  2. Receives access token (15 minutes).
  3. Receives refresh token (7–30 days).
  4. When the access token expires, the client sends the refresh token.
  5. Server validates the refresh token.
  6. Server issues a new access token (and often a new refresh token).

This keeps users signed in without requiring frequent logins.


Why Use Refresh Tokens?

Without refresh tokens:

  • Users must log in every time the access token expires.

With refresh tokens:

  • Sessions remain seamless.
  • Short-lived access tokens improve security.
  • Stolen access tokens become less useful because they expire quickly.

Store Refresh Tokens Securely

Do not store refresh tokens in plain text in your database.

Instead:

  1. Hash the refresh token before storing it.
  2. Compare the hash when the token is presented.

This reduces the risk if your database is compromised.


JWT Strategy

NestJS integrates well with Passport.

A JWT strategy should:

  • Extract the bearer token.
  • Verify the signature.
  • Validate expiration.
  • Attach the user payload to the request.

Once validated, controllers can access the authenticated user directly.


Protecting Routes with Guards

NestJS guards make it easy to protect endpoints.

Example:

@UseGuards(JwtAuthGuard)
@Get("profile")
getProfile() {
    ...
}

Only authenticated users can access this endpoint.


Getting the Current User

A custom decorator simplifies access to the authenticated user.

Example:

@Get("profile")
getProfile(@CurrentUser() user) {
    return user;
}

This keeps controller methods clean and avoids repeatedly accessing request.user.


Role-Based Access Control (RBAC)

Authentication identifies the user.

RBAC determines what they can do.

Example roles:

ADMIN
MANAGER
EDITOR
USER

An endpoint can require a specific role:

@Roles("ADMIN")

The guard checks whether the authenticated user has the required role before allowing access.


Typical RBAC Examples

Admin

  • Manage users
  • Delete content
  • View analytics

Manager

  • Manage team resources
  • Approve requests

User

  • Update own profile
  • View personal data

Each role should have only the permissions it truly needs.


Combining Guards

A common pattern is to combine authentication and authorization:

@UseGuards(JwtAuthGuard, RolesGuard)

The request must:

  1. Be authenticated.
  2. Have the required role.

This layered approach keeps authorization logic organized.


Token Rotation

A production system should rotate refresh tokens.

Flow:

  1. Client sends refresh token.

  2. Server validates it.

  3. Server issues:

    • New access token
    • New refresh token
  4. Old refresh token is invalidated.

Benefits:

  • Prevents replay attacks.
  • Reduces the impact of stolen refresh tokens.
  • Supports secure logout from other devices.

Logging Out

Logging out should do more than remove tokens from the client.

A secure logout process:

  1. Delete or invalidate the stored refresh token.
  2. Clear authentication cookies (if used).
  3. Remove tokens from the client.

This prevents the old refresh token from being reused.


Security Best Practices

  • Use HTTPS in production.
  • Store secrets in environment variables.
  • Hash passwords with bcrypt.
  • Hash refresh tokens before storing them.
  • Keep access tokens short-lived.
  • Rotate refresh tokens.
  • Validate all user input.
  • Rate-limit login endpoints.
  • Lock accounts after repeated failed login attempts.
  • Log authentication events for auditing.
  • Use strong, unique JWT secrets.

Common Mistakes

Storing plain-text passwords

Always hash passwords.

Long-lived access tokens

Short expirations reduce risk.

Embedding sensitive data in JWTs

Keep JWT payloads minimal.

Trusting client-side role checks

Authorization must always be enforced on the server.

Forgetting to revoke refresh tokens

Always invalidate refresh tokens during logout or rotation.


Frequently Asked Questions

Why use both access and refresh tokens?

Access tokens are short-lived for security. Refresh tokens allow users to stay signed in without re-entering credentials.


Should refresh tokens be stored in cookies?

For browser-based applications, HTTP-only, Secure cookies are generally a good choice because they are not accessible via JavaScript and help mitigate XSS risks. For mobile or native apps, use the platform's secure storage mechanisms.


Can I store user permissions inside the JWT?

You can include roles or lightweight permission data, but avoid putting large or frequently changing authorization data in the token. If permissions change often, consider checking them against your database or a cache.


Should I use sessions instead of JWT?

It depends on your application:

  • Sessions are simple and work well for traditional server-rendered applications.
  • JWTs are often preferred for stateless APIs, microservices, and distributed systems.

Choose the approach that best fits your architecture.


Is RBAC enough?

RBAC works well for many applications, but some systems need more granular control. In those cases, you may combine RBAC with permission-based or attribute-based authorization.


Final Thoughts

A secure authentication system is more than issuing JWTs. It combines strong password handling, short-lived access tokens, refresh token rotation, and robust authorization to protect your application.

By following the practices in this guide, you can build a NestJS authentication system that is maintainable, scalable, and suitable for production workloads.

Whether you're creating a startup MVP or an enterprise platform, investing in a solid authentication foundation will save time, improve security, and make future features easier to build.

Comments · 0

Sign in to join the conversation.

Be the first to comment.