Starting from this chapter?

Clone the application repo and check out the creating-endpoints-data-validation branch:

git clone git@github.com:auth0-blog/wab-menu-api-nestjs.git \
nest-restaurant-api \
--branch creating-endpoints-data-validation

Make the project folder your current directory:

cd nest-restaurant-api

Then, install the project dependencies:

npm i

Finally, create a .env hidden file:

touch .env

Populate .env with this:

PORT=7000

Setting Up an Auth0 Application

One of the requirements for this project is that only authenticated users can write records to the store. To easily and securely achieve that, you are going to use Auth0 to manage your application's user credentials.

First, you have to create a free Auth0 account if you don't have one yet.

After creating your account, head to the APIs section in the Auth0 Dashboard, and hit the Create API button.

Then, in the form that Auth0 shows:

  • Add a Name to your API. Something like Menu API, for example.

  • Set its Identifier to https://menu-api.demo.com.

  • Leave the signing algorithm as RS256 as it's the best option from a security standpoint.

Auth0 Dashboard new API form

Identifiers are unique strings that help us differentiate between your different API's. We recommend using URL's as they facilitate creating unique identifiers in a predictable fashion; however, these URL's will never be called by Auth0.

With these values in place, hit the Create button.

Now, click on the Quick Start tab. This page presents instructions on how to set up different APIs. From the code box, choose Node.js. Keep this window open as you'll be using the values from the code snippet up next.

Menu API Node.js Quickstart

Creating an Authentication Module

As mentioned earlier, you'll use Auth0 to manage users and credentials. Additionally, you'll use the Passport authentication library to abstract the nuances of implementing authentication.

The authentication flow is as follows:

  • Users will start by authenticating with a username and password managed by Auth0.

  • Once authenticated, the Auth0 authentication server will issue a JWT (JSON Web Token) to the client called an access token.

  • Then, the access token can be sent as a bearer token in an authorization header to prove authentication to the server on requests made to protected endpoints.

You will create guards to protect endpoints that write to the store. These guards will reject any request that doesn't contain a valid access token. All of your authentication logic will be bundled in a module named AuthModule.

As before, use the NestJS CLI to create your new authentication module:

npx nest generate module auth

Just like that, NestJS creates an auth directory under the src directory and places an auth.module.ts file within it that defines the basic structure for AuthModule. If you inspect the AppModule definition, you'll see that NestJS has added AuthModule to its imports array.

// src/app.module.ts

import { Module } from '@nestjs/common';
import { ItemsModule } from './items/items.module';
import { AuthModule } from './auth/auth.module';

@Module({
  imports: [ItemsModule, AuthModule],
  controllers: [],
  providers: [],
})
export class AppModule {}

This establishes a module dependency and lets you use any of the exposed functionality of AuthModule throughout your application.

You are now ready to set up Passport and get Auth0 to work for you!

Using Passport with NestJS

Passport.js offers different authentication mechanisms, known as strategies, to cater to the unique authentication requirements each application has. Strategies are packaged as individual modules and you can choose which strategies to employ, without creating unnecessary dependencies. The @nestjs/passport module wraps these strategies into idiomatic NestJS constructs.

For your application, you'll create a JSON Web Token (JWT) Passport strategy that you'll bundle within AuthModule. To start, install the following dependencies:

npm i passport @nestjs/passport passport-jwt jwks-rsa

Here's a breakdown of what these packages do:

  • passport: Express-compatible authentication middleware for Node.js.

  • @nestjs/passport: The Passport utilities module for Nest.

  • passport-jwt: Passport strategy for authenticating with a JSON Web Token (JWT).

  • jwks-rsa: A library to retrieve RSA signing keys from a JWKS (JSON Web Key Set) endpoint.

In reality, Passport acts more like an authentication framework than a library. It abstracts the authentication process into a series of standard steps that are customized based on the authentication strategy being implemented. Passport needs two key elements to process authentication:

  • Configuration options for the strategy.

  • A verify callback, which has the purpose of finding the user that possesses a set of credentials. When Passport authenticates a request, it parses the credentials or any other authentication information contained in the request. It then invokes the verify callback with the authentication data as arguments, such as an access token, that your application can then consume.

Let's see this in action.

To configure a JwtStrategy Passport strategy, you need two values from Auth0: an Auth0 domain and an Auth0 audience. You'll put them in your .env file that will be loaded by dotenv.config() when the strategy is initialized:

PORT=7000
AUTH0_DOMAIN=<Your Auth0 domain>
AUTH0_AUDIENCE=<Your Auth0 audience>

Ensure that you replace the placeholder values with the corresponding Auth0 values from the Node.js quickstart code that you saw earlier.

The Auth0 domain is the value of the issuer property without the forward slash at the end:

https://<Tenant Name>.auth0.com

The Auth0 audience is the value of the audience property.

Do not include the quotes as part of the .env variable value. Only include the string within the quotes.

Next, create a jwt.strategy.ts file under the src/auth directory:

touch src/auth/jwt.strategy.ts

Update this file as follows:

// src/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';
import * as dotenv from 'dotenv';

dotenv.config();

@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: any) {
    return payload;
  }
}

Using the @nestjs/passport module, a Passport strategy is created by extending the abstract class returned by the PassportStrategy function. This function takes as argument the Strategy that you want to implement, which in this case is the JWT strategy imported from passport-jwt.

The strategy options passed through the super() call within the constructor let you parse JWT-formatted access tokens and configure your API to accept RS256 signed tokens.

With the NestJS wrapper applied, the verify callback becomes the validate() method. In this case, the implementation of validate() is very simple because Auth0 handles all of the user authentication tasks for you in the server. When this method is called, Auth0 has already determined the identity of the user that is logging in to your application and passes data about that user within the payload object. This payload is then attached to the request object that any of the participants of the request-response cycle can access, such as your router handlers and any middleware.

The next step is to configure Passport with JwtStrategy and to register it with AuthModule. Open src/auth/auth.module.ts and update it 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 {}

You import the PassportModule, which is a NestJS wrapper on Passport, and register jwt as its default strategy.

You have the mechanics in place to receive an access token and verify if a user is authenticated or not. The next step is to create guards that will block unauthenticated requests to protected endpoints using the @UseGuards() decorator.

Creating Endpoint Guards with NestJS

Update src/items/items.controller.ts as follows:

import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Post,
  Put,
  UseGuards,
} from '@nestjs/common';
import { ItemsService } from './items.service';
import { Items } from '../items';
import { Item } from '../item';
import { AuthGuard } from '@nestjs/passport';

@Controller('items')
export class ItemsController {
  constructor(private readonly itemsService: ItemsService) {}

  @Get()
  async findAll(): Promise<Items> {
    return this.itemsService.findAll();
  }

  @Get(':id')
  async find(@Param('id') id: number): Promise<Item> {
    return this.itemsService.find(id);
  }

  @UseGuards(AuthGuard('jwt'))
  @Post()
  create(@Body('item') item: Item) {
    this.itemsService.create(item);
  }

  @UseGuards(AuthGuard('jwt'))
  @Put()
  update(@Body('item') item: Item) {
    this.itemsService.update(item);
  }

  @UseGuards(AuthGuard('jwt'))
  @Delete(':id')
  delete(@Param('id') id: number) {
    this.itemsService.delete(id);
  }
}

Let's look at the POST items/ endpoint:

@UseGuards(AuthGuard('jwt'))
@Post()
create(@Body('item') item: Item) {
  this.itemsService.create(item);
}

With @UseGuards(AuthGuard('jwt')), you are using an AuthGuard that @nestjs/passport automatically provisioned for your AuthModule when you configured the passport-jwt module in your Passport strategy, JwtStrategy. The @UseGuards() decorator references this guard by its default name, jwt, which matches the value of the defaultStrategy you registered for the PassportModule within the AuthModule definition.

When you make a request to the POST items/ protected endpoint, the guard automatically invokes your JwtStrategy configuration logic and blocks the request if the JWT is not valid or it's absent.

Try it out:

curl -X POST -H 'Content-Type: application/json' -d '{
  "item": {
    "name": "Salad",
    "price": 4.99,
    "description": "Fresh",
    "image": "https://cdn.auth0.com/blog/whatabyte/salad-sm.png"
  }
}' http://localhost:7000/items -i

It won't work. You'll get a 401 Unauthorized response indicating that the request was rejected due to not having valid authentication credentials required by the target resource, the POST items/ endpoint.

However, making a request to the GET items/ endpoint works:

curl http://localhost:7000/items -i

To test the authentication feature of your application you'll need a valid access token. A client, such as a Single-Page Application (SPA), would get the access token by performing a log in and then passing the access token in an authorization header to your API. You don't have a client built yet, but you can use this Demo Client: WHATABYTE Dashboard.

Creating an Auth0 Client Application

WHATABYTE is a fictional restaurant that uses a dashboard to manage its menu. It can consume the API that you have created so far. To configure this client, you need to create an Auth0 Single Page Application in the Auth0 dashboard. That will give you the Auth0 Domain and Auth0 Client ID, which will allow this application to talk to the Auth0 authentication server and get access tokens for your logged in users.

The process of creating an Auth0 client application is quite easy:

  • Open the Auth0 Applications section of the Auth0 Dashboard.

  • Click on the Create Application button.

  • Provide a Name value such as WAB Dashboard.

  • Choose Single Page Web Applications as the application type.

  • Click on the Create button.

On the application page that loads, click on the Settings tab. Use the Auth0 values present there to fill the missing values in Auth0 Demo Settings form of the WAB Dashboard client, namely Auth0 Domain and Auth0 Client ID.

WHATABYTE Dashboard demo settings form

Click the Save button below the form. The WAB Dashboard Home view should load. The WAB Dashboard is a client to your NestJS server application. To test this connection, click on the Menu tab. You will see the following error:

Network Error. Unable to retrieve menu information.

If you open the browser developer console, you'll notice that a CORS error has occurred. However, fixing this is easy with NestJS.

Enabling CORS in NestJS

Open src/main.ts and update it as follows:

// src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as dotenv from 'dotenv';
import { ValidationPipe } from '@nestjs/common';

dotenv.config();

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.enableCors();
  app.useGlobalPipes(
    new ValidationPipe({
      disableErrorMessages: true,
    }),
  );

  await app.listen(process.env.PORT);
}
bootstrap();

You call the enableCors() method of your app instance to allow resources to be requested from another domain.

Head back to the WAB Dashboard Menu View and refresh it. This time around, the three items available in your NestJS store load up and the CORS error disappears from the developer console.

You can't sign into the WAB Dashboard just yet as your Auth0 client application needs to be configured to accept authentication requests from the WAB Dashboard client application. You'll do that next.

Connecting a Client Application With Auth0

Head back to the Settings tab of your Auth0 client application. Update the following setting fields:

Allowed Callback URLs

Use the value of Auth0 Callback URL from the Auth0 Demo Settings form, https://dashboard.whatabyte.now.sh/home.

After a user authenticates, Auth0 will only call back to any of the URLs listed there. You can specify multiple valid URLs by comma-separating them (typically to handle different environments like QA or testing). Make sure to specify the protocol, http:// or https://, otherwise the callback may fail in some cases.

Allowed Web Origins

Use https://dashboard.whatabyte.now.sh.

This field holds a comma-separated list of allowed origins for use with web message response mode, which makes it possible to log in using a pop-up, as you'll soon see in the next section.

Allowed Logout URLs

Use https://dashboard.whatabyte.now.sh/home.

This field holds a set of URLs that are valid to redirect to after a user logs out of your application. The demo client has been configured to use the provided value for redirecting users.

Once these values are in place, scroll to the bottom and click on the Save Changes button.

Signing In

Head back to the WAB Dashboard and click on the Sign In button. Since this may be the first user you are adding to Auth0, go ahead and click on the Sign Up tab in the pop-up that comes up and provide an email and password to register your new user.

Auth0 sign-up form

Once signed in, the user interface changes:

  • The Sign In button becomes a Sign Out button

  • A user tab is now displayed below the Sign Out button.

Click on the user tab and you'll see a custom page with your email as the title.

The WAB Dashboard is built to cater to two types of users: regular users and users with a menu-admin role. This role allows the user to create, update, and delete menu items in the WAB Dashboard.

In the next section you are going to create the menu-admin role, associate permissions with it, and assign it to a new user that you'll create through the Auth0 Dashboard. This privileged user will be able to unlock the admin features of the WAB Dashboard.

Checkpoint

Add your current project files to the repository:

git add .

Commit the file bundle as follows:

git commit -m "Secure API with Auth0 authentication"

I've secured my NestJS app with Auth0 authentication