Skip to main content
Version: 2.x

Advanced External Logins

Since version 2.13 (Beta feature ~ API may change)

Extending external logins

Before extending external logins please refer to the External Logins docs to see what Front-Commerce has out of the box.

The external logins is made of three parts:

  • The login providers. Responsible for handling authenticating the user with the external systems.
  • The external login handlers responsible to logging authorized user into our platform (Magento, Proximis, BigCommerce...).
  • A module to link the providers and the external login handler.

Creating your own provider

Front-Commerce uses passport internally for the Facebook and Google providers though this makes things easier it is neither a requirement nor a recommendation.

To create a provider you need to register a key and a function with the auth-external-login module.

The key will be used in the redirect url (like "google" in /external-login/google and /external-login/google/callback) and the function will be called to create the provider.

The function will be called with 3 arguments:

  • The current request
  • The externalLoginHandler
  • The redirectUrl

The provider returned by the registered function should implement the LoginProviderInterface i.e. it should have 2 middlewares requestLoginMiddleware and loginCallbackMiddleware.

The requestLoginMiddleware will be called both when the user clicks on the "Login with Provider" button and when the provider redirects back to the site. The role of requestLoginMiddleware is to first redirect the user to the external site to authenticate and later when the user is redirected back to the site (from the external system) to extract the user information.

The loginCallbackMiddleware is called after the user is redirected back to the site (from the external system) and also after requestLoginMiddleware's second call by this time requestLoginMiddleware would have hopefully extracted all needed user fields and made them available for loginCallbackMiddleware. (We/passort does it by exposing a field called user on the request object itself). Here is where the external login handler is used to log the user in.

import configService from "server/core/config/configService";
import { getCurrentShopConfig } from "server/core/config/getShopConfig";
import customConfigProvider from "path_to_your_customConfigProvider";
import { registerExternalLoginProvider } from "auth-external-login/server/providers";

configService.append(customConfigProvider);

const makeCustomProvider = (request, externalLoginHandler, callbackUrl) => {
const providerConfig = request.config.customProviderConfig;
if (!providerConfig.clientId || !providerConfig.clientSecret) {
throw new Error(
`Custom provider is not properly configured. clientId and clientSecret are required!`
);
}
const shopUrl = getCurrentShopConfig(req.config).url; // frequently needed to keep current store selected upon redirection
return new CustomProvider(externalLoginHandler, providerConfig, {
shopUrl,
callbackUrl,
});
};

registerExternalLoginProvider("custom", makeCustomProvider);

The LoginProviderBase

By implementing the Google and Facebook providers using passport these patterns emerged:

  • The loginCallbackMiddleware is just a call to the external login handler's login function.
  • The provider commonly needs:
    • The login handler
    • The provider's own config
    • The shop and the callback URLs

LoginProviderBase is a simple implementation of the LoginProviderInterface that has the constructor (see below for signature) and the loginCallbackMiddleware implemented.

  constructor(externalLoginHandler, providerConfig, { shopUrl, callbackUrl }) {
...
}

What is left is implementing requestLoginMiddleware which is easily doable using passport:

requestLoginMiddleware(req, res, next) {

const passportAuthenticator = new Passport();

passportAuthenticator.use(
new FacebookStrategy(
{
clientID: this.providerConfig.clientId,
clientSecret: this.providerConfig.clientSecret,
callbackURL: `${this.shopUrl}${this.callbackUrl}`,
profileFields: ["email", "name"],
},
(_, __, profile, cb) => {
const {
email,
first_name: firstname,
last_name: lastname,
} = profile._json;

if (!(email && firstname && lastname)) {
cb(new MissingInformationError());
} else {
cb(null, new ExternalLoginUser(email, firstname, lastname));
}
}
)
);

passportAuthenticator.authenticate("facebook", {
failureRedirect: `${this.shopUrl}/login`,
session: false,
scope: ["email"],
})(req, res, next);
}

Creating your own ExternalLoginHandler

The ExternalLoginHandler is responsible of actually logging the user into your platform. It should first create the user if he does not exist. Next it should log in the user (old or new) into the system.

The external loging handler should implement the ExternalLoginHandlerInterface interface which means that it must have a method called login (see signature below) that when called will ensure the user exist or otherwise create him and finally log him/her in.

class CustomExternalLoginHander extends ExternalLoginHandlerInterface {
async login(request, user) {
// ensure/create then login user
}
}

We also provide a ExternalLoginHandlerBase class that implements the ExternalLoginHandlerInterface. It only implements a constructor with the folowing signature:

constructor(globalConfig, staticConfigFromProviders) {
}

globalConfig, staticConfigFromProviders are used since they are what is sent to the makeExternalLoginEndpoint (see below), and for the globalConfig you can use it as follows:

import ExternalLoginHandlerBase from "auth-external-login/server/ExternalLoginHandlerBase";
import makeLoadersFromRequest from "server/core/graphql/makeLoadersFromRequest";

export default class CustomLoginHandler extends ExternalLoginHandlerBase {
async login(req, user) {
const loaders = makeLoadersFromRequest(this.globalConfig, req);
// you have access to all your loaders!!! like loaders.Product, loaders.Customer...
}
}

Putting it all together

As discussed previously the providers provide the middleware to handle the login with the external system, and the external login handler handles actually logging they user in. Now we are going to link the providers with the external login handler in module that we can register into .front-commerce.js

A Front-Commerce module is basically a directory that follows certain conventions. It can be placed any where in your folder structure but is commonly placed either under ./modules/your_custom_module_name or ./src/modules/your_custom_module_name.

For our purposes we are only interensted in the server part of the module so we need to create your_custom_module_name/server/module.config.js file.

The your_custom_module_name/server/module.config.js file should export an object with the providers middleware as follows:

import makeExternalLoginEndpoint from "auth-external-login/server/makeExternalLoginEndpoint";
import CustomExternalLoginHandler from "you_external_login_handler/CustomExternalLoginHandler";

export default {
endpoint: makeExternalLoginEndpoint(
(config, staticConfigFromProviders) =>
new CustomExternalLoginHandler(/** some parameters you extract from the configs */)
),
};

This is pretty much all that is needed. Typically the ExternalLoginHandler in placed inside the same module but this is just a recomendation and not a requirement.

Now you can register the new custom module in your .front-commerce.js file and do not forget to also register the base auth-external-login module.

.front-commerce.js
module.exports = {
name: "Front-Commerce DEV",
url: "http://www.front-commerce.test",
modules: [
"./node_modules/front-commerce/theme-chocolatine",
"./node_modules/front-commerce/modules/auth-external-login",
"./modules/my-custom-external-login",
],
};