Loopback passport-facebook for multiple user models

Tags: , , , ,



I’m using passport to authenticate users for my application using Facebook. In my application, I have 2 different users: customers and professionals (transitioning to “experts”).

I followed the Loopback docs to implement the loopback-component-passport package and passport-facebook and have it working for my customers.

Because I use custom User models, I followed this SO Post on how to get that fixed (note: I still use the build-in AccessToken model).

Angular Frontend

I use an Angular frontend. To redirect the user back to my frontend, I created a bootscript that looks like this:

'use strict';

module.exports = function(app) {
  var router = app.loopback.Router();
  var cookieParser = require('cookie');

 // Redirect user to page set in env.APP_HOST
 router.get('/auth/customer/success', function(req, res, next) {
     const cookie = req.headers.cookie;
     var result = cookieParser.parse(cookie);
     res.redirect(process.env.APP_HOST + '/auth/facebook/?token=' + 
         result['access_token'] + '&id=' + result['userId'] + 
         '&err=&fbsr=');
 });

 router.get('/auth/expert/success', function(req, res, next) {
     const cookie = req.headers.cookie;
     var result = cookieParser.parse(cookie);
    res.redirect(process.env.APP_HOST + '/expert/auth/facebook/?token=' 
        + result['access_token'] + '&id=' + result['userId'] + 
        '&err=&fbsr=');
 });

 app.use(router);
};

When I authenticate as a customer, I should be redirected to https://example.com/auth/customer/success When I authenticate as a professional/expert, I should be redirected to https://example.com/auth/expert/success.

Setup a second passport-facebook

I have to load the configuration for both the customer and professional/expert which I’m doing in server.js. I created 2 providers.json files (providers.customer.json and provider.professional.json) containing their specific info.

This is how I load both configurations (perhaps a little messy):

var passportCustomer = require('loopback-component-passport');
var PassportConfiguratorCustomer = passportCustomer.PassportConfigurator;
var passportConfiguratorCustomer = new PassportConfiguratorCustomer(app);
var passportProfessional = require('loopback-component-passport');
var PassportConfiguratorProfessional = passportProfessional.PassportConfigurator;
var passportConfiguratorProfessional = new PassportConfiguratorProfessional(app);

// Setup passport configuration
var passportCustomerConfig = {};
var passportProfessionalConfig = {};
try {
    passportCustomerConfig = require('./providers.customer.json');
    passportProfessionalConfig = require('./providers.professional.json');
} catch(err) {
    console.log('Please configure the passport strategy in providers.customer.json');
    console.error(err);
    process.exit(1); // Fatal error
}

// Init passport for customer
passportConfiguratorCustomer.init(false);
passportConfiguratorProfessional.init(false);

// Setup passport models
passportConfiguratorCustomer.setupModels({
  userModel: app.models.Customer,
  userIdentityModel: app.models.UserIdentityCustomer,
  userCredentialModel: app.models.UserCredentialsCustomer
});

passportConfiguratorProfessional.setupModels({
  userModel: app.models.Professional,
  userIdentityModel: app.models.UserIdentityProfessional,
  userCredentialModel: app.models.UserCredentialsProfessional
});

// Configure passport for customer
for(var s in passportCustomerConfig) {
    var c = passportCustomerConfig[s];
    c.session = c.session !== false;
    passportConfiguratorCustomer.configureProvider(s, c);
}

// Configure passport for professional/expert
for(var s in passportProfessionalConfig) {
    var c = passportProfessionalConfig[s];
    c.session = c.session !== false;
    // passportConfiguratorProfessional.configureProvider(s, c);
    passportConfiguratorCustomer.configureProvider(s, c);
}

The actual problem

I have 2 different apps in Facebook (1 for customers, 1 for professionals/experts). When I authenticate using localhost:3000/auth/customer/facebook or localhost:3000/auth/expert/facebook I see that both apps are used for the correct endpoint. But no matter what endpoint I use, after authentication I’m always redirected to http://example.com/expert/auth/facebook

So my question is: How can I fix this issue so that customers are redirected to the customer endpoint and experts/professionals are redirected to their expert endpoint?

Additional information

  • Registration works fine, I can find customers in customer table and expert in expert table in my database

For reference: providers.customer.json

{
  "facebook-login": {
    "provider": "facebook",
    "module": "passport-facebook",
    "clientID": "OhOh, I removed it :)",
    "clientSecret": "Supa Dupa secret",
    "callbackURL": "/auth/customer/facebook/callback",
    "authPath": "/auth/customer/facebook",
    "callbackPath": "/auth/customer/facebook/callback",
    "successRedirect": "/auth/customer/success",
    "failureRedirect": "/auth/customer/failure",
    "scope": ["email"],
    "failureFlash": true,
    "profileFields" : ["locale", "name", "email"]
  },
  "facebook-link": {
    "provider": "facebook",
    "module": "passport-facebook",
    "clientID": "OhOh, I removed it :)",
    "clientSecret": "Supa Dupa secret",
    "callbackURL": "/link/customer/facebook/callback",
    "authPath": "/link/customer/facebook",
    "callbackPath": "/link/customer/facebook/callback",
    "successRedirect": "/auth/customer/success",
    "failureRedirect": "/auth/customer/failure",
    "scope": ["email"],
    "link": true,
    "failureFlash": true
  }
}

For reference: providers.professional.json

{
  "facebook-login": {
    "provider": "facebook",
    "module": "passport-facebook",
    "clientID": "Aaaaand",
    "clientSecret": "It's gone",
    "callbackURL": "/auth/expert/facebook/callback",
    "authPath": "/auth/expert/facebook",
    "callbackPath": "/auth/expert/facebook/callback",
    "successRedirect": "/auth/expert/success",
    "failureRedirect": "/auth/expert/failure",
    "scope": ["email"],
    "failureFlash": true,
    "profileFields" : ["locale", "name", "email"]
  },
  "facebook-link": {
    "provider": "facebook",
    "module": "passport-facebook",
    "clientID": "Aaaaand",
    "clientSecret": "It's gone",
    "callbackURL": "/link/expert/facebook/callback",
    "authPath": "/link/expert/facebook",
    "callbackPath": "/link/expert/facebook/callback",
    "successRedirect": "/auth/expert/success",
    "failureRedirect": "/auth/expert/failure",
    "scope": ["email"],
    "link": true,
    "failureFlash": true
  }
}

Answer

So I was able to fix this by using the PassPortConfigurator#configureProvider method. Instead of using the JSON files to setup the facebook authentication, I did it in JS.

var passportCustomer = require('loopback-component-passport');
var PassportConfiguratorCustomer = passportCustomer.PassportConfigurator;
var passportConfiguratorCustomer = new PassportConfiguratorCustomer(app); // Note the first letter is not a capital one

// Init passport for customer
passportConfiguratorCustomer.init(false);

// Setup passport models
passportConfiguratorCustomer.setupModels({
  userModel: app.models.Customer,
  userIdentityModel: app.models.UserIdentityCustomer,
  userCredentialModel: app.models.UserCredentialsCustomer
});

// Load configuration here instead of the JSON file.
passportConfiguratorProfessional.configureProvider('facebook-login-expert', {
  module: 'passport-facebook',
  clientID: XXX,
  clientSecret: XXX,
  callbackURL: '/auth/expert/facebook/callback',
  authPath: '/auth/expert/facebook',
  callbackPath: '/auth/expert/facebook/callback',
  successRedirect: '/auth/expert/success',
  failureRedirect: '/auth/expert/failure',
  scope: ['email'],
  failureFlash: true,
  profileFields: ['locale', 'name', 'email']
});


Source: stackoverflow