Search code examples
node.jsmongoosepassport.jspassport-facebook

mongoose-findorcreate writing duplicated users when using passport-facebook


I'm trying to setup user authentication with passportJS. I decided to use this npm package called mongoose-findorcreate instead of writing a findOrCreate function to work with passport on my own.

So now I am testing my app, and I can login using my facebook credentials as expected, but the issue is that I am getting multiple entries in my database for the same facebook login.

This is my user model:

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

let findOrCreate = require('mongoose-findorcreate');

// User schema definition
let UserSchema = new Schema(
  {
    provider: { type: String, required: true },
    id: { type: String, default: 'unknown' },
    displayName: { type: String, default: 'unknown' },
    name: { type: Object },
    emails: { type: Array },
    photos: { type: Array },
    createdAt: { type: Date, default: Date.now },
    updatedAt: { type: Date, default: Date.now },
  }
);

UserSchema.plugin(findOrCreate);

// Sets the updatedAt parameter equal to the current time
UserSchema.pre('save', (next) => {
  now = new Date();
  if(!this.updatedAt) {
    this.updatedAt = now;
  }
  next();
});

module.exports = mongoose.model('user', UserSchema);

These are my routes:

let passport = require('passport');
let FacebookStrategy = require('passport-facebook').Strategy;

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

passport.use(new FacebookStrategy({
    clientID: 12345,
    clientSecret: 'supersecretsecret',
    callbackURL: 'http://localhost:1337/auth/facebook/callback'
  },
  (accessToken, refreshToken, profile, done) => {
    User.findOrCreate( profile, (err, user) => {
      if (err) {
        return done(err);
      }
      done(null, user);
    })
  }
));

passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

function redirectFB() {
  return passport.authenticate('facebook');
}

function callbackFB() {
  return passport.authenticate('facebook', {
    successRedirect: '/word',
    failureRedirect: '/'
  })
}

module.exports = { redirectFB, callbackFB };

An example of duplicated users:

{  
   "_id":ObjectId("584ade8915644f1186fa8a96"),
   "provider":"facebook",
   "updatedAt":   ISODate("2016-12-09T16:40:41.921   Z"),
   "createdAt":   ISODate("2016-12-09T16:40:41.921   Z"),
   "photos":[  ],
   "emails":[  ],
   "displayName":"Miha Šušteršič",
   "id":"10154914634234238",
   "__v":0
}{  
   "_id":ObjectId("584adea3b8adfb11948ed1d6"),
   "provider":"facebook",
   "updatedAt":   ISODate("2016-12-09T16:41:07.626   Z"),
   "createdAt":   ISODate("2016-12-09T16:41:07.625   Z"),
   "photos":[  ],
   "emails":[  ],
   "displayName":"Miha Šušteršič",
   "id":"10154914634234238",
   "__v":0
}

I can't figure out if this is an issue with my code (passing the profile to the findOrCreate function or not.


Solution

  • The reason why findOrCreate will not work is because findOrCreate query is passing the entire profile object into the query criteria.

    If you look here: https://github.com/drudge/mongoose-findorcreate/blob/master/index.js#L22

    Depending on what type of data you get back from Facebook, your query will always return empty unless the profile object matches your schema EXACTLY.

    What you can do here is this:

    User.findOrCreate({ provider: profile.provider, id: profile.id, .... }, (err, user) => {
      if (err) {
        return done(err);
      }
      done(null, user);
    })
    

    instead of profile pass in { provider: profile.provider, id: profile.id, .... }

    of course, fix the { provider: profile.provider, id: profile.id, .... } to be matched with your schema definition.