Auth0 / Okta CIC

Step-Up Authentication with Auth0

Emmanuel Gautier Emmanuel Gautier

What Is Step-Up Authentication?

Step-up authentication is a security mechanism that requires a user to re-authenticate with a stronger level of assurance when accessing sensitive resources or performing critical actions. It ensures that the user requesting access has a verified identity that matches the sensitivity of the operation or data.

If you are interested to know more about step-up authentication with OpenID Connect, check out our Step-Up Authentication with OpenID Connect article. The implementation with Auth0 is similar, but the configuration and setup are specific to the Auth0 platform.

How Does Step-Up Authentication Work?

When a user authenticates with Auth0, the platform issues an ID token that contains claims about the authentication event and the user. By including specific claims in the ID token, you can enforce step-up authentication based on the user's context or the requested operation.

These claims can be used to determine the level of assurance provided by the authentication process and whether step-up authentication is required. For example, you can use the acr (Authentication Context Class Reference) claim to represent the authentication context class reference during the process.

Step-Up Authentication with the acr Claim

The acr claim in the ID token represents the authentication context class reference during the process. For instance, it can indicate the level of assurance provided by the authentication process for the user in the current session. For example: acr=loa1 represents a basic level of assurance, such as password-based authentication.

When a user authenticates, Auth0 includes the acr claim in the ID token based on the level of assurance provided. Your application can then evaluate the acr claim to determine if step-up authentication is required for the requested operation.

Auth0 defines common values for the acr claim, but you can also define custom values based on your application's requirements. The most common value is http://schemas.openid.net/pape/policies/2007/06/multi-factor, which indicates that multi-factor authentication was used during the authentication process.

Auth0 also supports the amr (Authentication Methods References) claim, which provides information about the authentication methods used during the authentication process. This claim can be used to determine the specific authentication methods that were applied.

Example:

{
    "iss": "https://{yourDomain}/",
    "sub": "auth0|1a2b3c4d5e6f7g8h9i",
    "aud": "{yourClientId}",
    "iat": 1522838054,
    "exp": 1522874054,
    "acr": "http://schemas.openid.net/pape/policies/2007/06/multi-factor",
    "amr": [
        "mfa"
    ]
}

Implementing Step-Up Authentication with Auth0

To implement step-up authentication with Auth0, you need to develop a custom Auth0 action on post-login that evaluates the acr_values attached to the OpenID Connect request. The acr_values parameter allows you to request specific authentication context classes during the authentication process.

Then you can challenge the user with additional authentication factors. For example, you can prompt the user to provide a one-time password (OTP) or use biometric verification depending on the requested operation or the enrolled factors.

Example with acr_values

Here is an example of a custom Auth0 action that enforces step-up authentication based on the acr_values:

/**
* Handler that will be called during the execution of a PostLogin flow.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
  // Step-up is trigger depending on a specific acr value
  if (!event.transaction?.acr_values?.includes('http://schemas.openid.net/pape/policies/2007/06/multi-factor')) {
    return;
  }

  // Check if offline access is requested. If so, deny the request because we don't allow refresh tokens in this context.
  if (event.transaction.requested_scopes?.some(scope => ['offline_access', 'offline'].includes(scope))) {
    return api.access.deny('offline_access_not_allowed');
  }

  api.multifactor.enable('any', { allowRememberBrowser: false });
  return api.authentication.challengeWithAny([
    { type: 'otp' },
    { type: 'phone' },
    { type: 'webauthn-platform' },
    { type: 'webauthn-roaming' },
  ]);
};

/**
* Handler that will be invoked when this action is resuming after an external redirect. If your
* onExecutePostLogin function does not perform a redirect, this function can be safely ignored.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
// exports.onContinuePostLogin = async (event, api) => {
// };

Example with Scope

Here is the same example but using sensitive scopes to trigger the step-up authentication:

const sensitiveScopes = ['transfer:money', 'view:account'];

/**
* Handler that will be called during the execution of a PostLogin flow.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
  // Step-up is trigger depending on if the OpenID Connect request includes sensitive scopes
  if (!event.transaction?.requested_scopes?.some(scope => sensitiveScopes.includes(scope))) {
    return;
  }

  // Check if offline access is requested. If so, deny the request because we don't allow refresh tokens in this context.
  if (event.transaction.requested_scopes?.some(scope => ['offline_access', 'offline'].includes(scope))) {
    return api.access.deny('offline_access_not_allowed');
  }

  api.multifactor.enable('any', { allowRememberBrowser: false });
  return api.authentication.challengeWithAny([
    { type: 'otp' },
    { type: 'phone' },
    { type: 'webauthn-platform' },
    { type: 'webauthn-roaming' },
  ]);
};

/**
* Handler that will be invoked when this action is resuming after an external redirect. If your
* onExecutePostLogin function does not perform a redirect, this function can be safely ignored.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
// exports.onContinuePostLogin = async (event, api) => {
// };

Written by


Emmanuel Gautier

Emmanuel Gautier

CerberAuth Founder and Core Contributor