Saturday, 30 November 2019

Node.js - JWT, Passport.js, API, Authentication: How To Implement Multiple Passport.js Middleware?

So I am trying to build two JWT authentication systems for one app. One for the users and one for businesses. The authentication uses passport (local and JWT). I started working on the user aspect of the authentication and I was able to make it work as I wanted. I thought it would be the same for business. All I did was simply copy paste and change few variable. Normally the business endpoints should work but it does not. So I tried to debug what was going on and I am still trying to figure out what it is. My guess is that when I load the strategies in my app.js, only the passport strategies for users get called. If I permute positions between passport and business-passport, everything breaks.

Here is my code.

Passport Middleware For User passport.js

const passport = require('passport');
const JwtStrategy = require('passport-jwt').Strategy;
const {ExtractJwt} = require('passport-jwt');
const LocalStrategy = require('passport-local').Strategy;
const { JWT_SECRET } = require('./config');
const User = require('./models/user');
// const Business = require('./models/business');

passport.use(new JwtStrategy({
    jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
    secretOrKey: JWT_SECRET,
    passReqToCallback: true
}, async (req, payload, done) => {
    console.log('payload', payload)

    try {
        // Find the user specified in token
        const user = await User.findById(payload.sub);

        // Handle if User dont exist
        if (!user) {
            return done(null, false);
        }

        // Otherwise, return the user
        req.user = user;
        done(null, user);
    } catch (error) {
        done(error, false);
    }

}));
// Local Strategy
passport.use(new LocalStrategy({
    usernameField: 'email'
}, async (email, password, done) => {

    try {
        // Find the user given the email
        const user = await User.findOne({email});

        // If not, handle it
        if (!user) {
            return done(null, false);
        }

        // check if password is correct

        const isMatch = await user.isValidPassword(password);

        // if not, handle it
        if (!isMatch) {
            return done(null, false);
        }

        // Otherwise, return the user
        done(null, user);

    } catch (error) {
        done(error, false);
    }

}));

Passport Middleware For Business passport-business.js

Just change User for Business

Controllers for Users controllers/user.js

const fs = require('fs');
const mongoose = require('mongoose');
const JWT = require('jsonwebtoken');
const { JWT_SECRET } = require('../config');

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

signToken = user => {
    // Respond with token
    return JWT.sign({
        iss: 'My Business, Inc.',
        userType: 'users',
        sub: user._id, /* You can also use newUser.id */
        iat: new Date().getTime(), /* Current Time */
        exp: new Date().setDate(new Date().getDate() + 1), /* Current Time + 1 day ahead */
    }, JWT_SECRET);
};

module.exports = {

    signup: async (req, res, next) => {
        console.log('req.value.body', req.value.body);

        const {email, password} = req.value.body;

        // Check if User already exist by email address
        const foundUser = await User.findOne({email});
        if(foundUser) {
            return res.status(403).json({error: 'Email is already in use'});
        }

        // Create a new User
        const newUser = new User({email, password});
        await newUser.save();

        // Generate the token
        const token = signToken(newUser);

        // Respond with token
        res.status(200).json({token});
    },
    signin: async (req, res, next) => {
        // Generate token
        const token = signToken(req.user);
        res.status(200).json({token});
        // console.log('signin');
    },
};

The reason why I have req.value... is because of a middleware that I used to validate my body and url params.

Controllers for Business controllers/business.js . Everything is the same, just change User with Business.

I should also point out that whenever I use jwt.io to see if my token signature is valid, it always says invalid signature, but everything works on my application as expected. Wondering why it is saying invalid signature.

Routes for Users routes/user.js . Please remember that it is the same for Business.

const express = require('express');

// this router deals better with "try{} catch{}" situations"
const router = require('express-promise-router')();

const passport = require('passport');

const UserController = require('../controllers/user');

require('../passport');

const { validateBody, schemas, validateParam } = require('../helpers/route-helpers');

const StrategyLocal = passport.authenticate('local', {session: false});
const StrategyJWT = passport.authenticate('jwt', {session: false});

router.get('/', (req, res) => {

    res.json({message: "Welcome to our User's API Endpoint!"});
});

router.route('/signup')
    // .get(UserController.index)
    .post([validateBody(schemas.authSchema)], UserController.signup);
router.route('/signin')
    // Makes sense to implement localstrat here because jWt will always work because the user was issued a token at signup
    // So both solutions will work here as a strategy
    .post(validateBody(schemas.authSchema), StrategyLocal, UserController.signin);

module.exports = router;   

And finally, the file that I think is causing the problem... Introducing...
app.js

const express = require('express');
const logger = require('morgan');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const passport = require('passport');
const path = require('path');
const cors = require('cors');

const { dbName } = require('./config');

mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost/' + dbName, {
    useNewUrlParser:true,
    useUnifiedTopology:true,
    useCreateIndex: true,
    useFindAndModify: false
});

const app = express();

// 👇👇👇👇 This is where the problem is happening in my opinion
require('./passport-business')
require('./passport')


const user = require('./routes/user');
const business = require('./routes/business');

// Middlewares
// set the static files location /public/img will be /img for users
app.use(logger('dev'));
app.use(cors());
app.use(bodyParser.urlencoded({ extended: false}));
app.use(bodyParser.json());

app.use((req, res, next) => {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, noauth");
    if (req.method === 'OPTIONS'){
        res.header("Access-Control-Allow-Methods", "PUT, POST, PATCH, DELETE, OPTIONS");
        res.setHeader('Access-Control-Allow-Credentials', false);
        return res.status(200).json({});
    }
    next();
});

app.use('/api/users', user);
app.use('/api/businesses', business);


// Start the server

const port =  app.get('port') || 2019;
app.listen(port, () => console.log(`Server is listening on port ${port}`));

If I keep everything as is, all User endpoints work, signup works for Business, but I get 401 when I try to signin (Business). But if I do this...

require('./passport')
require('./passport-business')    

I get 401 when I try to signin (User) and I get 500 when I try to signin (Business).
Can someone tell me what I am doing wrong here? Maybe it is the order in which I load these files in app.js. Maybe it is the fact that I am creating two separate passport files for User and Business. I would love to know how to combine those two into one. .



from Node.js - JWT, Passport.js, API, Authentication: How To Implement Multiple Passport.js Middleware?

No comments:

Post a Comment