In this tutorial, I am going to show you how authenticate your API using JWT and Passport. JWT is an open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.

Prerequisites

  • Node.js
  • To Continue with this tutorial, you should have installed Node.js in your pc. If you are still not installed Node.js in your machine you can download it from here.
  • Mongo DB
  • To Continue with this tutorial, you should have installed Mongo DB in your pc. If you are still not installed Mongo in your machine you can download it from here.
  • Postman
  • To test our API endpoints, you need a API Testing application like Postman. You can download Postman from here.

    Setting up the Project

    First you need to create a directory for your project. If you are going to use terminal to create directory, run below command on your termianl.
    1
    mkdir jwt-and-passport-auth

    Then navigate to your project directory.>

    1
    cd jwt-and-passport-auth

    Then we need to initialize a new package.json:

    1
    npm init -y

    Next, we need to install to required dependencies:

    1
    npm install --save bcrypt body-parser express jsonwebtoken mongoose passport passport-jwt passport-local

    We need,
    bcrypt for hashing password
    jsonwebtoken for signing tokens
    passport-local for implementing local strategy
    passport-jwt for retrieving and verifying JWTs

    At this point, we have been initialized and all the dependencies have been installed. Usually we start a project with creating our entry file such like app.js but in here, I start with creatind Database Schema.

    Setting up the Database

    Create a directory call model in your project's root directory.
    1
    npm init -y

    Inside the model directory create a file namedmodel.js and place below code,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const mongoose = require('mongoose');

    const Schema = mongoose.Schema;

    const UserSchema = new Schema({
    email: {
    type: String,
    required: true,
    unique: true
    },
    password: {
    type: String,
    required: true
    }
    });

    const UserModel = mongoose.model('user', UserSchema);

    module.exports = UserModel;

    When you store your passwords, you should avoid storing passwords in plain text.

    To avoid this, we will use a package called bcryptto hash user passwords and store them safely. Add the library and the following lines of code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // ...

    const bcrypt = require('bcrypt');

    // ...

    const UserSchema = new Schema({
    // ...
    });

    UserSchema.pre(
    'save',
    async function(next) {
    const user = this;
    const hash = await bcrypt.hash(this.password, 10);

    this.password = hash;
    next();
    }
    );

    // ...

    module.exports = UserModel;

    UserScheme.pre() function will get the plain text password, hash it, and store it before user information is saved in the database.
    this refers to the current document about to be saved.
    Then we also need to make sure that the user trying to log in has the correct credentials. To validate is add below code after the UserScheme.pre() function:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // ...

    const UserSchema = new Schema({
    // ...
    });

    UserSchema.pre(
    // ...
    });

    UserSchema.methods.isValidPassword = async function(password) {
    const user = this;
    const compare = await bcrypt.compare(password, user.password);

    return compare;
    }

    // ...

    module.exports = UserModel;

    bcrypt hashes the password sent by the user for login and then checks if the hashed password stored in the database matches the one sent by the user. It will return true if there is a match. Otherwise, it will return false.

    Setting up Registration and Login Middleware

    In here, we used Passport authentication middleware to authenticate requests.

    It allows developers to use different strategies for authenticating users, such as using a local database or connecting to social networks through APIs like login with Twitter, LinkedIn, Facebook, GitHub and many more.

    In here, we use the local strategy (email and password).

    You will use the passport-local strategy to create middleware that will handle user registration and login. This will then be plugged into certain routes and be used for authentication.

    To implement this authentication, we create and directory called auth and create new file called auth.js within it. Then you should start it by requiring passport,passport-local, and the UserModel that was created in the previous step:

    1
    2
    3
    const passport = require('passport');
    const localStrategy = require('passport-local').Strategy;
    const UserModel = require('../model/model');

    First, add a Passport middleware to handle user registration:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // ...

    passport.use(
    'signup',
    new localStrategy(
    {
    usernameField: 'email',
    passwordField: 'password'
    },
    async (email, password, done) => {
    try {
    const user = await UserModel.create({ email, password });
    return done(null, user);
    } catch (error) {
    done(error);
    }
    }
    )
    );

    Main functionality of this code is save the information provided by the user to the database, and then sends the user information to the next middleware if successful.

    Next, add a Passport middleware to handle user login:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    // ...

    passport.use(
    'login',
    new localStrategy(
    {
    usernameField: 'email',
    passwordField: 'password'
    },
    async (email, password, done) => {
    try {
    const user = await UserModel.findOne({ email });

    if (!user) {
    return done(null, false, { message: 'User not found' });
    }

    const validate = await user.isValidPassword(password);

    if (!validate) {
    return done(null, false, { message: 'Wrong Password' });
    }

    return done(null, user, { message: 'Logged in Successfully' });
    } catch (error) {
    return done(error);
    }
    }
    )
    );

    This code will use to finds one user associated with the email provided.

  • If the user does not match any users in the database, it returns a "User not found" error.
  • If the password does not match the password associated with the user in the database, it returns a "Wrong Password" error.
  • If the user and password match, it returns a "Logged in Successfully" message, and the user information is sent to the next middleware.
  • Creating the Signup Endpoint

    To provide routing, we use popular Node.js framework Express. If you want to know more about Node.js frameworks, you can read my article about Node.js frameworks from here.

    To provide routing functionality to our application, we need to create a directory called routes and create new file called routes.js within it.

    Then you should start by requiring express and passport:

    1
    2
    3
    4
    5
    6
    const express = require('express');
    const passport = require('passport');

    const router = express.Router();

    module.exports = router;

    Then we need to handling the POST request for signup:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // ...

    const router = express.Router();

    router.post(
    '/signup',
    passport.authenticate('signup', { session: false }),
    async (req, res, next) => {
    res.json({
    message: 'Signup successful',
    user: req.user
    });
    }
    );

    module.exports = router;

    Creating the Login Endpoint and Signing the JWT

    To proceed with Login, first we need to require jsonwebtoken:
    1
    2
    3
    4
    5
    const express = require('express');
    const passport = require('passport');
    const jwt = require('jsonwebtoken');

    // ...

    Then we need to handling the POST request for login:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    // ...

    const router = express.Router();

    // ...

    router.post(
    '/login',
    async (req, res, next) => {
    passport.authenticate(
    'login',
    async (err, user, info) => {
    try {
    if (err || !user) {
    const error = new Error('An error occurred.');

    return next(error);
    }

    req.login(
    user,
    { session: false },
    async (error) => {
    if (error) return next(error);

    const body = { _id: user._id, email: user.email };
    const token = jwt.sign({ user: body }, 'TOP_SECRET');

    return res.json({ token });
    }
    );
    } catch (error) {
    return next(error);
    }
    }
    )(req, res, next);
    }
    );

    module.exports = router;

    We should not store sensitive information such as the user’s password in the token.

    We store the id and email in the payload of the JWT. You then sign the token with a secret or key (TOP_SECRET). Finally, you send back the token to the user.

    We have login endpoint now . A successfully logged in user will generate a token. Now we need to Verifying the JWT.

    Verifying the JWT

    To verifying the JWT, put below code below the require and above Passport middleware of handle user registration:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // ...

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

    passport.use(
    new JWTstrategy(
    {
    secretOrKey: 'TOP_SECRET',
    jwtFromRequest: ExtractJWT.fromUrlQueryParameter('secret_token')
    },
    async (token, done) => {
    try {
    return done(null, token.user);
    } catch (error) {
    done(error);
    }
    }
    )
    );

    This code uses passport-jwt to extract the JWT from the query parameter. It then verifies that this token has been signed with the secret or key set during logging in (TOP_SECRET). If the token is valid, the user details are passed to the next middleware.

    Creating Secure Routes

    Now, we need to create secure routes that only users with verified tokens can access.

    Create a new filesecure-routes.js in routes directory and add the following lines of code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const express = require('express');
    const router = express.Router();

    router.get(
    '/profile',
    (req, res, next) => {
    res.json({
    message: 'You made it to the secure route',
    user: req.user,
    token: req.query.secret_token
    })
    }
    );

    module.exports = router;

    This code handles GET request for profile. It returns a “You made it to the secure route” message also also returns information about the user and token.

    Create app.js

    Now create file called app.js which we are going to implement our database connection and etc. Then paste below code in it:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    const express = require('express');
    const mongoose = require('mongoose');
    const passport = require('passport');
    const bodyParser = require('body-parser');

    const UserModel = require('./model/model');


    mongoose.connect('mongodb://127.0.0.1:27017/passport-jwt', { useMongoClient: true });

    or

    mongoose.connect("mongodb+srv://<user>:<password>@learning.eauwn.mongodb.net/<database>?retryWrites=true&w=majority", {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    });

    mongoose.connection.on('error', error => console.log(error) );
    mongoose.Promise = global.Promise;

    require('./auth/auth');

    const routes = require('./routes/routes');
    const secureRoute = require('./routes/secure-routes');

    const app = express();

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

    app.use('/', routes);

    // Plug in the JWT strategy as a middleware so only verified users can access this route.
    app.use('/user', passport.authenticate('jwt', { session: false }), secureRoute);

    // Handle errors.
    app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.json({ error: err });
    });

    app.listen(3000, () => {
    console.log('Server started.')
    });

    Run Application

    To run your application, use below command,
    1
    2
    3
    4
    5
    nodemon app.js

    or

    node app.js

    Testing with Postman

    * Signup

    Method: POST
    URL:http://localhost:3000/signup
    Body
    x-www-form-urlencoded

    Key Value
    email [email protected]
    password password
    • Login

    Method: POST
    URL:http://localhost:3000/login
    Body
    x-www-form-urlencoded

    Key Value
    email [email protected]
    password password
    • Profile

    Method: GET
    URL:http://localhost:3000/user/profile
    Params

    Key Value
    secret_token token

    Conclusion

    In this tutorial, you set up API authentication with JWT and tested it with Postman. You can obtain complete source code for this tutorial from this GitHub repository.