Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoose User model for handling local and social auth providers

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 });
like image 372
Codearts Avatar asked Mar 05 '17 13:03

Codearts


1 Answers

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.

like image 80
Codearts Avatar answered Oct 27 '22 12:10

Codearts