Secure Your NestJS App: Implementing Authentication with Auth0
With NestJS, a powerful framework for building scalable server-side applications, and Auth0, a leading provider of authentication services, you can easily implement robust authentication mechanisms.
With NestJS, a powerful framework for building scalable server-side applications, and Auth0, a leading provider of authentication services, you can easily implement robust authentication mechanisms.
This guide will walk you through securing your NestJS app using Auth0.
Let's get into it!
Introduction to Auth0
Auth0 is a flexible, drop-in solution to add authentication and authorization services to your applications.
Besides the standard username-password system, it supports various identity providers, including social login providers like Google and GitHub, and enterprise identity providers such as Microsoft Active Directory.
Auth0 simplifies the process of securing your application by handling user authentication, session management, and more for you, so you can focus on other parts of your app.
Prerequisites
Before we dive in, ensure you have the following:
Setting Up the Project
First, we set up a basic NestJS project.
1. Creating a New NestJS Project
Start by creating a new NestJS project:
$ npm i -g @nestjs/cli
$ nest new my-nestjs-app
Navigate into the project directory:
$ cd my-nestjs-app
2. Install Dependencies
Install the necessary packages:
$ npm install passport @nestjs/passport passport-jwt jwks-rsa
Configuring Auth0
Now let's set up Auth0.
First, create a free Auth0 account if you don't already have one.
After creating your account, set up an Auth Tenant. This tenant acts as a container for your identity service configuration and user data, isolated from other Auth0 customers, similar to individual apartments in a building.
Next, create an Auth0 API within your tenant. This API will handle authentication and authorization requests from your applications. Navigate to the APIs section of the Auth0 Dashboard and click 'Create API.'
Fill out the form as follows:
Name: NestJS API
Identifier: https://nestjs.demo.com
Signing Algorithm: RS256.
Auth0 allows you to create multiple APIs, each with a unique identifier (often structured as a URL) for easy differentiation. Note that Auth0 does not call these URLs.
After creating the API, you will see your Auth0 API details. Select the Quick Start tab to find guidance on setting up various backend technologies. Choose Node.js from the code box.
You will need two values from the code snippet in the box to configure Passport: an Auth0 Issuer URL
and an Auth0 Audience
.
These values should be stored in a .env
(environment variables) file at the root of your project:
AUTH0_ISSUER_URL=https://<AUTH0-TENANT-NAME>.auth0.com/
AUTH0_AUDIENCE=https://nestjs.demo.com
Implementing Authentication
Now that we've set up Auth0, let's write the code to make use of it.
Authentication Flow
So, here's how our Authentication flow will work:
- A User attempts a login from the frontend client.
- Our NestJS backend receives the request and forwards it to Auth0 for authentication (Auth0 handles user authentication and manages users for you).
- Auth0 issues a JWT to our client which it can then check for on any subsequent requests to grant access to protected resources.
Let's get into it!
1. Creating the Auth Module
Create a new Auth
module. This module holds our core authentication logic:
$ nest g module auth
In auth/auth.module.ts
, set up the module as follows:
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [PassportModule.register({ defaultStrategy: 'jwt' })],
providers: [JwtStrategy],
exports: [PassportModule],
})
export class AuthModule {}
What's going on?
PassportModule
: We import thePassportModule
to integratePassport.js
, which is a popular middleware for handling various authentication strategies. In this case, we configure it to use JWT as the default authentication strategy.JwtStrategy
: This is the "strategy" that handles JWT validation. We will define it next, and it will be used to authenticate requests that include a valid JWT.- The
AuthModule
exportsPassportModule
so that we can use it elsewhere in the application.
2. Implementing the JwtStrategy
In NestJS, strategies are a core concept used to handle different types of authentication. A strategy defines how a specific kind of authentication is handled, such as verifying a JWT, handling OAuth tokens, or managing session cookies.
The JwtStrategy
here is used to validate JSON Web Tokens (JWTs) received from the client, ensuring that only authorized requests can access protected resources.
Create a new strategy file auth/jwt.strategy.ts
:
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { passportJwtSecret } from 'jwks-rsa';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`,
}),
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
audience: process.env.AUTH0_AUDIENCE,
issuer: `${process.env.AUTH0_DOMAIN}/`,
algorithms: ['RS256'],
});
}
validate(payload: unknown): unknown {
return payload;
}
}
What's going on?
secretOrKeyProvider
: This is a function from jwks-rsa that fetches the public key from Auth0's JWKS (JSON Web Key Set) endpoint. This key is used to validate the JWT's signature.jwtFromRequest
: This tells Passport how to extract the JWT from the incoming request. We use ExtractJwt.fromAuthHeaderAsBearerToken(), which looks for the token in the Authorization header in the form ofBearer <token>
.audience
andissuer
: These are set to match the values that Auth0 uses to issue the tokens. They help ensure that the token is intended for our application and was issued by the correct source.validate
: This method is called after the JWT is validated. In this case, we simply return the payload, which will now contain information about the authenticated user, and will be the attached to the request.
Securing Routes
Now that we have our authentication logic in place, let’s secure our application routes.
1. Updating the App Module
We need to import the AuthModule
into our main application module so that we can use it throughout the app.
In app.module.ts
, import the AuthModule
like this to make the authentication functionality available throughout our app:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
@Module({
imports: [AuthModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
2. Securing the Controller
Now, let’s protect the routes in the controller (which simply handles request routing) using the AuthGuard
, which is essentially a gatekeeper for your routes.
In app.controller.ts
, update the controller as follows:
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { AuthGuard } from '@nestjs/passport';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@UseGuards(AuthGuard('jwt'))
@Get('protected')
getProtectedResource() {
return { message: 'This is a protected resource' };
}
}
What's going on?
@UseGuards(AuthGuard('jwt'))
: This decorator applies the JWT authentication guard to the getProtectedResource route. It ensures that any request to this route must include a valid JWT. If the token is invalid or missing, the request will be rejected with an unauthorized error.AuthGuard('jwt')
: This specifies that the JwtStrategy (the 'jwt' strategy) should be used for authentication.getProtectedResource
: This route will only be accessible to authenticated users. If the JWT is valid, the user will be able to access this resource.
We’ve now set up JWT-based authentication and protected certain routes to ensure only authorized users can access them.
So, let's test it.
Testing Authentication
Before we can test, we'll need to first register a client application with Auth0.
1. Register Client with Auth0
Follow these steps to register a client with Auth0:
-
Open the Auth0 Applications section of the Auth0 Dashboard.
-
Click on the Create Application button.
-
Provide a Name such as "Postman".
-
Choose
Single Page Web Applications
as the application type. -
Click on the Create button.
-
Finally, note down your
Domain
,Client ID
, andClient Secret
. These will be used later.
đź’ˇ Tip
Under the Authentication tab in the left sidebar of your Auth0 dashboard, you can manage the different ways users can authenticate.
For example, you can easily set up social login to allow users authenticate with their social profiles rather than just a username and password.
2. Test with Postman
To test authentication, you'll need a frontend client application. Here, we don't have one built so we'll be using Postman—an API testing platform—instead.
Step 1: Set Up Postman
Ensure you have Postman installed. If not, download it from the Postman website.
Step 2: Configure Postman to Get a Token from Auth0
-
Open Postman:
- Launch Postman and create a new request.
-
Go to the Authorization Tab:
- In your request, navigate to the "Authorization" tab.
- From the "Type" dropdown, select "OAuth 2.0".
-
Configure the Token Details:
- Click on the "Get New Access Token" button.
- Fill in the following details:
- Token Name: Give your token a name (e.g., Auth0 Token).
- Grant Type: Choose "Authorization Code".
- Access Token URL: Use
https://YOUR_AUTH0_DOMAIN/oauth/token
(replaceYOUR_AUTH0_DOMAIN
with your actual Auth0 domain). - Auth URL: Use
https://YOUR_AUTH0_DOMAIN/authorize
- Client ID: Enter the Client ID from your Auth0 application settings that you noted down earlier.
- Client Secret: Enter the Client Secret from your Auth0 application settings.
- Audience: Use the audience specified in your API settings (your
AUTH0_AUDIENCE
in the.env
file). - Client Authentication: Select "Send as Basic Auth Header".
ℹ️ Note You need to add the URL in the `Callback URL` field to the "Allowed Callback URLs" field of your Auth0 Application's setting.
-
Get the Token:
- Click "Request Token" to generate a new access token.
- Postman will send a request to Auth0 and fetch a token after you log in. Once successful, you'll see your token listed under "Available Tokens".
-
Use the Token:
- Click "Use Token" to attach it to your request.
- Postman will automatically add the token to the Authorization header as a Bearer token.
Step 3: Test Your API Endpoints
Make a request to the /protected
http:localhost:3000/protected from Postman.
If everything is set up correctly, you'll receive a response indicating successful access to the protected resource (in this case a response containing a This is a protected resource
message ).
{ message: 'This is a protected resource' }
Conclusion
And that's it! You have successfully secured your NestJS application using Auth0 and tested your secure API with Postman.
With these steps, you've built the groundwork for a robust authentication system. You may now explore additional Auth0 features like Role-Based Access Control (RBAC) to further enhance security.