Angular and the REST, Nest.js and JWT

bhaidar

Bilal Haidar

Posted on August 1, 2019

Angular and the REST, Nest.js and JWT

In this, the final installment on Angular and the REST, I implement authentication on the backend Nest.js Web API using Passport.js and JWT (JSON Web Token). In addition, I add a new authentication module on the Angular app side, so access is restricted to authenticated users only by way of Login.

The first installment of this week, I introduced Nrwl Nx Workspace and created the Movie Tracker monorepo workspace including a Nest.js app for the backend and an Angular app for the client-side. The app so far has a full implementation of the CRUD actions and movie search. On the client-side, we built an Angular v8 app that communicates with the backend RESTful Web API by means of a Movie Tracker Service. The app displays all movies, allows the user to filter the movies, add a new movie, edit an existing movie and delete a movie.

Here’s a link to that article.
Angular and the REST with Nest.js.

Today, let’s start by adding support for Passport and JWT authentication to our Nest.js app.

In case you have not read the entire series on Angular and the REST, these are the links:

Passport.js is one of the most mature authentication middlewares known for Node.js development. It provides a high level of flexibility in terms of choosing what authentication strategy to use to authenticate your users.

Nest.js offers the @nestjs/passport authentication library that wraps the functionality of Passport.js and integrates smoothly with Nest.js Dependency Injection system.

Let's start implementing authentication on the backend side of the Movie Tracker app.

You can find the source code of this article at this GitHub repo.

Adding Authentication to Nest.js

First of all we start by installing a set of NPM packages that we need to add authentication to the app.

Run the following command to install the packages:
yarn add @nestjs/passport passport passport-local passport-jwt bcryptjs

In addition, I will install the corresponding Typescript definitions for the following packages by running this command:
yarn add @types/passport @types/bcryptjs -D

The @nestjs/passport library counts on the passport,passport-local and passport-jwt packages and therefore it is required to install them too.

Passport.js is usually used and configured as a Node Middleware. However, in Nest.js, the @nestjs/passport library providers the AuthGuard. This is used to imply that Passport authentication is required on a specific Controller or Action in the Controller.

To learn more about guards in Nest.js go to their official documentation on guards.

Therefore, to ensure a request is authenticated prior to accessing a Controller or Action you decorate your Controller or Action with this guard (or you can define your guard globally for all the controllers). The rest is handled by @nestjs/passport and passport together.

Step 1
Let's do a change on the ormconfig.json file to disable automatic synchronization and show you how we can start using migrations to control what is being changed in the database.

Replace the content of the ormconfig.json file located in the root folder of the workspace with the following:

The named connection, the first, is used by the application when it runs. The second is the nameless or default and used by the TypeORM CLI tools to generate or create migrations and run them too.

Once a new migration is generated or created, it is automatically stored inside the path apps\api\migrations. Once a migration is run, the Nest.js app is built, the output files are copied to the path dist\api\migration and the migration is run from there. This is all configured in ormconfig.json file.

At this stage, you should remove the old Docker container by following the steps below:

  • Run docker ps -a: This command lists all containers created on your machine (both running and not)
  • Locate the Docker Container ID for the Postgres container and run this command: docker stop {DOCKER-CONTAINER-ID}
  • Remove the container by running this command: docker rm {DOCKER-CONTAINER-ID}

Then, add the following NPM scripts to the package.json file to make it easy to interact with TypeORM CLI:

The scripts added are used to generate a migration based on changes introduced to the entities in the app, create new empty migrations (you will fill in the blanks) and run SQL queries against the database using the TypeORM CLI.

Step 2
Add the UsersModule to manage users in the backend app. Create the following files as shown in the figure below:

The user.entity.ts file contains the User Entity:

The Entity defines the columns that belong to a single User. Notice the implementation of the @BeforeInsert() hook. I use this hook to hash the password using the bcryptjs library before storing the User Entity record in the database.

The users.service.ts file contains the UserService:

Currently this service provides implementation for a single findOne() method that retrieves a user from the database based on the username.

In case you want to implement a full user management service you can add further methods (create user, etc.)

Finally, the users.module.ts file defines the user module:

The UsersModule defines the single UserEntity to track in the database. It also provides and exports the UserService to be used by other modules as we will shortly see.

Step 3
Now that all entities are ready in the app, let’s generate a migration and run it against the database.

To generate a migration run the following command:
yarn db:migration:generate -n InitialCreate

This command generates the following migration file:

The up() method creates two tables in the database: movie and users. Whereas, the down() method drops both tables.

Let’s run the migration against the database. Start by running the Postgres Docker container by running this command:
yarn run:service

Once the container is up and ready, run the following command to run the migration:
yarn db:migrate

The command runs the migrations against and the database and creates the table.

Now, I will create a new empty migration and seed a user record to use for testing purposes.

Run the following command to create an empty migration:
yarn db:migration:create -n SeedUserRecord

This command creates the following:

Now add the code to the up() method to seed a user record:

The code retrieves a repository instance for the UserEntity. It then creates and saves a user in the database.

To run the migration simply run this command: yarn db:migrate.

In addition, I will repeat the same steps to seed a few movie records. You can check the source code in the GitHub repo to see the migration there.

Step 4
Now that the database is ready, let's add the authentication module.

The auth.service.ts file defines the authentication service as follows:

This service contains the validateUser() method to validate the user and ensure the username and password provided by the client-side app matches those of the user stored in the database.

Note that the code hashes the password received from the client-side app and compares it to the hashed password stored inside the database.

Also, the service contains the login() method that will be used later to return an access token based on the authenticated user. More to come on this later.

The local.strategy.ts file defines the Passport Local strategy used to validate a user via username and password by extending the base PassportStrategy class.

When it is time to run this strategy, @nestjs/passport calls the validate() method and passes in the username and password fields retrieved from the current HTTP Request. Internally, the method uses the AuthService to validate the user credentials. If the validation succeeds, the validate() method returns the user record. Otherwise, it returns null.

Based on the return value of the validate() method the request proceeds or fails.

The jwt.strategy.ts file defines the Passport JWT strategy used to verify that the JWT received from the client-side app is not expired and valid, extending the PassportStrategy class.

When extending the PassportStrategy class you have to call super() inside the constructor. You do so to instruct this strategy to read the JWT from the Authentication header of the request by looking at the Bearer token. In addition, it defines a secret key to use to decode the payload of the token. This same secret key is used to encrypt and sign the payload of the token and is defined in the jwt.constants.ts file as follows:



export const jwtConstants = {
  secret: 'v%re$1%3432F'
};


Enter fullscreen mode Exit fullscreen mode

The validate() method in this strategy is only called once @nestjs/passport module verifies that the token is valid. It then calls this method providing it a valid decoded token. The method returns the User Id and the username fields. In this case, the request continues safely. Otherwise, if the token is invalid, the request is blocked and a 401 Unauthorized response is sent back to the client-side app.

All strategies defined by Passport, store the result of validate() method execution as a user property on the current HTTP Request object. You can customize the name of this property as you will see shortly.

Finally, the auth,module,ts file defines the AuthModule as follows:

It imports the UsersModule, the PassportModule and configures the latter by specifying the defaultStrategy and property name used to store the validate() results on the HTTP Request. It also exports the AuthService and all strategies so that they are imported by the root module of the app and made available.

Step 5
Import the AuthModule to the AppModule so that the authentication feature becomes available in the app.

Locate the app.controller.ts and add the login action as follows:

The controller defines the login() action to allow the user to login to the app. Notice the user of AuthGuard in this case specifying the name of the Passport strategy to use? In our case the local strategy.

When the user hits the /login URL this action executes and the AuthGuard executes whatever strategy it is defined with. In this case, it loads the LocalStrategy and executes the validate() method passing to it the username and password the login() action method receives.

If the user is successfully validated against the credentials stored in the database, the body of the login() method executes. Inside the login() method the code calls the authentication/Service.login() method which in turn returns the access token to the client-side app.

The authors behind the @nestjs/passport offer the same flexibility like that of Passport.js. By making the AuthGuard flexible, it accepts a strategy to execute when it is time to authenticate the user.

Let's import the UsersModule and AuthModule to the AppModule.

Finally, let's test this endpoint via Postman and see the results together:

The payload of the request contains the user credentials. The payload of the response contains the access token. You may add more fields (user id, username, etc.) to the response of logging in to the app.

The backend authentication is ready. Let's move on and add an authentication module to the Angular app so that it can authenticate users against the backend.

Adding authentication to the Angular app

Let's start by generating a new library by running this command:
ng g @nrwl/angular:lib auth --directory="movietracker"

The command generates a new auth module under the parent movietracker library folder.

Building the authentication service

The authentication.service.ts file defines the authentication service. You generate the service by running this command:
ng g service services/authentication --project=movietracker-auth

The service defines the login() method as:

The login() method uses the HttpClient class to send a POST request to the backend Web API Endpoint /auth/login with a request payload in the body containing the username and password of the user trying to login.

Upon a successful login, the token returned from the backend API is stored inside LocalStorage to be used on subsequent requests to send it over as a Bearer token authentication header.

As well, the currentUserSubject of type BehaviorSubject is triggered with the user payload. This subject is defined as follows inside the authentication service:

The currentUserSubject is defined as a BehaviorSubject with an initial value equal to whatever user is stored in the LocalStorage.

Also, the currentUser variable is defined as an Observable on top of the currentUserSubject.

Other components and services in the application can now subscribe to the currentUser Observable since every time a successful login operation executes, it is guaranteed that this Observable will execute with the latest user signed in as a payload.

In addition, the currentUserValue is defined as a public getter to retrieve the value of the currentUserSubject variable. Later on we will use this value inside the Auth Guard as you will see shortly.

To logout a user, we simply delete the entry from the LocalStorage and populate the currentUserSubject with a null value signaling to all components and services that the user has signed out of the application.

Now let's amend the apps/movietracker/proxy.conf.json file as:

I've added a new redirection for all URLs starting with /auth to simply redirect them to the URL where the backend is running and also remove /auth from the URL. For instance, when the Angular app requests the URL http://localhost:4200/auth/login the internal Webpack server, instructed by the proxy.conf.json will transform this request to http://localhost:3000/login to match the login() endpoint URL defined on the backend.

Configure Routing

To restrict access to authenticated users only, let's define the following routing structure inside the movietracker app as follows:

The default route now lives under admin/movie-tracker.

The path /admin now has a related component the AppMainComponent. This component renders an Angular Material layout for the entire application.

In addition, any access to /admin is now restricted by the AuthGuard defined by implementing the canActivate() handler that is executed before navigating to the requested route.

The movie-tracker route is now defined as an admin child route.

A new route is added to route requests to the Login component that we will build shortly.

The catch all route is added, so that if the user accesses any route that is not defined here, it redirects their request to the default route in the app.

Finally, the movietracker/movies library defines a single route as follows:



const routes: Routes = [
  {
    path: '',
    component: MovieTrackerMoviesListComponent
  }
];


Enter fullscreen mode Exit fullscreen mode

AuthGuard details

Let's go back to the authentication module and go through the AuthGuard:

You generate this guard by running this command:
ng g guard guards/auth --project=movietracker-auth

The AuthGuard implements the CanActivate interface and provides implementation for the CanActivate() handler.

The handle calls the authenticationService.currentUserValue getter to check if there is an existing user stored in the LocalStorage. If there is one, the code returns true meaning that it is safe to allow the route navigation.

Otherwise, the user is redirected to the Login route.

This AuthGuard is provided at the root of the application and hence it is accessible everywhere in the application.

All the services, guards and interceptors in the AuthModule are provided at the root module of the application and are available everywhere to all modules.

The login.components.ts file defines the Login component for the application:

The component defines two form fields: username and password. It then communicates with the backend app through the AuthenticationService to login the user. You can refer to the source code of this component on GitHub repo.

Angular HTTP Interceptors

Moreover, two interceptors are defined in the AuthModule.

The error.interceptor.ts is defined to handle Unauthorized requests and redirect the user back to the Login page.

On the other hand, the jwt.interceptor.ts is defined to attach an access token to each request sent to the backend server whenever possible.

Both interceptor files export a Provider that can be provided by the root app module so that both interceptors are registered in the app.



export const errorInterceptorProvider = {
  provide: HTTP_INTERCEPTORS,
  useClass: ErrorInterceptor,
  multi: true
};


Enter fullscreen mode Exit fullscreen mode

Finally, we look at the movietracker-auth.module.ts file:

It imports the MovietrackerCoreModule, declares and exports the LoginComponent. All other services in this module are automatically provided at the root app module.

Running the app

Let's run the app by issuing the following commands:



yarn dev:server
yarn watch:movietracker


Enter fullscreen mode Exit fullscreen mode

Let's try an invalid combination of a username and password and see how the Login component behaves.

Now let's try to sign in with the user credentials that we have previously seeded into the backend Web API and notice how the application now redirects us to the Movie Tracker route:

You are now redirected successfully. You can see the new UI layout and also click the Logout button whenever you want to sign out of the application. You may now continue managing your movies after you've signed in to the application.

You may now try out all the functionality provided by the app for editing an existing movie, creating a new movie, deleting an existing movie and filtering out movies.

You can refer to the previous article to get more details on how these features work.

Note
You will notice that in the GitHub repo I've moved the AuthenticationService in the Angular app to the api-services library. Keeping all services that communicate with the backend in the same library is a good practice.

Conclusion

This article ends the series on Angular and the REST. Now with knowledge at hand, you can make an informed decision. Either build a full stack app by using a single monorepo or two separate repos.

From my experience, using a monorepo makes life easier, is time efficient and more productive.

This post was written by Bilal Haidar, a mentor with This Dot.

You can follow him on Twitter at @bhaidar.

Need JavaScript consulting, mentoring, or training help? Check out our list of services at This Dot Labs.

💖 💪 🙅 🚩
bhaidar
Bilal Haidar

Posted on August 1, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related