Stateless Authentication with JSON Web Tokens

Stateless Authentication with JSON Web Tokens

Stateless Authentication with JSON Web Tokens: Learn how to protect your microservice, API, or web app using JSON Web Tokens to create a stateless authentication protocol.

Stateless Authentication with JSON Web Tokens: Learn how to protect your microservice, API, or web app using JSON Web Tokens to create a stateless authentication protocol.

Whether you’re writing a public API or an internal microservice, getting authentication right can make or break your API. Let’s take a look at a JSON Web Token-based authentication system.

We’ll begin with basic authentication & JWT concepts, followed by a detailed walkthrough of designing an authentication service with plenty of code examples.

Before we begin, some definitions are in order:

  • Credential: a fact that describes an identity
  • Authentication: Validation of a credential to identify an entity
  • Authorization: Verification that an entity is allowed to access a resource or perform an action
What are JSON Web Tokens?

JSON Web Tokens (JWT - pronounced “jot”) are a compact and self-contained way for securely transmitting information and represent claims between parties as a JSON object.

This is an encoded JSON Web Token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1MWQ4NGFjMS1kYjMxLTRjM2ItOTQwOS1lNjMwZWJiYjgzZGYiLCJ1c2VybmFtZSI6Imh1bnRlcjIiLCJzY29wZXMiOlsicmVwbzpyZWFkIiwiZ2lzdDp3cml0ZSJdLCJpc3MiOiIxNDUyMzQzMzcyIiwiZXhwIjoiMTQ1MjM0OTM3MiJ9.cS5KkPxtEJ9eonvsGvJBZFIamDnJA7gSz3HZBWv6S1Q 

JSON Web Tokens such as the one shown is a string consisting of three components, each component delimited by a . (period) character.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJqdGkiOiI1MWQ4NGFjMS1kYjMxLTRjM2ItOTQwOS1lNjMwZWJiYjgzZGYiLCJ1c2VybmFtZSI6Imh1bnRlcjIiLCJzY29wZXMiOlsicmVwbzpyZWFkIiwiZ2lzdDp3cml0ZSJdLCJpc3MiOiIxNDUyMzQzMzcyIiwiZXhwIjoiMTQ1MjM0OTM3MiJ9
.
cS5KkPxtEJ9eonvsGvJBZFIamDnJA7gSz3HZBWv6S1Q

Base64Url decoding a JSON Web Token gives us the following:

{
  "alg": "HS256",
  "typ": "JWT"
}
.
{
  "jti": "51d84ac1-db31-4c3b-9409-e630ebbb83df",
  "username": "hunter2",
  "scopes": ["repo:read", "gist:write"],
  "iss": "1452343372",
  "exp": "1452349372"
}
.
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

JSON Web Tokens consists of the following three components: the Header, Payload, and Signature. A token is constructed as follows:

  1. You generate a claim of arbitrary JSON data (the Payload), which in our case contains all the required information about a user for the purposes of authentication. A Header typically defines the signing algorithm alg and type of token typ.
  2. You decorate it with some metadata, such as when the claim expires, who the audience is, etc. These are known as claims, defined in the JWT IETF Draft.
  3. The data (both Header and Payload) is then cryptographically signed with a Hash-based Message Authentication Code (HMAC) secret. This Signature is used to verify that the sender of the JWT is who it says it is and to ensure that the message was’t changed in the way.
  4. The Header, Payload, and Signature are then Base64 encoded and concatenated together with periods to delimit the fields, which results in the token we see in the first example.

JWTs can also be signed using a secret (with HMAC algorithm) OR a public/private key pair using RSA.

For authentication purposes, a JWT serves as the credential/identity object that clients must show to gatekeepers to verify that you’re allowed access protected resources you want to access. It can be signed by a trusted party, and verified by gatekeepers.

JSON Web Tokens work across different programming languages. You should be able to find clients you can use to sign and verify tokens written for your stack.

Authentication Flow

One of the primary use cases of using JWTs is to authenticate requests. Once a user is logged in, each subsequent request can include the JWT to access previously inaccessible protected resources and services.

To illustrate, let’s imagine an authentication layer for a set of microservices containing a user’s protected resource.

Our authentication flow happens between the following parties:

  • Resource Owner (the User): the party that owns the resource to be shared. Let’s call our user Tom.
  • Resource Server: the service that holds the protected resource. Our WalletService holds the Wallet resource, which is a user’s digital wallet.
  • Authorization Server: the service that verifies the identity of users. Let’s call this AuthService.
  • Client: the application (web/mobile/others) that makes requests to the Resource Server on behalf of the Resource Owner. Let’s have a WalletApp Android app.

If you’re familiar with OAuth2, our flow is similar to the Resource Owner Password Credentials Grant flow. Depending on your use case, other flows may be a better fit for your application.

Our entire flow goes as follows:

  1. Tom the Resource Owner wants to view the contents of his digital wallet through the Client.
  2. The Client talks to WalletService, requesting for Tom’s Wallet resource.
  3. Unfortunately, Wallets are a protected resource. Clients will need to pass an access token to continue.
  4. The Client talks to AuthService, requesting an access token. AuthService responds by asking for the user’s credentials.
  5. The Client redirects Tom the Resource Owner to the AuthService, which gives Tom the option to either deny or accept the Client’s request for access.
  6. AuthService verifies Tom’s credentials, redirects her back to the Client, and grants an Authorization Code to the Client.
  7. The Client presents the authorization code to the AuthService, returning an access token (a JWT) to the Client if successful.
  8. WalletApp presents the access token to the WalletService, requesting for Tom’s Wallet resource. Whenever the client wants to access a protected route or resource, it should send the JWT, typically in the Authorization header using the Bearer schema e.g. Authorization: Bearer
  9. WalletService validates the token, decoding the JWT, and parsing its contents.
  10. (Optional, see Revoking Tokens) WalletService asks AuthService to validate the token.
  11. If the access token is valid for the requested operation and resource, WalletService returns Tom’s Wallet to the WalletApp Client.
  12. WalletApp shows Tom his Wallet.

Note that the Resource Owner does not share their credentials to the Client directly. Instead, users notify the Authorizer that the Client may access whatever it is that they requested, and the Client authenticates separately with an authorization code. For more details, check out the OpenID Connect spec.

In this article, we’re focusing primarily on step 8 to 12.

A Minimum Viable Authentication Service

Let’s work on an authentication service for the flow above using plain old Node + Express. Of course, you’re free to use whatever you’d like for your own authentication service.

We need at minimum a single endpoint:

// Authentication Service API Login endpoint

var _ = require('underscore');
var Promise = require('bluebird');
var express = require('express');
var router = express.Router();

var models = require('../models');
var User = models.User;
var JWT = require('../utils/jwt');

// Login
router.post('/sessions', function(req, res, next) {
  var params = _.pick(req.body, 'username', 'password', 'deviceId');
  if (!params.username || !params.password || !params.deviceId) {
    return res.status(400).send({error: 'username, password, and deviceId ' +
                                'are required parameters'});
  }

  var user = User.findOne({where: {username: params.username}});
  var passwordMatch = user.then(function(userResult) {
    if (_.isNull(userResult)) {
      return res.status(404).send({error: 'User does not exist'});
    }
    return userResult.comparePassword(params.password);
  });

  Promise.join(user, passwordMatch, function(userResult, passwordMatchResult) {
    if (!passwordMatchResult) {
      return res.status(403).send({
        error: 'Incorrect password'
      });
    }

      var userKey = uuid.v4();
      var issuedAt = new Date().getTime();
      var expiresAt = issuedAt + (EXPIRATION_TIME * 1000);

      var token = JWT.generate(user.username, params.deviceId, userKey, issuedAt, expiresAt);

      return res.status(200).send({
            accessToken: token;
      });
  })
    .catch(function(error) {
      console.log(error);
      next(error);
    });
});
//lib/utils/jwt.js

var _ = require('underscore');
var config = require('nconf');
var jsrsasign = require('jsrsasign');

var sessionKey = require('../utils/sessionKey');
var JWT_ENCODING_ALGORITHM = config.get('jwt:algorithm');
var JWT_SECRET_SEPARATOR = config.get('jwt:secret_separator');

function JWT() {
  this.secretKey = config.get('jwt:secret');
}

// Generate a new JWT
JWT.prototype.generate = function(user, deviceId, userKey, issuedAt,
                                  expiresAt) {
  if (!user.id || !user.username) {
    throw new Error('user.id and user.username are required parameters');
  }

  var header = {
    alg: JWT_ENCODING_ALGORITHM, typ: 'JWT'
  };
  var payload = {
    username: user.username,
    deviceId: deviceId,
    jti: sessionKey(user.id, deviceId, issuedAt),
    iat: issuedAt,
    exp: expiresAt
  };
  var secret = this.secret(userKey);
  var token = jsrsasign.jws.JWS.sign(JWT_ENCODING_ALGORITHM,
                         JSON.stringify(header),
                         JSON.stringify(payload),
                         secret);
  return token;
};

// Token Secret generation
JWT.prototype.secret = function(userKey) {
  return this.secretKey + JWT_SECRET_SEPARATOR + userKey;
};

module.exports = new JWT();

Great! We can now return access tokens upon succesful login. In the next sections, we’ll take a look at introducing additional capabilities for our authentication system as well as writing an authentication middleware that we can easily use to protect the routes of future microservices.

But first, let’s learn more about the reasons why we use JWTs instead of regular plaintext tokens.

Benefits of Using JWTs for Authentication

Using a JSON Web Token as your identity object gives you a handful of advantages compared to an opaque OAuth2 Bearer token:

1. Fine Grained Access Control: You can specify detailed access control information within the token itself as part of its payload. In the same way that you can create AWS security policies with very specific permissions, you can limit the token to only give read/write access to a single resource. In contrast, API Keys tend to have a coarse all-or-nothing access.

[

You can populate your tokens with private claims containing a dynamic set of scopes with JWTs. For example:

{
  "jti": "51d84ac1-db31-4c3b-9409-e630ebbb83df",
  "username": "hunter2",
  "scopes": ["repo:read", "gist:write"],
  "iss": "1452343372",
  "exp": "1452349372"
}

Your authentication middleware can parse this JWT metadata and perform validation without making a request to the authorization server. The API endpoint would simply check for the presence of the right scope atribute as follows.

We’ve covered this in the previous section, alongside code examples.

2. Introspectable: A JSON Web Token carries a header-like metadata that can be easily inspected for client-side validation purposes, unlike plaintext Bearer OAuth tokens which we can’t decode and inspect without making calls to our database.

3. Expirable: JSON Web Tokens can have built-in expiry mechanisms through the exp property. The exp (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing.

4. Stateless: All the information needed to complete a particular request is sent along with it, including an Authorization HTTP header which contains our JWT which serves as an ‘identity object.’ Since the payload contains all the required information for us to authenticate the user, we can avoid making repeated calls to our database.

5. Encrypted: While a JWT’s signature prevents malicious parties from tampering with it, the token’s header is only Base64 encoded. When dealing with confidential identifiers in your tokens, you should encrypt your tokens using AES.

At this point you might be thinking:

“Wow, that’s great! I can implement my authentication entirely stateless without having to store any session information!”

The above is true in that you can perform client side validation on the exp expiry time claim to invalidate an expired token.

However, we notice a few issues that our current design have not addressed:

What happens when you want to log a user out of an application? Say you updated the schema of your tokens. How do you handle the old tokens that still haven’t expired? When you deploy this update to the application, how do you invalidate the current sessions? When you’re storing sessions?

At this point, we don’t have a way for our authorization server to invalidate a session that has not yet expired.

Revoking Tokens

One issue with a purely stateless approach is we have no way to revoke/invalidate issued tokens before they expire. In other words, we can’t manually log out a user. If a malicious party manages to acquire a token and we KNOW that a malicious party has the token, we’d be sitting ducks. We have no way to take away access for already issued tokens.

We could have client-side logic that clears any expired session tokens during validation. However, client-side security is insufficient. To help prevent token misuse, we need the ability to revoke tokens that have already been issued.

There’s the chance that malicious parties can see your services’ request headers. Tou should use TLS for client-server and intra-service communication to ensure that nobody sniffs your network requests. Having said that, we want our authentication system to be safe even if an attacker can intercept the network communication between the client and the server.

Depending on your use case, there are two approaches we can take to support two different token invalidation capabilities. Both approaches require the use of additional storage such as Redis for storing some form of a token’s identifier.

Redis is a wonderful key-value storage and can be extremely useful to save ephemeral data such as our tokens. It includes features like automatic deletion or expiration for tokens, can handle lots of writes, and is horizontally scalable.

Both approaches also require our validation middleware to make requests to the authorization server for token verification. Let’s take a look at how we can implement them:

1. To be able to revoke all tokens belonging to a single user, we can simply sign JWTs belonging to that user with her very own private secret. You can dynamically generate these secrets or you can use a hash of their password.

Then, during our token validation process, we can retrieve this private secret from a DB/service (in our case from KeyService) to verify the token’s signature.

Revoking the token can be done by changing or deleting that user’s secret, thus invalidating all issued tokens belonging that that user.

2. To be able to revoke an individual token, where users can have multiple tokens on different devices, will require us to generate a unique jti identifier for each JWT, which we can use as an identifier in KeyService for retrieving a dynamically generated, session-specific secret created for the purposes of signing and verifying a single token.

  // Verify JWT
  KeyService.get(payload.jti)
    .then(function(userKey) {
      var authenticated = JWT.verify(token, userKey);
      if (authenticated) {
        return next();
      }

      return next(new Error('403 Invalid Access Token'));
    });

The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. One approach that can help minimize collisions is to use uuids instead of integers as your identifier.

For both approaches, we also need to prevent replay attacks by including unique identifiers in the jti and timestamps in the iat (issued at) claim as part of your JWT payload. This ensures that each token generated is unique.

We need to add additional endpoints:

The GET endpoint will be primarily used by our authentication middleware to retrieve the secret used to sign the JWT and verify if the signature is valid.

The DELETE endpoint will either change or remove the secret used for the user’s session at a particular device so that the JWT signature verification fails and a 403 Forbidden response is triggered.

We also create a service wrapper for storing user/session-specific secrets used to sign JWTs, with methods get, set, and delete:

// KeyService.js, a key storage backed by Redis

// KeyService stores and manages user-specific keys used to sign JWTs
var redis = require('redis');
var Promise = require('bluebird');
var config = require('nconf');
var uuid = require('node-uuid');

var JWT = require('../utils/jwt');
var EXPIRATION_TIME = config.get('key_service:expires_seconds');
var sessionKey = require('../utils/sessionKey');
Promise.promisifyAll(redis.RedisClient.prototype);

function KeyService() {
  this.client = redis.createClient(config.get('key_service:port'),
                                   config.get('key_service:host'));
  this.client.on('connect', function() {
    console.log('Redis connected.');
  });
  console.log('Connecting to Redis...');
}

// Retrieve a JWT user key
KeyService.prototype.get = function(sessionKey) {
  return this.client.getAsync(sessionKey);
};

// Generate and store a new JWT user key
KeyService.prototype.set = function(user, deviceId) {
  var userKey = uuid.v4();
  var issuedAt = new Date().getTime();
  var expiresAt = issuedAt + (EXPIRATION_TIME * 1000);

  var token = JWT.generate(user, deviceId, userKey, issuedAt, expiresAt);
  var key = sessionKey(user.id, deviceId, issuedAt);

  var setKey = this.client.setAsync(key, userKey);
  var setExpiration = setKey.then(this.client.expireAsync(key,
                                  EXPIRATION_TIME));
  var getToken = setExpiration.then(function() {
    return token;
  });

  return getToken;
};

// Manually remove a JWT user key
KeyService.prototype.delete = function(sessionKey) {
  return this.client.delAsync(sessionKey);
};

module.exports = new KeyService();

Note that an expiry mechanism is built in, which utilizes Redis’ [EXPIRE](https://redis.io/commands/expire) feature to automatically remove sessions that have expired, thereby invalidating any issued tokens signed with that secret.

Here is our main router, updated to handle the additional endpoints and talk to KeyService:

// Authentication Service API endpoints

var _ = require('underscore');
var Promise = require('bluebird');
var express = require('express');
var router = express.Router();

var models = require('../models');
var User = models.User;
var KeyService = require('../services/KeyService');
var ErrorMessage = require('../utils/error');

// Register
router.post('/users', function(req, res, next) {
  var params = _.pick(req.body, 'username', 'password');
  if (!params.username || !params.password) {
    return res.status(400).send({error: 'username and password ' +
                                'are required parameters'});
  }

  User.findOrCreate({
    where: {username: params.username},
    defaults: {password: params.password}
  })
  .spread(function(user, created) {
    if (!created) {
      return res.status(409).send({error: 'User with that username ' +
                                  'already exists.'});
    }
    res.status(201).send(user);
  })
  .catch(function(error) {
    res.status(400).send(ErrorMessage(error));
  });
});

// Login
router.post('/sessions', function(req, res, next) {
  var params = _.pick(req.body, 'username', 'password', 'deviceId');
  if (!params.username || !params.password || !params.deviceId) {
    return res.status(400).send({error: 'username, password, and deviceId ' +
                                'are required parameters'});
  }

  var user = User.findOne({where: {username: params.username}});
  var passwordMatch = user.then(function(userResult) {
    if (_.isNull(userResult)) {
      return res.status(404).send({error: 'User does not exist'});
    }
    return userResult.comparePassword(params.password);
  });

  Promise.join(user, passwordMatch, function(userResult, passwordMatchResult) {
    if (!passwordMatchResult) {
      return res.status(403).send({
        error: 'Incorrect password'
      });
    }

    return KeyService.set(userResult, params.deviceId)
        .then(function(token) {
          res.status(200).send({
            accessToken: token
          });
        });
  })
    .catch(function(error) {
      console.log(error);
      next(error);
    });
});

// Get Session
router.get('/sessions/:sessionKey', function(req, res, next) {
  var sessionKey = req.params.sessionKey;
  if (!sessionKey) {
    return res.status(400).send({error: 'sessionKey is a required parameters'});
  }

  KeyService.get(sessionKey)
    .then(function(result) {
      if (_.isNull(result)) {
        return res.status(404).send({error: 'Session does not exist or has ' +
                                    'expired. Please sign in to continue.'});
      }
      res.status(200).send({userKey: result});
    })
    .catch(function(error) {
      console.log(error);
      next(error);
    });
});

// Logout
router.delete('/sessions/:sessionKey', function(req, res, next) {
  var sessionKey = req.params.sessionKey;
  if (!sessionKey) {
    return res.status(400).send({error: 'sessionKey is a required parameter'});
  }

  KeyService.delete(sessionKey)
    .then(function(result) {
      if (!result) {
        return res.status(404).send();
      }
      res.status(204).send();
    })
    .catch(function(error) {
      console.log(error);
      next(error);
    });
});

module.exports = router;
Updated Authentication Flow

Below is our updated flow with support for revoking already issued tokens:

We introduce some additional steps in our token validation process (this happens in our middleware) that communicates with an external private secret storage KeyService to retrieve the secrets necessary for decoding and verifying the JWT signature.

As we’ve talked about, this allows us to introduce the ability to expire and manually revoke already issued tokens at the cost of some complexity.

A Minimum Viable Authentication Middleware

Alongside our AuthService, we can and should write a companion Node.js module that other developers can use to easily add authentication to their microservices. For example:

var auth = require('auth');
router.post('/protected', auth.isAuthenticated, function(req, res, next) {
  res.status(200).send();
});

FYI, the isAuthenticated middleware checks that the Authorization: Bearer headers in each incoming request to that route is valid. The source code is supplied below.

You can also protect ALL routes like so:

var auth = require('auth');
app.use(auth.isAuthenticated);

The isAuthenticated middleware can be written as follows:

// index.js

var base64url = require('base64url');
var JWT = require('./lib/utils/jwt');
var KeyService = require('./lib/services/KeyService');

function isAuthenticated(req, res, next) {
  // Guard clauses
  var authorization = req.headers.authorization;
  if (!authorization || !(authorization.search('Bearer ') === 0)) {
    return next(new Error('401 Missing Authorization Header'));
  }
  var token = authorization.split(' ')[1];
  if (!token) {
    return next(new Error('401 Missing Bearer Token'));
  }

  // Unpack JWT
  var components = token.split('.');
  var header = JSON.parse(base64url.decode(components[0]));
  var payload = JSON.parse(base64url.decode(components[1]));
  var signature = components[2];

  // Verify JWT
  KeyService.get(payload.jti)
    .then(function(userKey) {
      var authenticated = JWT.verify(token, userKey);
      if (authenticated) {
        return next();
      }

      return next(new Error('403 Invalid Access Token'));
    });
}

module.exports = {
  isAuthenticated: isAuthenticated
};

KeyService is a wrapper over the Redis storage used to store session-specific user keys, indexed by a uuid identifier contained in the token’s jti claim. We’ve seen this before, except the operations defined for our middleware is strictly read-only.

// KeyService stores and manages user-specific keys used to sign JWTs
var redis = require('redis');
var Promise = require('bluebird');
var config = require('nconf');

Promise.promisifyAll(redis.RedisClient.prototype);

function KeyService() {
  this.client = redis.createClient(config.get('key_service:port'),
                                   config.get('key_service:host'));
  this.client.on('connect', function() {
    console.log('Redis connected.');
  });
  console.log('Connecting to Redis...');
}

// Retrieve a JWT user key
KeyService.prototype.get = function(sessionKey) {
  return this.client.getAsync(sessionKey);
};

module.exports = new KeyService();

JWT is a lightweight wrapper of the jsrsasign crypto library. We use the jsrsassign crypto library to verify our JWTs:

Be careful when choosing which crypto library you use to sign and verify JWTs. Be sure to check jwt.io for any security vulnerabilities.

// lib/utils/jwt.js

var _ = require('underscore');
var config = require('nconf');
var jsrsasign = require('jsrsasign');
var base64url = require('base64url');

var JWT_ENCODING_ALGORITHM = config.get('jwt:algorithm');
var JWT_SECRET_SEPARATOR = config.get('jwt:secret_separator');

function JWT() {
  this.secretKey = config.get('jwt:secret');
}

JWT.prototype.verify = function(token, userKey) {
  var secret = this.secret(userKey);
  var isValid = jsrsasign.jws.JWS.verifyJWT(token,
                                            secret,
                                            {
                                              alg: [JWT_ENCODING_ALGORITHM],
                                              verifyAt: new Date().getTime()});
  return isValid;
};

JWT.prototype.secret = function(userKey) {
  return this.secretKey + JWT_SECRET_SEPARATOR + userKey;
};

module.exports = new JWT();

Q: Why are we using both a global secret alongside the user-specific secret when computing secret?

A: Having a global secret lets us easily invalidate ALL tokens belonging to ALL users by simply changing this single secret value, thus invaliding the JWT signatures of all already issued tokens.

Writing modules for cross-cutting concerns such as authentication in this way lets you save development time and effort on future microservices. You can quickly bootstrap new services with an increasingly rich set of capabilities as you write more and more reusable modules. Shared modules also helps keep behaviour consistent across all your different services.

Other JWT Use Cases

JSON Web Tokens can securely transmit information between parties, since its signature lets us be sure that its senders are who we expect. Other use cases involving JWTs include as tokens within reset password links. We can use JWTs to create signed hyperlinks without needing to store password reset tokens in a database.

In Closing

I’ve presented one approach to building an authentication layer using JSON Web tokens. We’ve also gone through a number of the design decisions to help prevent some security loopholes.

While JWTs may seem like a fairly sane method of authentication it’s important for us to not ignore the lessons we’ve learned from older authentication schemes with years of combat experience.

Through this process, I hope I’ve shared with you how client-side authentication schemes using JWTs has its own risks and limitations which needs to be investigated throughly before going into implementation.

Let me know what you think in the comments below!

Build Secure (JWT) Token Based Authentication API with Node.js

Build Secure (JWT) Token Based Authentication API with Node.js

In this tutorial, we are going to learn how to build a secure token-based user authentication REST APIs using JWT (JSON web token), bcrypt, Node, Express, and MongoDB.

Welcome, programming, buddies! Today, In this tutorial, we are going to learn how to build a secure token-based user authentication REST APIs using JWT (JSON web token), bcrypt, Node, Express, and MongoDB.

Creating authentication REST API with Node Js is merely effortless. We will be taking the help of Express js to create the authentication endpoints and also make the MongoDB connection to store the user’s data in it.

Click on the below button to get the complete code of this project on GitHub.

Git Repo

What are JSON Web Tokens (JWT)?

JSON Web Token (JWT) is a JSON object that is described in RFC 7519 as a safe approach to transfer a set of information between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.

Authentication Workflow with JSON Web Tokens

Let’s understand from the below diagram how does the secure authentication system work with JSON web token.

image source medium.com

  • A client makes the API call and sends the user information such as username and password to the webserver.
  • On successful authentication a webserver generates a string-based token and returns to the client system.
  • A client can store this token in the browser’s local storage or in a session.
  • Client sets this token in a header something like “Bearer xxx.xxx.xxx”.
  • On next API call JWT token communicateS with the server, and after the successful verification, the server returns the response to the client.

Table of contents

  1. Initiate Node Token-Based Authentication Project
  2. Define Mongoose Schema
  3. Implement MongoDB Database in Node App
  4. Create Secure Token-based Authentication REST API in Node
  5. Verify Node Authentication REST API
  6. Adding Input Validation in Express RESTful API
  7. Node Server Configuration
  8. Start Node Server
  9. Conclusion
Initiate Node Token-Based Authentication Project

Create a project folder to build secure user authentication REST API, run the following command.

mkdir server

Get inside the project folder.

cd server

Let’s start the project by first creating the package.json file by running the following command.

npm init

Install NPM Packages to Create Secure Auth API

Next, install the NPM dependencies for the authentication API by running the given below command.

npm install express jsonwebtoken bcryptjs body-parser 
cors mongoose-unique-validator mongoose --save

Next, install the nodemon NPM module, it helps in starting the node server when any change occurs in the server files.

npm install nodemon --save-dev
Define Mongoose Schema

Next, we are going to define user schema using mongoose ODM. It allows us to retrieve the data from the database.

Create a folder and name it models inside the project directory, create a file User.js in it.

To prevent storing the duplicate email id in MongoDB database install mongoose-unique-validator package. Below we will learn how to use in mongoose schema to validate duplicate email id from MongoDB database.

npm i mongoose-unique-validator --save

Next, add the following code in models/User.js file:

// models/User.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const uniqueValidator = require('mongoose-unique-validator');

let userSchema = new Schema({
    name: {
        type: String
    },
    email: {
        type: String,
        unique: true
    },
    password: {
        type: String
    }
}, {
    collection: 'users'
})

userSchema.plugin(uniqueValidator, { message: 'Email already in use.' });
module.exports = mongoose.model('User', userSchema)
  • The userSchema.plugin(uniqueValidator) method won’t let duplicate email id to be stored in the database.
  • The unique: true property in email schema does the internal optimization to enhance the performance.
Implement MongoDB Database in Node App

Create database folder in the project folder and create a new file database/db.js in it.

module.exports = {
    db: 'mongodb://localhost:27017/meanauthdb'
}
Create Secure Token-based Authentication REST API in Node

To build secure user authentication endpoints in node, create routes folder, and auth.routes.js file in it.

Here, we will define CRUD Restful APIs using the npm packages for log-in, sign-up, update-user, and delete-user.

// routes/auth.routes.js

const express = require("express");
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");
const router = express.Router();
const userSchema = require("../models/User");

// Sign-up
router.post("/register-user", (req, res, next) => {
    bcrypt.hash(req.body.password, 10).then((hash) => {
        const user = new userSchema({
            name: req.body.name,
            email: req.body.email,
            password: hash
        });
        user.save().then((response) => {
            res.status(201).json({
                message: "User successfully created!",
                result: response
            });
        }).catch(error => {
            res.status(500).json({
                error: error
            });
        });
    });
});

// Sign-in
router.post("/signin", (req, res, next) => {
    let getUser;
    userSchema.findOne({
        email: req.body.email
    }).then(user => {
        if (!user) {
            return res.status(401).json({
                message: "Authentication failed"
            });
        }
        getUser = user;
        return bcrypt.compare(req.body.password, user.password);
    }).then(response => {
        if (!response) {
            return res.status(401).json({
                message: "Authentication failed"
            });
        }
        let jwtToken = jwt.sign({
            email: getUser.email,
            userId: getUser._id
        }, "longer-secret-is-better", {
            expiresIn: "1h"
        });
        res.status(200).json({
            token: jwtToken,
            expiresIn: 3600,
            msg: getUser
        });
    }).catch(err => {
        return res.status(401).json({
            message: "Authentication failed"
        });
    });
});

// Get Users
router.route('/').get((req, res) => {
    userSchema.find((error, response) => {
        if (error) {
            return next(error)
        } else {
            res.status(200).json(response)
        }
    })
})

// Get Single User
router.route('/user-profile/:id').get((req, res, next) => {
    userSchema.findById(req.params.id, (error, data) => {
        if (error) {
            return next(error);
        } else {
            res.status(200).json({
                msg: data
            })
        }
    })
})

// Update User
router.route('/update-user/:id').put((req, res, next) => {
    userSchema.findByIdAndUpdate(req.params.id, {
        $set: req.body
    }, (error, data) => {
        if (error) {
            return next(error);
            console.log(error)
        } else {
            res.json(data)
            console.log('User successfully updated!')
        }
    })
})

// Delete User
router.route('/delete-user/:id').delete((req, res, next) => {
    userSchema.findByIdAndRemove(req.params.id, (error, data) => {
        if (error) {
            return next(error);
        } else {
            res.status(200).json({
                msg: data
            })
        }
    })
})

module.exports = router;
  • To secure the password, we are using the bcryptjs, It stores the hashed password in the database.
  • In the signin API, we are checking whether the assigned and retrieved passwords are the same or not using the bcrypt.compare() method.
  • In the signin API, we set the JWT token expiration time. Token will be expired within the defined duration.
Verify Node Authentication REST API

Next, we will verify the auth API using the JWT token. Create a middlewares folder and create a auth.js file inside of it, then include the following code in it.

Note: In the real world app the secret should not be kept in the code as declared below. The best practice is to store as an environment variable and it should be complex combination of numbers and strings.

// middlewares/auth.js

const jwt = require("jsonwebtoken");

module.exports = (req, res, next) => {
    try {
        const token = req.headers.authorization.split(" ")[1];
        jwt.verify(token, "longer-secret-is-better");
        next();
    } catch (error) {
        res.status(401).json({ message: "Authentication failed!" });
    }
};

Now, we will learn to implement JWT verification in the /user-profile endpoint. Import the following auth.js file from middlewares folder.

// Get User Profile
router.route('/user-profile/:id').get(authorize, (req, res, next) => {
    userSchema.findById(req.params.id, (error, data) => {
        if (error) {
            return next(error);
        } else {
            res.status(200).json({
                msg: data
            })
        }
    })
})

We added the authorize variable inside the user-profile API. It won’t render the data unless it has the valid JWT token. As you can see in the below screenshot, we have not defined the JWT token in get request, so we are getting the “No token provided” error.

Adding Input Validation in Express RESTful API

Next, we will learn to implement validation in Express auth API using POST body request. Install express-validator npm library to validate name, email and password.

The express-validator is an express.js middleware for validating POST body requests.

Run the below command to install the express-validator package.

npm install express-validator --save

Add the following code in the middlewares/auth.routes.js file.

// routes/auth.routes.js

const express = require("express");
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");
const router = express.Router();
const userSchema = require("../models/User");
const authorize = require("../middlewares/auth");
const { check, validationResult } = require('express-validator');

// Sign-up
router.post("/register-user",
    [
        check('name')
            .not()
            .isEmpty()
            .isLength({ min: 3 })
            .withMessage('Name must be atleast 3 characters long'),
        check('email', 'Email is required')
            .not()
            .isEmpty(),
        check('password', 'Password should be between 5 to 8 characters long')
            .not()
            .isEmpty()
            .isLength({ min: 5, max: 8 })
    ],
    (req, res, next) => {
        const errors = validationResult(req);
        console.log(req.body);

        if (!errors.isEmpty()) {
            return res.status(422).jsonp(errors.array());
        }
        else {
            bcrypt.hash(req.body.password, 10).then((hash) => {
                const user = new userSchema({
                    name: req.body.name,
                    email: req.body.email,
                    password: hash
                });
                user.save().then((response) => {
                    res.status(201).json({
                        message: "User successfully created!",
                        result: response
                    });
                }).catch(error => {
                    res.status(500).json({
                        error: error
                    });
                });
            });
        }
    });

// Sign-in
router.post("/signin", (req, res, next) => {
    let getUser;
    userSchema.findOne({
        email: req.body.email
    }).then(user => {
        if (!user) {
            return res.status(401).json({
                message: "Authentication failed"
            });
        }
        getUser = user;
        return bcrypt.compare(req.body.password, user.password);
    }).then(response => {
        if (!response) {
            return res.status(401).json({
                message: "Authentication failed"
            });
        }
        let jwtToken = jwt.sign({
            email: getUser.email,
            userId: getUser._id
        }, "longer-secret-is-better", {
            expiresIn: "1h"
        });
        res.status(200).json({
            token: jwtToken,
            expiresIn: 3600,
            _id: getUser._id
        });
    }).catch(err => {
        return res.status(401).json({
            message: "Authentication failed"
        });
    });
});

// Get Users
router.route('/').get((req, res) => {
    userSchema.find((error, response) => {
        if (error) {
            return next(error)
        } else {
            res.status(200).json(response)
        }
    })
})

// Get Single User
router.route('/user-profile/:id').get(authorize, (req, res, next) => {
    userSchema.findById(req.params.id, (error, data) => {
        if (error) {
            return next(error);
        } else {
            res.status(200).json({
                msg: data
            })
        }
    })
})

// Update User
router.route('/update-user/:id').put((req, res, next) => {
    userSchema.findByIdAndUpdate(req.params.id, {
        $set: req.body
    }, (error, data) => {
        if (error) {
            return next(error);
            console.log(error)
        } else {
            res.json(data)
            console.log('User successfully updated!')
        }
    })
})

// Delete User
router.route('/delete-user/:id').delete((req, res, next) => {
    userSchema.findByIdAndRemove(req.params.id, (error, data) => {
        if (error) {
            return next(error);
        } else {
            res.status(200).json({
                msg: data
            })
        }
    })
})

module.exports = router;

We Passed the validation array with the check() method inside the post() method as a second argument. Next, we called the validationResult() method to validate errors, and it returns the errors if found any.

Following validation we implemented in ("/register-user") api.

  • Check if the value is required.
  • Check min and max character’s length.

Final auth.routes.js

// routes/auth.routes.js

const express = require("express");
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");
const router = express.Router();
const userSchema = require("../models/User");
const authorize = require("../middlewares/auth");
const { check, validationResult } = require('express-validator');

// Sign-up
router.post("/register-user",
    [
        check('name')
            .not()
            .isEmpty()
            .isLength({ min: 3 })
            .withMessage('Name must be atleast 3 characters long'),
        check('email', 'Email is required')
            .not()
            .isEmpty(),
        check('password', 'Password should be between 5 to 8 characters long')
            .not()
            .isEmpty()
            .isLength({ min: 5, max: 8 })
    ],
    (req, res, next) => {
        const errors = validationResult(req);
        console.log(req.body);

        if (!errors.isEmpty()) {
            return res.status(422).jsonp(errors.array());
        }
        else {
            bcrypt.hash(req.body.password, 10).then((hash) => {
                const user = new userSchema({
                    name: req.body.name,
                    email: req.body.email,
                    password: hash
                });
                user.save().then((response) => {
                    res.status(201).json({
                        message: "User successfully created!",
                        result: response
                    });
                }).catch(error => {
                    res.status(500).json({
                        error: error
                    });
                });
            });
        }
    });

// Sign-in
router.post("/signin", (req, res, next) => {
    let getUser;
    userSchema.findOne({
        email: req.body.email
    }).then(user => {
        if (!user) {
            return res.status(401).json({
                message: "Authentication failed"
            });
        }
        getUser = user;
        return bcrypt.compare(req.body.password, user.password);
    }).then(response => {
        if (!response) {
            return res.status(401).json({
                message: "Authentication failed"
            });
        }
        let jwtToken = jwt.sign({
            email: getUser.email,
            userId: getUser._id
        }, "longer-secret-is-better", {
            expiresIn: "1h"
        });
        res.status(200).json({
            token: jwtToken,
            expiresIn: 3600,
            msg: getUser
        });
    }).catch(err => {
        return res.status(401).json({
            message: "Authentication failed"
        });
    });
});

// Get Users
router.route('/').get(authorize, (req, res) => {
    userSchema.find((error, response) => {
        if (error) {
            return next(error)
        } else {
            res.status(200).json(response)
        }
    })
})

// Get Single User
router.route('/user-profile/:id').get((req, res, next) => {
    userSchema.findById(req.params.id, (error, data) => {
        if (error) {
            return next(error);
        } else {
            res.status(200).json({
                msg: data
            })
        }
    })
})

// Update User
router.route('/update-user/:id').put((req, res, next) => {
    userSchema.findByIdAndUpdate(req.params.id, {
        $set: req.body
    }, (error, data) => {
        if (error) {
            return next(error);
            console.log(error)
        } else {
            res.json(data)
            console.log('User successfully updated!')
        }
    })
})

// Delete User
router.route('/delete-user/:id').delete((req, res, next) => {
    userSchema.findByIdAndRemove(req.params.id, (error, data) => {
        if (error) {
            return next(error);
        } else {
            res.status(200).json({
                msg: data
            })
        }
    })
})

module.exports = router;
Node Server Configuration

Create a server.js file in the token-based authentication project’s folder and paste the following code in it.

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const bodyParser = require('body-parser');
const dbConfig = require('./database/db');

// Express APIs
const api = require('./routes/auth.routes');

// MongoDB conection
mongoose.Promise = global.Promise;
mongoose.connect(dbConfig.db, {
    useNewUrlParser: true,
    useUnifiedTopology: true
}).then(() => {
    console.log('Database connected')
},
    error => {
        console.log("Database can't be connected: " + error)
    }
)

// Remvoe MongoDB warning error
mongoose.set('useCreateIndex', true);

// Express settings
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
    extended: false
}));
app.use(cors());

// Serve static resources
app.use('/public', express.static('public'));

app.use('/api', api)

// Define PORT
const port = process.env.PORT || 4000;
const server = app.listen(port, () => {
    console.log('Connected to port ' + port)
})

// Express error handling
app.use((req, res, next) => {
    setImmediate(() => {
        next(new Error('Something went wrong'));
    });
});

app.use(function (err, req, res, next) {
    console.error(err.message);
    if (!err.statusCode) err.statusCode = 500;
    res.status(err.statusCode).send(err.message);
});

In this file we defined mongoDB database, express routes, PORT and errors.

Start Node Server

Now, we have placed everything at its place, and now it’s time to start the Node server. Open the terminal and run the given below commands to start the Node server and mongoDB:

Start the MongoDB database:

mongod

Start the nodemon server:

nodemon

You can test Node server on the following URL:
http://localhost:4000/api

Here, are the user authentication CRUD REST APIs built with Node.js.

Conclusion

Finally, we have completed secure Token-Based Authentication REST API with Node.js tutorial. So far, In this tutorial we have learned how to securely store the password in the database using the hash method with bcryptjs, how to create JWT token to communicate with the client and a server using jsonwebtoken. We also implemented the Express input validation using the express-validator plugin.

I hope you liked this tutorial, please share it with others, thanks for reading!

Top Vue.js Developers in USA

Top Vue.js Developers in USA

Vue.js is an extensively popular JavaScript framework with which you can create powerful as well as interactive interfaces. Vue.js is the best framework when it comes to building a single web and mobile apps.

We, at HireFullStackDeveloperIndia, implement the right strategic approach to offer a wide variety through customized Vue.js development services to suit your requirements at most competitive prices.

Vue.js is an open-source JavaScript framework that is incredibly progressive and adoptive and majorly used to build a breathtaking user interface. Vue.js is efficient to create advanced web page applications.

Vue.js gets its strength from the flexible JavaScript library to build an enthralling user interface. As the core of Vue.js is concentrated which provides a variety of interactive components for the web and gives real-time implementation. It gives freedom to developers by giving fluidity and eases the integration process with existing projects and other libraries that enables to structure of a highly customizable application.

Vue.js is a scalable framework with a robust in-build stack that can extend itself to operate apps of any proportion. Moreover, vue.js is the best framework to seamlessly create astonishing single-page applications.

Our Vue.js developers have gained tremendous expertise by delivering services to clients worldwide over multiple industries in the area of front-end development. Our adept developers are experts in Vue development and can provide the best value-added user interfaces and web apps.

We assure our clients to have a prime user interface that reaches end-users and target the audience with the exceptional user experience across a variety of devices and platforms. Our expert team of developers serves your business to move ahead on the path of success, where your enterprise can have an advantage over others.

Here are some key benefits that you can avail when you decide to hire vue.js developers in USA from HireFullStackDeveloperIndia:

  • A team of Vue.js developers of your choice
  • 100% guaranteed client satisfaction
  • Integrity and Transparency
  • Free no-obligation quote
  • Portal development solutions
  • Interactive Dashboards over a wide array of devices
  • Vue.js music and video streaming apps
  • Flexible engagement model
  • A free project manager with your team
  • 24*7 communication with your preferred means

If you are looking to hire React Native developers in USA, then choosing HireFullStackDeveloperIndia would be the best as we offer some of the best talents when it comes to Vue.js.