NestJS Authentication Deep Dive
NestJS Authentication Deep Dive: Leveraging Passport, JWT, and bcrypt User authentication is a critical aspect of any application, providing a layer of security to protect user data and sensitive information.Passport is the most popular node.js authentication library that is successfully used in many production applications. BLOGS Posted on May 24, 2024 By Yunus (Application Developer) May 24, 2024 Introduction: User authentication is a critical aspect of any application, providing a layer of security to protect user data and sensitive information. Passport is the most popular node.js authentication library that is successfully used in many production applications. Nest applications can be integrated with the Passport library using the @nestjs/passport module. Passport uses the concept of strategies to authenticate requests. Strategies can range from verifying username and password credentials, delegated authentication using OAuth (for example, via Facebook or Twitter) etc. Please refer https://www.passportjs.org/packages/ to identify the strategies suitable for your application needs. In this article, we are making use of two passport strategies, namely: passport-local: to authenticate a user based on username and password passport-jwt: to authenticate a user based on the JSON Web Token. After an initial successful local username and password authentication by the user, corresponding JWT can be generated. This JWT can then be used to get authenticated when accessing subsequent protected routes within the application. Pre-requisites In a real world Nest application, following setup would have been completed before implementing user authentication. We will however not be discussing them in detail. Install Nest CLI Create a new NestJs project using the Nest CLI that generates a base project structure with the initial core Nest files and supporting modules Install @nestjs/config package and import its ConfigModule and use it to read the environment variables configurations from the .env file Install the associated client API libraries for TypeORM integration with PostgreSQL database Create a ‘database’ module and into that import TypeOrmModule and configure it using the ConfigService by reading the PostgreSQL database environment variables Packages required Following is a list of packages that are to be installed or commands to be executed to meet the prerequisites and to get the authentication function implemented. Project Folder structure The folder structure of the described NestJS application is shown below. User Module The user module represents the user store within our Nest JS application.The User entity and its DTO are shown here. The UsersController has a user signup route /api/v1/users configured and it invokes the create() method of UsersService The utils method hashpassword() makes use of the bcrypt package. The findOneByUsername() method of the UsersService returns a full user object if the passed username matches, but otherwise returns a null object. We will soon observe that this method is invoked from an AuthService. Apart from these methods, there are other methods too like finding all users, updating the details of a specific user or removing a user from the user store etc, however they are not related to the topic of user authentication and hence not relevant in this discussion. Authentication requirements For this use case, we have following authentication requirements: A client application authenticates with a username and password via a login route. Once authenticated, the server will issue a JWT. We will create a protected route that is accessible only to requests that contain a valid JWT. The client application can send the JWT received from step-1 (after successful login), as a bearer token in the authorization header on subsequent requests to prove authentication – this would be required to access the protected route/s. In case the routes are protected globally across the application or protected at a module-level, we will need to skip JWT authentication checks on a few of the routes. For example, protect all API routes of the user module but only keep the user signup route open / public. Local authentication Local authentication refers to when the client application or end-user sends in username and password for login, to a login API route. A login route is set-up in the AppController of our Nest application as shown below. There is a guard named ‘LocalAuthGuard’ placed in front of the login route and so all login requests are first handled by this guard, before it hands over the processed result to the login() method. The @nestjs/passport module provides us with a built-in guard ‘AuthGuard’ that invokes the required Passport strategy. The string parameter to the AuthGuard informs which Passport strategy needs to be invoked (as it is possible that an application has configured more than one passport strategy at the same time). In this case, the LocalAuthGuard uses the string parameter ‘local’ and so invokes the passport-local strategy for the incoming username and password parameters of the login request. In general, there are two things to be provided when configuring any of the passport strategies: A set of options that are specific to that strategy. These strategy options are passed by calling the super() method A “verify callback”, which is implemented by a validate() method and defines some kind of interaction with the user store (either checking if the user exists, or checking if credentials are valid or fetching more information about the user). In all these cases, if validation succeeds it returns a user object or returns null if it fails.  In the LocalStrategy we simply call super() as we do not have any options to specify. Note! Instead of using default property names (username and password) for login, if for example you are using ’email’ instead of username, then this needs to be included in the options when calling super(), example: super({ usernameField: ’email’ }). In the validate() method, it uses the AuthService. The validateUser() method checks if a user exists with the passed-in username. This is done by calling the findOneByUsername() of the UsersService. If the user exists and the passed-in password matches with the actual password of the user, then it returns the user object containing the id and the name of the user (after stripping off the sensitive details such as
NestJS Authentication Deep Dive Read More »