NestJS Access & Refresh Token JWT Auth
By: Jake Engel
Tags: Typescript, JavaScript, Authentication, Nestjs, Jwt

Getting Started

Let’s start by installing Nest’s first-party authentication packages, which will provide 90% of the logic for our implementation, as well as a few external modules for additional support.

$ npm install --save @nestjs/passport @nestjs/jwt passport passport-local passport-jwt bcrypt class-validator $ npm install --save-dev @types/passport-local @types/passport-jwt @types/bcrypt @types/jsonwebtoken

Cool! @nestjs/passport and @nestjs/jwt are Nest’s first-party packages, offering the basic functionality, while passport, passport-local and passport-jwt are the “standard” packages authentication in Node. We’ll also include bcrypt for hashing support and class-validator for request validation. We’ll also of course include the types for these packages (assuming you’re using TypeScript, which you should be!), as well as the types for jsonwebtoken, which is already included as a dependency of @nestjs/jwt.

With the dependencies installed, let’s discuss the basic structure of our app. While implementing the authentication, we’ll tie together two modules which we’ll refer to as the UsersModule and the AuthenticationModule using Nest’s modules system. While we won’t focus specifically on the database portion of this implementation, we will discuss a general structure that works well and stores the most essential data. The user data will be handled by our UsersService and UsersRepository , leaving the refresh tokens to be handled by the RefreshTokensRepository and all tied together by the TokensService logic.

While the database implementation is less important and will work with whatever provider you choose — Sequelize, TypeORM, MongoDB, etc. we’ll still need to create two models/entities to support our authentication and user modules. Once again, for the sake of simplicity, we’ll use Nest’s first-party Sequelize package, but converting it to the package of your choosing should be fairly straightforward.

Let’s put it all together in tree form for an idea of what you’ll end up with.

app/

|-- application.module.ts

|-- requests.ts

|-- modules/

|-- users/

|-- users.module.ts

-- users.service.ts

|-- authentication

|-- authentication.module.ts

-- authentication.controller.ts

-- refresh-tokens.repository.ts

-- tokens.service.ts

-- jwt.guard.ts

-- jwt.strategy.ts

|-- models/

|-- user.model.ts

|-- refresh-token.model.ts

There’s a few files that we haven’t discussed such as requests.ts and jwt.guard.ts but their usage will be more apparent later on.

Of course everything can be tailored to fit your app’s structure, and you may even choose to create a separate module for tokens or create an additional folder or folders to further separate your repositories and services.

Users and Tokens

Now that we’ve laid out the structure of our app, we can get down to the important part — the logic. First, we should probably talk users, since they’re at the core of authentication.

We’ll use the simplest form of a User model, one which contains two properties — a username and password but can easily be modified to add any additional information that you may desire.

The model definition is fairly straightforward, but you can read more about Sequelize’s TypeScript implementation here.

While we’re here, we’ll also create a RefreshToken model, which will allow us to store reusable refresh tokens for each user, while also supporting set expiration dates and revocability.

The user_id column of course refers to the owning-user, is_revoked offering the ability to immediately revoke a token, and expires providing a timestamp for automatic revocation. Technically, we don’t necessarily need to include an expires field because we’ll embed the expiration date in the refresh token, but storing it in the database allows us to optionally purge expired tokens in the future.

Models are done, but we’ll need a way to store and retrieve their data. Let’s create a couple repository classes that provide reusable functionality for this.

If we think ahead, we’ll need a few methods — one to find a user by their ID for generating refresh tokens, one to find them by their username at login, and a final method to create a new user for registration.

As mentioned earlier, we’ll refer to this class as the UsersRepository with an implementation similar to the following.

If this is your first look at a class making use of Sequelize models in NestJS, make sure to check out their reference guide here, especially in reference to the constructor injections.

Both findForId(id) and create(username, password) are self-explanatory, while findByUsername(username) might be slightly more complex because of the interpolated Sequelize functions for adding support for case-insensitive username logins. Thankfully, each of these built-in functions are described in the Sequelize documentation for quick reference.

Before we tie everything together in the services and controllers, let’s add one last repository for our refresh tokens. We’ll need the ability to create a token, as well as find one by its ID.

Again, fairly straightforward. We’ll create a new RefreshToken by associating it with a User and setting the expiration date based on the ttl parameter, specifying the number of seconds until the token expires.

As a side note — in the first repository, we used Sequelize’s repository pattern, but used the static access methods for the refresh tokens. The latter allows for directly accessing methods like find and findOne from the model class, while the repository pattern allows for a better separation of concerns — but either method is equally correct.

Service Layer

The database logic is done. We’ve created methods for creating and retrieving new users and their refresh tokens, but we’ll need a one last layer between our repositories and API controllers — our services, which will provide the business logic connecting the two.

First up, we’ll want to create reusable logic for generating access and refresh tokens, which is where our TokensService will come into play.

Let’s take a more in-depth look at what we’re implementing.

First of all, we’ll setup our class by injecting the RefreshTokensRepository class that we created earlier. We’ll also inject Nest’s built-in JwtService class, which provides wrapping functions around jsonwebtoken for signing and decoding JWTs. We’ll also setup a constant variable that declares the claims that will be shared across all tokens we generate — refresh and access alike. Although they’re not strictly necessary, they can provide additional validation for your token. All potential claims are documented in the JWT RFC.

Onto the methods. Our first method, generateAccessToken is the simpler of the two. Given a User we can ask our injected JwtService to sign an access token with our BASE_OPTIONS claims, as well as an additional subject claim ( sub for short), which will identify the user for which the token was generated. This will be embedded in the JWT, allowing us to later decode it and quickly retrieve the user from the database based on their ID.

You might wonder if this is secure. Thankfully, the JWT payload is by design immutable. Since the payload is verified by a hash of its contents, a payload modified by a potential hacker or wrongdoer would be considered invalid by the server, which we’ll see in a moment.

We’ll return the signed JWT as a string, which we later return to the client in the API controller.

As for the remaining method — generateRefreshToken — we’ll use very similar logic for signing our refresh tokens. Given that the method accepts an expiresIn parameter, we will once again pass this onto the signing options, allowing us to specify the expiration date of the refresh token. In addition, we need to include the jwtid claim ( jit for short), which functions as its name suggests, embedding the ID of the token. In the exact same way that we’ll be able to look up the User by the subject claim, we can just as easily pull the RefreshToken by the ID decoded from the claim later on.

Assuming that those methods are in place, we can extend the class a little further. With the logic for initially generating access and refresh tokens ready, we’re just missing a couple methods for generating access tokens from the refresh tokens.

Let’s add some additional methods to the TokensService class.

Five new methods, but they each implement small pieces of the larger puzzle, making them easy to decipher.

Let’s take a look at decodeRefreshToken first, which is passed a string—a.k.a the encoded JWT. Once again, we’ll use the built in JwtService but this time using verifyAsync which will decode the JWT token and return its payload in object form.

Speaking of — we should define what we expect that payload to look like. We’ll use a TypeScript interface, naming it RefreshTokenPayload with two properties: jti and sub which are the proper versions of the full claim that we passed into signAsync earlier, jwtid and subject respectively. These two properties will return the exact same values that were embedded when the token was signed.

You might also note that we intentionally capture the built in TokenExpiredError from jsonwebtoken when decoding the token. As we mentioned earlier, we don’t necessarily need to store the expiration date in the database because this logic will ensure the expiration date embedded in the JWT is valid. We’ll also catch any additional errors and return them in a format that the controllers will understand.

Two other methods getUserFromRefreshTokenPayload and getStoredTokenFromRefreshTokenPayload are fairly self-explanatory, and do almost exactly what you might assume from their names. Given a decoded RefreshTokenPayload that we defined earlier, the methods will simply extract the necessary field from the payload and find and return either the User or RefreshToken respectively. As with before, we’ll throw exceptions here in case the fields are not present in the decoded payload.

With the ability to decode refresh tokens and retrieve their associated token and user records from the database, we can bring this functionality together to create our resolveRefreshToken method, which will decode and return both the RefreshToken and User models from the database, assuming that the token is valid and passes all additional checks that we’ve put in place.

Given the refresh token as a string, we will decode the payload and fetch the RefreshToken from the database. Assuming that the token exists, and that its is_revoked field hasn’t been toggled, we’ll be able to retrieve the user from the database before returning both the user and token.

In our final method, createAccessTokenFromRefreshToken we can simply use the resolveRefreshToken method that we just implemented to retrieve the User from a refresh token, based on the sub , and then generate a new access token for that user.

Tokens? Done. Users? Almost.

While we’re here, let’s create a super simple UsersService to provide additional functionality to the earlier UsersRepository that we created.

Personally, I like to use the service classes to handle the business logic, and repositories to interact with the database, but it’s totally dependent on your own preferences. In this case, I’ll use the service layer to handle the interaction between the request itself and the database. Before we can create this service, and more importantly before we can create our controllers, we’ll need to define our HTTP request bodies, for which we’ll make use of class-validator, which as with many other features, Nest provides first-class support.

We’ll setup our requests in our requests.ts file, but you can choose to split these into several files in any structure that you’d like.

All three request classes are fairly self-explanatory, one providing login functionality, another registration, and the final requiring a refresh_token which will allow us to generate a new access token.

The requests are defined, so let’s create our UsersService class.

Each method is essentially a wrapper around existing logic, but makes implementing it less verbose and easily reusable. The validateCredentials method uses bcrypt’s compare function to compare the user’s hashed password stored in the database to the password that they attempted to use to login. The last new method, createUserFromRequest takes in a RegisterRequest object from the requests defined earlier, validates the uniqueness of the username and then passes it and the password onto the UsersRepository to create a new user. The final two methods simply proxy the call into our UsersRepository which prevents us from having to inject both the repository and service later on.

API Controller Routes

The business logic is done. That was exhausting. It’s time to bring every component together to create the most essential piece — the routing. In this instance, we’ll only use one controller — our AuthenticationController providing methods to register, login and refresh our users and their tokens.

We’ll probably want to start with our register endpoint, since there’s no point in trying to login or refresh users that don’t even exist!

Let’s setup our AuthenticationController by injecting our UsersService and TokensService from earlier, as well as implementing our register method. We’ll also want to add Nest’s @Controller() decorator, setting up the controller’s base path at /api/auth making our registration endpoint available at /api/auth/register as a POST request.

As with the rest of the logic, we’re keeping things simple. We’re creating a user by passing the request body as a RegisterRequest containing our username and password attributes into our UsersService which will handle creating our new user. We’ll then take the newly created user and generate an access token, as well as a refresh token expiring in 30 days.

We’ve also added a private helper method to the controller called buildResponsePayload accepting the user, access token, and optionally a refresh token as parameters. We’ll re-use this function multiple times to build a formatted payload to return as a response from each of our authentication endpoints. In doing so, we also defined our AuthenticationPayload interface, representing the exact structure that we expect to receive each time this method is called.

Now that we can register users, let’s add a method to log them in, available at /api/auth/login also via POST request. The logic is nearly identical to the registration endpoint, but instead of creating the user, it will be resolved from the username and password.

We’ll only need to review the first few lines of the login method. Since we’ll need to verify the password after retrieving the user, we’ll first attempt to find the User record by their username, using the UsersService method created earlier.

Assuming the user exists, we’ll attempt to match the inputted password against the hashed password in the database, once again using the validateCredentials method from our UsersService class.

If the user doesn’t exist or the password doesn’t match, we’ll throw an UnauthorizedException to inform the user of an invalid username or password. Otherwise, we’ll continue on to the exact same logic that we used to generate access and refresh tokens for a newly registered user.

Almost done! Let’s create our last endpoint to refresh users — /api/auth/refresh also available as a POST request. This endpoint will accept a refresh_token in the body, which we will use to identify the user and generate the new access token.