August 06 2018

NodeJS - JWT Authentication Tutorial with Example API

In this tutorial we'll go through a simple example of how to implement JWT authentication in a NodeJS API with JavaScript. For an extended example that includes role based access control check out Node.js - Role Based Authorization Tutorial with Example API.

The example API has just two endpoints/routes to demonstrate authenticating with JWT and accessing a restricted route with JWT:

  • /users/authenticate - public route that accepts HTTP POST requests containing the username and password in the body. If the username and password are correct then a JWT authentication token is returned.
  • /users - secure route that accepts HTTP GET requests and returns a list of all the users in the application if the HTTP Authorization header contains a valid JWT token. If there is no auth token or the token is invalid then a 401 Unauthorized response is returned.

The tutorial project is available on GitHub at https://github.com/cornflourblue/node-jwt-authentication-api.

Running the Node JWT Authentication API Locally

  1. Download or clone the tutorial project code from https://github.com/cornflourblue/node-jwt-authentication-api
  2. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  3. Start the api by running npm start from the command line in the project root folder, you should see the message Server listening on port 4000. You can test the api directly using an application such as Postman or you can test it with one of the single page example applications below.


Running an Angular 6 client app with the Node JWT Auth API

For full details about the example Angular 6 application see the post Angular 6 - JWT Authentication Example & Tutorial. But to get up and running quickly just follow the below steps.

  1. Download or clone the Angular 6 tutorial code from https://github.com/cornflourblue/angular-6-jwt-authentication-example
  2. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  3. Remove or comment out the line below the comment // provider used to create fake backend located in the /src/app/app.module.ts file.
  4. Start the application by running npm start from the command line in the project root folder, this will launch a browser displaying the Angular example application and it should be hooked up with the Node JWT Auth API that you already have running.


Running a React client app with the Node JWT Auth API

For full details about the example React application see the post React + Redux - JWT Authentication Tutorial & Example. But to get up and running quickly just follow the below steps.

  1. Download or clone the React tutorial code from https://github.com/cornflourblue/react-redux-jwt-authentication-example
  2. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  3. Remove or comment out the 2 lines below the comment // setup fake backend located in the /src/index.jsx file.
  4. Start the application by running npm start from the command line in the project root folder, this will launch a browser displaying the React example application and it should be hooked up with the Node JWT Auth API that you already have running.


Running a VueJS client app with the Node JWT Auth API

For full details about the example VueJS JWT application see the post Vue.js + Vuex - JWT Authentication Tutorial & Example. But to get up and running quickly just follow the below steps.

  1. Download or clone the VueJS tutorial code from https://github.com/cornflourblue/vue-vuex-jwt-authentication-example
  2. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  3. Remove or comment out the 2 lines below the comment // setup fake backend located in the /src/index.js file.
  4. Start the application by running npm start from the command line in the project root folder, this will launch a browser displaying the VueJS example application and it should be hooked up with the Node JWT Auth API that you already have running.
 

Node JWT Authentication Project Structure

The project is structured into "feature folders" (users) "non-feature / shared component folders" (_helpers). Shared component folders contain code that can be used by multiple features and other parts of the application, and are prefixed with an underscore to group them together so it's easy to see what's what at a glance.

The example only contains the single users feature, but this can be easily extended to handle any other feature by copying the users folder and following the same pattern.

 

Node JWT Helpers Folder

Path: /_helpers

The helpers folder contains all the bits and pieces that don't fit into other folders but don't justify having a folder of their own.

 

Node JWT Global Error Handler Middleware

Path: /_helpers/error-handler.js

The global error handler is used catch all errors and remove the need for redundant error handler code throughout the application. It's configured as middleware in the main server.js file.

module.exports = errorHandler;

function errorHandler(err, req, res, next) {
    if (typeof (err) === 'string') {
        // custom application error
        return res.status(400).json({ message: err });
    }

    if (err.name === 'UnauthorizedError') {
        // jwt authentication error
        return res.status(401).json({ message: 'Invalid Token' });
    }

    // default to 500 server error
    return res.status(500).json({ message: err.message });
}
 

Node JWT Token Verification Middleware

Path: /_helpers/jwt.js

The node JWT middleware checks that the JWT token received in the http request from the client is valid before allowing access to the API, if the token is invalid a "401 Unauthorized" response is sent to the client.

const expressJwt = require('express-jwt');
const config = require('config.json');

module.exports = jwt;

function jwt() {
    const { secret } = config;
    return expressJwt({ secret }).unless({
        path: [
            // public routes that don't require authentication
            '/users/authenticate'
        ]
    });
}
 

Node JWT Users Folder

Path: /users

The users folder contains all code that is specific to the users feature of the api.

 

Node JWT User Service

Path: /users/user.service.js

The user service contains a method for authenticating user credentials and returning a JWT token, and a method for getting all users in the application.

I hardcoded the array of users in the example to keep it focused on JWT authentication, in a production application it is recommended to store user records in a database with hashed passwords. For an extended example that includes support for user registration and stores data in MongoDB check out NodeJS + MongoDB - Simple API for Authentication, Registration and User Management.

The top of the file contains the exported service method definitions so it's easy to see all methods at a glance, the rest of the file contains the method implementations.

const config = require('config.json');
const jwt = require('jsonwebtoken');

// users hardcoded for simplicity, store in a db for production applications
const users = [{ id: 1, username: 'test', password: 'test', firstName: 'Test', lastName: 'User' }];

module.exports = {
    authenticate,
    getAll
};

async function authenticate({ username, password }) {
    const user = users.find(u => u.username === username && u.password === password);
    if (user) {
        const token = jwt.sign({ sub: user.id }, config.secret);
        const { password, ...userWithoutPassword } = user;
        return {
            ...userWithoutPassword,
            token
        };
    }
}

async function getAll() {
    return users.map(u => {
        const { password, ...userWithoutPassword } = u;
        return userWithoutPassword;
    });
}
 

Node JWT Users Controller

Path: /users/users.controller.js

The users controller defines all user routes for the api, the route definitions are grouped together at the top of the file and the implementations are below.

Express is the web server used by the api, it's one of the most popular web application frameworks for NodeJS.

const express = require('express');
const router = express.Router();
const userService = require('./user.service');

// routes
router.post('/authenticate', authenticate);
router.get('/', getAll);

module.exports = router;

function authenticate(req, res, next) {
    userService.authenticate(req.body)
        .then(user => user ? res.json(user) : res.status(400).json({ message: 'Username or password is incorrect' }))
        .catch(err => next(err));
}

function getAll(req, res, next) {
    userService.getAll()
        .then(users => res.json(users))
        .catch(err => next(err));
}
 

Node JWT App Config

Path: /config.json

The app config file contains configuration data for the api.

IMPORTANT: The "secret" property is used by the api to sign and verify JWT tokens for authentication, update it with your own random string to ensure nobody else can generate a JWT to gain unauthorised access to your application.

{
    "secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING"
}
 

Node JWT Main Server Entrypoint

Path: /server.js

The server.js file is the entry point into the api, it configures application middleware, binds controllers to routes and starts the Express web server for the api.

require('rootpath')();
const express = require('express');
const app = express();
const cors = require('cors');
const bodyParser = require('body-parser');
const jwt = require('_helpers/jwt');
const errorHandler = require('_helpers/error-handler');

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

// use JWT auth to secure the api
app.use(jwt());

// api routes
app.use('/users', require('./users/users.controller'));

// global error handler
app.use(errorHandler);

// start server
const port = process.env.NODE_ENV === 'production' ? 80 : 4000;
const server = app.listen(port, function () {
    console.log('Server listening on port ' + port);
});

 

Web Development Sydney

Feel free to contact me if you're looking for a web developer in Sydney, I also provide remote contracting services for clients outside Sydney.

 


Sponsored by