Authentication

Authentication is an essential part of most applications. There are many different approaches and strategies to handle authentication. The approach taken for any project depends on its particular application requirements. This chapter presents several approaches to authentication that can be adapted to a variety of different requirements.

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. At a high level, Passport executes a series of steps to:

  • Authenticate a user by verifying their “credentials” (such as username/password, JSON Web Token (JWT), or identity token from an Identity Provider)
  • Manage authenticated state (by issuing a portable token, such as a JWT, or creating an Express session)
  • Attach information about the authenticated user to the Request object for further use in route handlers

Passport has a rich ecosystem of strategies that implement various authentication mechanisms. While simple in concept, the set of Passport strategies you can choose from is large and presents a lot of variety. Passport abstracts these varied steps into a standard pattern, and the @nestjs/passport module wraps and standardizes this pattern into familiar Nest constructs.

In this chapter, we’ll implement a complete end-to-end authentication solution for a RESTful API server using these powerful and flexible modules. You can use the concepts described here to implement any Passport strategy to customize your authentication scheme. You can follow the steps in this chapter to build this complete example. You can find a repository with a completed sample app here.

Authentication Requirements

Let’s flesh out our requirements. For this use case, clients will start by authenticating with a username and password. Once authenticated, the server will issue a JWT that can be sent as a bearer token in an authorization header on subsequent requests to prove authentication. We’ll also create a protected route that is accessible only to requests that contain a valid JWT.

We’ll start with the first requirement: authenticating a user. We’ll then extend that by issuing a JWT. Finally, we’ll create a protected route that checks for a valid JWT on the request.

First we need to install the required packages. Passport provides a strategy called passport-local that implements a username/password authentication mechanism, which suits our needs for this portion of our use case.

  1. $ npm install --save @nestjs/passport passport passport-local
  2. $ npm install --save-dev @types/passport-local

warning Notice For any Passport strategy you choose, you’ll always need the @nestjs/passport and passport packages. Then, you’ll need to install the strategy-specific package (e.g., passport-jwt or passport-local) that implements the particular authentication strategy you are building. In addition, you can also install the type definitions for any Passport strategy, as show above with @types/passport-local, which provides assistance while writing TypeScript code.

Implementing Passport strategies

We’re now ready to implement the authentication feature. We’ll start with an overview of the process used for any Passport strategy. It’s helpful to think of Passport as a mini framework in itself. The elegance of the framework is that it abstracts the authentication process into a few basic steps that you customize based on the strategy you’re implementing. It’s like a framework because you configure it by supplying customization parameters (as plain JSON objects) and custom code in the form of callback functions, which Passport calls at the appropriate time. The @nestjs/passport module wraps this framework in a Nest style package, making it easy to integrate into a Nest application. We’ll use @nestjs/passport below, but first let’s consider how vanilla Passport works.

In vanilla Passport, you configure a strategy by providing two things:

  1. A set of options that are specific to that strategy. For example, in a JWT strategy, you might provide a secret to sign tokens.
  2. A “verify callback”, which is where you tell Passport how to interact with your user store (where you manage user accounts). Here, you verify whether a user exists (and/or create a new user), and whether their credentials are valid. The Passport library expects this callback to return a full user if the validation succeeds, or a null if it fails (failure is defined as either the user is not found, or, in the case of passport-local, the password does not match).

With @nestjs/passport, you configure a Passport strategy by extending the PassportStrategy class. You pass the strategy options (item 1 above) by calling the super() method in your subclass, optionally passing in an options object. You provide the verify callback (item 2 above) by implementing a validate() method in your subclass.

We’ll start by generating an AuthModule and in it, an AuthService:

  1. $ nest g module auth
  2. $ nest g service auth

As we implement the AuthService, we’ll find it useful to encapsulate user operations in a UsersService, so let’s generate that module and service now:

  1. $ nest g module users
  2. $ nest g service users

Replace the default contents of these generated files as shown below. For our sample app, the UsersService simply maintains a hard-coded in-memory list of users, and a find method to retrieve one by username. In a real app, this is where you’d build your user model and persistence layer, using your library of choice (e.g., TypeORM, Sequelize, Mongoose, etc.).

  1. @@filename(users/users.service)
  2. import { Injectable } from '@nestjs/common';
  3. export type User = any;
  4. @Injectable()
  5. export class UsersService {
  6. private readonly users: User[];
  7. constructor() {
  8. this.users = [
  9. {
  10. userId: 1,
  11. username: 'john',
  12. password: 'changeme',
  13. },
  14. {
  15. userId: 2,
  16. username: 'chris',
  17. password: 'secret',
  18. },
  19. {
  20. userId: 3,
  21. username: 'maria',
  22. password: 'guess',
  23. },
  24. ];
  25. }
  26. async findOne(username: string): Promise<User | undefined> {
  27. return this.users.find(user => user.username === username);
  28. }
  29. }
  30. @@switch
  31. import { Injectable } from '@nestjs/common';
  32. @Injectable()
  33. export class UsersService {
  34. constructor() {
  35. this.users = [
  36. {
  37. userId: 1,
  38. username: 'john',
  39. password: 'changeme',
  40. },
  41. {
  42. userId: 2,
  43. username: 'chris',
  44. password: 'secret',
  45. },
  46. {
  47. userId: 3,
  48. username: 'maria',
  49. password: 'guess',
  50. },
  51. ];
  52. }
  53. async findOne(username) {
  54. return this.users.find(user => user.username === username);
  55. }
  56. }

In the UsersModule, the only change needed is to add the UsersService to the exports array of the @Module decorator so that it is visible outside this module (we’ll soon use it in our AuthService).

  1. @@filename(users/users.module)
  2. import { Module } from '@nestjs/common';
  3. import { UsersService } from './users.service';
  4. @Module({
  5. providers: [UsersService],
  6. exports: [UsersService],
  7. })
  8. export class UsersModule {}
  9. @@switch
  10. import { Module } from '@nestjs/common';
  11. import { UsersService } from './users.service';
  12. @Module({
  13. providers: [UsersService],
  14. exports: [UsersService],
  15. })
  16. export class UsersModule {}

Our AuthService has the job of retrieving a user and verifying the password. We create a validateUser() method for this purpose. In the code below, we use a convenient ES6 spread operator to strip the password property from the user object before returning it. We’ll be calling into the validateUser() method from our Passport local strategy in a moment.

  1. @@filename(auth/auth.service)
  2. import { Injectable } from '@nestjs/common';
  3. import { UsersService } from '../users/users.service';
  4. @Injectable()
  5. export class AuthService {
  6. constructor(private readonly usersService: UsersService) {}
  7. async validateUser(username: string, pass: string): Promise<any> {
  8. const user = await this.usersService.findOne(username);
  9. if (user && user.password === pass) {
  10. const { password, ...result } = user;
  11. return result;
  12. }
  13. return null;
  14. }
  15. }
  16. @@switch
  17. import { Injectable, Dependencies } from '@nestjs/common';
  18. import { UsersService } from '../users/users.service';
  19. @Injectable()
  20. @Dependencies(UsersService)
  21. export class AuthService {
  22. constructor(usersService) {
  23. this.usersService = usersService;
  24. }
  25. async validateUser(username, pass) {
  26. const user = await this.usersService.findOne(username);
  27. if (user && user.password === pass) {
  28. const { password, ...result } = user;
  29. return result;
  30. }
  31. return null;
  32. }
  33. }

Warning Warning Of course in a real application, you wouldn’t store a password in plain text. You’d instead use a library like bcrypt, with a salted one-way hash algorithm. With that approach, you’d only store hashed passwords, and then compare the stored password to a hashed version of the incoming password, thus never storing or exposing user passwords in plain text. To keep our sample app simple, we violate that absolute mandate and use plain text. Don’t do this in your real app!

Now, we update our AuthModule to import the UsersModule.

  1. @@filename(auth/auth.module)
  2. import { Module } from '@nestjs/common';
  3. import { AuthService } from './auth.service';
  4. import { UsersModule } from '../users/users.module';
  5. @Module({
  6. imports: [UsersModule],
  7. providers: [AuthService],
  8. })
  9. export class AuthModule {}
  10. @@switch
  11. import { Module } from '@nestjs/common';
  12. import { AuthService } from './auth.service';
  13. import { UsersModule } from '../users/users.module';
  14. @Module({
  15. imports: [UsersModule],
  16. providers: [AuthService],
  17. })
  18. export class AuthModule {}

Implementing Passport local

Now we can implement our Passport local authentication strategy. Create a file called local.strategy.ts in the auth folder, and add the following code:

  1. @@filename(auth/local.strategy)
  2. import { Strategy } from 'passport-local';
  3. import { PassportStrategy } from '@nestjs/passport';
  4. import { Injectable, UnauthorizedException } from '@nestjs/common';
  5. import { AuthService } from './auth.service';
  6. @Injectable()
  7. export class LocalStrategy extends PassportStrategy(Strategy) {
  8. constructor(private readonly authService: AuthService) {
  9. super();
  10. }
  11. async validate(username: string, password: string): Promise<any> {
  12. const user = await this.authService.validateUser(username, password);
  13. if (!user) {
  14. throw new UnauthorizedException();
  15. }
  16. return user;
  17. }
  18. }
  19. @@switch
  20. import { Strategy } from 'passport-local';
  21. import { PassportStrategy } from '@nestjs/passport';
  22. import { Injectable, UnauthorizedException, Dependencies } from '@nestjs/common';
  23. import { AuthService } from './auth.service';
  24. @Injectable()
  25. @Dependencies(AuthService)
  26. export class LocalStrategy extends PassportStrategy(Strategy) {
  27. constructor(authService) {
  28. this.authService = authService
  29. super();
  30. }
  31. async validate(username, password) {
  32. const user = await this.authService.validateUser(username, password);
  33. if (!user) {
  34. throw new UnauthorizedException();
  35. }
  36. return user;
  37. }
  38. }

We’ve followed the recipe described earlier for all Passport strategies. In our use case with passport-local, there are no configuration options, so our constructor simply calls super(), without an options object.

We’ve also implemented the validate() method. For each strategy, Passport will call the verify function (implemented with the validate() method in @nestjs/passport) using an appropriate strategy-specific set of parameters. For the local-strategy, Passport expects a validate() method with the following signature: validate(username: string, password:string): any.

Most of the validation work is done in our AuthService (with the help of our UserService), so this method is quite straightforward. The validate() method for any Passport strategy will follow a similar pattern, varying only in the details of how credentials are represented. If a user is found and the credentials are valid, the user is returned so Passport can complete its tasks (e.g., creating the user property on the Request object), and the request handling pipeline can continue. If it’s not found, we throw an exception and let our exceptions layer handle it.

Typically, the only significant difference in the validate() method for each strategy is how you determine if a user exists and is valid. For example, in a JWT strategy, depending on requirements, we may evaluate whether the userId carried in the decoded token matches a record in our user database, or matches a list of revoked tokens. Hence, this pattern of sub-classing and implementing strategy-specific validation is consistent, elegant and extensible.

We need to configure our AuthModule to use the Passport features we just defined. Update auth.module.ts to look like this:

  1. @@filename(auth/auth.module)
  2. import { Module } from '@nestjs/common';
  3. import { AuthService } from './auth.service';
  4. import { UsersModule } from '../users/users.module';
  5. import { PassportModule } from '@nestjs/passport';
  6. import { LocalStrategy } from './local.strategy';
  7. @Module({
  8. imports: [UsersModule, PassportModule],
  9. providers: [AuthService, LocalStrategy],
  10. })
  11. export class AuthModule {}
  12. @@switch
  13. import { Module } from '@nestjs/common';
  14. import { AuthService } from './auth.service';
  15. import { UsersModule } from '../users/users.module';
  16. import { PassportModule } from '@nestjs/passport';
  17. import { LocalStrategy } from './local.strategy';
  18. @Module({
  19. imports: [UsersModule, PassportModule],
  20. providers: [AuthService, LocalStrategy],
  21. })
  22. export class AuthModule {}

Built-in Passport Guards

The Guards chapter describes the primary function of Guards: to determine whether a request will be handled by the route handler or not. That remains true, and we’ll use that standard capability soon. However, in the context of using the @nestjs/passport module, we will also introduce a slight new wrinkle that may at first be confusing, so let’s discuss that now. Consider that your app can exist in two states, from an authentication perspective:

  1. the user/client is not logged in (is not authenticated)
  2. the user/client is logged in (is authenticated)

In the first case (user is not logged in), we need to perform two distinct functions:

  • Restrict the routes an unauthenticated user can access (i.e., deny access to restricted routes). We’ll use Guards in their familiar capacity to handle this function, by placing a Guard on the protected routes. As you may anticipate, we’ll be checking for the presence of a valid JWT in this Guard, so we’ll work on this Guard later, once we are successfully issuing JWTs.

  • Initiate the authentication step itself when a previously unauthenticated user attempts to login. This is the step where we’ll issue a JWT to a valid user. Thinking about this for a moment, we know we’ll need to POST username/password credentials to initiate authentication, so we’ll set up a POST /auth/login route to handle that. This raises the question: how exactly do we invoke the passport-local strategy in that route?

The answer is straightforward: by using another, slightly different type of Guard. The @nestjs/passport module provides us with a built-in Guard that does this for us. This Guard invokes the Passport strategy and kicks off the steps described above (retrieving credentials, running the verify function, creating the user property, etc).

The second case enumerated above (logged in user) simply relies on the standard type of Guard we already discussed to enable access to protected routes for logged in users.

Login route

With the strategy in place, we can now implement a bare-bones /auth/login route, and apply the built-in Guard to initiate the passport-local flow.

Open the app.controller.ts file and replace its contents with the following:

  1. @@filename(app.controller)
  2. import { Controller, Request, Post, UseGuards } from '@nestjs/common';
  3. import { AuthGuard } from '@nestjs/passport';
  4. @Controller()
  5. export class AppController {
  6. @UseGuards(AuthGuard('local'))
  7. @Post('auth/login')
  8. async login(@Request() req) {
  9. return req.user;
  10. }
  11. }
  12. @@switch
  13. import { Controller, Bind, Request, Post, UseGuards } from '@nestjs/common';
  14. import { AuthGuard } from '@nestjs/passport';
  15. @Controller()
  16. export class AppController {
  17. @UseGuards(AuthGuard('local'))
  18. @Post('auth/login')
  19. @Bind(Req())
  20. async login(req) {
  21. return req.user;
  22. }
  23. }

With @UseGuards(AuthGuard('local')) we are using an AuthGuard that @nestjs/passport automatically provisioned for us when we extended the passport-local strategy. Let’s break that down. Our Passport local strategy has a default name of 'local'. We reference that name in the @UseGuards() decorator to associate it with code supplied by the passport-local package. This is used to disambiguate which strategy to invoke in case we have multiple Passport strategies in our app (each of which may provision a strategy-specific AuthGuard). While we only have one such strategy so far, we’ll shortly add a second, so this is needed for disambiguation.

In order to test our route we’ll have our /auth/login route simply return the user for now. This also lets us demonstrate another Passport feature: Passport automatically creates a user object, based on the value we return from the validate() method, and assigns it to the Request object as req.user. Later, we’ll replace this with code to create and return a JWT instead.

Since these are API routes, we’ll test them using the commonly available cURL library. You can test with any of the user objects hard-coded in the UsersService.

  1. $ # POST to /auth/login
  2. $ curl -X POST http://localhost:3000/auth/login -d '{"username": "john", "password": "changeme"}' -H "Content-Type: application/json"
  3. $ # result -> {"userId":1,"username":"john"}

JWT functionality

We’re ready to move on to the JWT portion of our auth system. Let’s review and refine our requirements:

  • Allow users to authenticate with username/password, returning a JWT for use in subsequent calls to protected API endpoints. We’re well on our way to meeting this requirement. To complete it, we’ll need to write the code that issues a JWT.
  • Create API routes which are protected based on the presence of a valid JWT as a bearer token

We’ll need to install a couple more packages to support our JWT requirements:

  1. $ npm install @nestjs/jwt passport-jwt
  2. $ npm install @types/passport-jwt --save-dev

The @nest/jwt package (see more here) is a utility package that helps with JWT manipulation. The passport-jwt package is the Passport package that implements the JWT strategy and @types/passport-jwt provides the TypeScript type definitions.

Let’s take a closer look at how a POST /auth/login request is handled. We’ve decorated the route using the built-in AuthGuard provided by the passport-local strategy. This means that:

  1. The route handler will only be invoked if the user has been validated
  2. The req parameter will contain a user property (populated by Passport during the passport-local authentication flow)

With this in mind, we can now finally generate a real JWT, and return it in this route. To keep our services cleanly modularized, we’ll handle generating the JWT in the authService. Open the auth.service.ts file in the auth folder, and add the login() method, and import the JwtService as shown:

  1. @@filename(auth/auth.service)
  2. import { Injectable } from '@nestjs/common';
  3. import { UsersService } from '../users/users.service';
  4. import { JwtService } from '@nestjs/jwt';
  5. @Injectable()
  6. export class AuthService {
  7. constructor(
  8. private readonly usersService: UsersService,
  9. private readonly jwtService: JwtService
  10. ) {}
  11. async validateUser(username: string, pass: string): Promise<any> {
  12. const user = await this.usersService.findOne(username);
  13. if (user && user.password === pass) {
  14. const { password, ...result } = user;
  15. return result;
  16. }
  17. return null;
  18. }
  19. async login(user: any) {
  20. const payload = { username: user.username, sub: user.userId };
  21. return {
  22. access_token: this.jwtService.sign(payload),
  23. };
  24. }
  25. }
  26. @@switch
  27. import { Injectable, Dependencies } from '@nestjs/common';
  28. import { UsersService } from '../users/users.service';
  29. import { JwtService } from '@nestjs/jwt';
  30. @Dependencies(UsersService, JwtService)
  31. @Injectable()
  32. export class AuthService {
  33. constructor(usersService, jwtService)
  34. ) {
  35. this.usersService = usersService;
  36. this.jwtService = jwtService;
  37. }
  38. async validateUser(username, pass) {
  39. const user = await this.usersService.findOne(username);
  40. if (user && user.password === pass) {
  41. const { password, ...result } = user;
  42. return result;
  43. }
  44. return null;
  45. }
  46. async login(user) {
  47. const payload = { username: user.username, sub: user.userId };
  48. return {
  49. access_token: this.jwtService.sign(payload),
  50. };
  51. }
  52. }

We’re using the @nestjs/jwt library, which supplies a sign() function to generate our JWT from a subset of the user object properties, which we then return as a simple object with a single access_token property. Note: we choose a property name of sub to hold our userId value to be consistent with JWT standards. Don’t forget to inject the JwtService provider into the AuthService.

We now need to update the AuthModule to import the new dependencies and configure the JwtModule.

First, create constants.ts in the auth folder, and add the following code:

  1. @@filename(auth/constants)
  2. export const jwtConstants = {
  3. secret: 'secretKey',
  4. };
  5. @@switch
  6. export const jwtConstants = {
  7. secret: 'secretKey',
  8. };

We’ll use this to share our key between the JWT signing and verifying steps.

Warning Warning Do not expose this key publicly. We have done so here to make it clear what the code is doing, but in a production system you must protect this key using appropriate measures such as a secrets vault, environment variable, or configuration service.

Now, open auth.module.ts in the auth folder and update it to look like this:

  1. @@filename(auth/auth.module)
  2. import { Module } from '@nestjs/common';
  3. import { AuthService } from './auth.service';
  4. import { LocalStrategy } from './local.strategy';
  5. import { UsersModule } from '../users/users.module';
  6. import { PassportModule } from '@nestjs/passport';
  7. import { JwtModule } from '@nestjs/jwt';
  8. import { jwtConstants } from './constants';
  9. @Module({
  10. imports: [
  11. UsersModule,
  12. PassportModule,
  13. JwtModule.register({
  14. secret: jwtConstants.secret,
  15. signOptions: { expiresIn: '60s' },
  16. }),
  17. ],
  18. providers: [AuthService, LocalStrategy],
  19. exports: [AuthService],
  20. })
  21. export class AuthModule {}
  22. @@switch
  23. import { Module } from '@nestjs/common';
  24. import { AuthService } from './auth.service';
  25. import { LocalStrategy } from './local.strategy';
  26. import { UsersModule } from '../users/users.module';
  27. import { PassportModule } from '@nestjs/passport';
  28. import { JwtModule } from '@nestjs/jwt';
  29. import { jwtConstants } from './constants';
  30. @Module({
  31. imports: [
  32. UsersModule,
  33. PassportModule,
  34. JwtModule.register({
  35. secret: jwtConstants.secret,
  36. signOptions: { expiresIn: '60s' },
  37. }),
  38. ],
  39. providers: [AuthService, LocalStrategy],
  40. exports: [AuthService],
  41. })
  42. export class AuthModule {}

We configure the JwtModule using register(), passing in a configuration object. See here for more on the Nest JwtModule and here for more details on the available configuration options.

Now we can update the /auth/login route to return a JWT.

  1. @@filename(app.controller)
  2. import { Controller, Request, Post, UseGuards } from '@nestjs/common';
  3. import { AuthGuard } from '@nestjs/passport';
  4. import { AuthService } from './auth/auth.service';
  5. @Controller()
  6. export class AppController {
  7. constructor(private readonly authService: AuthService) {}
  8. @UseGuards(AuthGuard('local'))
  9. @Post('auth/login')
  10. async login(@Request() req) {
  11. return this.authService.login(req.user);
  12. }
  13. }
  14. @@switch
  15. import { Controller, Bind, Request, Post, UseGuards } from '@nestjs/common';
  16. import { AuthGuard } from '@nestjs/passport';
  17. import { AuthService } from './auth/auth.service';
  18. @Controller()
  19. export class AppController {
  20. constructor(private readonly authService: AuthService) {}
  21. @UseGuards(AuthGuard('local'))
  22. @Post('auth/login')
  23. @Bind(Req())
  24. async login(req) {
  25. return this.authService.login(req.user);
  26. }
  27. }

Let’s go ahead and test our routes using cURL again. You can test with any of the user objects hard-coded in the UsersService.

  1. $ # POST to /auth/login
  2. $ curl -X POST http://localhost:3000/auth/login -d '{"username": "john", "password": "changeme"}' -H "Content-Type: application/json"
  3. $ # result -> {"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
  4. $ # Note: above JWT truncated

Implementing Passport JWT

We can now address our final requirement: protecting endpoints by requiring a valid JWT be present on the request. Passport can help us here too. It provides the passport-jwt strategy for securing RESTful endpoints with JSON Web Tokens. Start by creating a file called jwt.strategy.ts in the auth folder, and add the following code:

  1. @@filename(auth/jwt.strategy)
  2. import { ExtractJwt, Strategy } from 'passport-jwt';
  3. import { PassportStrategy } from '@nestjs/passport';
  4. import { Injectable } from '@nestjs/common';
  5. import { jwtConstants } from './constants';
  6. @Injectable()
  7. export class JwtStrategy extends PassportStrategy(Strategy) {
  8. constructor() {
  9. super({
  10. jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  11. ignoreExpiration: false,
  12. secretOrKey: jwtConstants.secret,
  13. });
  14. }
  15. async validate(payload: any) {
  16. return { userId: payload.sub, username: payload.username };
  17. }
  18. }
  19. @@switch
  20. import { ExtractJwt, Strategy } from 'passport-jwt';
  21. import { PassportStrategy } from '@nestjs/passport';
  22. import { Injectable } from '@nestjs/common';
  23. import { jwtConstants } from './constants';
  24. @Injectable()
  25. export class JwtStrategy extends PassportStrategy(Strategy) {
  26. constructor() {
  27. super({
  28. jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  29. ignoreExpiration: false,
  30. secretOrKey: jwtConstants.secret,
  31. });
  32. }
  33. async validate(payload) {
  34. return { userId: payload.sub, username: payload.username };
  35. }
  36. }

With our JwtStrategy, we’ve followed the same recipe described earlier for all Passport strategies. This strategy requires some initialization, so we do that by passing in an options object in the super() call. You can read more about the available options here. In our case, these options are:

  • jwtFromRequest: supplies the method by which the JWT will be extracted from the Request. We will use the standard approach of supplying a bearer token in the Authorization header of our API requests. Other options are described here.
  • ignoreExpiration: just to be explicit, we choose the default false setting, which delegates the responsibility of ensuring that a JWT has not expired to the Passport module. This means that if our route is supplied with an expired JWT, the request will be denied and a 401 Unauthorized response sent. Passport conveniently handles this automatically for us.
  • secretOrKey: we are using the expedient option of supplying a symmetric secret for signing the token. Other options, such as a PEM-encoded public key, may be more appropriate for production apps (see here for more information). In any case, as cautioned earlier, do not expose this secret publicly.

The validate() method deserves some discussion. For the jwt-strategy, Passport first verifies the JWT’s signature and decodes the JSON. It then invokes our validate() method passing the decoded JSON as its single parameter. Based on the way JWT signing works, we’re guaranteed that we’re receiving a valid token that we have previously signed and issued to a valid user.

As a result of all this, our response to the validate() callback is trivial: we simply return an object containing the userId and username properties. Recall again that Passport will build a user object based on the return value of our validate() method, and attach it as a property on the Request object.

It’s also worth pointing out that this approach leaves us room (‘hooks’ as it were) to inject other business logic into the process. For example, we could do a database lookup in our validate() method to extract more information about the user, resulting in a more enriched user object being available in our Request. This is also the place we may decide to do further token validation, such as looking up the userId in a list of revoked tokens, enabling us to perform token revocation. The model we’ve implemented here in our sample code is a fast, “stateless JWT” model, where each API call is immediately authorized based on the presence of a valid JWT, and a small bit of information about the requester (its userId and username) is available in our Request pipeline.

Add the new JwtStrategy as a provider in the AuthModule:

  1. @@filename(auth/auth.module)
  2. import { Module } from '@nestjs/common';
  3. import { AuthService } from './auth.service';
  4. import { LocalStrategy } from './local.strategy';
  5. import { JwtStrategy } from './jwt.strategy';
  6. import { UsersModule } from '../users/users.module';
  7. import { PassportModule } from '@nestjs/passport';
  8. import { JwtModule } from '@nestjs/jwt';
  9. import { jwtConstants } from './constants';
  10. @Module({
  11. imports: [
  12. UsersModule,
  13. PassportModule,
  14. JwtModule.register({
  15. secret: jwtConstants.secret,
  16. signOptions: { expiresIn: '60s' },
  17. }),
  18. ],
  19. providers: [AuthService, LocalStrategy, JwtStrategy],
  20. exports: [AuthService],
  21. })
  22. export class AuthModule {}
  23. @@switch
  24. import { Module } from '@nestjs/common';
  25. import { AuthService } from './auth.service';
  26. import { LocalStrategy } from './local.strategy';
  27. import { JwtStrategy } from './jwt.strategy';
  28. import { UsersModule } from '../users/users.module';
  29. import { PassportModule } from '@nestjs/passport';
  30. import { JwtModule } from '@nestjs/jwt';
  31. import { jwtConstants } from './constants';
  32. @Module({
  33. imports: [
  34. UsersModule,
  35. PassportModule,
  36. JwtModule.register({
  37. secret: jwtConstants.secret,
  38. signOptions: { expiresIn: '60s' },
  39. }),
  40. ],
  41. providers: [AuthService, LocalStrategy, JwtStrategy],
  42. exports: [AuthService],
  43. })
  44. export class AuthModule {}

By importing the same secret used when we signed the JWT, we ensure that the verify phase performed by Passport, and the sign phase performed in our AuthService, use a common secret.

Implement protected route and JWT strategy guards

We can now implement our protected route and its associated Guard.

Open the app.controller.ts file and update it as shown below:

  1. @@filename(app.controller)
  2. import { Controller, Get, Request, Post, UseGuards } from '@nestjs/common';
  3. import { AuthGuard } from '@nestjs/passport';
  4. import { AuthService } from './auth/auth.service';
  5. @Controller()
  6. export class AppController {
  7. constructor(private readonly authService: AuthService) {}
  8. @UseGuards(AuthGuard('local'))
  9. @Post('auth/login')
  10. async login(@Request() req) {
  11. return this.authService.login(req.user);
  12. }
  13. @UseGuards(AuthGuard('jwt'))
  14. @Get('profile')
  15. getProfile(@Request() req) {
  16. return req.user;
  17. }
  18. }
  19. @@switch
  20. import { Controller, Bind, Get, Request, Post, UseGuards } from '@nestjs/common';
  21. import { AuthGuard } from '@nestjs/passport';
  22. import { AuthService } from './auth/auth.service';
  23. @Controller()
  24. export class AppController {
  25. constructor(private readonly authService: AuthService) {}
  26. @UseGuards(AuthGuard('local'))
  27. @Post('auth/login')
  28. @Bind(Req())
  29. async login(req) {
  30. return this.authService.login(req.user);
  31. }
  32. @UseGuards(AuthGuard('jwt'))
  33. @Get('profile')
  34. @Bind(Req())
  35. getProfile(req) {
  36. return req.user;
  37. }
  38. }

Once again, we’re applying the AuthGuard that the @nestjs/passport module has automatically provisioned for us when we configured the passport-jwt module. This Guard is referenced by its default name, jwt. When our GET /profile route is hit, the Guard will automatically invoke our passport-jwt custom configured logic, validating the JWT, and assigning the user property to the Request object.

Ensure the app is running, and test the routes using cURL.

  1. $ # GET /profile
  2. $ curl http://localhost:3000/profile
  3. $ # result -> {"statusCode":401,"error":"Unauthorized"}
  4. $ # POST /auth/login
  5. $ curl -X POST http://localhost:3000/auth/login -d '{"username": "john", "password": "changeme"}' -H "Content-Type: application/json"
  6. $ # result -> {"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2Vybm... }
  7. $ # GET /profile using access_token returned from previous step as bearer code
  8. $ curl http://localhost:3000/profile -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2Vybm..."
  9. $ # result -> {"userId":1,"username":"john"}

Note that in the AuthModule, we configured the JWT to have an expiration of 60 seconds. This is probably too short an expiration, and dealing with the details of token expiration and refresh is beyond the scope of this article. However, we chose that to demonstrate an important quality of JWTs and the passport-jwt strategy. If you wait 60 seconds after authenticating before attempting a GET /profile request, you’ll receive a 401 Unauthorized response. This is because Passport automatically checks the JWT for its expiration time, saving you the trouble of doing so in your application.

We’ve now completed our JWT authentication implementation. JavaScript clients (such as Angular/React/Vue), and other JavaScript apps, can now authenticate and communicate securely with our API Server. You can find a complete version of the code in this chapter here.

Default strategy

In our AppController, we pass the name of the strategy in the @AuthGuard() decorator. We need to do this because we’ve introduced two Passport strategies (passport-local and passport-jwt), both of which supply implementations of various Passport components. Passing the name disambiguates which implementation we’re linking to. When multiple strategies are included in an application, we can declare a default strategy so that we no longer have to pass the name in the @AuthGuard decorator if using that default strategy. Here’s how to register a default strategy when importing the PassportModule. This code would go in the AuthModule:

  1. import { Module } from '@nestjs/common';
  2. import { AuthService } from './auth.service';
  3. import { LocalStrategy } from './local.strategy';
  4. import { UsersModule } from '../users/users.module';
  5. import { PassportModule } from '@nestjs/passport';
  6. import { JwtModule } from '@nestjs/jwt';
  7. import { jwtConstants } from './constants';
  8. import { JwtStrategy } from './jwt.strategy';
  9. @Module({
  10. imports: [
  11. PassportModule.register({ defaultStrategy: 'jwt' }),
  12. JwtModule.register({
  13. secret: jwtConstants.secret,
  14. signOptions: { expiresIn: '60s' },
  15. }),
  16. UsersModule
  17. ],
  18. providers: [AuthService, LocalStrategy, JwtStrategy],
  19. exports: [AuthService],
  20. })
  21. export class AuthModule {}

Customize Passport

Any standard Passport customization options can be passed the same way, using the register() method. The available options depend on the strategy being implemented. For example:

  1. PassportModule.register({ session: true });

You can also pass strategies an options object in their constructors to configure them.For the local strategy you can pass e.g.:

  1. constructor(private readonly authService: AuthService) {
  2. super({
  3. usernameField: 'email',
  4. passwordField: 'password',
  5. });
  6. }

Take a look at the official Passport Website for property names.

Named strategies

When implementing a strategy, you can provide a name for it by passing a second argument to the PassportStrategy function. If you don’t do this, each strategy will have a default name (e.g., ‘jwt’ for jwt-strategy):

  1. export class JwtStrategy extends PassportStrategy(Strategy, 'myjwt')

Then, you refer to this via a decorator like @AuthGuard('myjwt').

GraphQL

In order to use an AuthGuard with GraphQL, extend the built-in AuthGuard class and override the getRequest() method.

  1. @Injectable()
  2. export class GqlAuthGuard extends AuthGuard('jwt') {
  3. getRequest(context: ExecutionContext) {
  4. const ctx = GqlExecutionContext.create(context);
  5. return ctx.getContext().req;
  6. }
  7. }

To use the above construct, be sure to pass the request (req) object as part of the context value in the GraphQL Module settings:

  1. GraphQLModule.forRoot({
  2. context: ({ req }) => ({ req }),
  3. });

To get the current authenticated user in your graphql resolver, you can define a User decorator:

  1. import { createParamDecorator } from '@nestjs/common';
  2. export const CurrentUser = createParamDecorator(
  3. (data, [root, args, ctx, info]) => ctx.req.user,
  4. );

To use above decorator in your resolver, be sure to include it as a parameter of your query or mutation:

  1. @Query(returns => User)
  2. @UseGuards(GqlAuthGuard)
  3. whoAmI(@CurrentUser() user: User) {
  4. return this.userService.findById(user.id);
  5. }