What is function User.findOrCreate doing and when is it called in passport?

Problem

I can't find documentation on this function and therefor I can't make it work right. When is that function being called, what is it doing and what is it taking as first parameters? I'm trying to get access token from passport, but can't reach it anyhow.

passport.use(new FacebookStrategy({
    clientID:   APP_ID,
    clientSecret:   APP_SECRET,
    callbackURL: "http://localhost:3000/",
  },
  function(accessToken, refreshToken, profile, done) {
    User.findOrCreate({// what are these parameters?}, function (err, user) {
        // when is this function called and what is it doing? 
       });

  }
));

How can I get access token from passport?

Problem courtesy of: Tommz

Solution

User.findOrCreate is a made-up function that represents whatever function you have to find a user by Facebook ID, or to create one if the user doesn't exist. I think your first problem is that your callback URL is just going to your root, so you probably are never getting to that function.

Your callback URL should be like http://localhost:3000/auth/facebook/callback.

And then handle that URL:

app.get('/auth/facebook/callback', 
  passport.authenticate('facebook', { failureRedirect: '/login' }),
  function(req, res) {
    res.redirect('/');
  });

At this point Authentication is complete. accessToken is returned to you -- "this is needed any time the app calls an API to read, modify or write a specific person's Facebook data on their behalf". You should save this off in some table where you store access tokens for a user. profile is the other key variable because that is the info about the user (what info depends on the service).

What you do inside that function is up to you. So, make your own User.findOrCreate. Here is the code from passport for Facebook with some comments to explain it. This assumes you are using something like MongoDB and have a User table. User in this case is whatever variable you declared that can interface with the User table.

//Use facebook strategy
passport.use(new FacebookStrategy({
        clientID: config.facebook.clientID,
        clientSecret: config.facebook.clientSecret,
        callbackURL: config.facebook.callbackURL
    },
    function(accessToken, refreshToken, profile, done) {
        //check user table for anyone with a facebook ID of profile.id
        User.findOne({
            'facebook.id': profile.id 
        }, function(err, user) {
            if (err) {
                return done(err);
            }
            //No user was found... so create a new user with values from Facebook (all the profile. stuff)
            if (!user) {
                user = new User({
                    name: profile.displayName,
                    email: profile.emails[0].value,
                    username: profile.username,
                    provider: 'facebook',
                    //now in the future searching on User.findOne({'facebook.id': profile.id } will match because of this next line
                    facebook: profile._json
                });
                user.save(function(err) {
                    if (err) console.log(err);
                    return done(err, user);
                });
            } else {
                //found user. Return
                return done(err, user);
            }
        });
    }
));

Personally I also use a "membership" table to track multiple accounts per user (so they can authenticate with multiple accounts), as I set it up through mongoose. This is actually where I store that access token. I prefer this to having a facebook column in the user table.... but that is up to you.

var mongoose = require('mongoose'),
    Schema = mongoose.Schema,
    ObjectId = Schema.ObjectId;

var membershipSchema = new Schema({
    provider:  String,
    providerUserId:  String,
    accessToken: String,
    userId: {type: ObjectId, ref: 'User'},
    dateAdded: {type: Date, default: Date.now}
});

module.exports = mongoose.model('Membership', membershipSchema);

and as such, my version of User.findOrCreate starts off like this:

function(accessToken, refreshToken, profile, done) {
    Membership.findOne({
        providerUserId: profile.id
    }, function(err,membershipData) {
            //blah blah blah

where membership is that model above, and is defined as a variable as:

var Membership =  require('./models/membership.js')
Solution courtesy of: MikeSmithDev

Discussion

If you would like to use findOrCreate, try the npm package mongoose-findorcreate, or supergoose

e.g. mongoose-findorcreate

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost');

var findOrCreate = require('mongoose-findorcreate')
var Schema = mongoose.Schema;
var UserSchema = new Schema({ facebookId: Number});
UserSchema.plugin(findOrCreate);
var User = mongoose.model('User', UserSchema);

passport.use(new FacebookStrategy({
        clientID: 'clientID',
        clientSecret: 'clientSecret',
        callbackURL: "/auth/facebook/callback"
    },
    function(accessToken, refreshToken, profile, cb) {
        User.findOrCreate({ facebookId: profile.id }, function (err, user) {
          console.log('A new uxer from "%s" was inserted', user.facebookId);
          return cb(err, user);
        });
    }
));
Discussion courtesy of: Vinnie James

This recipe can be found in it's original form on Stack Over Flow.