JWT Authentication in Express.js using Passport.js

Passportjs for JWT authentication in Expressjs.

In the previous post, we created an express.js application step by step. If you haven’t read that post, I suggest you do it because the basic steps are covered in the post. Therefore, you can check it out from here: https://nepcodex.com/2019/09/create-an-express-application-with-database/. Also, since we are trying to build a REST API, we will remove many components like views and static files. In this post, we are going to build an authentication process for our express.js application using passport.js and jwt. In this case, we are using JSON Web Token (JWT) for authentication, however, there are many more ways to authenticate using passport.js. You can check about this module from the link below: http://www.passportjs.org/. Before diving into the lesson, I would like to show some changes in index.js file of the root directory.

Expressjs is a nodejs framework. We are going to build an authentication for expressjs application.
const express = require('express'); // Our express app
// const path = require('path');   // A built-in library for handling project path
const logger = require('morgan'); // For logging the requests output in console
const cookieParser = require('cookie-parser'); // For cookies
const createError = require('http-errors'); // For detailed error display in browser

const mongoose = require('mongoose');
//For cors
const cors = require('cors');
// Import routers
const indexRouter = require('./routes/index');

// Create instance of an app
const app = express();

// Setup template engine
// app.set('views', path.join(__dirname, 'views'));
// app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(cors());

// app.use(express.static(path.join(__dirname, 'public')));

mongoose.connect('mongodb://localhost:27017/nepcodex', { useNewUrlParser: true, useUnifiedTopology: true })
    .then(() => console.log('Connected to NepcodeX database'))
    .catch((err) => console.log(err));

// Use it above the error handling middleware
app.use('/', indexRouter);


// catch 404 and forward to error handler
app.use(function (req, res, next) {
    next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
    // set locals, only providing error in development
    res.locals.message = err.message;
    res.locals.error = err; // In development only, comment this out in production

    // Uncomment the following in production
    // res.locals.error = {};


    // render the error page
    res.status(err.status || 500);
    res.send(res.locals);
});

app.listen(8000);

module.exports = app;

Note: We have added a new module cors. So, make sure you installed it too.

User Model – JWT Authentication in Express.js using Passport.js

First of all, we are going to create a user model for authentication. Therefore, let’s create a directory models in the root directory. Inside that directory, let’s create a file user.js with following content.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const userSchema = new Schema({
    name: {
        type: String,
        required: [true, 'You probably have a name, don\'t you?']
    },
    email: {
        type: String,
        required: [true, 'Please enter a unique email address'],
        unique: [true, 'The email is already used'],
        validate: {
            validator: v => /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(v),
            message: props => `${props.value} is not a valid email address`
        }
    },
    password: {
        type: String,
        required: [true, 'Please enter a valid password']
    }
});

const User = mongoose.model('User', userSchema);

module.exports = User;

We just used name, email and password field for the user. Furthermore, this code uses email regex from https://emailregex.com/. I hope it is not that difficult to understand the code above. You can learn more about the models from the documentation of mongoose. https://mongoosejs.com/

Signup Controller

In the first place, we are going to create some users. A point to remember is that, we cannot store the passwords in raw format. Hence, we require a module bcrypt to hash the password.

npm install --save bcrypt

Now, let’s create a controllers and auth folders in the root directory. We will put the authentication related configurations and constants in auth directory. Therefore, we need to create a file constants.js and add a constant as follows.

exports.salt_rounds = 12

More the salt rounds, more secure is the password but with an expense of processing time.

Now, inside controllers, let’s create an auth.js file which contains the code for signup.

const User = require('../models/user');
const bcrypt = require('bcrypt');

const {salt_rounds} = require('../auth/constants');

exports.signup = async (req, res, next) => {
    user = await User.findOne({'email': req.body.email});
    if(user) {
        return res.status(409).send({'message': 'User already exists'});
    }
    const name = req.body.name;
    const email = req.body.email;
    const password = req.body.password;
    if(!password || password < 6) {
        return res.status(422).send({'message': 'Provide password at least of length 6'});
    }
    bcrypt.hash(password, salt_rounds, async (err, hash) => {
        if(err) {
            return res.status(422).send(err);
        }
        const newUser = new User({
            name: name,
            email: email,
            password: hash
        });
        await newUser.save((err, user) => {
            if(err) {
                return res.status(422).send(err);
            }
            return res.send(user);
        });
    });
}

In the code above, we checked if the user already exists. If not, we created a new user by hashing the password. Also, I would like to tell you that, I would not have bothered to validate on database since we are using NoSQL. It would make more sense in the server-side logic to implement validation.

Now, we have to add the routes for signup. Here, I will be using /auth routes for all the authentication. Hence, add the middleware in the index.js file inside root directory.

// index.js
const authRouter = require('./routes/auth');

app.use('/auth', authRouter);

Signup Route

Let’s create a POST route for signup in auth.js file inside routes folder. Therefore, create a file auth.js inside routes directory and copy the following lines of code.

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

const authController = require('../controllers/auth');

router.post('/signup', authController.signup);

module.exports = router;

Quickly, we can test if the routes work or not. Run the following command in your terminal.

Tips: Use Postman for your own ease!

curl -X POST \
  http://localhost:8000/auth/signup \
  -H 'Accept: */*' \
  -H 'Content-Type: application/json' \
  -d '{
	"name": "Apple Pie",
	"email": "applepie@nepcodex.com",
	"password": "mysecret"
}'

Login Controller – JWT Authentication in Express.js using Passport.js

Additionally, we have to create a login controller for getting a token which we have to sent in every request. Also, our auth/constants.js now contains some important constants as follows.

exports.secret = 'SECRET_KEY';
exports.expires_in = 3600;
exports.issuer = 'nepcodex.com';
exports.audience = 'nepcodex.com';

exports.salt_rounds = 12

With this in mind, let’s create a function in controllers/auth.js and add the following lines of code.

const jwt = require('jsonwebtoken');
const { salt_rounds, secret, expires_in, issuer, audience } = require('../auth/constants');
const User = require('../models/user');
const bcrypt = require('bcrypt');

exports.login = async (req, res, next) => {
    if (!req.body.email || !req.body.password) {
        return res.status(401).send({ message: 'Please fill all the fields before submitting!' });
    }
    const email = req.body.email;
    const password = req.body.password;

    const user = await User.findOne({ email: email });
    bcrypt.compare(password, user.password, (err, response) => {
        if (err) {
            return res.status(422).send(err);
        } else if (response) {
            return res.send({
                token: 'Bearer ' + jwt.sign({ email: email }, secret, {
                    expiresIn: expires_in,
                    issuer: issuer,
                    audience: audience,
                })
            });
        } else {
            return res.status(401).send({ message: 'Password didn\'t match!' });
        }

    });
}

Here, we just signed email with json web token. We must use the same options for signing and verifying the token using passport.

Login Route

Now, let’s update the login route in auth/routes.js.

router.get('/signup', (req, res, next) => res.send({ 'message': 'Post here for signing up!' }));
router.post('/signup', authController.signup);
router.get('/login', (req, res, next) => res.send({ 'message': 'Post here to login' }));
router.post('/login', authController.login);

Configuration for passport.js

Meanwhile, we have to configure the jwt strategy. Therefore, we are going to create a config.js file inside auth directory with the following contents.

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

const User = require('../models/user');

const { secret, expires_in, issuer, audience } = require('./constants');

const jwtOptions = {};

jwtOptions.jwtFromRequest = ExtractJWT.fromAuthHeaderAsBearerToken();
jwtOptions.secretOrKey = secret;
jwtOptions.expiresIn = expires_in;
jwtOptions.issuer = issuer;
jwtOptions.audience = audience;

passport.use(new JWTStrategy(jwtOptions, (jwt_payload, done) => {
    console.log('Payload: ', jwt_payload);
    User.findOne({ 'email': jwt_payload.email }, (err, user) => {
        if (err) {
            return done(err, false);
        }
        if (user) {
            done(null, user);
        } else {
            done(null, false);
        }
    });

}));

module.exports = passport;

This configuration should be used in our app. Therefore, we have to import this configuration in our main index.js file. Then, we will protect the index route. So, make some changes in index.js file.

// Configuration for PassportJS
const passport = require('./auth/config');

app.use(passport.initialize());

app.use('/', passport.authenticate('jwt',
    {
        session: false,
        failureRedirect: '/auth/signup'
    }), indexRouter
);

Check JWT Authentication in Express.js using Passport.js

At this instant, if you visit the home page, you will be redirected toward login route. This is because we don’t have a valid bearer token sent as Authorization header. Also, if we sent a login request, we will get a bearer token as a response. Formerly, we created a user account in our app. Now, we are going to login with that account. To login with the credentials, run the following command in your terminal.

curl -X POST \
  http://localhost:8000/auth/login \
  -H 'Content-Type: application/json' \
  -d '{
	"email": "applepie@nepcodex.com",
	"password": "mysecret"
}'

We get a token response, and copy the actual string of the bearer token (i.e. exclude the quotation). One thing important to realize is that, we have to attach this bearer token with Authorization header in the every request. So, let’s enter the following command in terminal (use your own bearer token).

curl -X GET \
  http://localhost:8000/ \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFwcGxlcGllQG5lcGNvZGV4LmNvbSIsImlhdCI6MTU3MDAyNTQ5NSwiZXhwIjoxNTcwMDI5MDk1LCJhdWQiOiJuZXBjb2RleC5jb20iLCJpc3MiOiJuZXBjb2RleC5jb20ifQ.gvrcF9pblklkKEWKDjBD_fPClj7zwbPfx3ZRQYay4EY' \
  -H 'Content-Type: application/json' \

Finally, you will get the appropriate response. Of course, you can try other routes with this bearer token, you will get 404 error rather than 401 status code.

You will get the full code from here. https://github.com/mrkrishnaupadhyay/express-jwt-authentication

I am a Computer Engineer from Pulchowk Campus, IOE. I love programming, and music. Python, Java, Php, Javascript, Django, Laravel, Spring, express.js, etc. are my specialities.

Leave a Reply

%d bloggers like this: