Enterprise-grade authentication using AWS Cognito and OneLogin with FeathersJS v3

Luc Claustres
The Feathers Flightpath
4 min readJun 17, 2018

--

In a previous article we detailed how you can setup OAuth with FeathersJs to typically integrate social identity providers like Google in your application. However, enterprises often need solutions able to integrate with their directories behind a corporate firewall.

Preambule

Identity as a Service (IDaaS) is cloud-based authentication operated by a third-party provider. A growing number of companies are choosing IDaaS to provide federation capabilities rather than on-premises federation solutions. It typically allows organizations to use single sign-on with SAML or OpenID Connect to provide secure access to their growing number of software and SaaS applications.

In this article we detail how to integrate your FeathersJS application with two IDaaS providers: AWS Cognito and OneLogin.

The goal is not to detail the complete setup for each provider because it really depends on the target enterprise needs. Instead we’d like to explain how you have to tune your FeathersJS application to integrate with it smoothly.

AWS Cognito

Here is the main things you will have to do first on the AWS side, the AWS documentation contains everything you need (and more !):

For the client application take care to correctly setup the callback URL of your backend (by default http://your-domain.com/auth/cognito/callback). Then to select “Authorization code grant” under “Allowed OAuth Flows”. Last but not least, add your “Cognito User Pool” as one of the “Enabled Identity Providers”, as well as your external identity providers.

As described in our previous article, use the feathers-authentication module and its oauth2 plugin to enable OAuth with the AWS Cognito provider and the corresponding passport strategy. Then modify your app configuration config/default.jsonto integrate Cognito settings like this:

{
...
"authentication": {
...
cognito: {
clientID: 'xxx',
clientSecret: 'xxx',
clientDomain: 'https://your-app.auth.eu-west-1.amazoncognito.com',
callbackURL: 'https://your-domain.com/auth/cognito/callback',
successRedirect: 'https://your-domain.com/',
region: 'eu-west-1'
}
}
}

Finally, configure your app to use the Cognito authentication strategy:

...
import configuration from 'feathers-configuration'
import authentication from 'feathers-authentication'
import jwt from 'feathers-authentication-jwt'
import local from 'feathers-authentication-local'
import oauth2 from 'feathers-authentication-oauth2'
import CognitoStrategy from 'passport-oauth2-cognito'
import Verifier from './verifier'
let app = feathers()
// Load app configuration first
app.configure(configuration())
// Set up authentication
const config = app.get('authentication')
app.configure(authentication(config))
app.configure(jwt())
app.configure(local())
app.configure(oauth2({
name: 'cognito',
Strategy: CognitoStrategy,
Verifier
}))

As you can see, you will need to customize the Verifier, i.e. the object responsible of handling the provider response, to make everything work smoothly. Indeed, the default verifier checks for a returned idin the user profile but Cognito handle it using the subject claim of the token instead. Morevoer, the default verifier checks if you have already logged in with your provider by looking at an existing user with the target providerId field (eg githubId). If it finds one it updates the profile information of the existing user, otherwise a new user is created. So if you want to uniquely identify your user accounts by the emailfield and log in with different providers where the same e-mail is used for the different accounts it will eventually fail. You will find a complete custom verifier hereafter. As input it takes the path to the email field in each provider profile and convert the subject claim into the user id required by FeathersJs.

By default each provider profile information will be stored in a nested object whose key is named according to the provider. You can add a hook to “extract” and/or “transform” the relevant data for your own user model on first login:

function processProfile(provider, user) {
let profile = user[provider].profile
// As an example extract the user email
if (profile.email) {
user.email = profile.email
}
if (profile.name) {
user.name = profile.name
}
}
app.service('users').hooks({
before: {
create: [ (hook) => processProfile('cognito', hook.data) ],
update: []
}
})

OneLogin

The previous setup is similarly applicable to make your application work with OneLogin using OpenID Connect. You will simply need to use our OpenID Connect passport strategy based on a fork of passport-openidconnect. Indeed, the original module appeared to be in stale mode and we needed to integrated a PR allowing to pass client credentials in authorization headers instead of inside the URL as required by OneLogin and a fix to ensure it works smoothly with Cognito as well. Your app configuration config/default.jsonto integrate OneLogin settings will look like this:

{
...
"authentication": {
...
oidc: {
clientID: 'xxx',
clientSecret: 'xxx',
issuer: 'https://openid-connect-eu.onelogin.com/oidc',
authorizationURL: 'https://your-app.onelogin.com/oidc/auth',
tokenURL: 'https://your-app.onelogin.com/oidc/token',
userInfoURL: 'https://your-app.onelogin.com/oidc/me',
callbackURL: 'https://your-domain.com/auth/oidc/callback',
successRedirect: 'https://your-domain.com/',
state: false,
sessionKey: 'your-app',
useAuthorizationHeaderForToken: true,
scope: ['email', 'profile']
}
}
}

By default the OpenID Connect passport strategy manages the state to protect against cross site scripting attacks using express-session. However we had problems making it work with FeathersJS. Fortunately, the strategy allows to setup a custom state management by implementing its session store interface:

...
import configuration from 'feathers-configuration'
import authentication from 'feathers-authentication'
import jwt from 'feathers-authentication-jwt'
import local from 'feathers-authentication-local'
import oauth2 from 'feathers-authentication-oauth2'
import OpenIDStrategy from 'passport-openidconnect'
import Verifier from './verifier'
let app = feathers()
// Load app configuration first
app.configure(configuration())
// Set up authentication
const config = app.get('authentication')
app.configure(authentication(config))
app.configure(jwt())
app.configure(local())
app.configure(oauth2({
name: 'oidc',
Strategy: OpenIDStrategy,
Verifier,
store: {
store (req, meta, cb) {
let nonce = generateKey()
let state = Object.assign({ nonce }, meta)
req.oidc = state
return cb(null, nonce)
},
verify (req, state, cb) {
if (req.oidc.nonce !== state) {
return callback(null, false, { message: 'Invalid authorization request state.' })
}
return cb(null, true, req.oidc)
}
}
}))

Conclusion

We hope you now know how to make your FeathersJS app enterprise-ready.

If you liked this article feel free to have a look at our Open Source solutions and enjoy our other articles on Feathers, the Kalisio team !

--

--

Digital Craftsman, Co-Founder of Kalisio, Ph.D. in Computer Science, Guitarist, Climber, Runner, Reader, Lover, Father