So I am working on an Express (with Mongoose and Passport) app and I want to include facebook as an authentication method. I have already done it and it works but I don't think I have a proper User model to handle the authentication process for multiple social providers. I want to merge the different social accounts. Here is my current user model which works for facebook auth:
let userSchema = mongoose.Schema({
email: { type: String, unique: true },
name: { type: String },
password: { type: String },
facebookId: String,
facebookToken: String
}, { timestamps: true });
I think of the following two approaches but I am not sure if they are viable and which will be the most flexible and independent from the social provider. I am thinking of having local and social arrays like this:
let userSchema = mongoose.Schema({
local: {
email: { type: String, unique: true },
name: { type: String },
password: { type: String },
},
facebook: {
id: String,
token: String,
email: String,
name: String
},
google: {
id: String,
token: String,
email: String,
}
}, { timestamps: true });
The third approach is just overwriting the social provider id (I am not sure if this is okay).
let userSchema = mongoose.Schema({
email: { type: String, unique: true },
name: { type: String },
id: String,
token: String,
}, { timestamps: true });
So I found a working solution for myself which might help other people with the same problem. In my User model I have my usual fields and for each social provider I have a separate array like so (users/User.js):
let userSchema = mongoose.Schema({
email: { type: String, unique: true },
name: { type: String },
password: { type: String },
roles: [String],
confirmation_code: String,
confirmed: { type: Boolean, default: false },
facebook: {
id: String,
token: String,
email: String,
name: String
},
google: {
id: String,
token: String,
email: String,
name: String
}
}, { timestamps: true });
When authenticating with a social provider I make an extra check if a user with the same email already exists. If it doesn't, I create a new user. If it does I just add the social provider data (id, token, etc.) to the already existing users array like so (config/passport.js):
passport.use(new FacebookStrategy({
clientID: oauth.facebook.clientID,
clientSecret: oauth.facebook.clientSecret,
callbackURL: oauth.facebook.callbackURL,
profileFields: ['id', 'emails', 'name']
},
function (accessToken, refreshToken, profile, done) {
process.nextTick(function () {
User.findOne({
$or: [
{ 'facebook.id': profile.id },
{ 'email': profile.emails[0].value }
]
}, function (err, user) {
if (err) {
return done(err);
}
if (user) {
if (user.facebook.id == undefined) {
user.facebook.id = profile.id;
user.facebook.token = accessToken;
user.facebook.email = profile.emails[0].value;
user.facebook.name = profile.name.givenName + ' ' + profile.name.familyName;
user.save();
}
return done(null, user);
} else {
let newUser = new User();
newUser.facebook.id = profile.id;
newUser.facebook.token = accessToken;
newUser.facebook.email = profile.emails[0].value;
newUser.facebook.name = profile.name.givenName + ' ' + profile.name.familyName;
newUser.name = profile.name.givenName + ' ' + profile.name.familyName;
newUser.email = profile.emails[0].value;
newUser.save(err => {
if (err) {
console.log(err);
throw err;
}
return done(null, newUser);
});
}
});
});
}
));
With this approach you can connect one profile with multiple social providers. However there is one downside. If the user registers a new profile for the first time through a social provider, he won't have a password because social providers don't give back password data (duh). He just needs to change (set) his password through his profile afterwards.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With