oe-component-passport

This project implements multiple authentication capability in oe-cloud application using strategies provided by passportjs.

Pre-requisites

  • oe-cloud
  • oe-logger
  • loopback-component-passport
  • Configure model-config.json of application with UserIdentity and UserCredential with proper datasource as per application’s datasource configuration

Features

  1. Local, oauth2 and ldap authentication support extended from loopback component passport ( https://loopback.io/doc/en/lb3/Third-party-login-using-Passport.html)
  2. JWT authentication support, with option to inject principles in context (for role based ACL to work)
  3. Configurable “Cookie” generation with users/login api (set ENABLE_COOKIE=true)
  4. Parameterized providers.json

Setup

App-list configuration

Usage of this module needs an entry in package.json and also an entry to application’s app-list.json

{
    "path": "oe-component-passport",
    "enabled": true
  },

Inside your application, local authentication can be done using API call to “/User/login” or “/auth/local” (authPath in providers.js) which returns access_token as payload and in cookie if configured.

Configure config.json

In case of extensions of User, UserIdentity and UserCredential models configure them under passportConfig in the config.json file. In the example shown below AppUserCredential is an extension of UserCredential model. AppUserCredential model is as shwon below:

{
    "name": "AppUserCredential",
    "plural": "events",
    "base": "UserCredential",
    "properties": {
        "strategy" : {
            "type": "string"
        } 
    },
    "validations": [],
    "relations": {},
    "acls": []
}

The AppUserCredential model is configured in config.json as shown below.

"passportConfig": {
    "userCredentialModel": "AppUserCredential"
  }

Similarly if User and UserIdentity model is extended, it needs to be configured in passportConfig section with properties - userModel and userIdentityModel respectively.

providers.json

Add providers.json in you application directory or /server directory. This contains the configuration of available authentication options for your application. Its an opject map representing each IdP configuration. Following settings are for authorization-code flow with any oauth2 supported IdP and local authentication

{
    "oauth2-login": {
        "module": "passport-oauth2",
        "provider": "oAuth2",
        "strategy": "OAuth2Strategy",
        "clientID": "some-client-id",
        "clientSecret": "some-client-secret",
        "authPath": "/auth/oe",
        "callbackPath": "/oauth/callback",
        "callbackURL": "https://application-url/oauth/callback",
        "authorizationURL": "https://idp-url/auth",
        "tokenURL": "https://idp-url/token",
        "userInfoURL": "https://idp-url/userinfo",
        "cookie": true,
        "session": false,
        "failureQueryString": true,
        "successRedirect": "/",
        "failureRedirect": "/login",
        "scope": [
            "openid",
            "profile"
        ],
        "failureFlash": true
    },
    "local": {
      "provider": "local",
      "module": "passport-local",
      "usernameField": "username",
      "passwordField": "password",
      "authPath": "/auth/local",
      "successRedirect": "/explorer",
      "failureRedirect": "/login",
      "failureFlash": false,
      "callbackHTTPMethod": "post",
      "setAccessToken": true
  }
}

Parameterized providers.json

You can write providers json like this where you can parameterise a value like ${variable_name}

{
  "local": {
    "provider": "local",
    "module": "passport-local",
    "usernameField": "${userfieldname}",
    "passwordField": "${PASSWORD_FIELD_NAME}",
    "authPath": "/auth/local",
    "successRedirect": "/explorer",
    "failureRedirect": "/login",
    "failureFlash": false,
    "callbackHTTPMethod": "post",
    "setAccessToken": true
  }
}

In above example, usernameField value would be set to value of environment (or configuration) variable ‘userfieldname’ and passwordField value would be from environment (or configuration) variable ‘PASSWORD_FIELD_NAME’. If those environmental variables are not set or not in configuration, ’’ (blank string) would be assigned.

Using 3rd party trusted JWT/opaque token for authentication

Setup

To use the application with JWT obtained from 3rd party SSO/oauth2 IdP (for example in finacle SSO) enable and set the environment variable ENABLE_SSO_JWT to true. And JWT token is expected to be directly passed in “Authorization” header or “authorization” signed cookie for any API call - only to those which comes under restApiRoot path configured in config.js(or config.json or respective config file of each environment e.g. config.production.json). It creates a User with data from JWT claims.

NOTE: If application does not need a User to be created after successful JWT verification, set SKIP_USER_CREATION to ’true’. This would skip creating a local User based on data from JWT.

JWKS

You can get secret/public key from a JWKS secret store. You have to set environment variable SSO_JWKS_URI the url of JWKS from where the client secrect would be pulled based on the kid from the JWT token itself.

3rd party opaque token introspection

Opaque token generated by IdP can be validated using introspection URL. Currently it can call introspection URL using Basic authentication. Following env variable need to be set

"ENABLE_INTROSPECTION" - "true" or "false"; this would work only if ENABLE_SSO_JWT is not set
"INTROSPECTION_CLIENT_ID" - client id with which introspection call would be made
"INTROSPECTION_CLIENT_SECRET" - associated client secret 
"INTROSPECTION_URL" - full introspection URL
"INTROSPECTION_TOKEN_TYPE_HINT" - optional, if IdP expects token_hint_type for introspection call

Similar to JWT, opaque token would be taken from Authorization header of the request and would be used for introspection (only if ENABLE_INTROSPECTION is set to true and ENABLE_SSO_JWT is not set). Also similarly it would create a USER based in USER_ID, username, sub or client_id (from introspection data, if present);

Additional Info: Based on the Introspection, if valid, AccessToken would be created where userId would be sub claim of the token; if not present, it would be client_id. Similarly if roles claim comes in token, it would be added and injected to context; else roles would be blank (this affects ACL).

NOTE: If user creation to be skipped, set SKIP_USER_CREATION to ’true’. This would skip creating a local User based on data from Introspection. If introspection data contains only active property, user creation would be ignored anyway.

Roles

If authorization code flow is not used and oecloud application’s APIs are called using 3rd party token, roles claim in JWT or Introspected token data are added to the access-token. Framework expects roles claim is a string array. This roles claim in access-token is used by framework to verify ACL.

In regular authorization-code flow, access-token of oe-cloud application would be used after successful token response from IdP. No additional role injection happens based on roles claims in this case. User and role mapping needs to be done in application.

Public Key or secret

SECRET_OR_KEY or PUBLIC_KEY should be oauth2 IdP/fininfra’s public key as base64 string (should be properly formated and the public key) ENABLE_SSO_JWT set to true

How to get public key

Get public key for jwt verify from cacerts and key.jks comes with fininfra (docker image)

keytool -importkeystore -srckeystore key.jks -destkeystore somesso.p12 -srcstoretype jks -deststoretype pkcs12

keytool -exportcert -alias somesso -keystore key.jks | openssl x509 -inform DER >cert.pem

Certificate used as secret/public key (to decode JWT)

The certificate format should be similar to this

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCiUdFD5LPHdPKpSD+HpLzkfN6/
y0BDAbyo2srzBhQl81oqg+HPI/03jOsWs0cP0uS0eZOmrrlujLfbG+R3WKN5xPvB
brOBBA7N8axDRRZWoWkEX3KX2vaUfAxfQNp3tUhegliHtrLVPyutnowlY3f7/TzX
JbEND/PONc0VpaEf4wIDAQAB
-----END PUBLIC KEY-----

JWT_FOR_ACCESS_TOKEN

To improve performance in local authentication JWT can be used as access token. to enable that, set following environmental variable

SECRET_OR_KEY = 'secret'
JWT_FOR_ACCESS_TOKEN = true;

SECRET_OR_KEY could be any secret consisting alphanumeric value. If JWT_FOR_ACCESS_TOKEN is set true, and another IdP/sso also needs to be used, set certificate to PUBLIC_KEY

Please note that this implementation of JWT_FOR_ACCESS_TOKEN just replaces generic access-token with a JWT and saves checking user id from database for every request that needs authentication.

To implement custom JWT payload to have user roles(to use in ACL varification) and other details; override User.login function along with User.prototype.createAccessToken and AccessToken.resolve

For any other login related customization, like password complexity, password history etc; please extend User model and add customized code in extended model (some example available in oe-demo-app)