TypeScript Express tutorial #4. Registering users and authenticating with JWT

Express JavaScript

This entry is part 4 of 11 in the TypeScript Express tutorial

Today we cover an essential part of almost every application: registering users and authenticating them. To implement it, we use JSON Web Tokens (JWT). Instead of getting help from libraries like Passport, we build everything from the ground up to get the best understanding of how it works. As always, all of the code is available in the express-typescript repository. Feel free to give it a star if you find it helpful.

Registration

To start things up we create the User interface and model.

src/users/user.interface.ts

src/users/user.model.ts

Hashing

A catch here is that we don’t want to save the passwords in plain text! Imagine your database getting breached and all the passwords leaking out. Not good!

The purpose of a hashing algorithm is to turn one string into another string. If you change just one character in a string, the hash is entirely different. The most important thing is that it is a one-way operation: it can’t be reversed easily. When the user attempts to log in, you can hash his password again and compare with the one saved in the database.

Hashing the same string twice gives the same result. To prevent users that have the same password from having the same hash, we use salt. It is a random string that is added to the original password to achieve a different result each time. It should be different for each password.

Bcrypt

In this article, we use a bcrypt hashing algorithm implemented by the bcrypt npm package. It takes care of hashing the strings, comparing plain text strings with hashes and appending salt. Using it we define salt rounds. It is basically a cost factor: it controls the time needed to receive an output hash. Increasing the cost factor by one doubles the time. The more significant the cost factor, the more difficult is reversing the hash by brute-forcing. The salt that’s used for hashing someone’s password is a part of the saved hash itself, so no need to keep it separately.

Generally speaking, an amount of 10 salt rounds should be fine. As you can see the hashing and comparing strings is asynchronous – this is because the hashing done by bcrypt is intensive for the CPU and hashing strings it synchronously would block the application. Our bcrypt implementation uses a thread pool that allows the algorithm to run in an additional thread. Thanks to that, our app is free to do other tasks while waiting for the hash to be generated.

In this example I use async/await. If you would like to know more about it, check out Explaining async/await. Creating dummy promises

Registration and logging in implementation

Knowing all that we can implement the basics of registration and logging in functionalities.

src/authentication/authentication.controller.ts

In this example the return of   and   is a MongoDB document. The actual data is represented in   and   is just a getter that returns the data from  . To prevent sending the password back with a response you could also do  , but setting the    to undefined also does the trick and there is no trace of the password in the response.

I created a few additional files along the way, such as exceptions and DTO classes used for validation that we covered in the previous part of the tutorial. You can check them out in the repository. In the AuthenticationController above we created two route handlers:    and   with some basic error handling, such as not allowing more than one person with the same email.

A thing worth noticing is that we don’t make it clear whether it was the username or the password that the user got wrong when attempting to log in. Thanks to displaying a generic error message we prevent potential attackers from getting to know any valid usernames without knowing the passwords.

In the example, we create new users and let them access their data. The crucial thing to implement now is a way for them to authenticate to other parts of our application.

Authentication with JWT tokens

We want to restrict the access to certain parts of our application so that only registered users can use it. In the application that we are using as an example, such a part is creating posts. To implement it we need to create a certain way for users to authenticate and let us know that the request that they send is legitimate. A simple way to do it is with the usage of JSON Web Tokens. JWT is a piece of JSON data that is signed on our server using a secret key when the user is logged in and then sent to him in. When he makes other requests, he sends this token in the headers so that we can encode it back using the same secret key. If the token is valid, we know who the user that made the request is.

Signing tokens

The first thing to implement is creating the tokens. To do this, we use an implementation of JSON Web Tokens available in the NPM.

To the environment variables covered in the previous part of the tutorial, we added the JWT secret key. It can be any string but remember not to share it with anyone because using it they would be able to encode and decode tokens in your application. To generate a token we also should set its expiry time to increase security – this is because if someone’s token is stolen, the attacker has access to the application similar as if he would have the username and the password. Thanks to setting an expiry time, the issue is a bit smaller because the token expires soon anyway.

In the example above we encode the id of a user in the token so that when he authenticates, we know who he is. You could put more data there such as the name of the user to avoid fetching it from the database, but if the user changes for example his name, the data in the token wouldn’t be up-to-date until a new token is created.

Now we can update the code of our AuthenticationController.

When the user registers or logs in, we create the token and send it to him with the request in the Set-Cookie header.

If you would like to know more about cookies and why should we use the HttpOnly directive, check out Cookies: explaining document.cookie and the Set-Cookie header

Validating the token using middleware

We now expect our users to send the JWT in the form of cookies along with every request that they make. Since the cookie is just a simple string, for our convenience we use the cookie middleware that transforms it into an object.

src/app.ts

Thanks to   we have the contents of the cookies accessible through  .

Now we can create the middleware that checks the JWT token that the user sends. If the operation succeeds, the function appends the user data to the request object.

src/interfaces/requestWithUser.interface.ts

src/middleware/auth.middleware.ts

The function above verifies the JWT token using the same secret string that we used to create it. If the token is wrong, or it expired, the   function throws an error and we need to catch it.

Using the authentication middleware

We can use the middleware above in a few ways. One of them would be to apply it to a whole controller.

The issue with this is that we want everyone to be able to see our posts, guests included. We can apply the middleware for a specific handler:

That would mean adding it to every handler separately. To make our code shorter, we can create a chain of route handlers.

Using the   in such a way applies the middleware only to the route handlers in the chain that match the   route, including  .

Now the user data is available in the   function. Let’s use it to save the id of the post author.

Express Typescript Authorization Postman

Logging out

The thing with JWT is that it is stateless. It means that you can’t set the token to be invalid on demand. The easiest way is just to implement logging out as removing the token from a browser. Since the cookies storing the token are HttpOnly, we create an endpoint that serves that purpose.

After requesting this endpoint from the browser, the cookie is removed. The issue with that is the fact that the token that was deleted from the browser is still valid. It will expire after a certain amount of time if you set it up this way, but If you want to you can create a blacklist of tokens in your database and every time someone accesses the application check if his token is blacklisted.

Summary

In this article, we covered registering and logging in users in the Typescript Express application. To implement it we’ve got to know how to hash a password using bcrypt to keep it safe. The authentication that we implement here is done using JSON Web Tokens (JWT) that provide an easy way to identify the users and validate requests. Thanks to all that work we implemented a crucial part of a web application. Stay tuned because there are still things to cover!

Series Navigation<< TypeScript Express tutorial #3. Error handling and validating incoming dataTypeScript Express tutorial #5. MongoDB relationships between documents >>
avatar
  Subscribe  
newest oldest most voted
Notify of
Kaeon
Kaeon

Hi, thank you very much for the good tutorial, it’s been a great help. I downloaded your project but seem to have some issues validation.middleware.ts: Cannot find module ‘class-transformer’., Cannot find module ‘class-validator’ & Property ‘values’ does not exist on type ‘ObjectConstructor’.. The modules seem installed, tried several things but keep getting the issue. I use Visual Studio Code. Do you have an idea what the issue can be?

wutwutwut
wutwutwut

What happens after token is expired? User forced to login again? Should it be handled on frontend side?

Predrag Stojadinovic

In case someone has the same issue: I had to use user?: User; in my RequestWithUser interface in order to be able to compile and run…

Adam
Adam

I have encountered the same issue

testingname
testingname

Thanks you for this!!

Saurav
Saurav

async function authMiddleware(request: RequestWithUser,req:Request, response: Response, next: NextFunction) { const cookies = req.cookies; if (cookies && cookies.Authorization) { const secret = process.env.JWT_SECRET; try { const verificationResponse = jwt.verify(cookies.Authorization, secret) as DataStoredInToken; const id = verificationResponse._id; const user = await userModel.findById(id); if (user) { request.user = user; next(); } else { next(new WrongAuthenticationTokenException()); } } catch (error) { next(new WrongAuthenticationTokenException()); } } else { next(new AuthenticationTokenMissingException()); } } I have corrected the above code. I don’t know what mistake I am doing. It is not connecting with authenticated _id. Please help!