Another ExpressJS API tutorial for 2021, part 08 — Auth module
We reached the part number 8 of how to build an API using ExpressJS and Typescript. Today is time to configure our Authentication and Permission
Hi there, if it is your first time arriving here we are creating a series on how to build an API using ExpressJS and Typescript. The first article can be found here.

Just as a quick overview, here is the series of articles that we are writing:
- Create your hello world ExpressJS API (Yes… we always need to start with a hello world)
- Configure our hello world application to use Typescript (TS is cool, believe me ;) )
- Create our first CRUD API endpoints for an user (Everyone wants a registered user in their applications, no?)
- Create and configure your controllers (We will talk about it after)
- Create and configure your services (What the h. is that?)
- Middleware’s! (Yes, we need and use it everyday)
- End to end testings!
- Configuring a secure way to manage user permissions via API — We are here now ;)
- Putting all together into a Docker container
- Configuring a simple MongoDB to connect to our application
- Configuring permission level to the application
- Add logs with Winston!
Today we are going to manage user authentication to generate tokens to be used at API in a secure way.
To start, please use the previous code in this link. At our root of the project we will create a folder called auth. There we will have two folders and the route file:
- controllers — folder, inside of it, create a file called auth.controller.ts
- middlewares — folder. Inside of it, create two files: auth.middleware.ts and jwt.middleware.ts
- auth.routes.config.ts file
Before going further, please make sure to update your package.json dependencies as the following:
"dependencies": {
"argon2-pass": "^1.0.2",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"short-uuid": "^3.1.1"
}We are going to use argon2-pass and jsonwebtoken to create, encode and decode the tokens.
auth.middleware.ts
For the auth middleware we are focusing in creating and validating an Authentication with an email and password field at the body. Here we have two main functions that are validateBodyRequest and verifyUserPassword.
import express from 'express';
import {UsersService} from '../../users/services/user.services';
import {SecurePass} from 'argon2-pass';export class AuthMiddleware {
private static instance: AuthMiddleware;static getInstance() {
if (!AuthMiddleware.instance) {
AuthMiddleware.instance = new AuthMiddleware();
}
return AuthMiddleware.instance;
}async validateBodyRequest(req: express.Request, res: express.Response, next: express.NextFunction) {
if(req.body && req.body.email && req.body.password){
next();
}else{
res.status(400).send({error: 'Missing body fields: email, password'});
}
}async verifyUserPassword(req: express.Request, res: express.Response, next: express.NextFunction) {
const userService = UsersService.getInstance();
const user: any = await userService.getByEmail(req.body.email);
if (user) {
let passwordHash = user.password;
const sp = new SecurePass();
const passwordBuffer = Buffer.from(passwordHash, 'utf8');
const requestPassword = Buffer.from(req.body.password, 'utf8');
const result = await sp.verifyHash(requestPassword, passwordBuffer);
if (SecurePass.isValid(result)) {
req.body = {
userId: user.id,
email: user.email,
provider: 'email',
permissionLevel: user.permissionLevel,
};
return next();
} else {
res.status(400).send({errors: `Invalid e-mail and/or password`});
}
} else {
res.status(400).send({errors: `Invalid e-mail and/or password`});
}
}
}jwt.middleware.ts
In this middleware we are going to create a method that allows the client to create a new JWT token whenever it is needed.
Remember that in a live environment we must move out the jwtSecret to a secret place. For the safe of easy it up the explanation we are hardcoding the value.
import express from 'express';const jwt = require('jsonwebtoken');
const crypto = require('crypto');// todo: remove-me
const jwtSecret = 'My!@!Se3cr8tH4sh';export class JwtMiddleware {
private static instance: JwtMiddleware;static getInstance() {
if (!JwtMiddleware.instance) {
JwtMiddleware.instance = new JwtMiddleware();
}
return JwtMiddleware.instance;
}verifyRefreshBodyField(req: express.Request, res: express.Response, next: express.NextFunction) {
if (req.body && req.body.refreshToken) {
return next();
} else {
return res.status(400).send({error: 'need body field: refreshToken'});
}
};validRefreshNeeded(req: any, res: express.Response, next: express.NextFunction) {
let b = Buffer.from(req.body.refreshToken, 'base64');
let refreshToken = b.toString();
let hash = crypto.createHmac('sha512', req.jwt.refreshKey).update(req.jwt.userId + jwtSecret).digest("base64");
if (hash === refreshToken) {
delete req.jwt.iat;
delete req.jwt.exp;
req.body = req.jwt;
return next();
} else {
return res.status(400).send({error: 'Invalid refresh token'});
}
};validJWTNeeded(req: any, res: express.Response, next: express.NextFunction) {
if (req.headers['authorization']) {
try {
let authorization = req.headers['authorization'].split(' ');
if (authorization[0] !== 'Bearer') {
return res.status(401).send();
} else {
req.jwt = jwt.verify(authorization[1], jwtSecret);
next();
}} catch (err) {
return res.status(403).send();
}
} else {
return res.status(401).send();
}};
}auth.controller.ts file
This auth controller is responsible to grab the data that will be present at the jwt token and create the token to give it back to the client.
Remember that in a live environment we must move out the jwtSecret to a secret place. For the safe of easy it up the explanation we are hardcoding the value.
import express from 'express';const jwt = require('jsonwebtoken');
const crypto = require('crypto');// todo: move to a secure place
const jwtSecret = 'My!@!Se3cr8tH4sh';
const tokenExpirationInSeconds = 3600;export class AuthController {
constructor() {
}async createJWT(req: express.Request, res: express.Response) {
try {
let refreshId = req.body.userId + jwtSecret;
let salt = crypto.randomBytes(16).toString('base64');
let hash = crypto.createHmac('sha512', salt).update(refreshId).digest("base64");
req.body.refreshKey = salt;
let token = jwt.sign(req.body, jwtSecret, {expiresIn: tokenExpirationInSeconds});
let b = Buffer.from(hash);
let refreshToken = b.toString('base64');
return res.status(201).send({accessToken: token, refreshToken: refreshToken});
} catch (err) {
return res.status(500).send(err);
}
}
}In order to use all the code above, for this article we are going to add the middlewares at the auth.routes.config.ts as the following code:
import {CommonRoutesConfig, configureRoutes} from '../common/common.routes.config';
import {AuthController} from './controllers/auth.controller';
import {AuthMiddleware} from './middlewares/auth.middleware';
import {JwtMiddleware} from './middlewares/jwt.middleware';
import express from 'express';export class AuthRoutes extends CommonRoutesConfig implements configureRoutes{
constructor(app: express.Application) {
super(app, 'UsersRoute');
this.configureRoutes();
}configureRoutes() {
const usersController = new AuthController();
const authMiddleware = AuthMiddleware.getInstance();
const jwtMiddleware = JwtMiddleware.getInstance();this.app.post(`/auth`, [
authMiddleware.validateBodyRequest,
authMiddleware.verifyUserPassword,
usersController.createJWT
]);this.app.post(`/auth/refresh-token`, [
jwtMiddleware.validJWTNeeded,
jwtMiddleware.verifyRefreshBodyField,
jwtMiddleware.validRefreshNeeded,
usersController.createJWT
]);
}}
We have here two posts requests:
- /auth
We are using the middlewares to verify if we have the user at our database here and we are going to send a response with the generated JWT.
- /auth/refresh-token
Here we are going to validate the JWT and to create a new one based on the refresh token that we are generating
By the last thing, we need to add this routes to our app.ts file:
import express from 'express';
import * as http from 'http';
import * as bodyparser from 'body-parser';import {CommonRoutesConfig} from './common/common.routes.config';
import {UsersRoutes} from './users/users.routes.config';// here we are importing our AuthRoutes
import {AuthRoutes} from './auth/auth.routes.config'const app: express.Application = express();
const server: http.Server = http.createServer(app);
const port = 3000;
const routes: any = [];app.use(bodyparser.json({limit: '5mb'}));
routes.push(new UsersRoutes(app));// here we are initialising the AuthRoutes
routes.push(new AuthRoutes(app));app.get('/', (req: express.Request, res: express.Response) => {
res.status(200).send(`Server running at port ${port}`)
});server.listen(port, () => {
console.log(`Server running at port ${port}`);
routes.forEach((route: CommonRoutesConfig) => {
console.log(`Routes configured for ${route.getName()}`);
});
});export default app;All good for now, but how can we test it? Since the last article we started to create integration tests, now we will be using this at our favor.
Lets create an auth folder at the test folder and create some testing requests for our new auth routes inside the auth.test.ts file
import app from '../../app/app';
import {agent as request} from 'supertest';
import {expect} from 'chai';let firstUserIdTest = '';
let firstUserBody = {
"name": "Marcos Silva",
"email": "[email protected]",
"password": "Pass#your!word"
};let jwt = {
accessToken: '',
refreshToken: ''
};it('should POST /users', async function () {
const res = await request(app)
.post('/users').send(firstUserBody);
expect(res.status).to.equal(201);
expect(res.body).not.to.be.empty;
expect(res.body).to.be.an("object");
expect(res.body.id).to.be.an('string');
firstUserIdTest = res.body.id;
});it(`should POST to /auth and retrieve an access token`, async () => {
const res = await request(app)
.post('/auth').send({
"email" : firstUserBody.email,
"password" : firstUserBody.password
});
expect(res.status).to.equal(201);
expect(res.body).not.to.be.empty;
expect(res.body).to.be.an("object");
expect(res.body.accessToken).to.be.an("string");
expect(res.body.refreshToken).to.be.an("string");
jwt.accessToken = res.body.accessToken;
jwt.refreshToken = res.body.refreshToken;
});it(`should POST to /auth/refresh-token and receive 403 for having an invalid JWT`, async () => {
const res = await request(app)
.post('/auth/refresh-token')
.set('Accept', 'application/json')
.set('Authorization', `Bearer ${jwt.accessToken}123123`)
.send({
"refreshToken" : jwt.refreshToken
});
expect(res.status).to.equal(403);
});it(`should POST to /auth/refresh-token and receive 401 for not having a JWT set`, async () => {
const res = await request(app)
.post('/auth/refresh-token')
.set('Accept', 'application/json')
.send({
"refreshToken" : jwt.refreshToken
});
expect(res.status).to.equal(401);
});it(`should POST to /auth/refresh-token and receive 400 for having an invalid refreshToken`, async () => {
const res = await request(app)
.post('/auth/refresh-token')
.set('Accept', 'application/json')
.set('Authorization', `Bearer ${jwt.accessToken}`)
.send({
"refreshToken" : '123'
});
expect(res.status).to.equal(400);
});it(`should POST to /auth/refresh-token and retrieve a new access token`, async () => {
const res = await request(app)
.post('/auth/refresh-token')
.set('Accept', 'application/json')
.set('Authorization', `Bearer ${jwt.accessToken}`)
.send({
"refreshToken" : jwt.refreshToken
});
expect(res.status).to.equal(201);
expect(res.body).not.to.be.empty;
expect(res.body).to.be.an("object");
expect(res.body.accessToken).to.be.an("string");
expect(res.body.refreshToken).to.be.an("string");
jwt.accessToken = res.body.accessToken;
jwt.refreshToken = res.body.refreshToken;
});it('should DELETE /users/:userId', async function () {
const res = await request(app)
.delete(`/users/${firstUserIdTest}`).send();
expect(res.status).to.equal(204);
});Now we just need to run our tests and see the requests going and validate the responses. The code to run tests are still the same: npm run test-dev
This article was quite big and we just made a straight-forward approach without much explanation of how things works. For understanding it better, it would be ideal to go out and learn more about security and JWT and what is argon2.
Also, we have all configured for Auth but we are not using it at the users resource. We will come back to talk about permissions and add it to the users resource in a future article.
Thanks for reading and see you at the next article!
tips:
- the full code of this article is here
- don’t forget to use a nice app to test your API such as Postman or Insomnia
- I know you still didn’t… Read the ExpressJS documentation!
- Complete project is here! (Includes even the non existing articles yet)
