Rules Execution Best Practices

Rules Execution Best Practices

Each rule is executed as a JavaScript function called in the order defined. The next rule in order won’t execute until the previous rule has completed. In addition, the rule pipeline only executes for workflows that involve user credentials; the rule pipeline does not execute during the Client Credentials Flow. For functionality similar to a rule, an Action in the Machine to Machine Flow on the credentials-exchange trigger can be used instead.

In pipeline terms, a rule completes when the callback function supplied to the rule is called. Failure to call the function results in a stall of pipeline execution, and ultimately in an error being returned. Each rule must call the callback function exactly once.

Rule execution supports the asynchronous nature of JavaScript, and constructs such as Promise objects and the like can be used. Asynchronous processing effectively results in suspension of a pipeline pending completion of the asynchronous operation. An Auth0 serverless Webtask container typically has a circa 20-second execution limit, after which the container may be recycled. Recycling of a container due to this limit will prematurely terminate a pipeline—suspended or otherwise—ultimately resulting in an error in authentication being returned (as well as resulting in a potential reset of the global object).

Setting context.redirect triggers a redirection once all rules have completed (the redirect is not forced at the point it is set). While all rules must complete within the execution limit of the Webtask container for the redirect to occur, the time taken as part of redirect processing can extend beyond that limit. We recommend that redirection back to Auth0 via the /continue endpoint should ideally occur within one hour. Redirection back to the /continue endpoint will also cause the creation of a new container in the context of the current pipeline, in which all rules will again be run.

Asynchronous execution will result in a (JavaScript) callback being executed after the asynchronous operation is complete. This callback is typically fired at some point after the main (synchronous) body of a JavaScript function completes. If a rule is making use of asynchronous processing, then a call to the (Auth0) supplied callback function must be deferred to the point where asynchronous processing completes and must be the final thing called. As discussed above, the (Auth0) supplied callback function must be called exactly once; calling the function more than once within a rule will lead to unpredictable results and/or errors.

context object

The context object provides information about the context in which a rule is run (such as client identifier, connection name, session identifier, request context, protocol, etc). Using the context object, a rule can determine the reason for execution. For example, as illustrated in the sample fragment below, context.clientID as well as context.protocol can be used to implement conditional processing to determine when rule logic is executed. The sample also shows some best practices for exception handling, use of npm modules (for Promise style processing), and the callback object. To learn more, read Custom Database Action Script Environment Best Practices.

switch (context.protocol) {
    case 'redirect-callback':
      return callback(null, user, context);
    	break;

    default: {
      user.app_metadata = user.app_metadata || {};
      switch(context.clientID) {
        case configuration.PROFILE_CLIENT: {
          user.user_metadata = user.user_metadata || {};
          Promise.resolve(new
            Promise(function (resolve, reject) {
              switch (context.request.query.audience) {
                case configuration.PROFILE_AUDIENCE: {
                  switch (context.connection) {
                      .
                      .
                  }
                } break;
              .
              .
            })
          )
          .then(function () {
              .
              .
          })
          .catch(function (error) {
            return callback(new UnauthorizedError("unauthorized"), user, context);
          });
        } break;

        default:
          return callback(null, user, context);
          break;

    } break;

Was this helpful?

/

We highly recommend reviewing best practices when using contextual bypass logic for Multi-Factor Authentication checking. For example, serious security flaws can surface if use of MFA is predicated on context.request.query.prompt === 'none'. In addition, the content of the context object is security sensitive, so you should not directly pass the object to any external or third-party service.

Redirection

It may not be practical to collect information from a user as part of a login flow in situations where there are many applications and you want a centralized service to manage that, or if you are using a SPA and you want to prevent the user from getting an access token under certain conditions. In these cases, having a centralized way to collect information or provide a challenge to a user is necessary.

Auth0 allows you to redirect the user to any URL where you can collect information from that user and then return the user to the /continue endpoint where they can complete the original /authorize request that triggered the redirect. This is a powerful capability, and depending on the use case, the impact of doing it wrong can be anywhere from innocuous to leaving a security vulnerability in the application. As such, it is important to ensure that this is done correctly.

In most use cases, a redirect rule is in use to prompt the user to make some change to their profile such as:

  • Forcing a password change

  • Verifying their email

  • Adding information to their profile

We recommend that the rule check for some flag or value in the user's app_metadata, then redirect to an application that does its own /authorize call to Auth0 and make any changes to the user's metadata and redirect the user back to Auth0. This works great for any profile changing redirects or anything that does not need to restrict the user from logging in.

Redirect from rule allows you to implement custom authentication flows that require additional user interaction triggered by context.redirect. Redirect from rule can only be used when calling the /authorize endpoint.

Redirection to your own hosted user interface is performed before a pipeline completes and can be triggered once per context.clientID context. Redirection should only use HTTPS when executed in a production environment, and additional parameters should be kept to a minimum to help mitigate common security threats. Preferably, the Auth0-supplied state is the only parameter supplied.

Once redirected, your own hosted user interface executes in a user authenticated context, and obtains authenticity artifacts by the virtue of Auth0 SSO. Obtaining these artifacts—e.g., an ID Token in OpenID Connect (OIDC), and/or an Access Token in OAuth 2.0—is achieved by using a context.clientID context that is not the one which triggered redirect. To do this, redirect to the /authorize endpoint. In the case of a SPA for example, use silent authentication. This creates a new pipeline that causes all rules to execute again, and you can use the context object within a rule to perform conditional processing.

Upon completion of whatever processing is to be performed, pipeline execution continues by redirecting the user back to Auth0 via the /continue endpoint (and specifying the state supplied). This causes all rules to execute again within the current pipeline, and you can use the context object within a rule to perform conditional processing checks.

Beware of storing too much data in the Auth0 profile. This data is intended to be used for authentication and authorization purposes. The metadata and search capabilities of Auth0 are not designed for marketing research or anything else that requires heavy search or update frequency. Your system is likely to run into scalability and performance issues if you use Auth0 for this purpose. A better approach is to store data in an external system and store a pointer (the user ID) in Auth0 so that backend systems can fetch the data if needed. A simple rule to follow is to store only items that you plan to use in rules to add to tokens or make decisions.

Passing information back and forth in the front channel opens up surface area for bad actors to attack. This should definitely be done only in conditions where you must take action in the rule (such as rejecting the authorization attempt with UnauthorizedError).

user object

The user object provides access to a cached copy of the user account (user profile) record in Auth0. The object provides access to information regarding the user without the need to access the Auth0 Management API—access which is both rate limited and subject to latency.

While the contents of the user object can be modified—for example, one rule could make a change which another rule could use to influence its execution—any changes made will not be persisted. There may be occasions when it becomes necessary to persist, say, updates to metadata associated with a user, and the auth0 object can be used to perform such operations where required.

Updating a user via use of the auth0 object ultimately results in a call to the Auth0 Management API. As the Auth0 Management API is both rate limited and subject to latency, caution should be exercised regarding when and how often updates are performed.

The context object contains the primaryUser property which refers to the user identifier of the primary user. This user identifier will typically be the same as user_id property in the root of the user object. The primary user is the user that is returned to the Auth0 engine when the rule pipeline completes, and the user_id is a unique value generated by Auth0 to uniquely identify the user within the Auth0 tenant. This user_id should be treated as an opaque value.

There are occasions when primaryUser must be updated as the primary user may change—i.e., the user returned to the Auth0 engine will be different from the user on rule pipeline entry. On such occasions, a rule must update primaryUser to reflect the new primary user identifier. Note that this change will not affect any subsequent rule executed in the current instance of the pipeline; the user object will remain unchanged.

Identities

The user object also contains a reference to the identities associated with the user account. The identities property is an array of objects, each of which contain properties associated with the respective identity as known to the identity provider (for example, the provider name, associated connection in Auth0, and the profileData obtained from the identity provider during the last authentication using that identity). Linking user accounts creates multiple entries in the array.

Each identity in the identities array also contains a user_id property. This property is the identifier of the user as known to the identity provider. While the user_id property in the root of the user object may also include the identifier of the user as known to the identity provider, as a best practice, use of the user_id property in an array identity should be preferred. The user_id in the root of the user object should be treated as an opaque value and should not be parsed.

Metadata

The user_metadata property and the app_metadata property refer to the two different aspects of the metadata associated with a user. Both the user_metadata property and the app_metadata property provide access to cached copies of each.

Authorization-related attributes for a user—such as role(s), group(s), department, and job codes—should be stored in app_metadata and not user_metadata. This is because user_metadata can essentially be modified by a user, whereas app_metadata cannot.

There may be occasions when it becomes necessary to persist, say, updates to metadata associated with a user, and the auth0 object can be used to perform such operations where required. When updating either metadata object, it is important to be judicious regarding what information is stored: in line with metadata best practice, be mindful of excessive use of metadata, which can result in increased latency due to excessive processing within the pipeline. To learn more, read Metadata Field Names and Data Types. Use of the auth0 object also results in a call to the Auth0 Management API, so caution should be exercised regarding when and how often updates are performed since the Auth0 Management API is both rate limited and subject to latency.

callback function

The callback function supplied to a rule effectively acts as a signal to indicate completion of the rule. A rule should complete immediately following a call to the callback function, either implicitly or by explicitly executing a (JavaScript) return statement, and should refrain from any other operation.

Failure to call the function will result in a stall of pipeline execution, and ultimately in an error condition being returned. Each rule then must call the callback function exactly once. Calling it once prevents the stall of the pipeline, but more could cause unpredictable results or errors.

function (user, context, callback) {
  getRoles(user.user_id, (err, roles) => {
    if (err) return callback(err);

    context.idToken['https://example.com/roles'] = roles;

    return callback(null, user, context);
  });
}

Was this helpful?

/

As can be seen in the example above, the callback function can be called with up to three parameters. The first parameter is mandatory and provides an indication of the status of rule operation. The second and third parameters are optional and represent the user and the context to be supplied to the next rule in the pipeline. If these are specified, then it is a recommended best practice to pass the user and context object (respectively) as supplied to the rule.

While it can be acceptable to modify certain contents of either the user or the context object for certain situations, as a recommended best practice you should refrain from passing a newly-created instance of either the user or the context object. Passing anything other than a user or context object will have unpredictable results and may lead to an exception or error condition.

The status parameter should be passed as either null, an instance of an Error object, or an instance of an UnauthorizedError object. Specifying null will permit the continuation of pipeline processing, while any of the other values will terminate the pipeline; an UnauthorizedError signals denial of access and allows information to be returned to the originator of the authentication operation regarding the reason why access is denied. Passing any other value for any of these parameters will have unpredictable results and may lead to an exception or error condition.

As authentication has already occurred, any early exit of the pipeline with an (authorization) error will not impact the authenticated session within the browser; subsequent redirects to /authorize will typically result in an automatic login. The early exit of the pipeline simply stops tokens et al from being generated. One option is for the application to redirect to the Authentication API's Logout endpoint, if required, to force termination of the Auth0 session in the browser.

Any call to the Logout endpoint could be interrupted, so explicit Auth0 session termination is not guaranteed. This is important, as any explicit condition that caused an unauthorized error must be re-checked in any subsequent rule pipeline execution, and it should not be possible to bypass these condition check(s) through any other conditions (such as prompt===none).

Learn more