DEV Community

Arpy Vanyan
Arpy Vanyan

Posted on • Originally published at Medium on

Learn using JWT with Passport authentication

Introduction

Almost every web and mobile app nowadays have authentication. Most of them offer different login methods like Facebook, Google or email/password at once.

Passport is a Node.js middleware that offers a variety of different request authentication strategies that are easy to implement. By default, it stores the user object in session.

JSON Web Tokens is an authentication standard that works by assigning and passing around an encrypted token in requests that helps to identify the logged in user, instead of storing the user in a session on the server and creating a cookie. It has different integrations including a Node.js module.

Below is a tutorial about using this two modules together and setting up an authentication on an express based backend. Luckily, Passport allows an option to store the user object in request instead of the session.

The tutorial will use a simple local (email/password) authentication, but it may as well be used with any other strategy.

First, let's install the dependencies.

npm install --save passport passport-local passport-jwt jsonwebtoken
Enter fullscreen mode Exit fullscreen mode

Now here is how everything is going to work:

  • When the user logs in, the backend creates a signed token and returns it in response
  • The client saves the token locally (typically in localStorage ) and sends it back in every subsequent request that needs authentication
  • All requests needing authentication pass through a middleware that checks the provided token and allows the request only if the token is verified

So, let’s implement this logic.

Login

Assume we have set up and used the local passport strategy in a separate file next to app.js like this:

//passport.js

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;

passport.use(new LocalStrategy({
        usernameField: 'email',
        passwordField: 'password'
    }, 
    function (email, password, cb) {

//this one is typically a DB call. Assume that the returned user object is pre-formatted and ready for storing in JWT

return UserModel.findOne({email, password})
           .then(user => {
               if (!user) {
                   return cb(null, false, {message: 'Incorrect email or password.'});
               }

return cb(null, user, {message: 'Logged In Successfully'});
          })
          .catch(err => cb(err));
    }
));
Enter fullscreen mode Exit fullscreen mode

We need to require this file in  app.js.

//app.js

const express = require('express');
...
require('./passport');

const app = express();
...
const auth = require('./routes/auth');
app.use('/auth', auth);
Enter fullscreen mode Exit fullscreen mode

Now, in our auth.js route file, we’ll implement the login action. Here, we call the passport authentication function with local strategy, handle the errors and log in the user.

//routes/auth.js

const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const passport = require("passport”);

/* POST login. */
router.post('/login', function (req, res, next) {

passport.authenticate('local', {session: false}, (err, user, info) => {
        if (err || !user) {
            return res.status(400).json({
                message: 'Something is not right',
                user : user
            });
        }

req.login(user, {session: false}, (err) => {
           if (err) {
               res.send(err);
           }

// generate a signed son web token with the contents of user object and return it in the response

const token = jwt.sign(user, 'your_jwt_secret');
           return res.json({user, token});
        });
    })(req, res);
});
Enter fullscreen mode Exit fullscreen mode

Note, that we pass {session: false} in passport options so that it won't save the user in the session. Also, we create and return a signed JSON web token based on the user object to the client. You can, of course, choose any object to create a token with, as long as it will help you identify your user. The idea is, to store the minimum info that you can use without having to retrieve the user from the database in all the authenticated requests.

Protected requests

Now, we’ll create a middleware, that allows only requests with valid tokens to access some special routes needing authentication, eg. /user/profile. For this, we will use the passport-jwt strategy. We’ll add it in our passport.js file.

//passport.js

...
const passportJWT = require("passport-jwt");
const JWTStrategy = passportJWT.Strategy;
const ExtractJWT = passportJWT.ExtractJwt;
...

passport.use(new JWTStrategy({
        jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
        secretOrKey : 'your_jwt_secret'
    },
    function (jwtPayload, cb) {

        //find the user in db if needed. This functionality may be omitted if you store everything you'll need in JWT payload.
        return UserModel.findOneById(jwtPayload.id)
            .then(user => {
                return cb(null, user);
            })
            .catch(err => {
                return cb(err);
            });
    }
));
Enter fullscreen mode Exit fullscreen mode

Note, that we assume that the client will send the JWT token in Authorization Header as a Bearer Token. The Passport JWT Strategy supports many other ways of getting the token from requests. Choose whichever suits your needs.

Now, all we need to do is to use this middleware in our app for the protected routes. For this tutorial, we’ll prepare a simple user route like this:

//routes/user.js

const express = require('express');
const router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});

/* GET user profile. */
router.get('/profile', function(req, res, next) {
    res.send(req.user);
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

And use the passport authentication middleware on user route as shown below:

//app.js

const express = require('express');
...
require('./passport');

const app = express();
...
const auth = require('./routes/auth');
const user = require('./routes/user');

app.use('/auth', auth);
app.use('/user', passport.authenticate('jwt', {session: false}), user);
Enter fullscreen mode Exit fullscreen mode

And that’s it!

Go on and try out some requests, now they’ll be backed with JSON Web Token authorization with Passport 👍


Top comments (6)

Collapse
 
cmstiver profile image
cmstiver

The formatting in auth.js and passport.js are atrocious.

Collapse
 
qhungg289 profile image
Hung Duong Quang

It's unreadable.

Collapse
 
alexerdei73 profile image
AlexErdei73

The article says that JSON Web Tokens are encrypted. I would like to correct this. These are not encrypted, but Digitally Signed. Anybody can read the content of these tokens with the base64url library, which only changes the character encoding, so no encryption here. What does a Digital Signature do? It ensures that your app can check, that the token has not been changed by someone else, since it has been issued by your app. It also ensures that your app signed it after the user successfully logged in with the correct credentials. These two things make sure in most cases, that whoever has the token is your signed in user. Unfortunately it is not necessarily true. If your app uses third party JavaScript and that code is malicious it can take the token from local storage. This way someone else may sign in to the app, like your logged in user. This is a kind of security loophole regarding these tokens. You can hear about it more in the following talk: Why JSON Web Tokens Suck

Collapse
 
picode246 profile image
Pi-code246 • Edited

I think there is an error in the following line in the passport.js file:

        return UserModel.findOneById(jwtPayload.id)
Enter fullscreen mode Exit fullscreen mode

I'm almost positive (I'm still learning though) there is no findOneById() function. This throws an "findOneById() is not a function" error. It worked for me when I switched it to findOne()

There's also something wrong with the quote marks in this line in theroutes/auth.js file:

const passport = require("passport”);
Enter fullscreen mode Exit fullscreen mode

The first quote mark is Unicode Character U+0022
The second one is Unicode Character “”” (U+201D)

Collapse
 
bigvl profile image
Vlasa Andrei

On the newer version the findOneById was deprecated, but when the article came out it was still in use.

Collapse
 
m121 profile image
Mateo Perez Salazar

Thank you Arpy Vanyan