DEV Community

Cover image for Welcome, Passport and JWTicket please!
CoddingAddicts
CoddingAddicts

Posted on • Updated on

Welcome, Passport and JWTicket please!

Web development is all about building apps and ecosystems around causes and needs that bring people from similar and even different backgrounds together. The evolution of such space rest on its user being able to maintain identities. These are tied to their content and contributions within the network.

The process of identification or authentication is core to any application and the security at this level is quite important. Yet, what we are here to speak about today is to make the process of creating an identity less of a chore.

Think of the web as big continent where apps and subnetworks are like countries which come in touch and sometimes even overlap. Wouldn’t be nice to be able to port identities from one land to another? For that you need Passport.js.

From their main website,

Passport is authentication middleware for Node.js. Extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application.

This package come with 500+ authentication strategies to suit almost all needs. Whether your users comes from google, facebook or any other platform, there is a strategy you can implement to make their entry to your network easier than having to fill a form all over again.

The workflow has two phases in general, (1) define your strategy and (2) place it within your scheme. To do that, we have the following functions at our disposal.

passport.use(
    // Configure an Authentication Strategy
)

passport.initialize() // Initialize the Strategy

passport.authenticate(
    // Handle the Authentication Request
)
Enter fullscreen mode Exit fullscreen mode

We will detail how to use these within a working example right below. Before that, we point out that our choice of an authentication medium landed on an interesting piece of technology called JWT, or JSON Web Token. In contrast to a more classic way to store identification data, JWT offers flexible security in addition to the possibility to be employed in more complex schemes.

If you have never heard about JWT, well

JWT, is an open standard used to share security information between two parties — a client and a server. Each JWT contains encoded JSON objects, including a set of claims. JWTs are signed using a cryptographic algorithm to ensure that the claims cannot be altered after the token is issued.

A JWT is a string made up of three parts, separated by dots (.), and serialized using base64. The JWT looks something like this: xxxxx.yyyyy.zzzzz. And once decoded it gives us two JSON strings, header+payload and signature.

The header indicates the type of token and the signing algorithm. The payload contains the claims which technically are optional but certain fields are necessary for certain standards. And finally, the signature which works as a hash and helps us check the integrity of the payload against potenetial alteration.

To sum it up, JWT can be used as tickets to cross borders between platforms as the same entity and naturally, they have to be signed by the embassy!

Now time for some application. We will build the simplest working code that uses Google’s OAuth2 scheme. In a nutshell, it is a technology that employs two tokens, the access and refresh token. The first is meant for access and is short-lived while the second is used to refresh the access when the first expires.

What you need to keep in mind is that the Refresh Token, or RT, is the most important piece in this whole scheme.

Enough chatter, let’s get to the code.

Beside passport.js we will use the following packages, express.js, mongoose.js and jsonwebtoken.js and passport-google-oauth2. Make sure to have them installed.

We will build a simple express app which contains a route that implements authentication through Google using OAuth2. The tokens will naturally be JWT. We assume that you know how to set up mongoose as we will use it to store the pair of tokens related to a certain user.

The outline of the work is as follow,

  1. Construct a token schema.
  2. Define the refresh token middleware
  3. Specify our strategy.
  4. Make a route for Google authentication

1. Token Schema

/* filename: ***tokenSchema.js*** */
const jwt = require("jsonwebtoken")
const mongoose = require("mongoose")

// The Schema
const jwtSchema = new mongoose.Schema({
  userId: { type: mongoose.Schema.Types.ObjectId, ref: "users" },
  Token: { type: String },
  expAt: Date,
});

// Token Creation
jwtSchema.statics.CreateToken = async function (_id) {
  let expiredAt = new Date()
  expiredAt.setSeconds(expiredAt.getSeconds() + 60);
  let _token = jwt.sign({ id: _id }, JWTREFRESHTOKENSECRET);
  let _object = new this({
    userId: _id,
    Token: _token,
    expAt: expiredAt.getTime(),
  })
  let refreshToken = await _object.save()
  return refreshToken
}

// Token Verification
jwtSchema.statics.VerifyToken = async function (token) {
  return (await token.expAt.getTime()) < new Date().getTime();
};

const RefreshToken = mongoose.model("JwtCollection", jwtSchema);

module.exports = { RefreshToken };
Enter fullscreen mode Exit fullscreen mode

2. RT Middleware

/* Refresh Token */

const jwt = require("jsonwebtoken");
const RefreshToken = require(/* path to tokenSchema.js */);

const RTMiddleware = async (req, res, next) => {

  const { refreshToken: requestToken } = req.body

  if (requestToken == null) {
    return res.status(403).json({ message: "Refresh Token is required!" })
  }

  try {
    let refreshToken = await RefreshToken.findOne({ Token: requestToken });
    console.log(refreshToken)
    if (!refreshToken) {
      res.status(403).json({ message: "Refresh Token is not in database!" });
      return;
    }

    const isValid = await RefreshToken.VerifyToken(refreshToken)

    if (isValid) {
      RefreshToken.findByIdAndRemove(refreshToken._id, {
        useFindAndModify: false,
      }).exec()
      res.status(403).json({
        message: "Refresh Token expired. Please make a new sign-in request!",
      })
      return;
    }

    let newAccessToken = jwt.sign(
      { id: refreshToken.userId },
      JWTSECRET,
      {
        expiresIn: JWT_ACC_EXP_TIME,
      }
    )
    return res.status(200).json({
      accessToken: newAccessToken,
      refreshToken: refreshToken.Token,
    })
  } catch (err) {
    return res.status(500).send({ message: err })
  }
}

module.exports = { RTMiddleware }
Enter fullscreen mode Exit fullscreen mode

3. Strategy Definition

/* filename: authentication.js */

const passport = require("passport")
/*
    It is assumed that you have a userSchema.js file with the following fields:
    name, email, password, verified and picture.
*/
const userModel = require(/* path to the userSchema.js */) 

passport.use(
  new GoogleStrategy(
    {
      clientID: CLIENTID,
      clientSecret: SECRETID,
      callbackURL: "http://localhost:5000/auth/google/redirect",
    },
    async (accessToken, refreshToken, profile, done) => {

            // Deconstruct the data 
      const { email, name, email_verified, picture } = profile._json

      // Verify if user exists
      const userExists = await userModel.findOne({ email })

      // True: redirect user
      if (userExists) {
        return done(null, User)
      }
      // False: create new user, then redirect
      const newUser = await UserModel.create({
        email,
        name,
        verified: email_verified,
        picture,
      });
      done(null, newUser)
    }
  )
)
Enter fullscreen mode Exit fullscreen mode

4. Route Definition

/* filename: ***index.js*** */

const express = require("express")
const jwt = require("jsonwebtoken")
const passport = require(/* path to authentication.js */)
const { RefreshToken } = require(/* pathe to tokenSchema.js */);

const App = express()

App.get(
  "/auth/google",
  passport.authenticate("google", { scope: ["email", "profile"] })
);

App.get(
  "/auth/google/redirect",
  passport.authenticate("google", { failureRedirect: "/", session: false }),
  async (req, res) => {

    const { _id } = req.user;
    try {
      //AT = accses Token
      //RT = refresh Token
      const AT = jwt.sign({ id: _id }, JWTACSESSTOKENSECRET, {
        ACSESSTOKENTIME,
      });
      const RT = await RefreshToken.CreateToken(_id);
      res.status(200).json({ AT, RT });
    } catch (error) {
      res.status(500).json({ msg: "Something went wrong!" })
      console.log(error);
    }
  }
)

// ... define other routes and start the server.
Enter fullscreen mode Exit fullscreen mode

Explanation

To use Google sign-up, the user will visit the route /auth/google that we configured as shown above. Upon doing the following page will prompted.

Google Login

It is configured by passport and the Google API console

After filling the form and clicking next. There is verification process — detailed in the strategy specification — which checks if the user is already in our database or not and performs the action coded in authentication.js .

If the user exist, they will be logged in. If not, we add them to the database and then redirect them to the callback URL that we specified in Google console to start our JWT Process.

In the callback URL, we will craft the two tokens we spoke about before. The RT will be saved in the database and we will send both the AT and RT to the client.

The final piece to this strategy is the middleware which checks if there is a token attached to the request when the client tries to access private service or resource and refreshes the token if its the AT dead. If the RT expires then the client is required to login again.

Below you will find a typical response we would get from calling the /auth/google and then going through the callback URL

The response when you get make the first request

Database entries for,

(1) Tokens

the token in mongo db

(2) Users

the user in mongo db

This concludes our short exposition of this quite rich and indispensable tool for anything identity. For those of you who might interested in the full code, here is the repo. The usage can be extended for things like API access or anything similar.

As we said at the beginning this package comes with a lot of strategies. Each has its specificities and that is too much to cover in a single article. There is an interesting in-depth guide by Zach Gollwitze, so feel free to check it if you hungry for more.

As always this was the Codding Addicts, peace out!

Top comments (0)