Using Hooks with Client Credentials GrantBeta

You can now add Hooks into your client credentials flow. This way you can change the scopes and add custom claims to the tokens issued by Auth0.


Hooks allow you to customize the behavior of Auth0 using Node.js code.

They are actually Webtasks, associated with specific extensibility points of the Auth0 platform (like the Client Credentials grant). Auth0 invokes the Hooks at runtime to execute your custom logic.

You can manage Hooks using the Auth0 Dashboard or the Auth0 Command Line Interface (CLI). In this article we will see how you can do either.

Before you start

Please ensure that:

If you haven't done these yet, refer to these docs for details:

Use the Dashboard

  1. Go to the Hooks page of the Dashboard.

Dashboard Hooks

  1. Click the + Create New Hook button. On the New Hook pop-up window, set the Hook dropdown to Client Credentials Exchange and set a Name for your hook. Click Create.

New Client Credentials Hook

At this point, you will see your newly-created Hook listed under the Client Credentials Exchange.

You can create more than one hooks per extensibility point but only one can be enabled. The enabled hook will then be executed for all applications and APIs.

  1. Click the Pencil and Paper icon to the right of the Hook to open the Webtask Editor.

Edit Client Credentials Hook

  1. Using the Webtask Editor, write your Node.js code. As an example, we will add an extra scope. The claim's name will be and its value bar. Copy the sample code below and paste it in the Editor.
module.exports = function(client, scope, audience, context, cb) {
  var access_token = {};
  access_token[''] = 'bar';
  access_token.scope = scope;
  cb(null, access_token);

This sample hook will:

  • add an arbitrary claim ( to the Access Token
  • add an extra scope to the default scopes configured on your API.

Custom claims namespaced format

In order to improve compatibility for applications, Auth0 now returns profile information in a structured claim format as defined by the OpenID Connect (OIDC) specification. This means that in order to add custom claims to ID Tokens or Access Tokens, they must conform to a namespaced format to avoid possible collisions with standard OIDC claims.

Webtask Editor

Click Save (or hit Ctrl+S/Cmd+S) and close the Editor.

  1. That's it! Now you only need to test your hook. You can find detailed instructions at the Test your Hook paragraph.

Use the Auth0 CLI

Only tenants created prior to 17 July 2018 have access to and the Webtask CLI. If you are an enterprise customer with a newer tenant, please contact your account representative to request access. Other requests can be made through the Auth0 Contact Form and will be evaluated on a case-by-case basis.

  1. Make sure you have installed the Webtask CLI. You can find detailed instructions in the Dashboard's Webtask page.

  2. Create a file with your Node.js code. For our example, we will name the file myrule.js and copy the following code:

module.exports = function(client, scope, audience, context, cb) {
  var access_token = {};
  access_token[''] = 'bar';
  access_token.scope = scope;
  cb(null, access_token);
  1. Create the Webtask. The command is the following:
auth0 create -t credentials-exchange -n client-credentials-exchange-hook -p YOUR_DOMAIN-default file.js

Let's break this down:

  • auth0: The binary to use.
  • create: The sub-command for creating or updating a Hook. Run in your terminal auth0 -h to see the rest.
  • -t credentials-exchange: The hook type. For this use case, set to credentials-exchange.
  • -n client-credentials-exchange-hook: The webtask's name. Set this to your preference, we chose client-credentials-exchange-hook.
  • -p YOUR_DOMAIN-default: Your account's profile name. Get this value from Step 2 of the instructions on the Dashboard's Webtask page.
  • file.js: The name of the file you created in the previous step.

Run the command.

  1. You will see a message that your hook was created, but in disabled state. To enable the hook, run the command:
auth0 enable --profile YOUR_DOMAIN-default client-credentials-exchange-hook

Where client-credentials-exchange-hook is the name of the webtask, and YOUR_DOMAIN-default the name of your profile (the same as the one used in the previous step).

  1. That's it! Now you only need to test your hook. You can find detailed instructions at the Test your Hook paragraph.

Test your Hook

To test the hook you just created you need to run a Client Credentials exchange, get the Access Token, decode it and review its contents.

To get a token, make a POST request at the https://YOUR_DOMAIN/oauth/token API endpoint, with a payload in the following format.

to configure this snippet with your account

curl --request POST \
  --url 'https://YOUR_DOMAIN/oauth/token' \
  --header 'content-type: application/x-www-form-urlencoded' \
  --data grant_type=client_credentials \
  --data 'client_id=YOUR_CLIENT_ID' \
  --data client_secret=YOUR_CLIENT_SECRET \
  --data audience=YOUR_API_IDENTIFIER

to configure this snippet with your account

var client = new RestClient("https://YOUR_DOMAIN/oauth/token");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/x-www-form-urlencoded");
request.AddParameter("application/x-www-form-urlencoded", "grant_type=client_credentials&client_id=%24%7Baccount.clientId%7D&client_secret=YOUR_CLIENT_SECRET&audience=YOUR_API_IDENTIFIER", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);

to configure this snippet with your account

package main

import (

func main() {

	url := "https://YOUR_DOMAIN/oauth/token"

	payload := strings.NewReader("grant_type=client_credentials&client_id=%24%7Baccount.clientId%7D&client_secret=YOUR_CLIENT_SECRET&audience=YOUR_API_IDENTIFIER")

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

	req.Header.Add("content-type", "application/x-www-form-urlencoded")

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

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



to configure this snippet with your account

HttpResponse<String> response ="https://YOUR_DOMAIN/oauth/token")
  .header("content-type", "application/x-www-form-urlencoded")

to configure this snippet with your account

var request = require("request");

var options = {
  method: 'POST',
  url: 'https://YOUR_DOMAIN/oauth/token',
  headers: {'content-type': 'application/x-www-form-urlencoded'},
  form: {
    grant_type: 'client_credentials',
    client_id: 'YOUR_CLIENT_ID',
    client_secret: 'YOUR_CLIENT_SECRET',
    audience: 'YOUR_API_IDENTIFIER'

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


to configure this snippet with your account

#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"content-type": @"application/x-www-form-urlencoded" };

NSMutableData *postData = [[NSMutableData alloc] initWithData:[@"grant_type=client_credentials" dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[@"&client_id=YOUR_CLIENT_ID" dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[@"&client_secret=YOUR_CLIENT_SECRET" dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[@"&audience=YOUR_API_IDENTIFIER" dataUsingEncoding:NSUTF8StringEncoding]];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_DOMAIN/oauth/token"]
[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];

to configure this snippet with your account

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://YOUR_DOMAIN/oauth/token",
  CURLOPT_POSTFIELDS => "grant_type=client_credentials&client_id=%24%7Baccount.clientId%7D&client_secret=YOUR_CLIENT_SECRET&audience=YOUR_API_IDENTIFIER",
    "content-type: application/x-www-form-urlencoded"

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


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

to configure this snippet with your account

import http.client

conn = http.client.HTTPSConnection("")

payload = "grant_type=client_credentials&client_id=%24%7Baccount.clientId%7D&client_secret=YOUR_CLIENT_SECRET&audience=YOUR_API_IDENTIFIER"

headers = { 'content-type': "application/x-www-form-urlencoded" }

conn.request("POST", "/YOUR_DOMAIN/oauth/token", payload, headers)

res = conn.getresponse()
data =


to configure this snippet with your account

require 'uri'
require 'net/http'
require 'openssl'

url = URI("https://YOUR_DOMAIN/oauth/token")

http =, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request =
request["content-type"] = 'application/x-www-form-urlencoded'
request.body = "grant_type=client_credentials&client_id=%24%7Baccount.clientId%7D&client_secret=YOUR_CLIENT_SECRET&audience=YOUR_API_IDENTIFIER"

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

to configure this snippet with your account

import Foundation

let headers = ["content-type": "application/x-www-form-urlencoded"]

let postData = NSMutableData(data: "grant_type=client_credentials".data(using: String.Encoding.utf8)!)
postData.append("&client_id=YOUR_CLIENT_ID".data(using: String.Encoding.utf8)!)
postData.append("&client_secret=YOUR_CLIENT_SECRET".data(using: String.Encoding.utf8)!)
postData.append("&audience=YOUR_API_IDENTIFIER".data(using: String.Encoding.utf8)!)

let request = NSMutableURLRequest(url: NSURL(string: "https://YOUR_DOMAIN/oauth/token")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
  } else {
    let httpResponse = response as? HTTPURLResponse


If you don't know where to find the Client Id, Client Secret, or API Identifier information, refer to Where to Find the IDs.

A successful response will include:

  • an access_token,
  • its expiration time in seconds (expires_in),
  • the token's type set as Bearer (token_type), and
  • an extra scope (scope) (this scope was added by your hook)
HTTP/1.1 200 OK
Content-Type: application/json
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5ESTFNa05DTVRGQlJrVTRORVF6UXpFMk1qZEVNVVEzT1VORk5ESTVSVU5GUXpnM1FrRTFNdyJ9.eyJpc3MiOiJodHRwczovL2RlbW8tYWNjb3VudC5hdXRoMC3jb20vIiwic3ViIjoic0FRSlFpQmYxREw0c2lqSVZCb2pFRUZvcmRoa0o4WUNAY2xpZW50cyIsImF1ZCI6ImRlbW8tYWNjb3VudC5hcGkiLCJleHAiOjE0ODc3NjU8NjYsImlhdCI6MTQ4NzY3OTI2Niwic2NvcGUiOiJyZWFkOmRhdGEgZXh0cmEiLCJodHRwczovL2Zvby5jb20vY2xhaW0iOiKoPXIifQ.da-48mHY_7esfLZpvHWWL8sIH1j_2mUYAB49c-B472lCdsNFvpaLoq6OKQyhnqk9_aW_Xhfkusos3FECTrLFvf-qwQK70QtwbkbVye_IuPSTAYdQ2T-XTzGDm9Nmmy5Iwl9rNYLxVs2OoCdfpVMyda0OaI0AfHBgEdKWluTP67OOnV_dF3KpuwtK3dPKWTCo2j9VCa7X1I4h0CNuM79DHhY2wO7sL8WBej7BSNA3N2TUsp_YTWWfrvsr_vVhJf-32G7w_12ms_PNFUwj2C30ZZIPWc-uEkDztyMLdI-lu9q9TLrLdr0dOhfrtfkdeJx4pUSiHdJHf42kg7UAVK6JcA",
  "expires_in": 86400,
  "scope": "extra",
  "token_type": "Bearer"

Copy the Access Token.

The easiest way to decode it and review its contents is to use the Debugger.

Paste your Access Token at the left-hand editor. Automatically the JWT is decoded and its contents are displayed on the right-hand editor.

Decode Token with

Look into the last two items of the Payload. Both have been set by our hook:

  • "scope": "extra"
  • "": "bar"

Manage your Hooks

You can disable, enable, delete or edit hooks using either the Auth0 CLI or the dashboard. You can also review your logs using the Auth0 CLI. For details, refer to the articles below.

Use the Dashboard to:

Use the Auth0 CLI to:

Webtask Input Parameters

As you saw in our example, the webtask takes five input parameters. You can use these values for your custom logic.

Let's see what each one contains.

  • client (object): Information on the application asking for the token, including the client metadata (a key-value pair that can be set by application). Sample snippet:

      "tenant":  "tenant_name",
      "id": "tenant_id",
      "name": "test_client",
      "metadata": {
        "some_metadata": "value"
  • scope (string array): The scopes available on the API that you have defined.

  • audience (string): The API identifier available via the API settings page.

  • context (object): The contextual information about the request. Sample snippet:

      "ip": "",
      "userAgent": "...",
      "webtask": {
        "secrets": { "FOO": "bar" }
  • cb: The callback. In our example we returned the token (cb(null, access_token)). If you decide, however, not to issue a token, you can return Error (cb(new Error('access denied'))).

Keep reading