- 1. API with NestJS #1. Controllers, routing and the module structure
- 2. API with NestJS #2. Setting up a PostgreSQL database with TypeORM
- 3. API with NestJS #3. Authenticating users with bcrypt, Passport, JWT, and cookies
- 4. API with NestJS #4. Error handling and data validation
- 5. API with NestJS #5. Serializing the response with interceptors
- 6. API with NestJS #6. Looking into dependency injection and modules
- 7. API with NestJS #7. Creating relationships with Postgres and TypeORM
- 8. API with NestJS #8. Writing unit tests
- 9. API with NestJS #9. Testing services and controllers with integration tests
- 10. API with NestJS #10. Uploading public files to Amazon S3
- 11. API with NestJS #11. Managing private files with Amazon S3
- 12. API with NestJS #12. Introduction to Elasticsearch
- 13. API with NestJS #13. Implementing refresh tokens using JWT
- 14. API with NestJS #14. Improving performance of our Postgres database with indexes
- 15. API with NestJS #15. Defining transactions with PostgreSQL and TypeORM
- 16. API with NestJS #16. Using the array data type with PostgreSQL and TypeORM
- 17. API with NestJS #17. Offset and keyset pagination with PostgreSQL and TypeORM
- 18. API with NestJS #18. Exploring the idea of microservices
- 19. API with NestJS #19. Using RabbitMQ to communicate with microservices
- 20. API with NestJS #20. Communicating with microservices using the gRPC framework
- 21. API with NestJS #21. An introduction to CQRS
- 22. API with NestJS #22. Storing JSON with PostgreSQL and TypeORM
- 23. API with NestJS #23. Implementing in-memory cache to increase the performance
- 24. API with NestJS #24. Cache with Redis. Running the app in a Node.js cluster
- 25. API with NestJS #25. Sending scheduled emails with cron and Nodemailer
- 26. API with NestJS #26. Real-time chat with WebSockets
- 27. API with NestJS #27. Introduction to GraphQL. Queries, mutations, and authentication
- 28. API with NestJS #28. Dealing in the N + 1 problem in GraphQL
- 29. API with NestJS #29. Real-time updates with GraphQL subscriptions
- 30. API with NestJS #30. Scalar types in GraphQL
- 31. API with NestJS #31. Two-factor authentication
- 32. API with NestJS #32. Introduction to Prisma with PostgreSQL
- 33. API with NestJS #33. Managing PostgreSQL relationships with Prisma
- 34. API with NestJS #34. Handling CPU-intensive tasks with queues
- 35. API with NestJS #35. Using server-side sessions instead of JSON Web Tokens
- 36. API with NestJS #36. Introduction to Stripe with React
- 37. API with NestJS #37. Using Stripe to save credit cards for future use
- 38. API with NestJS #38. Setting up recurring payments via subscriptions with Stripe
- 39. API with NestJS #39. Reacting to Stripe events with webhooks
- 40. API with NestJS #40. Confirming the email address
- 41. API with NestJS #41. Verifying phone numbers and sending SMS messages with Twilio
- 42. API with NestJS #42. Authenticating users with Google
- 43. API with NestJS #43. Introduction to MongoDB
- 44. API with NestJS #44. Implementing relationships with MongoDB
- 45. API with NestJS #45. Virtual properties with MongoDB and Mongoose
- 46. API with NestJS #46. Managing transactions with MongoDB and Mongoose
- 47. API with NestJS #47. Implementing pagination with MongoDB and Mongoose
- 48. API with NestJS #48. Definining indexes with MongoDB and Mongoose
- 49. API with NestJS #49. Updating with PUT and PATCH with MongoDB and Mongoose
- 50. API with NestJS #50. Introduction to logging with the built-in logger and TypeORM
- 51. API with NestJS #51. Health checks with Terminus and Datadog
- 52. API with NestJS #52. Generating documentation with Compodoc and JSDoc
- 53. API with NestJS #53. Implementing soft deletes with PostgreSQL and TypeORM
- 54. API with NestJS #54. Storing files inside a PostgreSQL database
- 55. API with NestJS #55. Uploading files to the server
- 56. API with NestJS #56. Authorization with roles and claims
- 57. API with NestJS #57. Composing classes with the mixin pattern
- 58. API with NestJS #58. Using ETag to implement cache and save bandwidth
- 59. API with NestJS #59. Introduction to a monorepo with Lerna and Yarn workspaces
- 60. API with NestJS #60. The OpenAPI specification and Swagger
- 61. API with NestJS #61. Dealing with circular dependencies
- 62. API with NestJS #62. Introduction to MikroORM with PostgreSQL
- 63. API with NestJS #63. Relationships with PostgreSQL and MikroORM
- 64. API with NestJS #64. Transactions with PostgreSQL and MikroORM
- 65. API with NestJS #65. Implementing soft deletes using MikroORM and filters
- 66. API with NestJS #66. Improving PostgreSQL performance with indexes using MikroORM
- 67. API with NestJS #67. Migrating to TypeORM 0.3
- 68. API with NestJS #68. Interacting with the application through REPL
- 69. API with NestJS #69. Database migrations with TypeORM
- 70. API with NestJS #70. Defining dynamic modules
- 71. API with NestJS #71. Introduction to feature flags
- 72. API with NestJS #72. Working with PostgreSQL using raw SQL queries
- 73. API with NestJS #73. One-to-one relationships with raw SQL queries
- 74. API with NestJS #74. Designing many-to-one relationships using raw SQL queries
- 75. API with NestJS #75. Many-to-many relationships using raw SQL queries
- 76. API with NestJS #76. Working with transactions using raw SQL queries
- 77. API with NestJS #77. Offset and keyset pagination with raw SQL queries
- 78. API with NestJS #78. Generating statistics using aggregate functions in raw SQL
- 79. API with NestJS #79. Implementing searching with pattern matching and raw SQL
- 80. API with NestJS #80. Updating entities with PUT and PATCH using raw SQL queries
- 81. API with NestJS #81. Soft deletes with raw SQL queries
- 82. API with NestJS #82. Introduction to indexes with raw SQL queries
- 83. API with NestJS #83. Text search with tsvector and raw SQL
- 84. API with NestJS #84. Implementing filtering using subqueries with raw SQL
- 85. API with NestJS #85. Defining constraints with raw SQL
- 86. API with NestJS #86. Logging with the built-in logger when using raw SQL
- 87. API with NestJS #87. Writing unit tests in a project with raw SQL
- 88. API with NestJS #88. Testing a project with raw SQL using integration tests
- 89. API with NestJS #89. Replacing Express with Fastify
- 90. API with NestJS #90. Using various types of SQL joins
- 91. API with NestJS #91. Dockerizing a NestJS API with Docker Compose
- 92. API with NestJS #92. Increasing the developer experience with Docker Compose
- 93. API with NestJS #93. Deploying a NestJS app with Amazon ECS and RDS
- 94. API with NestJS #94. Deploying multiple instances on AWS with a load balancer
- 95. API with NestJS #95. CI/CD with Amazon ECS and GitHub Actions
- 96. API with NestJS #96. Running unit tests with CI/CD and GitHub Actions
- 97. API with NestJS #97. Introduction to managing logs with Amazon CloudWatch
- 98. API with NestJS #98. Health checks with Terminus and Amazon ECS
- 99. API with NestJS #99. Scaling the number of application instances with Amazon ECS
- 100. API with NestJS #100. The HTTPS protocol with Route 53 and AWS Certificate Manager
- 101. API with NestJS #101. Managing sensitive data using the AWS Secrets Manager
- 102. API with NestJS #102. Writing unit tests with Prisma
- 103. API with NestJS #103. Integration tests with Prisma
- 104. API with NestJS #104. Writing transactions with Prisma
- 105. API with NestJS #105. Implementing soft deletes with Prisma and middleware
- 106. API with NestJS #106. Improving performance through indexes with Prisma
- 107. API with NestJS #107. Offset and keyset pagination with Prisma
- 108. API with NestJS #108. Date and time with Prisma and PostgreSQL
- 109. API with NestJS #109. Arrays with PostgreSQL and Prisma
- 110. API with NestJS #110. Managing JSON data with PostgreSQL and Prisma
- 111. API with NestJS #111. Constraints with PostgreSQL and Prisma
- 112. API with NestJS #112. Serializing the response with Prisma
- 113. API with NestJS #113. Logging with Prisma
- 114. API with NestJS #114. Modifying data using PUT and PATCH methods with Prisma
- 115. API with NestJS #115. Database migrations with Prisma
- 116. API with NestJS #116. REST API versioning
- 117. API with NestJS #117. CORS – Cross-Origin Resource Sharing
- 118. API with NestJS #118. Uploading and streaming videos
- 119. API with NestJS #119. Type-safe SQL queries with Kysely and PostgreSQL
- 120. API with NestJS #120. One-to-one relationships with the Kysely query builder
- 121. API with NestJS #121. Many-to-one relationships with PostgreSQL and Kysely
- 122. API with NestJS #122. Many-to-many relationships with Kysely and PostgreSQL
- 123. API with NestJS #123. SQL transactions with Kysely
- 124. API with NestJS #124. Handling SQL constraints with Kysely
- 125. API with NestJS #125. Offset and keyset pagination with Kysely
- 126. API with NestJS #126. Improving the database performance with indexes and Kysely
- 127. API with NestJS #127. Arrays with PostgreSQL and Kysely
- 128. API with NestJS #128. Managing JSON data with PostgreSQL and Kysely
- 129. API with NestJS #129. Implementing soft deletes with SQL and Kysely
- 130. API with NestJS #130. Avoiding storing sensitive information in API logs
- 131. API with NestJS #131. Unit tests with PostgreSQL and Kysely
- 132. API with NestJS #132. Handling date and time in PostgreSQL with Kysely
- 133. API with NestJS #133. Introducing database normalization with PostgreSQL and Prisma
- 134. API with NestJS #134. Aggregating statistics with PostgreSQL and Prisma
- 135. API with NestJS #135. Referential actions and foreign keys in PostgreSQL with Prisma
- 136. API with NestJS #136. Raw SQL queries with Prisma and PostgreSQL range types
- 137. API with NestJS #137. Recursive relationships with Prisma and PostgreSQL
- 138. API with NestJS #138. Filtering records with Prisma
- 139. API with NestJS #139. Using UUID as primary keys with Prisma and PostgreSQL
- 140. API with NestJS #140. Using multiple PostgreSQL schemas with Prisma
- 141. API with NestJS #141. Getting distinct records with Prisma and PostgreSQL
- 142. API with NestJS #142. A video chat with WebRTC and React
- 143. API with NestJS #143. Optimizing queries with views using PostgreSQL and Kysely
- 144. API with NestJS #144. Creating CLI applications with the Nest Commander
- 145. API with NestJS #145. Securing applications with Helmet
- 146. API with NestJS #146. Polymorphic associations with PostgreSQL and Prisma
- 147. API with NestJS #147. The data types to store money with PostgreSQL and Prisma
- 148. API with NestJS #148. Understanding the injection scopes
- 149. API with NestJS #149. Introduction to the Drizzle ORM with PostgreSQL
- 150. API with NestJS #150. One-to-one relationships with the Drizzle ORM
- 151. API with NestJS #151. Implementing many-to-one relationships with Drizzle ORM
- 152. API with NestJS #152. SQL constraints with the Drizzle ORM
- 153. API with NestJS #153. SQL transactions with the Drizzle ORM
- 154. API with NestJS #154. Many-to-many relationships with Drizzle ORM and PostgreSQL
- 155. API with NestJS #155. Offset and keyset pagination with the Drizzle ORM
- 156. API with NestJS #156. Arrays with PostgreSQL and the Drizzle ORM
- 157. API with NestJS #157. Handling JSON data with PostgreSQL and the Drizzle ORM
- 158. API with NestJS #158. Soft deletes with the Drizzle ORM
- 159. API with NestJS #159. Date and time with PostgreSQL and the Drizzle ORM
- 160. API with NestJS #160. Using views with the Drizzle ORM and PostgreSQL
- 161. API with NestJS #161. Generated columns with the Drizzle ORM and PostgreSQL
- 162. API with NestJS #162. Identity columns with the Drizzle ORM and PostgreSQL
- 163. API with NestJS #163. Full-text search with the Drizzle ORM and PostgreSQL
- 164. API with NestJS #164. Improving the performance with indexes using Drizzle ORM
- 165. API with NestJS #165. Time intervals with the Drizzle ORM and PostgreSQL
- 166. API with NestJS #166. Logging with the Drizzle ORM
- 167. API with NestJS #167. Unit tests with the Drizzle ORM
- 168. API with NestJS #168. Integration tests with the Drizzle ORM
- 169. API with NestJS #169. Unique IDs with UUIDs using Drizzle ORM and PostgreSQL
- 170. API with NestJS #170. Polymorphic associations with PostgreSQL and Drizzle ORM
- 171. API with NestJS #171. Recursive relationships with Drizzle ORM and PostgreSQL
- 172. API with NestJS #172. Database normalization with Drizzle ORM and PostgreSQL
- 173. API with NestJS #173. Storing money with Drizzle ORM and PostgreSQL
- 174. API with NestJS #174. Multiple PostgreSQL schemas with Drizzle ORM
- 175. API with NestJS #175. PUT and PATCH requests with PostgreSQL and Drizzle ORM
- 176. API with NestJS #176. Database migrations with the Drizzle ORM
- 177. API with NestJS #177. Response serialization with the Drizzle ORM
Authentication is a crucial part of almost every web application. There are many ways to approach it, and we’ve handled it manually in our TypeScript Express series. This time we look into the passport, which is the most popular Node.js authentication library. We also register users and make their passwords secure by hashing.
You can find all of the code from this series in this repository. Feel free to give it a star.
Defining the User entity
The first thing to do when considering authentication is to register our users. To do so, we need to define an entity for our users.
users/user.entity.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity() class User { @PrimaryGeneratedColumn() public id?: number; @Column({ unique: true }) public email: string; @Column() public name: string; @Column() public password: string; } export default User; |
The only new thing above is the unique flag. It indicates that there should not be two users with the same email. This functionality is built into PostgreSQL and helps us to keep the consistency of our data. Later, we depend on emails being unique when authenticating.
We need to perform a few operations on our users. To do so, let’s create a service.
users/users.service.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import User from './user.entity'; import CreateUserDto from './dto/createUser.dto'; @Injectable() export class UsersService { constructor( @InjectRepository(User) private usersRepository: Repository<User> ) {} async getByEmail(email: string) { const user = await this.usersRepository.findOne({ email }); if (user) { return user; } throw new HttpException('User with this email does not exist', HttpStatus.NOT_FOUND); } async create(userData: CreateUserDto) { const newUser = await this.usersRepository.create(userData); await this.usersRepository.save(newUser); return newUser; } } |
users/dto/createUser.dto.ts
1 2 3 4 5 6 7 |
export class CreateUserDto { email: string; name: string; password: string; } export default CreateUserDto; |
All of the above is wrapped using a module.
users/users.module.ts
1 2 3 4 5 6 7 8 9 10 11 |
import { Module } from '@nestjs/common'; import { UsersService } from './users.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import User from './user.entity'; @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], exports: [UsersService] }) export class UsersModule {} |
Handling passwords
An essential thing about registration is that we don’t want to save passwords in plain text. If at any time our database gets breached, our passwords would have been directly exposed.
To make passwords more secure, we hash them. In this process, the hashing algorithm transforms one string into another string. If we change just one character of a string, the outcome is entirely different.
The above operation can only be performed one way and can’t be reversed easily. This means that we don’t know the passwords of our users. When the user attempts to log in, we need to perform this operation once again. Then, we compare the outcome with the one saved in the database.
Since hashing the same string twice gives the same result, we use salt. It prevents users that have the same password from having the same hash. Salt is a random string added to the original password to achieve a different result every time.
Using bcrypt
We use the bcrypt hashing algorithm implemented by the bcrypt npm package. It takes care of hashing the strings, comparing plain strings with hashes, and appending salt.
Using bcrypt might be an intensive task for the CPU. Fortunately, our bcrypt implementation uses a thread pool that allows it to run in an additional thread. Thanks to that, our application can perform other tasks while generating the hash.
1 |
npm install @types/bcrypt bcrypt |
When we use bcrypt, we define salt rounds. It boils down to being a cost factor and controls the time needed to receive a result. Increasing it by one doubles the time. The bigger the cost factor, the more difficult it is to reverse the hash with brute-forcing. Generally speaking, 10 salt rounds should be fine.
The salt used for hashing is a part of the result, so no need to keep it separately.
1 2 3 4 5 |
const passwordInPlaintext = '12345678'; const hash = await bcrypt.hash(passwordInPlaintext, 10); const isPasswordMatching = await bcrypt.compare(passwordInPlaintext, hashedPassword); console.log(isPasswordMatching); // true |
Creating the authentication service
With all of the above knowledge, we can start implementing basic registering and logging in functionalities. To do so, we need to define an authentication service first.
Authentication means checking the identity of user. It provides an answer to a question: who is the user?
Authorization is about access to resources. It answers the question: is user authorized to perform this operation?
authentication/authentication.service.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
export class AuthenticationService { constructor( private readonly usersService: UsersService ) {} public async register(registrationData: RegisterDto) { const hashedPassword = await bcrypt.hash(registrationData.password, 10); try { const createdUser = await this.usersService.create({ ...registrationData, password: hashedPassword }); createdUser.password = undefined; return createdUser; } catch (error) { if (error?.code === PostgresErrorCode.UniqueViolation) { throw new HttpException('User with that email already exists', HttpStatus.BAD_REQUEST); } throw new HttpException('Something went wrong', HttpStatus.INTERNAL_SERVER_ERROR); } } // (...) } |
createdUser.password = undefined is not the cleanest way to not send the password in a response. In the upcoming parts of this series we explore mechanisms that help us with that.
A few notable things are happening above. We create a hash and pass it to the usersService.create method along with the rest of the data. We use a try...catch statement here because there is an important case when it might fail. If a user with that email already exists, the usersService.create method throws an error. Since our unique column cases it the error comes from Postgres.
To understand the error, we need to look into the PostgreSQL Error Codes documentation page. Since the code for uniqe_violation is 23505, we can create an enum to handle it cleanly.
database/postgresErrorCodes.enum.ts
1 2 3 |
enum PostgresErrorCode { UniqueViolation = '23505' } |
Since in the above service we state explicitly that a user with this email already exists, it might a good idea to implement a mechanism preventing attackers from brute-forcing our API in order to get a list of registered emails
The thing left for us to do is to implement the logging in.
authentication/authentication.service.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
export class AuthenticationService { constructor( private readonly usersService: UsersService ) {} // (...) public async getAuthenticatedUser(email: string, hashedPassword: string) { try { const user = await this.usersService.getByEmail(email); const isPasswordMatching = await bcrypt.compare( hashedPassword, user.password ); if (!isPasswordMatching) { throw new HttpException('Wrong credentials provided', HttpStatus.BAD_REQUEST); } user.password = undefined; return user; } catch (error) { throw new HttpException('Wrong credentials provided', HttpStatus.BAD_REQUEST); } } } |
An important thing above is that we return the same error, whether the email or password is wrong. Doing so prevents some attacks that would aim to get a list of emails registered in our database.
There is one small thing about the above code that we might want to improve. Within our logIn method, we throw an exception that we then catch locally. It might be considered confusing. Let’s create a separate method to verify the password:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public async getAuthenticatedUser(email: string, plainTextPassword: string) { try { const user = await this.usersService.getByEmail(email); await this.verifyPassword(plainTextPassword, user.password); user.password = undefined; return user; } catch (error) { throw new HttpException('Wrong credentials provided', HttpStatus.BAD_REQUEST); } } private async verifyPassword(plainTextPassword: string, hashedPassword: string) { const isPasswordMatching = await bcrypt.compare( plainTextPassword, hashedPassword ); if (!isPasswordMatching) { throw new HttpException('Wrong credentials provided', HttpStatus.BAD_REQUEST); } } |
Integrating our authentication with Passport
In the TypeScript Express series, we’ve handled the whole authentication process manually. NestJS documentation suggests using the Passport library and provides us with the means to do so. Passport gives us an abstraction over the authentication, thus relieving us from some heavy lifting. Also, it is heavily tested in production by many developers.
Diving into how to implement the authentication manually without Passport is still a good idea. By doing so, we can get an even better understanding of this process
Applications have different approaches to authentication. Passport calls those mechanisms strategies. The first strategy that we want to implement is the passport-local strategy. It is a strategy for authenticating with a username and password.
1 |
npm install @nestjs/passport passport @types/passport-local passport-local @types/express |
To configure a strategy, we need to provide a set of options specific to a particular strategy. In NestJS, we do it by extending the PassportStrategy class.
authentication/local.strategy.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { Strategy } from 'passport-local'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable } from '@nestjs/common'; import { AuthenticationService } from './authentication.service'; import User from '../users/user.entity'; @Injectable() export class LocalStrategy extends PassportStrategy(Strategy) { constructor(private authenticationService: AuthenticationService) { super({ usernameField: 'email' }); } async validate(email: string, password: string): Promise<User> { return this.authenticationService.getAuthenticatedUser(email, password); } } |
For every strategy, Passport calls the validate function using a set of parameters specific for a particular strategy. For the local strategy, Passport needs a method with a username and a password. In our case, the email acts as a username.
We also need to configure our AuthenticationModule to use Passport.
authentication/authentication.module.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { Module } from '@nestjs/common'; import { AuthenticationService } from './authentication.service'; import { UsersModule } from '../users/users.module'; import { AuthenticationController } from './authentication.controller'; import { PassportModule } from '@nestjs/passport'; import { LocalStrategy } from './local.strategy'; @Module({ imports: [UsersModule, PassportModule], providers: [AuthenticationService, LocalStrategy], controllers: [AuthenticationController] }) export class AuthenticationModule {} |
Using built-in Passport Guards
The above module uses the AuthenticationController. Let’s create the basics of it now.
Below, we use Guards. Guard is responsible for determining whether the route handler handles the request or not. In its nature, it is similar to Express.js middleware but is more powerful.
We focus on guards quite a bit in the upcoming parts of this series and create custom guards. Today we only use the existing guards though.
authentication/authentication.controller.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import { Body, Req, Controller, HttpCode, Post, UseGuards } from '@nestjs/common'; import { AuthenticationService } from './authentication.service'; import RegisterDto from './dto/register.dto'; import RequestWithUser from './requestWithUser.interface'; import { LocalAuthenticationGuard } from './localAuthentication.guard'; @Controller('authentication') export class AuthenticationController { constructor( private readonly authenticationService: AuthenticationService ) {} @Post('register') async register(@Body() registrationData: RegisterDto) { return this.authenticationService.register(registrationData); } @HttpCode(200) @UseGuards(LocalAuthenticationGuard) @Post('log-in') async logIn(@Req() request: RequestWithUser) { const user = request.user; user.password = undefined; return user; } } |
Above we use @HttpCode(200) because NestJS responds with 201 Created for POST requests by default
authentication/localAuthentication.guard.ts
1 2 3 4 5 |
import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class LocalAuthenticationGuard extends AuthGuard('local') {} |
Passing the strategy name directly into AuthGuard() in the controller might not be considered a clean approach. Instead, we create our own class.
authentication/requestWithUser.interface.ts
1 2 3 4 5 6 7 8 |
import { Request } from 'express'; import User from '../users/user.entity'; interface RequestWithUser extends Request { user: User; } export default RequestWithUser; |
Thanks to doing all of the above, our /log-in route is handled by Passport. The data of the user is attached to the request object, and this is why we extend the Request interface.
If the user authenticates successfully, we return his data. Otherwise, we throw an error.
Using JSON Web Tokens
We aim to restrict some parts of the application. By doing so, only authenticated users can access them. We don’t want them to need to authenticate for every request. Instead, we need a way to let the users indicate that they have already logged in successfully.
A simple way to do so is to use JSON Web Tokens. JWT is a string that is created on our server using a secret key, and only we can decode it. We want to give it to the user upon logging in so that it can be sent back on every request. If the token is valid, we can trust the identity of the user.
1 |
npm install @nestjs/jwt passport-jwt @types/passport-jwt cookie-parser @types/cookie-parser |
The first thing to do is to add two new environment variables: JWT_SECRET and JWT_EXPIRATION_TIME.
We can use any string as a JWT secret key. It is important to keep it secret and not to share it. We use it to encode and decode tokens in our application.
We describe our expiration time in seconds to increase security. If someone’s token is stolen, the attacker has access to the application in a similar way to having a password. Due to the expiry time, the issue is partially dealt with because the token will expire.
app.module.ts
1 2 3 4 5 6 7 |
ConfigModule.forRoot({ validationSchema: Joi.object({ //... JWT_SECRET: Joi.string().required(), JWT_EXPIRATION_TIME: Joi.string().required(), }) }) |
Generating tokens
In this article, we want the users to store the JWT in cookies. It has a certain advantage over storing tokens in the web storage thanks to the HttpOnly directive. It can’t be accessed directly through JavaScript in the browser, making it more secure and resistant to attacks like cross-site scripting.
If you want to know more about cookies, check oout Cookies: explaining document.cookie and the Set-Cookie header
Now, let’s configure the JwtModule.
authentication/authentication.module.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import { Module } from '@nestjs/common'; import { AuthenticationService } from './authentication.service'; import { UsersModule } from '../users/users.module'; import { AuthenticationController } from './authentication.controller'; import { PassportModule } from '@nestjs/passport'; import { LocalStrategy } from './local.strategy'; import { JwtModule } from '@nestjs/jwt'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ UsersModule, PassportModule, ConfigModule, JwtModule.registerAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: async (configService: ConfigService) => ({ secret: configService.get('JWT_SECRET'), signOptions: { expiresIn: `${configService.get('JWT_EXPIRATION_TIME')}s`, }, }), }), ], providers: [AuthenticationService, LocalStrategy], controllers: [AuthenticationController] }) export class AuthenticationModule {} |
Thanks to that, we can now use JwtService in our AuthenticationService.
authentication/authentication.service.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Injectable() export class AuthenticationService { constructor( private readonly usersService: UsersService, private readonly jwtService: JwtService, private readonly configService: ConfigService ) {} // ... public getCookieWithJwtToken(userId: number) { const payload: TokenPayload = { userId }; const token = this.jwtService.sign(payload); return `Authentication=${token}; HttpOnly; Path=/; Max-Age=${this.configService.get('JWT_EXPIRATION_TIME')}`; } } |
authentication/tokenPayload.interface.ts
1 2 3 |
interface TokenPayload { userId: number; } |
We need to send the token created by the getCookieWithJwtToken method when the user logs in successfully. We do it by sending the Set-Cookie header. To do so, we need to directly use the Response object.
1 2 3 4 5 6 7 8 9 10 |
@HttpCode(200) @UseGuards(LocalAuthenticationGuard) @Post('log-in') async logIn(@Req() request: RequestWithUser, @Res() response: Response) { const {user} = request; const cookie = this.authenticationService.getCookieWithJwtToken(user.id); response.setHeader('Set-Cookie', cookie); user.password = undefined; return response.send(user); } |
When the browser receives this response, it sets the cookie so that it can use it later.
Receiving tokens
To be able to read cookies easily we need the cookie-parser.
main.ts
1 2 3 4 5 6 7 8 9 10 |
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as cookieParser from 'cookie-parser'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(cookieParser()); await app.listen(3000); } bootstrap(); |
Now, we need to read the token from the Cookie header when the user requests data. To do so, we need a second passport strategy.
authentication/jwt.strategy.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import { ExtractJwt, Strategy } from 'passport-jwt'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Request } from 'express'; import { UsersService } from '../users/users.service'; import TokenPayload from './tokenPayload.interface'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor( private readonly configService: ConfigService, private readonly userService: UsersService, ) { super({ jwtFromRequest: ExtractJwt.fromExtractors([(request: Request) => { return request?.cookies?.Authentication; }]), secretOrKey: configService.get('JWT_SECRET') }); } async validate(payload: TokenPayload) { return this.userService.getById(payload.userId); } } |
There are a few notable things above. We extend the default JWT strategy by reading the token from the cookie.
When we successfully access the token, we use the id of the user that is encoded inside. With it, we can get the whole user data through the userService.getById method. We also need to add it to our UsersService.
users/users.service.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Injectable() export class UsersService { constructor( @InjectRepository(User) private usersRepository: Repository<User> ) {} async getById(id: number) { const user = await this.usersRepository.findOne({ id }); if (user) { return user; } throw new HttpException('User with this id does not exist', HttpStatus.NOT_FOUND); } // (...) } |
Thanks to the validate method running under the hood when the token is encoded, we have access to all of the user data.
We now need to add our new JwtStrategy to the AuthenticationModule.
authentication/authentication.module.ts
1 2 3 4 5 |
@Module({ // (...) providers: [AuthenticationService, LocalStrategy, JwtStrategy] }) export class AuthenticationModule {} |
Requiring authentication from our users
Now, we can require our users to authenticate when sending requests to our API. To do so, we first need to create our JwtAuthenticationGuard.
authentication/jwt-authentication.guard.ts
1 2 3 4 5 |
import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export default class JwtAuthenticationGuard extends AuthGuard('jwt') {} |
Now, we can use it every time we want our users to authenticate before making a request. For example, we might want to do so, when creating posts through our API.
posts/posts.controller.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import { Body, Controller Post, UseGuards } from '@nestjs/common'; import PostsService from './posts.service'; import CreatePostDto from './dto/createPost.dto'; import JwtAuthenticationGuard from '../authentication/jwt-authentication.guard'; @Controller('posts') export default class PostsController { constructor( private readonly postsService: PostsService ) {} @Post() @UseGuards(JwtAuthenticationGuard) async createPost(@Body() post: CreatePostDto) { return this.postsService.createPost(post); } // (...) } |
Logging out
JSON Web Tokens are stateless. We can’t change a token to be invalid in a straightforward way. The easiest way to implement logging out is just to remove the token from the browser. Since the cookies that we designed are HttpOnly, we need to create an endpoint that clears it.
authentication/authentication.service.ts
1 2 3 4 5 6 7 |
export class AuthenticationService { // (...) public getCookieForLogOut() { return `Authentication=; HttpOnly; Path=/; Max-Age=0`; } } |
authentication/authentication.controller.ts
1 2 3 4 5 6 7 8 9 10 |
@Controller('authentication') export class AuthenticationController { // (...) @UseGuards(JwtAuthenticationGuard) @Post('log-out') async logOut(@Req() request: RequestWithUser, @Res() response: Response) { response.setHeader('Set-Cookie', this.authenticationService.getCookieForLogOut()); return response.sendStatus(200); } } |
Verifying tokens
One important additional functionality that we need is verifying JSON Web Tokens and returning user data. By doing so, the browser can check if the current token is valid and get the data of the currently logged in user.
1 2 3 4 5 6 7 8 9 10 11 |
@Controller('authentication') export class AuthenticationController { // (...) @UseGuards(JwtAuthenticationGuard) @Get() authenticate(@Req() request: RequestWithUser) { const user = request.user; user.password = undefined; return user; } } |
Summary
In this article, we’ve covered registering and logging in users in NestJS. To implement it, we’ve used bcrypt to hash passwords to secure them. To authenticate users, we’ve used JSON Web Tokens. There are still ways to improve the above features. For example, we should exclude passwords more cleanly. Also, we might want to implement the token refreshing functionality. Stay tuned for more articles about NestJS!
Hello, I did part 3 and when executing the command yarn start: dev, and returned the following error:
node_modules/@types/express/index.d.ts: 59: 29 – error TS2694: Namespace ‘serveStatic’ has no exported member ‘RequestHandlerConstructor’. var static: serveStatic.RequestHandlerConstructor <Response>; I didn’t understand how I could overcome this error, could you give me any tips?
I’ve got the same error when I updated @types/express from 4.17.8 to 4.17.9.
npm i -D @types/express@4.17.8
helped for me
Unfortunately, this seems like an issue that other people are also experiencing. Check out this issue on Github.
Hi there, I was wondering what should I do, if I need to have different roles for my users?
Like Admins, Subscribers, and normal users?
I created my authentication just like this post. But I need roles too.
I think it would be great if you write another post about it (:
Thanks.
In my opinion, the official docs do a good job explaining what you are looking for (authorization): https://docs.nestjs.com/security/authorization
Create a loginDto class and add a param to login method in AuthenticationController:
Thank you for an excellent series!
As a newcomer to Nest, I ran into one small problem that actually blocked me for a few hours while following these steps — not wiring up the AuthenticationModule in the AppModule. The error messages revolved around strategies not being found.
Awesome Serie!!! Helped me a lot.
I’m trying. run only the authentication part. With a bunch of data and I came across the error:
[Nest] 101 – 03/01/2021, 8:05:01 PM [ExceptionHandler] JwtStrategy requires a secret or key
Looks like you didn’t set up the
JWT_SECRET
variable in your.env
file.I have it defined in the .env, and I’m still getting the error. Do you might be knowing the reason?
Look closer at how your ConfigService is working and compare it to what Marcin implemented. May be this will help.
Or you could just try access environment variables through process.env
I had the same issue and solved it by checking if env variables are well retrieved, try to debug it in main.ts and see if you’re getting the right value, otherwise you might need to check your configModule if well imported…
use import { Strategy } from ‘passport-local’;
you miss somthing here
logIn(@Req() request:any, @Res({ passthrough: true }) response:Response):Observable<User>
If you want to leave the response handling logic to the framework, remember to set the
passthrough
option to true
Hi! Where is the definition of the RegisterDto?
go to src \ authentication \ dto \ register.dto.ts
Hello,
How can I get the correct payload typings when generating a Swagger doc for this controller?
For example, this is how my
POST /authentication/login
looks like on swagger (see screenshot). Username and password should be listed as parameters, but they don’t. How do you suggest to fix that? Thanks in advance.It seem that you should read this: https://docs.nestjs.com/openapi/types-and-parameters
Hello, thank you for the amazing series. I need help regarding authenticating another entity ‘company’ using the local strategy? I tried creating another module and passing a name for the PassportStrategy class but still i get ‘wrong credentials’ message. Any help would be appreciated as this is an urgent matter. Thanks !
tried creating another module
I don’t really understand about what module, you are talking about? In the article repo code, you can see that there is only one auth module. And in it, the different strategies are simply imported.
Assuming that you did everything right, and given the ‘wrong credentials’ message that you mentioned, i would ask, what route are you querying and what guard is there? Did you create a new one for your new strategy and are you using it in that route?
I created another module to authenticate company. still get this error when i try to login: [ExceptionsHandler] Cannot read property ‘id’ of undefined.
any ideas?
Here are some of my reflection when reading this article.
In the validate method of the JwtStrategy, you get the user data by making a call to the DB. That is basically undermining some of the advantages of using the JWT.
The responsibility of the validate method is to return some user data that passport will append to the request object (and can, for example, latter be used by a currentUser decorator).
With a simple auth JWT, the normal would be to just return the JWT payload. And if there is need for more data in, for example the currenteUser decorator, just add it to the payload at the time of creating the JWT.
That call to the DB in the validate would normally only done in a passport JWT refresh strategy.
Hi, I believe that you should revoke the token on logout)
how do i catch it in angular? any link?
Hi
There is a problem with the auth controller
Error TS2339: Property ‘send’ does not exist on type ‘Response’.
It should be response.setHeader('Set-Cookie', cookie) instead.
Property ‘setHeader’ does not exist on type ‘Response’
@UseGuards(JwtAuthenticationGuard)
@Post(‘log-out’)
async logOut(@Req() request: RequestWithUser, @Res() response: Response) {
response.setHeader(‘Set-Cookie’, this.authenticationService.getCookieForLogOut());
return response.sendStatus(200);
}
import {Response} from ‘express’
Or do response.req.setHeader ^^
Hi, I’ve got the same code you have and dont know how to fix this error
Unknown authentication strategy “jwt”
I have came across same error, did u solve it ?
May be this will help:
export class JwtStrategy extends PassportStrategy(Strategy, ‘jwt‘) {
Try name your strategy explicitly.
Had same error, forgot to import AuthenticationModule in AppModule.
How can I supplement the create() method to prohibit the creation of a user if the email already exists?
I keep on getting this error when I try to test the log-in and log-out endpoints with postman
Error [ERR_INTERNAL_ASSERTION]: This is caused by either a bug in Node.js or incorrect usage of Node.js internals.
Please open an issue with this stack trace at https://github.com/nodejs/node/issues
at assert (internal/assert.js:14:11)
at ServerResponse.detachSocket (_http_server.js:234:3)
at resOnFinish (_http_server.js:792:7)
at ServerResponse.emit (events.js:400:28)
at onFinish (_http_outgoing.js:792:10)
at callback (internal/streams/writable.js:513:21)
at afterWrite (internal/streams/writable.js:466:5)
at afterWriteTick (internal/streams/writable.js:453:10)
at processTicksAndRejections (internal/process/task_queues.js:81:21)
Same problem here!
Check your login controller. It doesn’t need have async and no need to return response. Just response.send() is enough.
Or use
async
andreturn user
bothAmazing series, but why I can’t get the user after log in?
Wasted few hours on debug but still nothing, will appreciate any help.
see postman screenshot
Thanks!
Hi. Is there any chance that your token is expired? Did you manage to get some useful information on this issue while debugging?
Hah, Thank you a lot. I forgot ‘s’ in expiresIn
Glad to be the part of this Series, it helped me alot.
here anyone can make me solve this error
@HttpCode(200)
@UseGuards(LocalAuthenticationGuard)
@Get(‘LogIn’)
async login (@Req() req:RequestWithUser,@Res() res:Response){
const {user} = req
const myCookie= this.authenticationService.getCookieWithJwtToken(user.id)
res.setHeader(‘Set-Cookie’,myCookie) // here is the issue
user.password=undefined
return res.send(user)
}
code is clean but when I hit the route i face the following issue:
ERROR [ExceptionsHandler] Invalid character in header content [“Set-Cookie”]
Nest 340901
Hi. Can you give an example of the
myCookie
value? It might help us solve this problem.myCookie value is :
/*
public getCookieWithJwtToken(UserId:number){
const payload:PayLoad = {UserId}
const token = this.jwtservice.sign(payload);
return
Authentication=${token}; HttpOnly; Path=/; Max-Age= ${process.env.JWT_EXPIRATION_TIME}
;}
You don’t “enter” line. It should be “Authentication… Path=/; Max-Age=${pro..”
I’m trying. run only the authentication part. But i got the message said. Cannot GET /authentication
Cannot read properties of undefined (reading ‘create’) when trying to register?
I have the same issue. Have you resolved it somehow?
Add @Injectable() to your AuthenticationService class.
Can it be consumed by a react app ?
I can log in, but always got 401 on every endpoint that is guarded…
Hi there. This is the best article about Authentication Strategies in NestJS on the entire web. But I have a question. Should I use both local and jwt strategies? What is the difference between them?
Hi, Thanks you for this article. I faced some problem, after login successfully, and try to get authenticate User and get 401 Unauthorized, don’t understand why !!!
It’s ok, i resolved my problem. Thanks you 🙏🙏
Hi! Hi did you resolve this problem?
In case somebody is having issue it might be because you import Strategy from “passport-local” rather than “passport-jwt” in jwt.strategy.ts file
The registration and login works well, it returns a cookie, in response header With Set-Cookie. but when i try to logout with provided token, it says unauthorized