Using Hooks with Client Credentials Grant

Heads up! As part of our efforts to improve security and standards-based interoperability, we have implemented several new features in our authentication flows and made changes to existing ones. For an overview of these changes, and details on how you adopt them, refer to Introducing OIDC Conformant Authentication.

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.

Overview

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 <strong>only one can be enabled</strong>. The enabled hook will then be executed for <strong>all</strong> clients 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 https://foo.com/claim 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['https://foo.com/claim'] = 'bar';
  access_token.scope = scope;
  access_token.scope.push('extra');
  cb(null, access_token);
};

This sample hook will:

  • add an arbitrary claim (https://foo.com/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 client applications, Auth0 now returns profile information in a structured claim format as defined by the 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. For example, if you choose the namespace https://foo.com/ and you want to add a custom claim named claim, you would name the claim https://foo.com/claim, instead of just claim.

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

  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['https://foo.com/claim'] = 'bar';
  access_token.scope = scope;
  access_token.scope.push('extra');
  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_AUTH0_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_AUTH0_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_AUTH0_DOMAIN-default client-credentials-exchange-hook

Where client-credentials-exchange-hook is the name of the webtask, and YOUR_AUTH0_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_AUTH0_DOMAIN/oauth/token API endpoint, with a payload in the following format.


curl --request POST \
  --url 'https://YOUR_AUTH0_DOMAIN/oauth/token' \
  --header 'content-type: application/json' \
  --data '{"grant_type":"client_credentials","client_id": "YOUR_CLIENT_ID","client_secret": "YOUR_CLIENT_SECRET","audience": "YOUR_API_IDENTIFIER"}'
var client = new RestClient("https://YOUR_AUTH0_DOMAIN/oauth/token");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/json");
request.AddParameter("application/json", "{\"grant_type\":\"client_credentials\",\"client_id\": \"YOUR_CLIENT_ID\",\"client_secret\": \"YOUR_CLIENT_SECRET\",\"audience\": \"YOUR_API_IDENTIFIER\"}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
package main

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

func main() {

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

	payload := strings.NewReader("{\"grant_type\":\"client_credentials\",\"client_id\": \"YOUR_CLIENT_ID\",\"client_secret\": \"YOUR_CLIENT_SECRET\",\"audience\": \"YOUR_API_IDENTIFIER\"}")

	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/oauth/token")
  .header("content-type", "application/json")
  .body("{\"grant_type\":\"client_credentials\",\"client_id\": \"YOUR_CLIENT_ID\",\"client_secret\": \"YOUR_CLIENT_SECRET\",\"audience\": \"YOUR_API_IDENTIFIER\"}")
  .asString();
var settings = {
  "async": true,
  "crossDomain": true,
  "url": "https://YOUR_AUTH0_DOMAIN/oauth/token",
  "method": "POST",
  "headers": {
    "content-type": "application/json"
  },
  "processData": false,
  "data": "{\"grant_type\":\"client_credentials\",\"client_id\": \"YOUR_CLIENT_ID\",\"client_secret\": \"YOUR_CLIENT_SECRET\",\"audience\": \"YOUR_API_IDENTIFIER\"}"
}

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

var options = { method: 'POST',
  url: 'https://YOUR_AUTH0_DOMAIN/oauth/token',
  headers: { 'content-type': 'application/json' },
  body: 
   { grant_type: 'client_credentials',
     client_id: 'YOUR_CLIENT_ID',
     client_secret: 'YOUR_CLIENT_SECRET',
     audience: 'YOUR_API_IDENTIFIER' },
  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 = @{ @"grant_type": @"client_credentials",
                              @"client_id": @"YOUR_CLIENT_ID",
                              @"client_secret": @"YOUR_CLIENT_SECRET",
                              @"audience": @"YOUR_API_IDENTIFIER" };

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

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_AUTH0_DOMAIN/oauth/token"]
                                                       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/oauth/token",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => "{\"grant_type\":\"client_credentials\",\"client_id\": \"YOUR_CLIENT_ID\",\"client_secret\": \"YOUR_CLIENT_SECRET\",\"audience\": \"YOUR_API_IDENTIFIER\"}",
  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 = "{\"grant_type\":\"client_credentials\",\"client_id\": \"YOUR_CLIENT_ID\",\"client_secret\": \"YOUR_CLIENT_SECRET\",\"audience\": \"YOUR_API_IDENTIFIER\"}"

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

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

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

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

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

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 = "{\"grant_type\":\"client_credentials\",\"client_id\": \"YOUR_CLIENT_ID\",\"client_secret\": \"YOUR_CLIENT_SECRET\",\"audience\": \"YOUR_API_IDENTIFIER\"}"

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

let headers = ["content-type": "application/json"]
let parameters = [
  "grant_type": "client_credentials",
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET",
  "audience": "YOUR_API_IDENTIFIER"
]

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

var request = NSMutableURLRequest(URL: NSURL(string: "https://YOUR_AUTH0_DOMAIN/oauth/token")!,
                                        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()

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 JWT.io 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 JWT.io

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

  • "scope": "extra"
  • "https://foo.com/claim": "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 client asking for the token, including the client metadata (a key-value pair that can be set by client). 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": "123.123.123.123",
      "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'))).

Read more

What are Hooks and how you can work with them

Overview of the Client Credentials Grant

How to set up a Client Credentials Grant using the Dashboard

How to set up a Client Credentials Grant using the Management API

How to execute a Client Credentials Grant