- Authentication is an
essential
part of most applications. - It helps us to secure our application from the third party viewers.
- There are many different approaches and strategies to handle authentication.
- For creating a authentication module in our boilerplate we will be using the library called
passport
. - Passport is the most popular node.js authentication library, well-known by the community and successfully used in many production applications.
- It's straightforward to integrate this library with a Nest application using the
@nestjs/passport
module.
- The method that we are using for authentication is the
NestJS JWT Authentication
usingPassport Strategy
. JWT
stands forJSON WEB TOKENS
. Basically, these tokens are issued by the server after user authentication and can be used for further requests as long as the token is valid.- The two high-level requirements for our
NestJS JWT Authentication
is as follows: - Let the users authenticate with a username/password. Upon authentication, return a JWT. This JWT can be used for subsequent calls to the application.
- Once the JWT token is generated then that token will be used in the hear of each request as a
Bearer Token
that will validate each API call. - See below illustration that explains the concept.
To get started with NestJS JWT, we need to install the below package.
npm install --save @nestjs/jwt passport-jwt npm install --save-dev @types/passport-jwt npm i --save @nestjs/passport passport
-
The
@nestjs/jwt
package helps with JWT manipulation. -
The
passport-jwt package
implements the JWT strategy. Also, the@types/passport-jwt
package provides the type definitions to make development easy. -
Once the installation is done we need to create a Authentication module which is done by creating
auth
folder inside thesrc
directory. -
In that folder we will be creating
auth.module.ts
,auth.controller.ts
,auth.service.ts
which are the essential files.
- The first step is for us to be able to generate a JWT and return it as response of the
auth/login
route insideauth.controller.ts
.
async generateToken(@Req() req: Request, @Res() res: Response): Promise<Response> {
try {
const user: ValidateUserDto = req.body;
const resWithAcessToken = await this.authService.generateToken(user);
return res.success(resWithAcessToken, StatusCodes.OK);
} catch (e) {
return res.error(e);
}
}
- The
AuthService
class looks like:
async generateToken(user: ValidateUserDto): Promise<any> {
try {
const userData = await this.findUserByEmail(user);
const isMatched = await comparePassword(user.password, userData.password);
if(isMatched) {
const payload = `${userData.firstName}${userData.id}`;
const accessToken = this.jwtService.sign(payload);
return {
access_token: accessToken,
};
} else {
throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED);
}
} catch (e) {
throw e;
}
}
-
The
generateToken
method of theAuthService
class created the JWT after the user credentials are validated. If the credentials are not validated then error will be thrown. -
The
sign
method from@nestjs/jwt
generates the token. -
The token which is generated will be in the below format:
-
Image
- The configuration inside the
auth.module.ts
is as follows.
@Module({
imports: [
UsersModule,
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (config: ConfigService) => {
return {
secret: config.get<string>('SECRET_JWT_KEY'),
};
},
inject: [ConfigService],
}),
ConfigModule,
PassportModule,
],
providers: [AuthService, JwtStrategy],
controllers: [AuthController],
// exports:[AuthService]
})
export class AuthModule {}
- Here, we are basically registering the
JwtModule
. While doing so, we specify thesecret
key. - The value of the secret key is been specified in the
.env
file.
- We need to do this to be able to address our second requirement of protecting endpoints using JWT.
- We will create a new file
jwt.strategy.ts
within the auth folder and place the below code.
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: config.get('auth.secretKey')
});
}
async validate(payload: any) {
return {
firstName: payload.firstName,
id: payload.id,
};
}
}
-
Basically, here we extend the
PassportStrategy
class and specify that we want to extend the Strategy frompassport-jwt
package. -
Next, we initialize the strategy by passing some parameters in the
super() call
. Below are the parameters in detail: -
jwtFromRequest: This parameter specifies the method using which we will extract the JWT from the request. Basically, we use the
fromAuthHeaderAsBearerToken()
method from theExtractJwt
package. This is because it is standard practice to pass the JWT token as the bearer token in the authorization header while making API requests. -
ignoreExpiration: We set this property as false. This basically means that we want to block requests with expired tokens. If a call is made with an expired token, our application will return 401 or Unauthorized response.
-
secretOrKey: This is basically the secret key from the
.env
file. Ideally, this should pem-encoded publish key. Also, it should not be exposed publicly. -
Next, we implement the validate() function in which we simply return the user object.
-
This might seem confusing as we are not processing anything in the function.
This is because for the JWT strategy, passport first verifies the signature of the JWT and decodes the JSON object. Only then does it actually call the validate() function and passes the decoded JSON as payload.
- Basically, this ensures that if the validate() function is called, it means the JWT is also valid
- Finally, we create an Auth Guard with the file name
jwt.auth.guard.ts
that uses the JWT strategy.
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
- Our JwtAuthGuard extends the AuthGuard provided by the
@nestjs/passport
package.
- Now, we can simply use this guard in one of the routes that we want to protect using the UseGuards() decotrator.
@UseGuards(JwtAuthGuard)
@Get(':id')
@ApiOkResponse({ description: apiResponse.apiUserGetById })
@ApiBearerAuth('JWT-auth')
async getUserById(@Req() req: Request, @Res() res: Response, @Param('id') id: string): Promise<Response> {
try {
const userById = await this.usersService.findOne(id);
return res.success(userById, StatusCodes.OK);
} catch (e) {
return res.error(e);
}
}
- Here, the request handler getUserById() uses the JwtAuthGuard. Basically, this means that unless there is a valid JWT in the Authorization header of the incoming HTTP request, the endpoint will not provide a valid response.
- CASE 1: If the token is not entered and the API is being accessed then following will be the output.
- CASE 2: If the user credentials are not valid.
- CASE 3: If the credentials are valid then the access token is generated.