CIMD Client Registration Lambda
On this page
This feature requires an Enterprise plan.
1.70.0
When a CIMD client logs in, FusionAuth fetches the client's metadata document and registers the client just-in-time. Using this lambda function, you can allow or reject that registration, change the scopes and token settings the client gets, or revoke a client that is already registered.
A registration lambda is the only way to filter which clients may register when a CIMD domain uses the * wildcard. Without a lambda that rejects clients you don't recognize, a domain that uses the * wildcard accepts every client that publishes a valid metadata document. See CIMD Security.
The CIMD client registration lambda has access to the following information:
- the client's metadata document, along with the host and path of its
client_idandclient_uriURLs - the scope and token settings FusionAuth will apply by default, taken from the CIMD domain
- the
resourcethe client requested - metadata about the request, such as the IP address and user agent
- authentication threats detected for the request, when Advanced Threat Detection is enabled
The lambda doesn't receive the user.
You can use Lambda HTTP Connect to access external resources from this lambda, but you should use external resources sparingly. The lambda runs during the user's login, so any additional latency will impact users.
When The Lambda Runs#
FusionAuth runs the registration lambda after the user logs in, in the following cases:
- A client registers through the domain for the first time.
- A user logs in after the client's cached metadata document has expired, and FusionAuth fetches the document again.
- A registered client requests a
resourcethat the lambda hasn't evaluated since the document was last fetched. This can happen during a login or a refresh token request. Each fetch clears the decisions for every other resource, so after a fetch the lambda runs again the first time the client uses each resource. - A registered client requests a new
resourceduring the domain's Grace period, after a fetch of the document has failed. In this case, the lambda evaluates the cached metadata.
The lambda doesn't run while the client's cached document is still valid and the lambda has already evaluated the requested resource. It also doesn't run when a resource server validates a token issued to the client.
Lambda Structure#
All lambda implementations must contain a function with the following signature:
function evaluate(result, clientMetadata, context) {
// Lambda code goes here
}
The evaluate function must accept the following parameters:
result- an object used to return the registration decision to FusionAuth. FusionAuth fills it in with the domain's settings before calling the lambda.clientMetadata- the read-only metadata from the client's document.context- a read-only object containing context for the current request.
result is of type ClientRegistrationLambdaResult, with the following structure:
class ClientRegistrationLambdaResult {
/**
* The registration decision. One of ALLOW, DENY, or REVOKE. Defaults to ALLOW.
* DENY rejects the request. FusionAuth stores nothing, and an existing
* registration and its refresh tokens are left unchanged.
* REVOKE deletes the client's registration and its refresh tokens.
* When the client is registering for the first time, REVOKE rejects the request.
* @type {string}
*/
decision;
/**
* The Id of the JWT populate lambda that populates the access token and Id token issued to this client.
* Defaults to the domain's JWT populate lambda. Must be the Id of a JWT populate lambda.
* Only applies when FusionAuth fetches the document; ignored when the lambda evaluates a new resource.
* @type {string}
*/
jwtPopulateLambdaId;
/**
* Controls the standard OpenID Connect scopes for this client. Defaults to the domain's provided scope policy.
* @type {ProvidedScopePolicy}
*/
providedScopePolicy;
/**
* The custom OAuth scopes this client may request. Defaults to the domain's custom scopes.
* Remove entries to narrow what the client may request, or add entries to grant scopes the domain doesn't define.
* @type {Array<Scope>}
*/
scopes;
/**
* How long, in seconds, FusionAuth caches the client's metadata document.
* Defaults to the value FusionAuth derived from the document's Cache-Control header and the domain's settings.
* FusionAuth limits the value to between 900 seconds and the domain's maximum cache duration.
* Only applies when FusionAuth fetches the document; ignored when the lambda evaluates a new resource.
* @type {number}
*/
ttlInSeconds;
}
class ProvidedScopePolicy {
/**
* Each property contains two booleans: `enabled` allows the client to request the scope,
* and `required` means the user must consent to the scope to complete authorization.
* @type {{enabled: boolean, required: boolean}}
*/
address;
email;
phone;
profile;
}
class Scope {
/**
* The name of the scope, which the client uses to request it.
* @type {string}
*/
name;
/**
* A description of the scope for internal use.
* @type {string}
*/
description;
/**
* The default detail to display on the consent screen if one cannot be found in the theme.
* @type {string}
*/
defaultConsentDetail;
/**
* The default message to display on the consent screen if one cannot be found in the theme.
* @type {string}
*/
defaultConsentMessage;
/**
* Whether the user must consent to this scope to complete authorization.
* @type {boolean}
*/
required;
}
How FusionAuth Uses Each Result Property#
Some properties apply to a single resource and others apply to the client's whole registration. FusionAuth stores the scopes and providedScopePolicy decisions separately for each resource the client uses. The ttlInSeconds and jwtPopulateLambdaId decisions apply to the whole registration.
| Property | Applies to | When FusionAuth uses it |
|---|---|---|
decision |
The current request, or the whole registration for REVOKE |
Every time the lambda runs. DENY rejects the request and leaves any existing registration unchanged. REVOKE deletes the registration and its refresh tokens. |
scopes |
The requested resource |
Every time the lambda runs. FusionAuth saves the result for that resource. |
providedScopePolicy |
The requested resource |
Every time the lambda runs. FusionAuth saves the result for that resource. |
ttlInSeconds |
The whole registration | Only when FusionAuth fetches the document. |
jwtPopulateLambdaId |
The whole registration | Only when FusionAuth fetches the document. |
Because of this, the lambda can't change ttlInSeconds or jwtPopulateLambdaId when it runs for a new resource inside the cache window. In that case, FusionAuth ignores both, and the registration keeps the values it got at the last fetch. The lambda's scopes and providedScopePolicy still apply to the new resource. For more on the cache window, see CIMD Registration Caching And Lifecycle.
FusionAuth recalculates a resource's scopes and providedScopePolicy the first time the client uses that resource after each fetch of the document, because a fetch clears them. FusionAuth fetches the document at the first login after the cache window ends. See When A Registration Is Refreshed.
clientMetadata is an immutable argument of type ClientMetadata, with the following structure:
class ClientMetadata {
/**
* The client's client_id, which is the URL of its metadata document.
* @type {string}
*/
clientId;
/**
* The host of the client_id URL, such as `app.example.com`.
* @type {string}
*/
clientIdHost;
/**
* The path of the client_id URL, such as `/oauth/client.json`.
* @type {string}
*/
clientIdPath;
/**
* The client_name from the document.
* @type {string}
*/
clientName;
/**
* The client_uri from the document. This is an optional value and may be null.
* @type {string}
*/
clientUri;
/**
* The host of the client_uri URL. This is an optional value and may be null.
* @type {string}
*/
clientUriHost;
/**
* The path of the client_uri URL. This is an optional value and may be null.
* @type {string}
*/
clientUriPath;
/**
* The redirect_uris from the document.
* @type {Array<string>}
*/
redirectUris;
}
context is an immutable argument of type Context, with the following structure:
class Context {
/**
* A list of authentication threats detected for this request. This will only be populated when
* Advanced Threat Detection is enabled.
* @type {Set<String>} - Possible values are [BlocklistedIp], [BotDetected], [DormantAccount], [DormantPassword],
* [ImpossibleTravel], [RecentIdentityChange], [RecentPasswordChange], [SuspiciousUserAgent], [UnrecognizedDevice],
* or [UntrustedDevice].
*/
authenticationThreats;
/**
* Event information about the current request. This is an optional value and may be null.
* @type {EventInfo} - See the MFA requirement lambda for details
*/
eventInfo;
/**
* The resource the client requested, which is the value of the `resource` request parameter.
* @type {string}
*/
resource;
/**
* Provides access to services available to the lambda, including secrets.
* Use `context.services.secrets.get('<key>')` to retrieve a secret value.
* @type {Services}
*/
services;
}
For the fields of eventInfo, see the EventInfo class on the MFA requirement lambda page. For more information on authentication threats, see the Advanced Threat Detection documentation.
Failure Handling#
The lambda fails closed. FusionAuth rejects the client when any of the following happens:
- The lambda throws an error. FusionAuth writes the error to the Event Log.
- The lambda sets
decisionto a value other thanALLOW,DENY, orREVOKE. Matching is case-sensitive, but FusionAuth ignores surrounding whitespace. - The lambda sets
decisiontonullor deletes it. - The lambda sets
scopesorprovidedScopePolicytonull. To remove every custom scope, setscopesto an empty array instead. - The lambda sets
jwtPopulateLambdaIdto an Id that doesn't belong to a JWT populate lambda.
Whenever the lambda rejects a client, on purpose or because it failed, FusionAuth writes an Error entry to the Event Log and sends a CIMD Registration Failed event with the reason lambdaRejected. Neither one says why the lambda rejected the client. To record the reason, call console.info from the lambda, which writes an Event Log entry of type Information.
Assigning The Lambda#
Once a lambda is created, you must assign it to a CIMD domain. Using the FusionAuth admin UI, edit the CIMD domain and find the Registration lambda field on the Lambdas tab. The field only lists lambdas of type CIMD client registration.
Or if you are using the CIMD Domain API, assign the lambda Id to the domain.registrationLambdaId field.
Example Lambdas#
The following example allows only clients hosted on claude.ai or chatgpt.com, which is useful on a CIMD domain that uses the * wildcard:
function evaluate(result, clientMetadata, context) {
var allowedHosts = ['claude.ai', 'chatgpt.com'];
if (!allowedHosts.includes(clientMetadata.clientIdHost)) {
result.decision = 'DENY';
console.info('Rejected CIMD client ' + clientMetadata.clientId);
}
}
The following example narrows what one client may request by removing a custom scope and disabling the phone scope:
function evaluate(result, clientMetadata, context) {
if (clientMetadata.clientIdHost === 'chatgpt.com') {
result.scopes = result.scopes.filter(function(scope) {
return scope.name !== 'reports:write';
});
result.providedScopePolicy.phone.enabled = false;
}
}
The following example revokes a specific client. FusionAuth deletes the client's registration and refresh tokens the next time the lambda runs for that client, as described in When The Lambda Runs:
function evaluate(result, clientMetadata, context) {
var revokedClientIds = ['https://app.example.com/oauth/client.json'];
if (revokedClientIds.includes(clientMetadata.clientId)) {
result.decision = 'REVOKE';
}
}
The following example rejects registrations from requests that look automated. This example assumes you are licensed for the Advanced Threat Detection feature:
function evaluate(result, clientMetadata, context) {
if (context.authenticationThreats && context.authenticationThreats.includes('BotDetected')) {
result.decision = 'DENY';
}
}
Related
Learn how long FusionAuth caches a Client ID Metadata Document, what happens when it expires, and how a CIMD client registration is refreshed or removed.
Learn how FusionAuth registers OAuth clients, either ahead of time as Applications or just-in-time from a Client ID Metadata Document.
Publish a Client ID Metadata Document so your OAuth client can log in to FusionAuth without being registered ahead of time.
Learn how FusionAuth protects itself when it fetches a Client ID Metadata Document from a URL a third party controls.