Customize Tokens Using Hooks with Client Credentials Flow

You can change scopes and add custom claims in the tokens issued through the Client Credentials Flow by adding Hooks.

Hooks allow you to customize the behavior of Auth0 using Node.js code. They are secure, self-contained functions associated with specific extensibility points of the Auth0 platform (like the Client Credentials flow). Auth0 invokes the Hooks at runtime to execute your custom logic.

You can manage Hooks using the Auth0 Dashboard or the Management API.

Although you may create multiple hooks for any given extensibility point, each extensibility point may have only one enabled hook at a time. Any subsequent hooks you create for that extensibility point are automatically disabled, so you must explicitly enable them. The enabled hook will be executed for all applications and APIs.

Prerequisites

Before beginning this tutorial, you must:

Steps

  1. Create Hook: Create a hook that will customize your token.

  2. Test Hook: Test your new hook by running a Client Credentials Flow and decoding the access token.

Create a Hook

In this example, you will:

  • add an arbitrary claim (https://foo.com/claim) to the Access Token

  • add an extra permission to your configured API

Create a Hook to customize your token. When asked to choose an extensibility point, select Client Credentials Exchange, and add the following code 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);
};

Auth0 returns profile information in a structured claim format as defined by the OpenID Connect (OIDC) specification. This means that custom claims added to ID Tokens or Access Tokens must conform to a namespaced format to avoid possible collisions with standard OIDC claims.

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.

Get token

To get a token, make a POST call to the Client Credentials Flow endpoint. Be sure to replace CLIENT_ID, CLIENT_SECRET, and API_IDENTIFIER placeholder values with your application's Client ID, your application's Client Secret, and your API's Identifier, respectively. You can find the Client ID and Client Secret in your Application settings and the API Identifier in your API settings.


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=CLIENT_SECRET \
  --data audience=API_IDENTIFIER
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=CLIENT_SECRET&audience=API_IDENTIFIER", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
package main

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

func main() {

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

	payload := strings.NewReader("grant_type=client_credentials&client_id=%24%7Baccount.clientId%7D&client_secret=CLIENT_SECRET&audience=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)

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

}
HttpResponse<String> response = Unirest.post("https://YOUR_DOMAIN/oauth/token")
  .header("content-type", "application/x-www-form-urlencoded")
  .body("grant_type=client_credentials&client_id=%24%7Baccount.clientId%7D&client_secret=CLIENT_SECRET&audience=API_IDENTIFIER")
  .asString();
var axios = require("axios").default;

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

axios.request(options).then(function (response) {
  console.log(response.data);
}).catch(function (error) {
  console.error(error);
});
#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=CLIENT_SECRET" dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:[@"&audience=API_IDENTIFIER" dataUsingEncoding:NSUTF8StringEncoding]];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_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, [
  CURLOPT_URL => "https://YOUR_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=%24%7Baccount.clientId%7D&client_secret=CLIENT_SECRET&audience=API_IDENTIFIER",
  CURLOPT_HTTPHEADER => [
    "content-type: application/x-www-form-urlencoded"
  ],
]);

$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=%24%7Baccount.clientId%7D&client_secret=CLIENT_SECRET&audience=API_IDENTIFIER"

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

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

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

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

url = URI("https://YOUR_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/x-www-form-urlencoded'
request.body = "grant_type=client_credentials&client_id=%24%7Baccount.clientId%7D&client_secret=CLIENT_SECRET&audience=API_IDENTIFIER"

response = http.request(request)
puts response.read_body
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=CLIENT_SECRET".data(using: String.Encoding.utf8)!)
postData.append("&audience=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) {
    print(error)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()

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 permission (scope) (which 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"
}

Decode token

The easiest way to decode the access token and review its contents is to use the JWT.io Debugger.

Copy your access token and paste it into the editor. The JWT is decoded automatically and its contents are displayed.

Note that the last two items of the Payload have both been set by your hook:

  • "scope": "extra"

  • "https://foo.com/claim": "bar"

Learn more