Frictionless Registration and Login using Facebook (and others)

Many sites offer Facebook registration and login in order to make user acquisition as frictionless as possible.

Loopback provides a Passport adapter called loopback-component-passport to help with this which glues passport to the loopback user model. Unfortunately we have found this scheme to be too inflexible for our purposes. In order to support our token & authentication model we have had to roll our own (with some inspiration from loopback-component-passport).

Install passport & strategies

npm install passport --save  
npm install passport-facebook --save  
npm install passport-twitter --save  

UserIdentity model

Each user can have many linked identities which hold the credentials for each passport authentication scheme.

common/models/user-identity.js defines the schema for this.

Define a hasMany relationship with UserIdentity in common/models/MyUser.json

"identities": {
  "type": "hasMany",
  "model": "UserIdentity",
  "foreignKey": "userId"
}

Add UserIdentity to server/model-config-localdev.js

'UserIdentity': {  
  'dataSource': 'db',
  'public': false
}

Set up facebook

(similar process for twitter and others)

Set up a facebook "app" for your users here

Typically you will want an app for each environment (local, development and production) as the OAuth callback for each will be different. Facebook has a scheme for this where you create the production app and then "test apps" based on the production app. Each has it's own credentials and callback url.

Add "Website" as an app platform and set the url to http://localhost:3000

Set app "domain" to localhost

You will need the following details from the facebook app to configure passport:

  • App ID
  • App Secret

Set up Passport Strategy & Auth Endpoints

This boot script sets up the passport strategies:
server/boot/passportAuthentication.js

Each provider needs the following functions

    // setup facebook strategy
    passport.use(new FacebookStrategy({
        clientID: process.env.FACEBOOK_CLIENT_ID,
        clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
        callbackURL: '/auth/facebook/callback',
        profileFields: ['id', 'displayName', 'email', 'first_name', 'middle_name', 'last_name', 'link', 'picture.type(large)'],
        passReqToCallback: true
    }, providerFacebook));

    function providerFacebook(req, token, tokenSecret, profile, done) {
        providerHandler('facebook', req, token, tokenSecret, profile, done);
    }

    // initiate facebook authentication
    router.get('/auth/facebook', getCurrentUser, passport.authenticate('facebook', {
        scope: ['public_profile', 'email', 'user_friends'],
        session: false
    }));

    // facebook callback
    // need access to the http context in order to handle login so wrap the passportResultHandler in closure
    router.get('/auth/facebook/callback', getCurrentUser, function (req, res, next) {
        passport.authenticate('facebook', function (err, user, info) {
            passportResultHandler('facebook', err, user, info, req, res, next);
        })(req, res, next);
    });

Notice that the ClientID & ClientSecret are stored in the environment so to launch the app we now need:

NODEENV=localdev FACEBOOKCLIENTID=xxx FACEBOOKCLIENT_SECRET=xxx node .

See the Keeping Secrets post for more on this.


Using the endpoints

/auth/facebook performs a redirect to facebook to authenticate the user and requested permissions. Once approved the user is redirected back to the callback /auth/facebook/callback with a token that is used by passport-facebook to retrieve information about the facebook user.

Passport takes over from there and implements linking accounts.

  • If the user is logged in we create a UserIdentity instance for the user to link the users site account to their facebook account.

  • If not logged in and the facebook user's facebook id is found in the user UserIdentity database, we login the user using the facebook id.

  • If not logged in an the facebook id is not found we create a new user and log them in.


Photo: Cock Of The Rock, Peru (2014)
Document version 1.1