Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linking multiple logins #81

Closed
dkarigithu opened this issue Nov 19, 2012 · 2 comments
Closed

Linking multiple logins #81

dkarigithu opened this issue Nov 19, 2012 · 2 comments

Comments

@dkarigithu
Copy link

I'd opened an issue earlier trying to link multiple oauth accounts together. I am having challenges implementing it from the PassportJS guid:

var UserSchema = new Schema({
  id: ObjectId,
  facebookId: String,
  twitterId: String,
  googleId: String,
  facebookToken: String,
  twitterToken: String,
  username: String,
  password: String,
  salt: String,
  firstName: String,
  lastName: String,
  email: String,
  birthday: Date,
  gender: String,
  location: String,
  phone: Number,
  verifiedPhone: Boolean,
  interests: {
    culture: Boolean,
    food: Boolean,
    business: Boolean,
    family: Boolean,
    learning: Boolean,
    sports: Boolean,
    movies: Boolean,
    music: Boolean,
    events: Boolean,
    nightlife: Boolean,
    health: Boolean,
    beauty: Boolean,
    fashion: Boolean,
    motoring: Boolean,
    electronics: Boolean,
    groceries: Boolean,
    travel: Boolean,
    decor: Boolean
  },
  weeklyNotifcation: Number,
  weeklyNotificationSent: Number,
  created: {type: Date, default: Date.now}
});

mongoose.connect('mongodb://localhost/mivalue');
mongoose.model('User', UserSchema);

var User = mongoose.model('User');

/* Facebook Strategy
   Authenticate */
passport.use(new facebook({
    clientID: conf.fb.appId,
    clientSecret: conf.fb.appSecret,
    callbackURL: "http://mivalue.co.ke:3000/auth/facebook/callback",
    passReqToCallback: true
  },
  function(req, accessToken, refreshToken, profile, done) {
    if (!req.user) {
      User.findOne({facebookId: profile.id}, function(err, user){
        var user = new User();
        user.facebookId = profile.id;
        user.firstName = profile.name.givenName;
        user.lastName = profile.name.familyName;
        user.birthday = profile.birthday;
        user.email = profile.email;
        user.gender = profile.gender;
        // Store Access Token in the DB
        user.facebookToken = accessToken;
        user.save(function(err) {
          if(err) {throw err; }
          done(null, user);
        });
      });
    } else {
      User.findOrCreate({facebookId: profile.id}, function(err, user) {
        var user = new User();
        user.facebookId = profile.id;
        user.facebookToken = token;
        user.save(function(err) {
          if(err) {throw err; }
          done(null, user);
        });
      });
    }
  }
));

/* Twitter Strategy
   Authenticate */
passport.use(new twitter({
    consumerKey: conf.twit.consumerKey,
    consumerSecret: conf.twit.consumerSecret,
    callbackURL: "http://127.0.0.1:3000/auth/twitter/callback",
    passReqToCallback: true
  },
  function(req, token, tokenSecret, profile, done) {
    if (!req.user) {
      User.findOrCreate({twitterId: profile.id}, function(err, user) {
        var user = new User();
        user.twitterId = profile.id;
        // Store Access Token in the DB
        user.twitterToken = token;
        user.save(function(err) {
          if(err) { throw err; }
          done(null, user);
        });
      });
    } else {
      User.findOne({twitterId: profile.id}, function(err, user) {
        var user = new User();
        user.twitterId = profile.id;
        user.twitterToken = token;
        user.save(function(err) {
          if(err) {throw err; }
          done(null, user);
        }); 
      });
    }
  }
));

// Set Cookies
passport.serializeUser(function(user, done) {
  done(null, user);
});

passport.deserializeUser(function(uid, done) {
  User.findOne({uid: user._id}, function (err, user) {
    done(err, user);
  });
});

// Facebook Auth
app.get('/auth/facebook', passport.authorize('facebook', { scope: ['email', 'user_birthday', 'user_location'] }));
app.get('/auth/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login' }), function(req, res) {
  var user = req.user,
      account = req.account;
  if (user.phone == null || user.interests == null || user.email == null || user.location == null) {
    return res.redirect('/register');
  } else {
    account.userId = uuser.facebookId;
    account.save(function(err) {
      if (err) {return self.error(err); }
        self.redirect('/');
    });
    // Successful authentication, redirect home.
    res.redirect('/');
  }
});

// Twitter Auth
app.get('/auth/twitter', passport.authenticate('twitter', { scope: ['email'] }));
app.get('/auth/twitter/callback', passport.authenticate('twitter', { failureRedirect: '/login' }), function(req, res) {
  var user = req.user,
      account = req.account;
  User.findOne({ uid: _id})
    if (user.phone == null || user.interests == null || user.email == null || user.location == null) {
      return res.redirect('/register');
    } else {
      account.userId = ruser.twitterId;
      account.save(function(err) {
        if (err) {return self.error(err); }
          self.redirect('/');
      });
      // Successful authentication, redirect home.
      res.redirect('/');
    }
});

Thanks
DK

@lukaszfiszer
Copy link

You can achieve this by using calling passport.authenticate to log in the user and passport.authorize to link other social accounts. Depending if user is already authenticated (req.user variable) you call the first or the second method.

Both methods use the same strategies, but passport.authorize will not override the session information if authentication is successful. Both share the same callback function as well, but again, different database function are called depending on req.user

This is how I achieved it in my express + passport app with instagram and facebook strategies. My configuration is split is 3 different modules, but it's think is clear enough to follow.

I used CouchDB and not MongoDB, so I skipped the whole db logic. Basically you just need two DB functions - both called from the callback. If you do this properly, there is no need to check anything against DB in your routing functions, as you will have access to the complete user object (as returned by deserialize function) from req.uservariable.

passportConf.js

var callback = exports.callback = function(req, accessToken, refreshToken, profile, done ){
    if( !req.user ){
       /* Check if user exists - if not, creates a new account in DB. Return user account to the callback */
       DB.getOrCreateUser(accessToken, refreshToken, profile, done); 
    }else{
       /* Link the login to the existing account in the DB, return full, updated user account to the callback */
       DB.linkProfile(req.user, accessToken, refreshToken, profile, done)
    }
};

exports.instagramStrategy = new InstagramStrategy({
  clientID: '-----',  clientSecret: '-----',
  callbackURL: "/connect/instagram/callback",
  passReqToCallback : true
}, callback);

exports.facebookStrategy = new FacebookStrategy({
  clientID: '-----',  clientSecret: '----',
  callbackURL: "/connect/facebook/callback",
  passReqToCallback : true
}, callback);

exports.serialize = function(account, done) {
  done(null, account.id );
};

exports.deserialize = function(id, done) {
  DB.getUserById( id, done ); /* should return full user account to the callback */
};

app.js

passport.use( passportConfig.facebookStrategy );
passport.use( passportConfig.instagramStrategy );

passport.serializeUser( passportConfig.serialize );
passport.deserializeUser( passportConfig.deserialize );

app.get('/connect/:method', routes.login );
app.get('/connect/:method/callback', routes.loginCallback);
(...)

routes.js

module.exports = {
  login: function(req, res, next) {
    if(req.user){
      /* This will call passport.authenticate('facebook') if the route was /connect/facebook
         and passport.authenticate('instagram') if the route was /connect/instagram */
      passport.authenticate(req.params.method)( req, res, next );
    }else{
      passport.authorize(req.params.method)( req, res, next );
    }
  },

  loginCallback: function(req, res, next){
    passport.authenticate(req.params.method, {
      successRedirect: '/account',
      failureRedirect: '/'
     })(req, res, next);
  }
}

@jaredhanson
Copy link
Owner

@lukaszfiszer Thanks for jumping in and providing some help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants