Custom

This page provides some guidance on how to define your own custom Authentication Provider class for use within the Application Server.

Note: If you're looking information on how to easily integrate a third-party Authentication Provider into the Application Server, we recommend first taking a look at the Local Provider as it can be configured to work with third-party endpoints pretty easily.

Implementation Class

We recommend extending the AbstractProvider class. The basic skeleton of the class might look like this:

var AbstractProvider = require("cloudcms-server/middleware/authentication/providers/abstract");

class CustomProvider extends AbstractProvider {

    constructor(req, config) {
        super(req, config);
    }

    handleAuth(req, res, next) {
        super.handleAuth(req, res, next);
    }

    handleAuthCallback(req, res, next, callback) {
        super.handleAuthCallback(req, res, next, callback);
    }

    userIdentifier(profile) {
        return super.userIdentifier(profile);
    }

    parseProfile(req, profile, callback) {
        super.parseProfile(req, profile, callback);
    }

    load(properties, callback) {
        super.load(properties, callback);
    }
}

module.exports = CustomProvider;

In your app.js file, you can then register this custom provider like this:

server.init(function(app, callback) {
    auth.registerProvider("customProvider", require("./customProvider"));
    callback();
});

And you can then register it via config:

server.start({
    "auth": {
        ...,
        "providers": {
            "myProvider": {
                "type": "customProvider"
            }
        }            
    }
});

Implementation Details

Here are some notes to aid in your implementation.

constructor

In this section, you will generally want to initialize any configuration options ahead of calling into the super method. The config that was provided in the server.start() call for the provider is available in the config variable.

For example, you might do this:

constructor(req, config) {

    if (!config.loginUrl) {
        config.loginUrl = "/login";
    }
    
    super(req, config);
}

The config is a member variable and can be accessed from the other methods, allowing everything to be configuration driven. The other methods can simply reference this.config to get at configuration settings.

handleAuth

This method is called when the browser is redirected to /auth/myProvider. Its job is to somehow get a login screen in front of the user. The most frequent way of doing this is to redirect to a login page somewhere.

If you're using the Facebook provider, as an example, this method redirects you to Facebook to log in. Facebook then receives your username and password and authenticates you. It then redirects the browser back to your app with a code that asserts the user's successful authentication.

For example, you might do this:

handleAuth(req, res, next) {
\   res.redirect("https://login.mycompany.com?callback=https://myapp.company.com/auth/myProvider/callback");
}

This would redirect to a company-wide login page for sign on (provided that you have one implemented). It also passes some information about where to pass the authentication code on success.

handleAuthCallback

This method gets invoked when the authentication code is passed back after a successful login against the identity provider. Its job is to decipher the authentication code and extract the profile that was sent back.

It should then fire the callback. The callback signature is:

callback(null, profile, info);

Where profile is the profile object and info is any optional info that might be of interest downstream. This is generally not needed but can be used if there are cases where you wish to send additional data that isn't part of the user profile.

The user profile should, at a minimum, look like something like this:

{
    "unique_name": "jsmith",
    "given_name": "Joe",
    "family_name": "Smith",
    "email": "jsmith@company.com"
}

Where:

  • unique_name is the unique username for the user
  • given_name is the first name of the user
  • family_name is the last name of the user
  • email is the unique email of the user

userIdentifier

This method gets called to identify the primary key for a profile. By default, this will extract the unique_name field from the profile. You can adjust this if you wish.

For example, you could do something like:

userIdentifier(profile) {
    return profile["email"];
}

parseProfile

This method takes a trusted profile and extracts any user properties or group information from it. The profile was either acquired via a handshake with the backend authentication provider or it was extracted successfully by the adapter from the incoming request.

It should extract the following:

  • userObject - a JSON object with the Cloud CMS user properties
  • groupsArray - an array of group IDs to which the user should be added
  • mandatoryGroupsArray - an array of group IDs that the user must belong to or the sync should fail

The callback should be invoked like this:

callback(err, userObject, groupsArray, mandatoryGroupsArray);

The userObject should effectively look something like this (at a minimum):

{
    "name": "jsmith",
    "firstName": "Joe",
    "lastName": "Smith",
    "email": "jsmith@company.com"
}

load

This method takes properties that were extracted from an adapter and loads the profile for the described user directly from the identity provider.

The incoming properties has a token property, like this:

{
    "token": {identifying token}
}

This token should be used to load the identity profile. The profile should then be passed back via the callback.

The callback should be invoked like this:

callback (err, profile)

verify

This method takes untrusted properties that were extracted by the adapter and verifies them against the identity provider. Specifically, it takes the token that was received and verifies with the identity provider that the token is real and valid and acquires the profile for it.

The incoming properties has a token property, like this:

{
    "token": {identifying token}
}

The callback should be invoked like this:

callback (err, valid, profile)

Where valid should be set to false if the token is not valid.