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'
};
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
}
];
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
};
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
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.
Top comments (8)
For anyone using passport for the the first time, please note that the request properties for local-strategy's validate function must be (username: string, password: string)., passport will give you a 401 error if you use anything else.
Thanks for the article, a small correction to the code in the Controller, you forgot to
await
the login result. It shoulb bereturn await this.authService.login(req.user);
Hey Ghislain. Nestjs handles
async login(@Request() req) {
return this.authService.login(req.user);
}
As a promise, so, Is redundant to
return await this.authService.login(req.user);
since internally Nestjs willawait
thelogin
function anyway, so if you return another async function from it, it will be internallyawaited
So nice ! Thanks you
I have a post for sharing!
Angular SpringBoot Jwt Authentication Example:
loizenai.com/angular-10-spring-boo...
Please check it for feedback! Thanks
Thanks for this! github.com/kelektiv/node.bcrypt.js should probably be used though, the other one seems abandoned.
Don't use JSON.stringify in login method if you are accessing the token property directly, only use it on the object.
Thank you for this, it confused me because of implementing jwt service before configuring it in the module, I think showing how to setup JWT earlier will be an improvement.
In the service for logging in, don't use JSON.stringify, it is unnecessary and could actually corrupt your token by adding quotation marks to it.