Nest.js, using version 5 the @nestjs/passport package, allows you to implement the authentication strategy that you need. Of course you can also do this manually using passport.
In this chapter you will see how to use passport by integrating it into your Nest.js project. We also cover what a strategy is, and how to configure the strategy to use with passport.
We will also manage restriction access using an authentication middleware, and see how guards can check data before the user accesses the handlers. In addition, we’ll show how to use the passport package provided by Nest.js in order to cover both possibilities.
As an example, we will use the following repository files:
/src/authentication/src/user/shared/middlewares/shared/guards Passport is a well known library that is popular and flexible to use. In fact, passport is flexible middleware that can be fully customized. Passport allows different ways to authenticate a user like the following:
local strategy that allows you to authenticate a user just with it’s own data email and password in most cases.jwt strategy that allows you to authenticate a user by providing a token and verifying this token using jsonwebtoken. This strategy is used a lot.Some strategies use the social network or Google in order to authenticate the user with a profile such as googleOAuth, Facebook, or even Twitter.
In order to use passport you have to install the following package: npm i passport. Before you see how to implement the authentication, you must implement the userService and the userModel.
In this section, we will implement the authentication manually using passport without using the Nest.js package.
In order to configure passport, three things need to be configured:
Passport uses the strategy to authenticate a request, and the verification of the credential is delegated to the strategies in some of the requests.
Before using passport, you must configure the strategy, and in this case we will use the passport-jwt strategy.
Before anything else, you must install the appropriate packages:
npm i passport-jwt @types/passport-jwtnpm i jsonwebtoken @types/jsonwebtoken In order to have a working example, you must implement some modules, and we will start with AuthenticationModule. The AuthenticationModule will configure the strategy using the jwt strategy. To configure the strategy we will extend the Strategy class provided by the passport-jwt package.
Here is an example of a strategy extending the Strategy class in order to configure it and use it in passport.
@Injectable()exportdefaultclassJwtStrategyextendsStrategy{constructor(privatereadonlyauthenticationService:AuthenticationService){super({jwtFromRequest:ExtractJwt.fromAuthHeaderAsBearerToken(),passReqToCallback:true,secretOrKey:'secret'},async(req,payload,next)=>{returnawaitthis.verify(req,payload,next);});passport.use(this);}publicasyncverify(req,payload,done){constisValid=awaitthis.authenticationService.validateUser(payload);if(!isValid){returndone('Unauthorized',null);}else{returndone(null,payload);}}}
The constructor allows you to pass some configuration parameters to the extended Strategy class. In this case we are using only three parameters:
jwtFromRequest option accepts a function in order to extract the token from the request. In our case we are using the ExtractJwt.fromAuthHeaderAsBearerToken() function provided by the passport-jwt package. This function will pick the token from the header of the request using the Authorization header, and pick the token
that follows the bearer word.passReqToCallback parameter takes a boolean in order to tell if you want to get the req in the verify method that you will see later.secretOrKey parameter takes a string or a buffer in order to verify the token signature.Other parameters are available to configure the strategy, but to implement our authentication we don’t need them.
Also, after passing the different previous parameters, we pass a callback function called verify. This function is asynchronous, and has the purpose to verify if the token passed and if the payload obtained from the token is valid or not. This function executes our verify method, which calls the authenticationService in order to validate the user with the payload as a parameter.
If the user is valid, we return the payload, otherwise we return an error to indicate that the payload is not valid.
As shown in the previous section, in order to verify the payload that you get from the token, call the validateUser method
provided by the AuthenticationService.
In fact, this service will implement another method in order to generate the token for the logged in user. The service can be implemented as the following example.
@Injectable()exportclassAuthenticationService{constructor(privatereadonlyuserService:UserService){}createToken(string,ttl?:number){constexpiresIn=ttl||60*60;constsecretOrKey='secret';constuser={};consttoken=jwt.sign(user,secretOrKey,{expiresIn});return{expires_in:expiresIn,access_token:token,};}asyncvalidateUser(payload:{string;password:string}):Promise<boolean>{constuser=awaitthis.userService.findOne({where:{payload.email}});return!!user;}}
The service injects the UserService in order to find the user using the payload pass to the validateUsermethod. If the email in the payload allows you to find the user, and if that user has a valid token, she can continue the
authentication process.
In order to provide a token for the user who try to logged in, implement the createToken method, which takes as parameters an email and an optional ttl. The ttl (Time to live) will configure the token to be valid for a period. The value of the ttl is expressed in seconds, and the default value that we have defined in 60 * 60, which means 1 hour.
In order to process the authentication of the user, implement the controller and provide a handler for the login endpoint.
@Controller()exportclassAuthenticationController{constructor(privatereadonlyauthenticationService:AuthenticationService,privatereadonlyuserService:UserService){}@Post('login')@HttpCode(HttpStatus.OK)publicasynclogin(@Body()body:any,@Res()res):Promise<any>{if(!body.||!body.password){returnres.status(HttpStatus.BAD_REQUEST).send('Missing email or password.');}constuser=awaitthis.userService.findOne({where:{body.email,password:crypto.createHmac('sha256',body.password).digest('hex')}});if(!user){returnres.status(HttpStatus.NOT_FOUND).send('No user found with this email and password.');}constresult=this.authenticationService.createToken(user.);returnres.json(result);}}
The controller provides the login handler, which is accessible by a call on the POST /login route. The purpose of this method is to validate the credentials provided by the user in order to find him in the database. If the user is found, create the appropriate token that will be returned as a response with the expiresInvalue corresponding to our previously defined ttl. Otherwise the request will be rejected.
We have now defined our service and strategy in order to configure passport and provide some method to create a token and validate a payload. Let’s define AuthenticationModule, which is similar to the following example.
@Module({})exportclassAuthenticationModule{staticforRoot(strategy?:'jwt'|'OAuth'|'Facebook'):DynamicModule{strategy=strategy?strategy:'jwt';conststrategyProvider={provide:'Strategy',useFactory:async(authenticationService:AuthenticationService)=>{constStrategy=(awaitimport(`./passports/${strategy}.strategy`)).default;returnnewStrategy(authenticationService);},inject:[AuthenticationService]};return{module:AuthenticationModule,imports:[UserModule],controllers:[AuthenticationController],providers:[AuthenticationService,strategyProvider],exports:[strategyProvider]};}}
As you can see, the module is not defined as a normal module, so it has no components or controller defined in the @Module() decorator. In fact, this module is a dynamic module. In order to provide a multiple strategy, we can implement a static method on the class in order to call it when we import the module in another one. This method forRoot takes as a parameter the name of the strategy that you want to use and will create a strategyProvider
in order to be added to the components list in the returned module. This provider will instanciate the strategy and provide the AuthenticationService as a dependency.
Let’s continue by creating something to protect, such as the UserModule.
The UserModule provides a service, a controller, and a model (see the sequelize chapter for the User model). We create some methods in the UserService in order to manipulate the data concerning the user. These methods are used in the UserController in order to provide some features to the user of the API.
All of the features can’t be used by the user or restricted in the data that is returned.
Let’s examine an example of the UserService and some methods in order to access and manipulate the data. All of the methods describe in this part will be used in the controller, and some of them are restricted by the authentication.
@Injectable()exportclassUserService() {// The SequelizeInstance come from the DatabaseModule have a look to the Sequelize chapterconstructor(@Inject('UserRepository')privatereadonlyUserRepository:typeofUser,@Inject('SequelizeInstance')privatereadonlysequelizeInstance){}/* ... */}
The service injects the UserRepository that we have described in the Sequelize chapter in order to access the model and the data store in the database. We also inject the SequelizeInstance, also described in the Sequelize chapter, in order to use the transaction.
The UserService implements the findOne method to find a user with a criteria passing in the options parameter. The options parameter can look like this:
{where:{:'some@email.test',firstName:'someFirstName'}}
Using this criteria, we can find the corresponding user. This method will return only one result.
@Injectable()exportclassUserService() {/* ... */publicasyncfindOne(options?:object):Promise<User|null>{returnawaitthis.UserRepository.findOne<User>(options);}/* ... */}
Let’s implement the findById method, which takes as a parameter an ID in order to find a unique user.
@Injectable()exportclassUserService() {/* ... */publicasyncfindById(id:number):Promise<User|null>{returnawaitthis.UserRepository.findById<User>(id);}/* ... */}
Then we need a way to create a new user in the database passing the user respecting the IUser interface. This method, as you can see, uses a this.sequelizeInstance.transaction transaction to avoid reading the data before everything is finished. This method passes a parameter to the create function, which is returning in order to get the instance user
that has been created.
@Injectable()exportclassUserService() {/* ... */publicasynccreate(user:IUser):Promise<User>{returnawaitthis.sequelizeInstance.transaction(asynctransaction=>{returnawaitthis.UserRepository.create<User>(user,{returning:true,transaction,});});}/* ... */}
Of course, if you can create a user, you also need the possibility to update it with the following method following the IUser interface. This method too will return the instance of the user that has been updated.
@Injectable()exportclassUserService() {/* ... */publicasyncupdate(id:number,newValue:IUser):Promise<User|null>{returnawaitthis.sequelizeInstance.transaction(asynctransaction=>{letuser=awaitthis.UserRepository.findById<User>(id,{transaction});if(!user)thrownewError('The user was not found.');user=this._assign(user,newValue);returnawaituser.save({returning:true,transaction,});});}/* ... */}
In order to make a round in all of the methods, we will implement the delete method to remove a user completely from the database.
@Injectable()exportclassUserService() {/* ... */publicasyncdelete(id:number):Promise<void>{returnawaitthis.sequelizeInstance.transaction(asynctransaction=>{returnawaitthis.UserRepository.destroy({where:{id},transaction,});});}/* ... */}
In all of the previous examples, we have define a complete UserService that allowed us to manipulate the data. We have the possibility to create, read, update, and delete a user.
If you wish to see the implementation of the user model, you can refer to the Sequelize chapter.
Now that we have created our service and model, we need to implement the controller to handle all the requests from the client. This controller provides at least a create, read, update and delete handler that should be implemented like the following example.
@Controller()exportclassUserController{constructor(privatereadonlyuserService:UserService){}/* ... */}
The controller injects the UserService in order to use the methods implemented in the UserService.
Provide a GET users route that allows access to all users from the database, and you will see how we don’t want the user accessing the data of all of the users, just only for himself. This is why we are using a guard that only allows a user to acces his own data.
@Controller()exportclassUserController{/* ... */@Get('users')@UseGuards(CheckLoggedInUserGuard)publicasyncindex(@Res()res){constusers=awaitthis.userService.findAll();returnres.status(HttpStatus.OK).json(users);}/* ... */}
The user has access to a route that allows you to create a new user. Of course, if you want, the user can register into the logged in application, which we must allow for those without a restriction.
@Controller()exportclassUserController{/* ... */@Post('users')publicasynccreate(@Body()body:any,@Res()res){if(!body||(body&&Object.keys(body).length===0))thrownewError('Missing some information.');awaitthis.userService.create(body);returnres.status(HttpStatus.CREATED).send();}/* ... */}
We also provide a GET users/:id route that allows you to get a user by his ID. Of course a logged in user should not be able to access the data from another user even from this route. This route is also protected by a guard in order to allow the user access to himself and not another user.
@Controller()exportclassUserController{/* ... */@Get('users/:id')@UseGuards(CheckLoggedInUserGuard)publicasyncshow(@Param()id:number,@Res()res){if(!id)thrownewError('Missing id.');constuser=awaitthis.userService.findById(id);returnres.status(HttpStatus.OK).json(user);}/* ... */}
A user can have the idea to update some of his own information, which is why we provide a way to update a user through the following PUT users/:id route. This route is also protected by a guard to avoid a user updating another user.
@Controller()exportclassUserController{/* ... */@Put('users/:id')@UseGuards(CheckLoggedInUserGuard)publicasyncupdate(@Param()id:number,@Body()body:any,@Res()res){if(!id)thrownewError('Missing id.');awaitthis.userService.update(id,body);returnres.status(HttpStatus.OK).send();}
Use deletion to finish the last handler. This route has to also be protected by a guard to avoid a user from deleting another user. The only user that can be deleted by a user is himself.
@Delete('users/:id')@UseGuards(CheckLoggedInUserGuard)publicasyncdelete(@Param()id:number,@Res()res){if(!id)thrownewError('Missing id.');awaitthis.userService.delete(id);returnres.status(HttpStatus.OK).send();}}
We have implemented all of the methods that we need in this controller. Some of them are restricted by a guard in order to apply some security and avoid a user from manipulating the data from another user.
To finish the implementation of the UserModule, we have to set up the module of course. This module contains a service,
a controller, and a provider that allows you to inject the user model and provides a way to manipulate the stored data.
@Module({imports:[],controllers:[UserController],providers:[userProvider,UserService],exports:[UserService]})exportclassUserModule{}
This module is imported like the AuthenticationModule into the main AppModule in order to use it in the app and be accessible.
The AppModule imports three modules for our example.
DatabaseModule accesses the sequelize instance and accesses the database.AuthenticationModule allows you to log into a user and use the appropriate strategy.UserModule exposes some endpoints that can be requested by the client.In the end, the module should looks like the following example.
@Module({imports:[DatabaseModule,// Here we specify the strategyAuthenticationModule.forRoot('jwt'),UserModule]})exportclassAppModuleimplementsNestModule{publicconfigure(consumer:MiddlewaresConsumer){consumer.apply(AuthenticationMiddleware).with(strategy).forRoutes({path:'/users',method:RequestMethod.GET},{path:'/users/:id',method:RequestMethod.GET},{path:'/users/:id',method:RequestMethod.PUT},{path:'/users/:id',method:RequestMethod.DELETE});}}
As you can see in this example, we have applied the AuthenticationMiddleware to the routes that we want to protect from a non-logged in user.
This middleware has the purpose of applying the passport middleware passport.authenticate, which verifies the token provided by the user and stores in the header the request as the Authorization value. This middleware will take the strategy parameter to correspond to the strategy that should be applied, which for us is strategy = 'jwt'.
This middleware is applied on almost all of the routes of the UserController, except for the POST /users that allows you to create a new user.
As seen in the previous section, we have applied the AuthenticationMiddleware, and we have seen that passport is
middleware to authenticate the user. This middleware will execute the passport.authenticate method using the strategy jwt, taking a callback function that will return the results of the authentication method.
As a result we can receive the payload corresponding to the token or an error in case the authentication doesn’t work.
@Injectable()exportclassAuthenticationMiddlewareimplementsNestMiddleware{constructor(privateuserService:UserService){}asyncresolve(strategy:string):Promise<ExpressMiddleware>{returnasync(req,res,next)=>{returnpassport.authenticate(strategy,async(...args:any[])=>{const[,payload,err]=args;if(err){returnres.status(HttpStatus.BAD_REQUEST).send('Unable to authenticate the user.');}constuser=awaitthis.userService.findOne({where:{payload.email}});req.user=user;returnnext();})(req,res,next);};}}
If the authentication work we will be able to store the user into the request req in order to be use by the controller
or the guard. the middleware implement the interface NestMiddleware in order to implement the resolve function.
It also inject the UserService in order to find the user authenticated.
Nest.js comes with a guard concept. This injectable has a single responsibility, which is to determine if the request has to be handled by the route handler.
The guard is used on a class that implements the canActivate interface in order to implement the canActivate method.
The guards are executed after every middleware and before any pipes. The interest of doing this is to separate the restriction logic of the middleware and reorganize this restriction.
Imagine using a guard to manage the access to a specific route and you want this route to only be accessible to the logged in user. To do that we have implemented a new guard, which has to return ‘true’ if the user accessing the route is the same as the one belonging to the resource that the user want to access. With this kind of guard, you avoid a user to access another user.
@Injectable()exportclassCheckLoggedInUserGuardimplementsCanActivate{canActivate(context:ExecutionContext):boolean|Promise<boolean>|Observable<boolean>{constrequest=context.switchToHttp().getRequest();returnNumber(req.params.userId)===req.user.id;}}
As you can see, you get the handler from the context that corresponds to the route handler on the controller where the guard is applied. You also get the userId from the request parameters to compare it from to the logged in user register into the request. If the user
who wants to access the data is the same, then he can access the references in the request parameter, otherwise he will receive a 403 Forbidden.
To apply the guard to the route handler, see the following example.
@Controller()@UseGuards(CheckLoggedInUserGuard)exportclassUserController{/*...*/}
Now that we have protected all of our route handlers of the userController, they are all accessible except for the delete one, because the user has to be an admin to access it. If the user does not have the appropriate role, they will receive a 403 Forbidden response.
The @nestjs/passport package is an extensible package that allows you to use any strategy from passport into Nest.js. As seen in the previous section, it is possible to implement the authentication manually, but if you want to
do it in a quicker way and have the strategy wrapped, then use the good package.
In this section, you will see the usage of the package using jwt as shown in the previous section. To use it you have to install the following package:
npm install --save @nestjs/passport passport passport-jwt jsonwebtoken
To use the package you will have the possibility to use the exact same AuthenticationService that you have implemented in the previous section, but remember to follow the next code sample.
@Injectable()exportclassAuthenticationService{constructor(privatereadonlyuserService:UserService){}createToken(string,ttl?:number){constexpiresIn=ttl||60*60;constsecretOrKey='secret';constuser={};consttoken=jwt.sign(user,secretOrKey,{expiresIn});return{expires_in:expiresIn,access_token:token,};}asyncvalidateUser(payload:{string;password:string}):Promise<boolean>{constuser=awaitthis.userService.findOne({where:{payload.email}});return!!user;}}
To instanciate the jwt strategy, you will also have to implement the JwtStrategy, but now you only need to pass the options
because the passport is wrapped by the package and will apply the strategy to passport automatically under the hood.
@Injectable()exportdefaultclassJwtStrategyextendsPassportStrategy(Strategy){constructor(privatereadonlyauthenticationService:AuthenticationService){super({jwtFromRequest:ExtractJwt.fromAuthHeaderAsBearerToken(),passReqToCallback:true,secretOrKey:'secret'});}publicasyncvalidate(req,payload,done){constisValid=awaitthis.authenticationService.validateUser(payload);if(!isValid){returndone('Unauthorized',null);}else{returndone(null,payload);}}}
As you can see, in this new implementation of the JwtStrategy you don’t need to implement the callback anymore. This is because you now extend the PassportStrategy(Strategy) where Strategy is the imported member from the passport-jwt library. Also, the PassportStrategy is a mixin that will call the validate method that we’ve implemented and named according
to the abstract member of this mixin class. This method will be called by the strategy as the validation method of the payload.
Another feature provided by the package is the AuthGuard that can be used with @UseGuards(AuthGuard('jwt')) to enable
the authentication on a specific controller method instead of using the middleware that we have implemented in the previous section.
The AuthGuard takes as parameters the name of the strategy that you want to apply, which in our example is jwt, and can also take some other parameters that follow the AuthGuardOptions interface. This interface defines three options that can be used:
session as a booleanproperty as a string to define the name of the property that you want to be add into the request to attach to the authenticated usercallback as a function that allows you to implement your own logicBy default the session is set to false and the property is set to user. By default, The callback will return the user or an UnauthorizedException.
And that’s it, you can now authenticate the user on any controller method and get the user from the request.
The only thing you have to do is to create the AuthModule as the following sample:
@Module({imports:[UserModule],providers:[AuthService,JwtStrategy],})exportclassAuthModule{}
And as you can see, it isn’t in your hands to create a provider to instanciate the strategy, because it’s now wrapped into the package.
In this chapter you have learned what a passport is and strategies to configure the different parts of the passport in order to authenticate the user and store it into the request. You have also seen how to implement the different modules,
AuthenticationModule and the UserModule, in order to be logged into the user and provide some endpoints accessible by the user. Of course, we have restricted the access to some data that applies the AuthenticationMiddleware and the CheckLoggedInUserGuard for more security.
You have also seen the new @nestjs/passport package, which allows you to implement in faster ways a few classes as AuthenticationService and JwtStrategy, and be able to authenticate any user on any controller method using the AuthGuard provided by the package.
In the next chapter you will learn about the Dependency Injection pattern.