# CIMD Client Registration Lambda

Learn how to allow or reject the CIMD clients that register with a CIMD domain.

> For the index of this section of the site, see [llms.txt](https://fusionauth.io/docs/llms.txt)

> **PLAN:** This feature requires [an Enterprise plan](https://fusionauth.io/docs/concepts/plans-and-limits/plans-features.md#enterprise-features).

Available since `1.70.0`

When a [CIMD client](https://fusionauth.io/docs/authentication/oauth/client-registration.md#client-id-metadata-documents) 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](https://fusionauth.io/docs/concepts/types/cimd-domains.md) 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](https://fusionauth.io/docs/security/clients/cimd-security.md#limit-who-can-register).

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_id` and `client_uri` URLs
*   the scope and token settings FusionAuth will apply by default, taken from the CIMD domain
*   the `resource` the 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 `resource` that 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 `resource` during 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:

```javascript
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:

```javascript
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](https://fusionauth.io/docs/authentication/oauth/cimd-registration-lifecycle.md#a-new-resource-inside-the-cache-window).

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](https://fusionauth.io/docs/authentication/oauth/cimd-registration-lifecycle.md#when-a-registration-is-refreshed).

`clientMetadata` is an immutable argument of type `ClientMetadata`, with the following structure:

```javascript
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:

```javascript
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](https://fusionauth.io/docs/lambdas/login/mfa-requirement.md#lambda-structure) page. For more information on authentication threats, see the [Advanced Threat Detection](https://fusionauth.io/docs/security/account-protection/advanced-threat-detection.md) 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 `decision` to a value other than `ALLOW`, `DENY`, or `REVOKE`. Matching is case-sensitive, but FusionAuth ignores surrounding whitespace.
*   The lambda sets `decision` to `null` or deletes it.
*   The lambda sets `scopes` or `providedScopePolicy` to `null`. To remove every custom scope, set `scopes` to an empty array instead.
*   The lambda sets `jwtPopulateLambdaId` to 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](https://fusionauth.io/docs/events/reference/cimd/cimd-registration-failed.md) 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](https://fusionauth.io/docs/apis/cimd-domains.md), 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:

```javascript
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:

```javascript
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](#when-the-lambda-runs):

```javascript
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](https://fusionauth.io/docs/security/account-protection/advanced-threat-detection.md) feature:

```javascript
function evaluate(result, clientMetadata, context) {
  if (context.authenticationThreats && context.authenticationThreats.includes('BotDetected')) {
    result.decision = 'DENY';
  }
}
```

Related

[Authentication CIMD Registration Caching And Lifecycle 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. cimdclient id metadata documentoauth client registration](https://fusionauth.io/docs/authentication/oauth/cimd-registration-lifecycle.md)

[Authentication OAuth Client Registration | Static and CIMD Learn how FusionAuth registers OAuth clients, either ahead of time as Applications or just-in-time from a Client ID Metadata Document. cimdclient id metadata documentmcp](https://fusionauth.io/docs/authentication/oauth/client-registration.md)

[Authentication Host A Client ID Metadata Document (CIMD) Publish a Client ID Metadata Document so your OAuth client can log in to FusionAuth without being registered ahead of time. cimdclient id metadata documentoauth client registration](https://fusionauth.io/docs/authentication/oauth/host-cimd-document.md)

[Security CIMD Security Learn how FusionAuth protects itself when it fetches a Client ID Metadata Document from a URL a third party controls. cimdclient id metadata documentoauth client registration](https://fusionauth.io/docs/security/clients/cimd-security.md)
---

## Other pages in Login

> For the full index of this section, see [Lambdas](https://fusionauth.io/docs/llms-lambdas.txt).

- [Login Validation Lambda](https://fusionauth.io/docs/lambdas/login/login-validation.md): Learn how to add your own validation to a login and reject logins that fail it.
- [MFA Requirement Lambda](https://fusionauth.io/docs/lambdas/login/mfa-requirement.md): Learn how to decide with custom logic when FusionAuth challenges a user for MFA.
- [Self-Service Registration Validation Lambda](https://fusionauth.io/docs/lambdas/login/self-service-registration.md): Learn how to validate the fields of an advanced registration form with custom logic.
