Introduction

These days most people have dozens of logins, and no one really wants to register for yet another website or app.  It’s easier to attract new users by allowing them to login with an existing account from a popular service such as Google or Facebook, for example.  Additionally, enterprise applications often need to work with existing authentication and identity systems such as LDAP, kerberos, and SAML.

We’re excited to announce that the LoopBack API framework now supports:

  • Third-party login, to enable your LoopBack apps to allow login using existing accounts on Facebook, Google, Twitter, or Github.
  • Integration with enterprise security services so that users can login with them instead of username and password-based authentication.
  • Account linking to link an account authorized through one of the above services with a LoopBack application user record. This enables you to manage and control your own user account records while still integrating with third-party services.

What’s LoopBack? It’s an open source API framework powered by Node.js for connecting devices and apps to enterprise data and services.

These new capabilities are provided through the loopback-passport module that integrates LoopBack and Passport, one of the leading modules for Node authentication middleware.

LoopBack authentication and authorization

LoopBack already provides full-featured support for managing users and client applications. The LoopBack user and application models enable applications to easily:

  • Sign up a new user
  • Log in using username/email and password
  • Log out
  • Verify a user registration by email
  • Sign up a new client application
  • Authenticate a client application by ID and keys

Like other models, the user and application models can be backed by any of the databases supported by LoopBack connectors (Oracle, MySQL, PostgreSQL, MongoDB, SQL Server and others). Thus, a LoopBack application can easily serve APIs to clients on behalf of authenticated users.

The loopback-passport module

The loopback-passport module has the following key components:

  • UserIdentity model – keeps track of third-party login profiles
  • UserCredential model – stores credentials from a third-party provider to represent users’ permissions and authorizations.
  • ApplicationCredential model – stores credentials associated with a client application
  • PassportConfigurator – bridge between LoopBack and Passport.

UserIdentity model

The UserIdentity model keeps track of third-party login profiles. Each user identity is uniquely identified by provider and externalId. The UserIdentity model comes with a ‘belongsTo’ relation to the User model.

Properties of the UserIdentity model are:

  • {String} provider: The auth provider name, such as facebook, google, twitter, linkedin
  • {String} authScheme: The auth scheme, such as oAuth, oAuth 2.0, OpenID, OpenID Connect
  • {String} externalId: The provider specific user id
  • {Object} profile: The user profile, see http://passportjs.org/guide/profile
  • {Object} credentialsoAuth: token, tokenSecret – oAuth 2.0: accessToken, refreshToken – OpenID: openId
  • OpenID Connect: accessToken, refreshToken, profile

  • {*} userId: The LoopBack user id
  • {Date} created: The created date
  • {Date} modified: The last modified date

UserCredential model

UserCredential has the same set of properties as UserIdentity. It’s used to store the credentials from a third party authentication/authorization provider to represent the permissions and authorizations of a user in the third-party system.

ApplicationCredential model

Interacting with third-party systems often requires client application level credentials. For example, you need oAuth 2.0 client ID and client secret to call Facebook APIs. Such credentials can be supplied from a configuration file to your server globally. But if your server accepts API requests from multiple client applications, each client application should have its own credentials. To support the multi-tenancy, this module provides the ApplicationCredential model to store credentials associated with a client application.

Properties of the ApplicationCredential model are:

  • {String} provider: The auth provider name, such as facebook, google, twitter, linkedin
  • {String} authScheme: The auth scheme, such as oAuth, oAuth 2.0, OpenID, OpenID Connect
  • {Object} credentials: The provider specific credentials – openId: {returnURL: String, realm: String}</span> – oAuth2: {clientID: String, clientSecret: String, callbackURL: String} – oAuth: {consumerKey: String, consumerSecret: String, callbackURL: String}
  • {Date} created: The created date</span>
  • {Date} modified: The last modified date</span>

ApplicationCredential model comes with a ‘belongsTo’ relation to the Application model.

PassportConfigurator

PassportConfigurator is the bridge between LoopBack and Passport. It provides the following functionality:

  • Sets up models with LoopBack
  • Initializes passport
  • Creates Passport strategies from provider configurations- Sets up routes for authorization and callback

Architectural diagram

Flows: Third-party login

The following steps use Facebook oAuth 2.0 login as an example.

  • A visitor requests to log in using Facebook by clicking on a link or button backed by LoopBack to initiate oAuth 2.0 authorization.
  • LoopBack redirects the browser to Facebook’s authorization endpoint so the user can log into Facebook and grant permissions to LoopBack.
  • Facebook redirects the browser to a callback URL hosted by LoopBack with the oAuth 2.0 authorization code- LoopBack makes a request to the Facebook token endpoint to get an access token using the authorization code.
  • LoopBack uses the access token to retrieve the user’s Facebook profile.
  • LoopBack searches the UserIdentity model by (provider, externalId) to see there is an existing LoopBack user for the given Facebook id.
  • If yes, set the LoopBack user to the current context.
  • If not, create a LoopBack user from the profile and create a corresponding record in UserIdentity to track the 3rd party login. Set the newly created user to the current context.

Flows: Third-party account linking flow

The following steps use Facebook oAuth 2.0 login as an example.

  • The user log into LoopBack first directly or through third party login.
  • The user clicks on a link or button by LoopBack to kick off oAuth 2.0 authorization code flow so that the user can grant permissions to LoopBack.
  • Perform the same steps 2-5 as third party login.
  • LoopBack searches the UserCredential model by (provider, externalId) to see there is an existing LoopBack user for the given Facebook id.
  • Link the Facebook account to the current user by creating a record in the UserCredential model to store the Facebook credentials, such as access token.

Now the LoopBack user wants to get a list of pictures from the linked Facebook account(s). LoopBack can look up the Facebook credentials associated with the current user and use them to call Facebook APIs to retrieve the pictures.

Use the module with a LoopBack application

A demo application is built with this module to showcase how to use the APIs with a LoopBack application. The code is available at:

https://github.com/strongloop-community/loopback-example-passport

Configure third-party providers

The following example shows two providers: facebook-login for login with facebook and google-link for linking your Google accounts with the current LoopBack user.

{
 "facebook-login": {
   "provider": "facebook",
   "module": "passport-facebook",
   "clientID": "{facebook-client-id-1}",
   "clientSecret": "{facebook-client-secret-1}",
   "callbackURL": "http://localhost:3000/auth/facebook/callback",
   "authPath": "/auth/facebook",
   "callbackPath": "/auth/facebook/callback",
   "successRedirect": "/auth/account",
   "scope": ["email"]
 },
 ...
 "google-link": {
   "provider": "google",
   "module": "passport-google-oauth",
   "strategy": "OAuth2Strategy",
   "clientID": "{google-client-id-2}",
   "clientSecret": "{google-client-secret-2}",
   "callbackURL": "http://localhost:3000/link/google/callback",
   "authPath": "/link/google",
   "callbackPath": "/link/google/callback",
   "successRedirect": "/link/account",
   "scope": ["email", "profile"],
   "link": true
 }
}

The authentication scheme or protocol for a given provider can be specified using the ‘authScheme’ property. The value can be ‘local’, ‘oAuth 2.0’, ‘oAuth’, or ‘OpenID’. For example, to use twitter oAuth 1.x, the provider can be configured as follows:

"twitter-login": {

     "provider": "twitter",
     "authScheme": "oauth",
     "module": "passport-twitter",
     "callbackURL": "http://localhost:3000/auth/twitter/callback",
     "authPath": "/auth/twitter",
     "callbackPath": "/auth/twitter/callback",
     "successRedirect": "/auth/account",
     "consumerKey": "{twitter-consumer-key}",
     "consumerSecret": "{twitter-consumer-secret}"

 }

Please be aware that you’ll need to register with Facebook and Google to get your own client ID and client secret.

Add code snippets to app.js

var loopback = require('loopback');
var path = require('path');
var app = module.exports = loopback();
// Create an instance of PassportConfigurator with the app instance
var PassportConfigurator = require('loopback-passport').PassportConfigurator;
var passportConfigurator = new PassportConfigurator(app);

app.boot(__dirname);
...
// Enable http session
app.use(loopback.session({ secret: 'keyboard cat' }));

// Load the provider configurations
var config = {};
try {
 config = require('./providers.json');
} catch(err) {
 console.error('Please configure your passport strategy in `providers.json`.');
 console.error('Copy `providers.json.template` to `providers.json` and replace the clientID/clientSecret values with your own.');
 process.exit(1);
}
// Initialize passport
passportConfigurator.init();

// Set up related models
passportConfigurator.setupModels({
 userModel: app.models.user,
 userIdentityModel: app.models.userIdentity,
 userCredentialModel: app.models.userCredential
});
// Configure passport strategies for third party auth providers
for(var s in config) {
 var c = config[s];
 c.session = c.session !== false;
 passportConfigurator.configureProvider(s, c);
}

What’s next?