Javascript API Authentication

Javascript API Authentication code.

Introduction

This blog post is a continuation for my three earlier blog posts:

In this new blog post I continue my Javascript / Typescript story implementing login and API authentication in the backend side. In my next blog post I describe how I add login and state management in the frontend side.

The demo app is in my Github repo: js-node-ts-react.

For experienced Javascript programmers, there is nothing new in this blog post. I created this exercise and this blog post just for my personal learning purposes to learn how to do authentication related solutions in a React application (and therefore also created a simple authentication solution in the Javascript backend as described in this blog post).

Login

First we need an API for users to login (router.mjs):

router.post('/login', jsonParser, (req, res, next) => {
  try {
    const { username, password } = req.body;
    if (!username || !password) {
      throw new NotFoundError('Invalid username or password');
    }
    const token = validateUser(username, password);
    const ret = { ret: 'ok', token };
    res.status(200).json(ret);
  } catch (err) {
    next(err);
  }
});

The validateUser validates the username and password (e.g. against some database), and then creates a token which is passed back to the frontend (users.mjs):

function validateUser(username, password) {
  logger.debug(`ENTER validateUser, username: ${username}, password: ${password}`);
  const valid = checkPassword(username, password);
  if (!valid) {
    throw new ValidationError('Invalid username or password');
  }
  const token = generateToken(username);
  const newSession = { username, password, token };
  addSession(newSession);

  return token;
}

The session is then added to the backend database. In this demonstration the user and session databases are kept in memory (this is just a demonstration).

Authenticate API Calls

Then we add middleware to authenticate all relevant API calls, example (router.mjs):

router.get('/product-groups', verifyToken, async (req, res, next) => {
  try {
    const productGroups = await getProductGroups();
    const ret = { ret: 'ok', product_groups: productGroups };
    res.status(200).json(ret);
  } catch (err) {
    next(err);
  }
});

The verifyToken is rather simple (middleware.mjs):

const verifyToken = (req, _res, next) => {
  const token = req.headers['x-token'];
  if (!token) {
    throw new NotFoundError('A token is required for authentication');
  }
  validateToken(token);
  return next();
};

and (users.mjs):

function validateToken(token) {
  const decodedToken = jwt.verify(token, SECRET);
  const session = findSessionByToken(token);
  if (!session) {
    const sessions = giveSessions();
    throw new ValidationError('Token not found in the session database');
  }
  if (!decodedToken) {
    throw new ValidationError('Could not decode token');
  }
  if (session.username !== decodedToken.username) {
    throw new ValidationError('Token username does not match the session username');
  }
  return decodedToken;
}

Testing

The following bash file depicts the API wall without the token, with a wrong token and with a valid token which should be passed with the request header x-token (product-groups.sh):

#!/bin/bash

RET=$(http POST http://localhost:6600/login username=jartsa password=joo Content-Type:application/json)
TOKEN=$(echo $RET | jq '.token' | tr -d '"') 
#echo $TOKEN

#http http://localhost:6600/product-groups
#http http://localhost:6600/product-groups x-token:"WRONG-TOKEN"
http http://localhost:6600/product-groups x-token:$TOKEN

And the same with an automatic integration test (router.test.mjs):

// eslint-disable-next-line no-undef
it('Call /product-groups', async () => {
  const res = await axios.post(`${baseUrl}/login`, { username: 'jartsa', password: 'joo' });
  const { token } = res.data;
  await spec()
    .get(`${baseUrl}/product-groups`)
    .withHeaders({ 'x-token': token })
    .expectStatus(200)
    .expectJsonMatch({ ret: 'ok', product_groups: [{ pgId: 1, name: 'Books' }, { pgId: 2, name: 'Movies' }] });
});

Conclusions

It is rather simple to create a basic API authentication scheme for a Javascript backend. In the next blog post, I describe how we call the /login API in the frontend side, and how we store the the returned token using the Redux Toolkit, and how we pass the token from the frontend to the backend.

The writer is working at a major international IT corporation building cloud infrastructures and implementing applications on top of those infrastructures.

Kari Marttila

Kari Marttila’s Home Page in LinkedIn: https://www.linkedin.com/in/karimarttila/