Node.js Basics — Add Authentication to an Express App with jsonwebtoken

John Au-Yeung - Jan 22 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

Node.js is a popular runtime platform to create programs that run on it.

It lets us run JavaScript outside the browser.

In this article, we’ll look at how to start using Node.js to create programs.

Bearer Token Authentication

We can generate a token to identify authenticated users.

When they want to make requests to our server, they send in the auth token to our server to decode it.

If it’s decoded successfully, then we let the user make the request.

We can install the jsonwebtoken package by running:

npm i jsonwebtoken
Enter fullscreen mode Exit fullscreen mode

Then we can use it by writing:

const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');

const users = {
  foo: {
    username: 'foo',
    password: 'bar',
    id: 1
  },
  bar: {
    username: 'bar',
    password: 'foo',
    id: 2
  }
}

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: true
}));

app.use(express.static('public'));
app.get('/', (req, res) => {
  res.sendFile('public/index.html');
});

app.post(
  '/login',
  function(req, res) {
    const {
      username,
      password
    } = req.body;
    const user = users[username];
    if (user === undefined) {
      return res.status(401).send('invalid user')
    }
    if (user.password !== password) {
      return res.status(401).send('invalid password')
    }

    const {
      id
    } = user;
    jwt.sign({
      id,
      username
    }, 'shhhhh', function(err, token) {
      res.send(token);
    });
  }
);

app.listen(3000, () => console.log('server started'));
Enter fullscreen mode Exit fullscreen mode

In the /login route, we get the username and password properties that we make a request within the JSON request body.

Then we check the user in the users object.

If the username or password is invalid, then we send the status 401 with a message indicating the error.

Once everything is valid, then we create the token with the jwt.sign method.

The first argument is the data that will be in the token.

Then 2nd is the secret that we sign the token with.

The last argument is the callback that’s run when the token is created.

Then we send the token back to the client.

So if we make a POST request with the /login route and the following request body:

{
    "username": "foo",
    "password": "a"
}
Enter fullscreen mode Exit fullscreen mode

We get 'invalid password' return with the 401 status.

If we make the same request with a valid body like:

{
    "username": "foo",
    "password": "bar"
}
Enter fullscreen mode Exit fullscreen mode

Then we get a signed token like:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJmb28iLCJpYXQiOjE2MDA2MjUzOTd9.05p7hoD3Qma4ShXIVWi0pKBQIv4wqSvEIvuNhaJtens
Enter fullscreen mode Exit fullscreen mode

Once we decode it, we should get all the data in the object returned.

To verify the token, we can use the jwt.verify method. For example, we can write:

const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');

const users = {
  foo: {
    username: 'foo',
    password: 'bar',
    id: 1
  },
  bar: {
    username: 'bar',
    password: 'foo',
    id: 2
  }
}

const app = express();
const SECRET= 'shhhhh';

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: true
}));

app.use(express.static('public'));
app.get('/', (req, res) => {
  res.sendFile('public/index.html');
});

app.post(
  '/login',
  function(req, res) {
    const {
      username,
      password
    } = req.body;
    const user = users[username];
    if (user === undefined) {
      return res.status(401).send('invalid user')
    }
    if (user.password !== password) {
      return res.status(401).send('invalid password')
    }

const {
      id
    } = user;
    jwt.sign({
      id,
      username
    }, SECRET, function(err, token) {
      res.send(token);
    });
  }
);

app.get('/secret', (req, res, next) => {
  console.log(req.headers)
  jwt.verify(req.headers.authorization, SECRET, function(err, decoded) {
    if (err){
      return res.status(403).send('unauthorized')
    }
    req.user = decoded;
    next()
  });
}, (req, res)=>{
  res.json(req.user);
})

app.listen(3000, () => console.log('server started'));
Enter fullscreen mode Exit fullscreen mode

We added the /secret route that has a middleware that checks the Authorization header for the token.

Then we pass that into the jwt.verify method to check the token.

The 2nd argument is the same secret string that we used to sign the token.

We need this to do the check properly.

Then once we decoded the string, we return 403 error if there’s an error.

Otherwise, we set req.user to the decoded token object so we can use it in our route.

Then we call next to call the route handler.

If we make a GET request to the /secret route, we should get the user data returned in the response.

Conclusion

We can use the jsonwebtoken package to issue and verify JSON web tokens for authentication.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .