OpenID Connect authorization

This UnityBase model implements SSO authentication over OpenID Connect (Authorization Code Flow) and generation of Clients Credentials and Resource Owner Password tokens. It comes with pre-configured Google and Azure providers.

See SMTP_OAuth2_Office365_Setup.md for setup account on Azure side for email sending.

Configuration

@unitybase/openid-connect should be added to application models list in the ubConfig.json:

"application": {
    "domain": {
        "models": [
          {
            "path": "./node_modules/@unitybase/openid-connect"
          },

Model ubConfig-partial.json adds Azure and Google pre-configured providers - see below for enabling.

If you need to use a specific provider configuration or an unsupported provider, add application.customSettings.openIDConnect.<providerName> property with the required options into the ubConfig.json and provirer will be registered automatically.

SSO for users: Authorization Code Flow

Users from browsers can be authenticated using OpenID Authorization Code Flow

  • https://tools.ietf.org/html/rfc6749#section-4.1
  • https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow

For adminUI "OpenIDConnect" should be added to the "authenticationMethods".

Configuration:

"security": {
  "authenticationMethods": ["UB", "OpenIDConnect"]
}

Pre-configured providers can be enabled using environment variables

# Google
DFX_EXT_OPENID_CLIENT_GOOGLE_ENABLED=true
DFX_EXT_OPENID_CLIENT_ID_GOOGLE=12345
DFX_EXT_OPENID_CLIENT_SECRET_GOOGLE=abcde
# Azure
DFX_EXT_OPENID_CLIENT_AZURE_ENABLED=true
DFX_EXT_OPENID_CLIENT_TENANT_AZURE=...
DFX_EXT_OPENID_CLIENT_ID_AZURE=..
DFX_EXT_OPENID_CLIENT_SECRET_AZURE=...

All providers what has authUrl defined will be automatically registered for SSO.

Mapping of username given by provider to UnityBase user can be tuned using custom getUserID function (define it in the customer model). Example:

const openID = require('@unitybase/openid-connect')

/**
 * @param {object} userInfo Object, returned by provider from `userinfo` endpoint  
 * @returns {number|null}
 */
function azureToMyDomain(userInfo) {
  if (!userInfo.email) {
    console.log('User email is not obtained from provider. Check the configured scopes.')
    return null
  }

  const ourUserName = 'myDomain/' + userInfo.email.split('@')[0].toLowerCase()
  const userID = Repository('uba_user')
    .attrs('ID')
    .where('name', '=', ourUserName)
    .selectScalar()

  return Number.isInteger(userID) ? userID : null
}
openID.registerOIDCCallback('azureToMyDomain', azureToMyDomain)

Added function name should be specified in provider configuration getUserID field. For pre-configured providers:

# Azure
DFX_EXT_OPENID_CLIENT_GETUSERID_FUNC_AZURE=azureToMyDomain
# Google
DFX_EXT_OPENID_CLIENT_GETUSERID_FUNC_GOOGLE=googleToMyDomain

For Machine2Machine

To communicate between UnityBase server and other services, what uses OpenID use one of available methods to get an Aunthorisation header value for your API calls

Client Credentials Flow

  • https://tools.ietf.org/html/rfc6749#section-4.4
  • https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow

Clients Credentials grant type must be enabled on OpenID provider side ("Service accounts roles" for KeyCloak provider)

const authVal = openID.provider('keyCloak').getCCToken()
UB.get('https://some.server/with/auth', {
  headers: {
    Authorization: authVal
  }
})

Resource Owner Password Flow

username and password MUST be stored in secure way on server. NEVER pass this values from client

const authVal = openID.provider('keyCloak').getROPToken(username, password)
UB.get('https://some.server/with/auth', {
  headers: {
    Authorization: authVal
  }
})

Email XOAUTH2 authorization

Starting from @unitybase/mailer@5.25 constructor for all mailer classes can accept OAuth2Token parameter, what contains an access_token value for mail provider. Use @unitybase/openid-connect to configure providers and get token. Example:

const openID = require('@unitybase/openid-connect')
const authType = 'Azure'
const oidcProv = openID.provider(authType)
if (!oidcProv) {
  throw new Error(`OIDC provider ${authType} is not registered, most likely ubConfig.application.customSettings.openIDConnect.${authType} section is empty`)
}
const accessToken = oidcProv.getCCToken({ scope: oidcProv.emailTokenScope, tokenOnly: true })
const senderCfg = {
  //....,
  OAuth2Token: accessToken
}
mailSender = new UBMail.TubMailSender(senderCfg)

Manual registration (deprecated)

Before @unitybase/openid-connect@5.25 (2026-02-20) providers was registered manually in customer model JS code. We strongly recommend to use pre-configured providers or customSettiogs.openIDConnect section for adding custom providers and let model to register they automatically.

For compatibility code below still work

const openID = require('@unitybase/openid-connect')
let oIdEndPoint = openID.registerEndpoint('openIDConnect')
// Google
// see https://accounts.google.com/.well-known/openid-configuration to get Url`s
oIdEndPoint.registerProvider('Google', {
  authUrl: 'https://accounts.google.com/o/oauth2/auth',
  tokenUrl: 'https://accounts.google.com/o/oauth2/token',
  userInfoUrl: 'https://www.googleapis.com/oauth2/v1/userinfo',
  userInfoHTTPMethod: 'GET',
  scope: 'openid',
  nonce: '123',
  response_type: 'code',
  client_id: '350085411136-lpj0qvr87ce0r0ae0a3imcm25joj2t2o.apps.googleusercontent.com',
  client_secret: 'dF4qmUxhHoBAj-E1R8YZUCqA',
  getOnFinishAction: function (response) {
    return 'opener.$App.onFinishOpenIDAuth(' + JSON.stringify(response) + '); close();'
  },
  getUserID: function(userInfo) {
    let inst = UB.Repository('uba_user').attrs(['ID'])
       .where('[name]', '=', userInfo.id).select()
    return inst.eof ? null : inst.get('ID')
  }
})

// Hideez
oIdEndPoint.registerProvider('Hideez', {
  authUrl: 'https://xxx.hideez.com/connect/authorize',
  tokenUrl: 'https://xxx.hideez.com/connect/token',
  userInfoUrl: 'https://xxx.hideez.com/connect/userinfo',
  userInfoHTTPMethod: 'GET',
  scope: 'openid email', //email - omportant 
  //scope: 'openid email phone roles profile',
  nonce: 'replaceToSomeRandom',
  response_type: 'code',
  client_id: '.....',
  client_secret: '....',
  getUserID: function(userInfo) {
    let inst = UB.Repository('uba_user').attrs(['ID'])
      .where('[name]', '=', (userInfo.email || '-').toLocaleLowerCase()).select().
      return inst.eof ? null : inst.get('ID')
  }
})

// Azure
// see https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration to get Url`s
oIdEndPoint.registerProvider('Azure', {
  authUrl: 'https://login.microsoftonline.com/{tenant-guid}/oauth2/v2.0/authorize',
  tokenUrl: 'https://login.microsoftonline.com/{tenant-guid}/oauth2/v2.0/token',
  userInfoUrl: 'https://graph.microsoft.com/oidc/userinfo',
  userInfoHTTPMethod: 'POST',
  scope: 'openid',
  nonce: '123',
  response_type: 'code',
  response_mode: 'query',
  client_id: 'YOUR_CLIENT_ID',
  client_secret: 'YOUR_CLIENT_SECRET',
  getUserID: function(userInfo) {
    // console.debug("userinfo=", JSON.stringify(userInfo))
    let inst = UB.Repository('uba_user').attrs(['ID'])
      .where('[name]', '=', userInfo.name).select()
    console.debug("user", inst.eof ? null : inst.get('ID'))
    return inst.eof ? null : inst.get('ID')
  }
})

// local keyCloak
const openID = require('@unitybase/openid-connect')
const keyCloakBaseUrl = 'http://localhost:8080/realms/myrealm/protocol/openid-connect'
openID.registerProvider('keyCloak', {
  authUrl: keyCloakBaseUrl + '/auth',
  tokenUrl: keyCloakBaseUrl + '/token',
  userInfoUrl: keyCloakBaseUrl + '/userinfo',
  logoutUrl: keyCloakBaseUrl + '/logout',

  client_id: 'oidKeycloak',

  client_secret: 'CLkwDlhcHGIwFh52HbDJF0mPk1jSdHYa',

  userInfoHTTPMethod: 'POST',
  scope: 'openid',
  nonce: '',
  response_type: 'code',
  response_mode: 'query'
})

Methods

# openIDConnectEp (reqTHTTPRequest, respTHTTPResponse) inner

OpenID Authorization Code Flow (user interaction) endpoint implementation

  • https://tools.ietf.org/html/rfc6749#section-4.1
  • https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow

If called as /endpoint - return a list of registered openIDConnect providers, If called as /endpoint/provider:

  • without parameters or with mode === 'auth' - redirect to provider authUrl
  • with parameters code and state - call doProviderAuthHandshake method
  • with parameters logout - redirect to log out url

Arguments:

# provider (providerNamestring) → OpenIdProvider | undefined inner

Return provider by name

Arguments:

# registerOpenIDEndpoint (endpointNamestring) → openIDEndpoint inner

Register openID connect endpoint. In case endpoint already registered - return existed

Return:

endpoint

Arguments:

# registerOpenIDProvider (namestring, providerConfigProviderConfig) inner

Register OpenID provider

Arguments:

Types

# openIDEndpoint inner

OpenID endpoint. Able to register providers

Properties