Client Credentials Exchange
At the Client Credentials Exchange extensibility point, Hooks let you execute custom actions when an Access Token is issued through the Authentication API POST /oauth/token
endpoint using the Client Credentials Flow. For example, you may deny the token from being issued, add custom claims to the access token, or modify its scopes. To learn more, read Client Credentials Flow.
Hooks at this extensibility point are blocking (synchronous), which means they execute as part of the trigger's process and prevent the rest of the Auth0 pipeline from running until the Hook is complete.
To learn about other extensibility points, read Extensibility Points.
Starter code and parameters
When creating a Hook executed at the Client Credentials Exchange extensibility point, you may find the following starter code helpful. Parameters that can be passed into and used by the Hook function are listed at the top of the code sample.
/**
@param {object} client - client information
@param {string} client.name - client name
@param {string} client.id - client ID
@param {string} client.tenant - Auth0 tenant name
@param {object} client.metadata - client metadata
@param {array|undefined} scope - either an array of strings representing the token's scope claim, or undefined
@param {string} audience - token's audience claim
@param {object} context - Auth0 context info
@param {object} context.webtask - Hook (webtask) context
@param {function} cb - function (error, accessTokenClaims)
*/
module.exports = function(client, scope, audience, context, cb) {
var access_token = {};
access_token.scope = scope; // do not remove this line
// Modify scopes or add extra claims
// access_token['https://example.com/claim'] = 'bar';
// access_token.scope.push('extra');
// Deny the token and respond with an OAuth2 error response
// if (denyExchange) {
// // To return an HTTP 400 with { "error": "invalid_scope", "error_description": "Not authorized for this scope." }
// return cb(new InvalidScopeError('Not authorized for this scope.'));
//
// // To return an HTTP 400 with { "error": "invalid_request", "error_description": "Not a valid request." }
// return cb(new InvalidRequestError('Not a valid request.'));
//
// // To return an HTTP 500 with { "error": "server_error", "error_description": "A server error occurred." }
// return cb(new ServerError('A server error occurred.'));
// }
cb(null, access_token);
};
Was this helpful?
Please note:
The callback function (
cb
) at the end of the sample code signals completion and must be included.
The line
access_token.scope = scope
ensures that all granted scopes will be present in the access token. Removing it will reset all scopes, and the token will include only scopes you add with the script.
Default response
When you run a Hook executed at the Client Credentials Exchange extensibility point, the default response object is:
{
"scope": "array of strings"
}
Was this helpful?
Starter code response
Once you've customized the starter code with your scopes and additional claims, you can test the Hook using the runner embedded in the Hook Editor. The runner simulates a call to the Hook with the same body and response that you would get with a Client Credentials Exchange.
When you run a Hook based on the starter code, the response object is:
{
"audience": "https://my-tenant.auth0.com/api/v2/",
"client": {
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "client-name",
"tenant": "my-tenant",
"metadata": {
"plan": "full"
}
},
"scope": [
"read:connections"
]
}
Was this helpful?
Sample script: Add an additional scope to the access token
In this example, we use a Hook to add an additional scope to those already existing for the access token.
module.exports = function(client, scope, audience, context, cb) {
// Scopes to be added
var access_token = {};
// Get the scope that's currently on the Access Token
// and add it to the object we're working with
// Do not remove this line!
access_token.scope = scope;
// Append the `read:resource` scope
access_token.scope.push('read:resource');
// Callback to indicate completion and to return new
// array of scopes
cb(null, access_token);
};
Was this helpful?
To learn more, read Scopes.
Response
When we run this Hook, the response object is:
{
"scope": [
"read:connections",
"read:resource"
]
}
Was this helpful?
Sample script: Add a claim to the access token
In this example, we add a namespaced custom claim and its value to the access token. To learn more, read Create Namespaced Custom Claims.
You can add the following as claims to the issued token:
The
scope
property of the response objectAny properties with namespaced property names
The extensibility point ignores all other response object properties.
module.exports = function(client, scope, audience, context, cb) {
// Claims to be added
var access_token = {};
// New claim to add to the token
access_token['https://example.com/foo'] = 'bar';
// Callback to indicate completion and to return new claim
cb(null, access_token);
};
Was this helpful?
Response
When we run this Hook, the response object is:
{
"https://example.com/foo": "bar"
}
Was this helpful?
Sample script: Raise an error or deny an access token
In this example, we use custom Error objects to generate OAuth2 Error Responses. (To learn more, see OAuth2 RFC - Section 5.2 in the IETF Datatracker.)
If a plain JavaScript error is returned in the callback, such as:
module.exports = function(client, scope, audience, context, cb) {
// Callback to indicate completion and to return new claim
cb(new Error("Unknown error occurred.");
};
Was this helpful?
Then when you request a client_credentials
grant from the /oauth/token
endpoint, Auth0 will respond with:
HTTP 500
{ "error": "server_error", "error_description": "Unknown error occurred." }
Was this helpful?
However, if you like additional control over the OAuth2 Error Response, three custom Error objects are available to use instead.
InvalidScopeError
module.exports = function(client, scope, audience, context, cb) {
const invalidScope = ...; // determine if scope is valid
if(invalidScope) {
cb(new InvalidScopeError("Scope is not permitted."));
}
};
Was this helpful?
Then when you request a client_credentials
grant is from the /oauth/token
endpoint, Auth0 responds with:
HTTP 400
{ "error": "invalid_scope", "error_description": "Scope is not permitted." }
Was this helpful?
InvalidRequestError
module.exports = function(client, scope, audience, context, cb) {
const invalidRequest = ...; // determine if request is valid
if(invalidRequest) {
cb(new InvalidRequestError("Bad request."));
}
};
Was this helpful?
Then when you request a client_credentials
grant from the /oauth/token
endpoint, Auth0 will respond with:
HTTP 400
{ "error": "invalid_request", "error_description": "Bad request." }
Was this helpful?
ServerError
module.exports = function(client, scope, audience, context, cb) {
callOtherService(function(err, response) {
if(err) {
return cb(new ServerError("Error calling remote system: " + err.message));
}
});
};
Was this helpful?
Then when you request a client_credentials
grant from the /oauth/token
endpoint, Auth0 responds with:
HTTP 400
{ "error": "server_error", "error_description": "Error calling remote system: ..." }
Was this helpful?