Rules

Versioncurrent

Rules are functions written in JavaScript that are executed when a user authenticates to your application. They run once the authentication process is complete and you can use them to customize and extend Auth0's capabilities. They can be chained together for modular coding and can be turned on and off individually.

Rule Flow

  1. An app initiates an authentication request to Auth0.
  2. Auth0 routes the request to an Identity Provider through a configured connection.
  3. The user authenticates successfully.
  4. The tokens (ID Token and/or Access Token) pass through the Rules pipeline, and are sent to the app.

What can I use rules for?

Among many possibilities, rules can be used to:

  • Profile enrichment: query for information on the user from a database/API, and add it to the user profile object.
  • Create authorization rules based on complex logic (anything that can be written in JavaScript).
  • Normalize attributes from different providers beyond what is provided by Auth0.
  • Reuse information from existing databases or APIs for migration scenarios.
  • Keep a white-list of users and deny access based on email.
  • Notify other systems through an API when a login happens in real-time.
  • Enable counters or persist other information. For information on storing user data, see: Metadata in Rules.
  • Enable multifactor authentication, based on context (such as last login, IP address of the user, location, and so on).
  • Modify tokens: Change the returned scopes of the Access Token and/or add claims to it, and to the ID Token.

Video: Using rules

Watch this video learn all about rules in just a few minutes.

Rule Syntax

A Rule is a function with the following arguments:

  • user: the user object as it comes from the identity provider. For a complete list of the user properties, see User Profile Structure.

  • context: an object containing contextual information of the current authentication transaction, such as user's IP address, application, location. For a complete list of context properties, see Context Argument Properties in Rules.

  • callback: a function to send back potentially modified tokens back to Auth0, or an error. Because of the async nature of Node.js, it is important to always call the callback function, or else the script will timeout.

Examples

To create a Rule, or try the examples below, go to New Rule in the Rule Editor on the Dashboard.

Select an empty rule to start from scratch, or use one of the templates. Name your rule, keeping in mind that it can only contain alphanumeric characters, spaces and '-', and cannot start, nor end, with '-' or spaces.

For more examples see our Github repo at auth0/rules.

Hello World

This rule will add a hello claim (with the value world) to the ID Token that will be afterwards sent to the application.

function (user, context, callback) {
  context.idToken["http://mynamespace/hello"] = "world";
  console.log('===> set "hello" for ' + user.name);
  callback(null, user, context);
}

Note that the claim is namespaced: we named it http://mynamespace/hello instead of just hello. This is what you have to do in order to add arbitrary claims to an ID Token or Access Token.

Namespace Identifiers

Any non-Auth0 HTTP or HTTPS URL can be used as a namespace identifier, and any number of namespaces can be used. An exception to that are webtask.io and webtask.run which are Auth0 domains and therefore cannot be used. The namespace URL does not have to point to an actual resource; it's only used as an identifier and will not be called by Auth0. For more information refer to User profile claims and scope.

Add roles to a user

In this example, all authenticated users will get a guest role, but johnfoo@gmail.com will also be an admin:

function (user, context, callback) {
  if (user.email === 'johnfoo@gmail.com') {
    context.idToken["http://mynamespace/roles"] = ['admin', 'guest'];
  }else{
    context.idToken["http://mynamespace/roles"] = ['guest'];
  }

  callback(null, user, context);
}

At the beginning of the rules pipeline, John's context object will be:

{
  "clientID": "YOUR_CLIENT_ID",
  "clientName": "YOUR_CLIENT_NAME",
  "clientMetadata": {},
  "connection": "YOUR_CONNECTION_NAME",
  "connectionStrategy": "auth0",
  "protocol": "oidc-implicit-profile",
  "accessToken": {},
  "idToken": {},
  //... other properties ...
}

After the rule executes, the context object will have the added namespaced claim as part of the ID Token:

{
  "clientID": "YOUR_CLIENT_ID",
  "clientName": "YOUR_CLIENT_NAME",
  "clientMetadata": {},
  "connection": "YOUR_CONNECTION_NAME",
  "connectionStrategy": "auth0",
  "protocol": "oidc-implicit-profile",
  "accessToken": {},
  "idToken": { "http://mynamespace/roles": [ "admin", "guest" ] },
  //... other properties ...
}

When your application receives the ID Token, it will verify and decode it, in order to access this added custom claim. The payload of the decoded ID Token will be similar to the following sample:

{
  "iss": "https://YOUR_AUTH0_DOMAIN/",
  "sub": "auth0|USER_ID",
  "aud": "YOUR_CLIENT_ID",
  "exp": 1490226805,
  "iat": 1490190805,
  "nonce": "...",
  "at_hash": "...",
  "http://mynamespace/roles": [
    "admin",
    "guest"
  ]
}

For more information on the ID Token, refer to ID Token.

Properties added in a rule are not persisted in the Auth0 user store. Persisting properties requires calling the Auth0 Management API.

Deny access based on a condition

In addition to adding claims to the ID Token, you can return an access denied error.

function (user, context, callback) {
  if (context.clientID === "BANNED_CLIENT_ID") {
    return callback(new UnauthorizedError('Access to this application has been temporarily revoked'));
  }

  callback(null, user, context);
}

This will cause a redirect to your callback url with an error querystring parameter containing the message you set. (such as https://yourapp.com/callback?error=unauthorized&error_description=Access%20to%20this%20application%20has%20been%20temporarily%20revoked). Make sure to call the callback with an instance of UnauthorizedError (not Error).

Error reporting to the app depends on the protocol. OpenID Connect apps will receive the error in the querystring. SAML apps will receive the error in a SAMLResponse.

Copy User Metadata to ID Token

This will read the favorite_color user metadata, and add it as a namespaced claim at the ID Token.

function(user, context, callback) {

  // copy user metadata value in ID Token
  context.idToken['http://fiz/favorite_color'] = user.user_metadata.favorite_color;

  callback(null, user, context);
}

API Authorization: Modify Scope

This will override the returned scopes of the Access Token. The rule will run after user authentication and before authorization.

function(user, context, callback) {

  // change scope
  context.accessToken.scope = ['array', 'of', 'strings'];

  callback(null, user, context);
}

The user will be granted three scopes: array, of, and strings.

API Authorization: Add Claims to Access Tokens

This will add one custom namespaced claim at the Access Token.

function(user, context, callback) {

  // add custom claims to Access Token
  context.accessToken['http://foo/bar'] = 'value';

  callback(null, user, context);
}

After this rule executes, the Access Token will contain one additional namespaced claim: http://foo/bar=value.

Using the Configuration Object

The global configuration object is available in your rules if you wish to save some commonly used items, such as credentials, URLs, and so on, that might be subject to change or that you wish to keep out of your Rule code.

The following example is a Rule template for sending a Slack message when a new user has signed up via Auth0:

function(user, context, callback) {
  // short-circuit if the user signed up already
  if (context.stats.loginsCount > 1) return callback(null, user, context);

  // get your slack's hook url from: https://slack.com/services/10525858050
  var SLACK_HOOK = configuration.SLACK_HOOK;

  var slack = require('slack-notify')(SLACK_HOOK);
  var message = 'New User: ' + (user.name || user.email) + ' (' + user.email + ')';
  var channel = '#some_channel';

  slack.success({
   text: message,
   channel: channel
  });

  // don’t wait for the Slack API call to finish, return right away (the request will continue on the sandbox)`
  callback(null, user, context);
}

This Rule will require that you have a configuration value set for the key SLACK_HOOK. At the Rules page in the Dashboard you can scroll down beneath your list of Rules to the configuration area and enter SLACK_HOOK as the key and your Slack URL to post a message to the appropriate channel as the value, then hit "Create". Now your URL will be available to all rules via configuration.SLACK_HOOK. Bear in mind that configuration is global to all rules on the account.

Rules Configuration

Note that you need to have created at least one rule in order for the configuration area to show up, otherwise the Rules demo shows instead.

Create Rules with the Management API

Rules can also be created by creating a POST request to /api/v2/rules using the Management APIv2.

This will creates a new rule according to the following input arguments:

  • name: The name of the rule. It can only contain alphanumeric characters, spaces and '-', and cannot start nor end with '-' or spaces.
  • script : Τhe script that contains the rule's code. This is the same as what you would enter when creating a new rule using the dashboard.
  • order: This field is optional and contains a number. This number represents the rule's order in relation to other rules. A rule with a lower order than another rule executes first. If no order is provided it will automatically be one greater than the current maximum.
  • enabled: This field can contain an optional boolean. If true, the rule will be enabled, if it's false it will be disabled.

Example of a body schema:

{
  "name": "my-rule",
  "script": "function (user, context, callback) {\n  callback(null, user, context);\n}",
  "order": 2,
  "enabled": true
}

Use this to create the POST request:


curl --request POST \
  --url 'https://YOUR_AUTH0_DOMAIN/api/v2/rules' \
  --header 'content-type: application/json' \
  --data '{"name":"my-rule","script":"function (user, context, callback) {callback(null, user, context);}","order":2,"enabled":true}'
var client = new RestClient("https://YOUR_AUTH0_DOMAIN/api/v2/rules");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/json");
request.AddParameter("application/json", "{\"name\":\"my-rule\",\"script\":\"function (user, context, callback) {callback(null, user, context);}\",\"order\":2,\"enabled\":true}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io/ioutil"
)

func main() {

	url := "https://YOUR_AUTH0_DOMAIN/api/v2/rules"

	payload := strings.NewReader("{\"name\":\"my-rule\",\"script\":\"function (user, context, callback) {callback(null, user, context);}\",\"order\":2,\"enabled\":true}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("content-type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.post("https://YOUR_AUTH0_DOMAIN/api/v2/rules")
  .header("content-type", "application/json")
  .body("{\"name\":\"my-rule\",\"script\":\"function (user, context, callback) {callback(null, user, context);}\",\"order\":2,\"enabled\":true}")
  .asString();
var settings = {
  "async": true,
  "crossDomain": true,
  "url": "https://YOUR_AUTH0_DOMAIN/api/v2/rules",
  "method": "POST",
  "headers": {
    "content-type": "application/json"
  },
  "processData": false,
  "data": "{\"name\":\"my-rule\",\"script\":\"function (user, context, callback) {callback(null, user, context);}\",\"order\":2,\"enabled\":true}"
}

$.ajax(settings).done(function (response) {
  console.log(response);
});
var request = require("request");

var options = { method: 'POST',
  url: 'https://YOUR_AUTH0_DOMAIN/api/v2/rules',
  headers: { 'content-type': 'application/json' },
  body: 
   { name: 'my-rule',
     script: 'function (user, context, callback) {callback(null, user, context);}',
     order: 2,
     enabled: true },
  json: true };

request(options, function (error, response, body) {
  if (error) throw new Error(error);

  console.log(body);
});
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"content-type": @"application/json" };
NSDictionary *parameters = @{ @"name": @"my-rule",
                              @"script": @"function (user, context, callback) {callback(null, user, context);}",
                              @"order": @2,
                              @"enabled": @YES };

NSData *postData = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:nil];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_AUTH0_DOMAIN/api/v2/rules"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"POST"];
[request setAllHTTPHeaderFields:headers];
[request setHTTPBody:postData];

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                if (error) {
                                                    NSLog(@"%@", error);
                                                } else {
                                                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
                                                    NSLog(@"%@", httpResponse);
                                                }
                                            }];
[dataTask resume];
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://YOUR_AUTH0_DOMAIN/api/v2/rules",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => "{\"name\":\"my-rule\",\"script\":\"function (user, context, callback) {callback(null, user, context);}\",\"order\":2,\"enabled\":true}",
  CURLOPT_HTTPHEADER => array(
    "content-type: application/json"
  ),
));

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
import http.client

conn = http.client.HTTPSConnection("")

payload = "{\"name\":\"my-rule\",\"script\":\"function (user, context, callback) {callback(null, user, context);}\",\"order\":2,\"enabled\":true}"

headers = { 'content-type': "application/json" }

conn.request("POST", "/YOUR_AUTH0_DOMAIN/api/v2/rules", payload, headers)

res = conn.getresponse()
data = res.read()

print(data.decode("utf-8"))
require 'uri'
require 'net/http'

url = URI("https://YOUR_AUTH0_DOMAIN/api/v2/rules")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Post.new(url)
request["content-type"] = 'application/json'
request.body = "{\"name\":\"my-rule\",\"script\":\"function (user, context, callback) {callback(null, user, context);}\",\"order\":2,\"enabled\":true}"

response = http.request(request)
puts response.read_body
import Foundation

let headers = ["content-type": "application/json"]
let parameters = [
  "name": "my-rule",
  "script": "function (user, context, callback) {callback(null, user, context);}",
  "order": 2,
  "enabled": true
]

let postData = NSJSONSerialization.dataWithJSONObject(parameters, options: nil, error: nil)

var request = NSMutableURLRequest(URL: NSURL(string: "https://YOUR_AUTH0_DOMAIN/api/v2/rules")!,
                                        cachePolicy: .UseProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.HTTPMethod = "POST"
request.allHTTPHeaderFields = headers
request.HTTPBody = postData

let session = NSURLSession.sharedSession()
let dataTask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    println(error)
  } else {
    let httpResponse = response as? NSHTTPURLResponse
    println(httpResponse)
  }
})

dataTask.resume()

You can use the auth0-custom-db-testharness library to deploy, execute, and test the output of Custom DB Scripts using a Webtask sandbox environment.

How to Debug Rules

You can add console.log lines in the rule's code for debugging. The Rule Editor provides two ways for seeing the output:

Rules Editor

  1. TRY THIS RULE: opens a pop-up where you can run a rule in isolation. The tool provides a mock user and context objects. Clicking TRY will result on the the Rule being run with those two objects as input. console.log output will be displayed too.

    Try this Rule

    Please note that this feature functions outside the context of a specific client. That is, it uses a default All Applications client application. Because you are unable to configure parameters for this default application, y ou may run into issues if your rule depends on data that would otherwise be provided when called from an actual application.

  2. REALTIME LOGS: an extension that displays all logs in real-time for all custom code in your account. This includes all console.log output, and exceptions. For more info see Real-time Webtask Logs Extension.

  3. DEBUG RULE: similar to the above, displays instructions for installing, configuring and running the webtask CLI for debugging rules. Paste these commands into a terminal to see the console.log output and any unhandled exceptions that occur during Rule execution.

For example:

~  npm install -g wt-cli
~  wt init --container "youraccount" --url "https://sandbox.it.auth0.com" --token "eyJhbGci...WMPGI" -p "youraccount-default-logs"
~  wt logs -p "youraccount-default-logs"
[18:45:38.179Z]  INFO wt: connected to streaming logs (container=youraccount)
[18:47:37.954Z]  INFO wt: webtask container assigned
[18:47:38.167Z]  INFO wt: ---- checking email_verified for some-user@mail.com! ----

This debugging method works for rules tried from the dashboard and those actually running during user authentication.

Cache expensive resources

The code sandbox Rules run on allows storing expensive resources that will survive individual execution.

This example, shows how to use the global object to keep a mongodb connection:

...

//If the db object is there, use it.
if (global.db){
  return query(global.db, callback);
}

//If not, get the db (mongodb in this case)
mongo('mongodb://user:pass@mymongoserver.com/my-db',  function (db){
  global.db = db;
  return query(db, callback);
});

//Do the actual work
function query(db, cb){
  //Do something with db
  ...
});

...

Notice that the code sandbox in which Rules run on, can be recycled at any time. So your code must always check global to contain what you expect.

Whitelist IP Addresses

If you are behind a firewall, the use of the following features may require whitelisting of the appropriate Auth0 IP addresses to ensure proper functionality:

Outbound Calls

Please note that IP addresses are subject to change during Auth0 Migrations. The lists provided are up-to-date at the time of writing, but check the Dashboard for the latest list.

When making outbound calls, the IP addresses are static. Auth0 translates internal IP addresses to one of the displayed options when reaching out using NAT.

The IP addresses are region-specific.

United States

35.167.74.121, 35.166.202.113, 35.160.3.103, 54.183.64.135, 54.67.77.38, 54.67.15.170, 54.183.204.205

Europe

52.28.56.226, 52.28.45.240, 52.16.224.164, 52.16.193.66, 34.253.4.94, 52.50.106.250, 52.211.56.181, 52.213.38.246, 52.213.74.69, 52.213.216.142, 35.156.51.163, 35.157.221.52, 52.28.184.187, 52.28.212.16, 52.29.176.99, 52.57.230.214

Australia

54.153.131.0, 13.210.52.131, 13.55.232.24, 13.54.254.182, 52.62.91.160, 52.63.36.78, 52.64.120.184, 54.66.205.24, 54.79.46.4

Inbound Calls

IP addresses related to inbound calls to Auth0 may be variable due to the lack of fixed IP addresses on the load balancers.

Please be sure to allow inbound connections from the region-specific set of IP addresses listed in the Dashboard. The specific set of IP addresses you should use is provided when you create your new Custom Database Connection, Hook, or Rule.

Available modules

For security reasons, your Rules code executes isolated from the code of other Auth0 tenants in a sandbox based on Extend.

Within the sandbox, you can access the full power of Node.js with a large number of Node.js modules. For a list of currently supported sandbox modules, see Modules Supported by the Sandbox and Additional Modules Available in Rules.

Keep reading