JWT nodeJS server

Let's build a practical example of the JWT concepts that we learn in the previous post.

To start to clone this project with a basic express server, it only contains 3 routes at this point:

  • login
  • logout
  • secret_page

By the end of this post, the secret page will be displayed only for authenticated users, the login page will generate a jwt and the logout will remove it.

Let's start by installing the following library

yarn add jsonwebtoken yarn add -D @types/jsonwebtoken

Now we need to configure the env file with our jwt secret, the env files should never be committed because it contains sensible information. For that reason, we have a .env.example that can be committed because it's empty, copy that file to a .env and add your secrets like this

PORT=5000 JWT_SECRET=mySuperSecret JWT_EXPIRATION_IN_MINUTES=30

Generating the token

To start let's go into the login route and read the email and password from the body request and confirm if the user exists on the database.

const { email = '', password = '' } = body; const isUserValid = ValidateUser(email, password); if (!isUserValid) { res.status(401).json({ err: 'email or password not valid' }); }

Once we get a confirmation that the user and password are correct we can generate a token using the jsonwebtoken package and set the Authorization header with the token before sending back the result.

import jwt from 'jsonwebtoken'; ... const token = jwt.sign( { username: 'John Doe', avatar: 'https://sample.com/image_1.jpg', id: 1 }, secret, { expiresIn: 60 * parseInt(jwtExpiration) } ); res.setHeader('Authorization', `Bearer ${token}`); res.status(200).send();

Let's look at the previous code.

On line 3 we have a method that will generate the token, let's see step by step.

  • line 4 has the payload of the token
  • line 5 is defined the secret that used to sign the token
  • line 6 finally is defined the expiration time of the token to be 30 minutes

Finally, once we have the token we need to register it on the Authorization header line 8 and 9.

You don't see the algorithm used to sign the token because by default it uses the HS256 but we can define another one like this:

const token = jwt.sign( { username: 'John Doe', avatar: 'https://sample.com/image_1.jpg', id: 1 }, secret, { expiresIn: 60 * parseInt(jwtExpiration), algorithm: 'HS512' } );

You can see the code done during this step on this branch.

Access the secret page

Now that we have the token generated and on the client-side, we can now protect the API allowing the user to get only the data if authenticated.

On the protected route we need to get the token that's on the Authentication header and parse the Bearer out to get only the JWT.

const token = req.headers['authorization']?.split(' ')[1]; if (!token) { res.status(401).json({ err: 'not authorized' }); }

Once that's done we need to verify the token integrity to be sure that's for that user and the data hasn't been modified.

jwt.verify(token, secret);

if the token is correct the code will continue and we can return the data otherwise it will trigger an error that we can catch and return a 401.

Here is the full request

app.get('/secret_page', (req: Request, res: Response) => { try { const token = req.headers['authorization']?.split(' ')[1]; if (!token) { res.status(401).json({ err: 'not authorized' }); return; } jwt.verify(token, secret); res.status(200).json({ secret: 'my protected info' }); } catch (err) { res.status(401).json({ err: 'not authorized' }); return; } });

We put this code inside of the request itself because it's easier to understand for this case and we only have a single request, but, if you have several functions that required auth you can move the validation to middleware and only call it on the request just like this:

const valdidateToken = (req: Request, res: Response, next: NextFunction) => { try { const token = req.headers['authorization']?.split(' ')[1]; if (!token) { res.status(401).json({ err: 'not authorized' }); return; } jwt.verify(token, secret); next(); } catch (err) { res.status(401).json({ err: 'not authorized' }); return; } }; // a secret page that can only be accessed when logged in. app.get('/secret_page', valdidateToken, (_req: Request, res: Response) => { res.status(200).json({ secret: 'my protected info' }); });

You can see the code done during this step on this branch.

Delete the token

Note: One of the main advantages of JWT is that it's stateless, by this, we mean that the tokens are not stored anywhere for validation. No requirement for DB or any other type of storage for them. But this is also a problem when it reaches the time for logout, we can't invalidate the token easily.The best and fastest way of doing it is to delete the token from the client side but if some malicious actor got that token before it will stay valid until it expired, that's why the token should have a short TTL.

But let's build a logout route that removes the token from the client.

app.post('/logout', (_req: Request, res: Response) => { res.removeHeader('Authorization'); res.status(200).json(); });

You can see the code done during this step on this branch.

Conclusion

With this, we have now a full API with JWT login and protected routes. You can see the final code with all the pieces here and test using this insomnia collection.