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!
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.
Before we dive in, ensure you have the following:
First, we set up a basic 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
Install the necessary packages:
$ npm install passport @nestjs/passport passport-jwt jwks-rsa
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
Now that we've set up Auth0, let's write the code to make use of it.
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!
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 {}
PassportModule
: We import the PassportModule
to integrate Passport.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.
AuthModule
exports PassportModule
so that we can use it elsewhere in the application.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;
}
}
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 of Bearer <token>
.
audience
and issuer
: 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 attached to the request.Now that we have our authentication logic in place, let’s secure our application routes.
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 {}
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' };
}
}
@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.
Before we can test, we'll need to first register a client application with Auth0.
Follow these steps to register a client with Auth0:
Single Page Web Applications
as the application type.Domain
, Client ID
, and Client 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.
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.
Ensure you have Postman installed. If not, download it from the Postman website.
Open Postman:
Go to the Authorization Tab:
Configure the Token Details:
https://YOUR_AUTH0_DOMAIN/oauth/token
(replace YOUR_AUTH0_DOMAIN
with your actual Auth0 domain).https://YOUR_AUTH0_DOMAIN/authorize
AUTH0_AUDIENCE
in the .env
file).ℹ️ 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:
Use the Token:
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' }
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.