Monday, 22 May 2017

Expanding PassportJS Local JWT strategy to support social logins

I am using Angular for my Front-End and ExpressJS for my Back-End. I am using Passport for login and registration using JWT. I would like to expand this to accept social logins/registrations.

I have the following (probably trivial and basic) PassportJS setup:

var bcrypt = require('bcrypt');
var ObjectId = require('mongodb').ObjectId;
const jwt = require('jsonwebtoken');
const passport = require('passport');
const ExtractJwt = require('passport-jwt').ExtractJwt;
const JwtStrategy = require('passport-jwt').Strategy;
var jwtOptions = {};
jwtOptions.jwtFromRequest = ExtractJwt.fromAuthHeader();
jwtOptions.secretOrKey = 'secret';
passport.use(new JwtStrategy(jwtOptions, function(jwt_payload, done) {
    var collection = db.get().collection('users');
    collection.findOne({
        _id: new ObjectId(jwt_payload.sub)
    }, function(err, user) {
        if (err) {
            return done(err, false);
        }
        if (user) {
            return done(null, user);
        } else {
            return done(null, false);
            // or you could create a new account
        }
    });
}));


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


router.post('/register', (req, res) => {
    var collection = db.get().collection('users');
    collection.findOne({'email': req.body.email},
        function(err, exists) {
        if (err) {
            res.send('Error in SignUp: ' + err);
            return done(err);
        }
        if (!_.isEmpty(exists)) {
            var errors = [];
            if (exists.email === req.body.email) {
                errors.push('Your email is linked to another account.')
            }
            res.send({errors: errors});
        } else {
            collection.insert({
                firstName: req.body.firstName,
                lastName: req.body.lastName,
                email: req.body.email,
                phone: req.body.phone,
                password: bcrypt.hashSync(req.body.passwords.password, 10),
                address: req.body.address
            })
            res.send({
                'success': 'You have successfully registered!'
            });
        }
    });

});

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

    var collection = db.get().collection('users');
    collection.findOne({ email: req.body.email}, function(err, user) {
        if (err) {
            res.send('Error in Login: ' + err);
            return done(err);
        } else if (user) {
            bcrypt.compare(req.body.password, user.password, function(err, success) {
                if (success) {
                    var payload = {
                        sub: user._id,
                        firstName: user.firstName,
                        lastName: user.lastName
                    };

                    res.send({
                        'token': jwt.sign(payload, jwtOptions.secretOrKey, {
                            expiresIn: '3d'
                        })
                    })
                } else {
                    console.log(success);
                    res.send({'login': false});
                }

            })

        } else if (!user) {
            res.send({'login': false})
        }
    })

})

router.post('/', (req, res) => {
    jwt.verify(req.body.token, jwtOptions.secretOrKey, (err, decoded) => {
        if (err) {
            res.send(false);
        }
        if (decoded) {
            res.send(true);
        }
    })
})


module.exports = router;

I am trying to add "Facebook," "Twitter," and other social logins based on passportjs' support library. My issue is, I already have a local db setup "users" where a certain schema is:

{
 _id: ObjectID
 firstName: string
 lastName: string
 email: string
 phone: number
 password: string
 address: {
   street: string
   city: string
   state: string
   zip: number
 }
}

How can I change my user DB or rather, how can I modify my approach to accept multiple forms of login, but still maintain a "schematic" database? In other words:

  1. How do I reason between registration/login type (local? Facebook? Twitter?)

  2. How do I store multiple registrations in one user Object (one user logs in with facebook, then twitter, but already has a local registration)?

  3. How do I handle missing data (I'm sure facebook/twitter will not allow me to just "scrape" their user's addresses from their API, so the address property will be blank in the database (not good!)?

[Note: this project is under heavy development and nothing is live, so if you have a recommendation that'll change the entire architecture of my auth.js file or my 'users' collection to reach my objective, I am all ears.]



via Moshe

No comments:

Post a Comment