Miha Šušteršič
Miha Šušteršič

Reputation: 10062

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.

Upvotes: 1

Views: 783

Answers (2)

tpae
tpae

Reputation: 6346

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.

Upvotes: 1

Miha Šušteršič
Miha Šušteršič

Reputation: 10062

Found the problem - I needed to pass user data to the findOrCreate function in the correct format.

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

Upvotes: 0

Related Questions