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 usergiven_name
is the first name of the userfamily_name
is the last name of the useremail
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 propertiesgroupsArray
- an array of group IDs to which the user should be addedmandatoryGroupsArray
- 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.