# Client authentication

Documentation owner: product `access`; documentation version: `next`.
Source: `content/products/access/next/modules/oidc/token-endpoint/client-authentication.mdx` at commit `eaf7a020437971baeee95e87eaf963636040ac71`.

Access authenticates a client before processing its token request. When Access acts as the OpenID Provider (OP), configure the allowed methods and the client's verification keys on its [Relying Party](/products/access/next/modules/oidc/relying-party/) configuration.

## Choose allowed methods

`token_endpoint_auth_methods` is an optional, non-empty list. Supported methods are `client_secret_basic`, `client_secret_post`, `client_secret_jwt` and `private_key_jwt`.

When the list is omitted, existing Basic/POST authentication and public authorization-code clients using PKCE retain their existing behavior. An explicit list requires one of those authentication methods even when the request includes a PKCE verifier. Authentication never replaces grant validation, redirect URI validation or PKCE validation.

Requests must use one authentication method. Do not send an `Authorization` header or `client_secret` together with a JWT assertion. Do not combine Basic authentication with a POST secret. Duplicate authentication parameters are rejected.

The initial JWT profile supports HS256 for `client_secret_jwt` and RS256 for `private_key_jwt`. `token_endpoint_auth_signing_alg_values` defaults to the algorithms required by the selected methods. Other algorithms are rejected.

## Configure a JWT method

### Shared secret

`client_secret_jwt` verifies the assertion using the client's shared secret. Use a randomly generated secret of at least 32 UTF-8 bytes. Protect the secret using the existing Access secret configuration.

```json
{
  "client_id": "service-client",
  "client_secret": "${secrets.service_client_secret}",
  "token_endpoint_auth_methods": ["client_secret_jwt"],
  "token_endpoint_auth_signing_alg_values": ["HS256"]
}
```

### Keystore certificates

For `private_key_jwt`, `verify_ref` is a list of certificate references. Each entry requires a unique external `kid`, a keystore path and a certificate alias. RSA keys must have at least 2048 bits. Certificates are checked for validity when an assertion is verified.

The alias must select a trusted certificate entry containing the client's public key. Private-key entries are rejected. `kid` matches the identifier used by the client in its JWT header; it is independent of the alias. Access does not need the client's private key or a `key_password`.

```json
{
  "client_id": "service-client",
  "token_endpoint_auth_methods": ["private_key_jwt"],
  "verify_ref": [
    {
      "kid": "client-2026-current",
      "keystore": {
        "path": "${system.customer_home}/resources/client-certificates.p12",
        "password": "${secrets.client_keystore_password}",
        "alias": "current"
      }
    },
    {
      "kid": "client-2026-next",
      "keystore": {
        "path": "${system.customer_home}/resources/client-certificates.p12",
        "password": "${secrets.client_keystore_password}",
        "alias": "next"
      }
    }
  ]
}
```

### Remote JWKS

Configure `jwks_uri` to retrieve the client's public keys. The URL must use HTTPS without credentials or a fragment. Access verifies the TLS certificate and hostname and does not follow redirects. The endpoint must return public asymmetric keys; private or symmetric material is rejected.

```json
{
  "client_id": "service-client",
  "token_endpoint_auth_methods": ["private_key_jwt"],
  "jwks_uri": "https://client.example.org/.well-known/jwks.json"
}
```

Use exactly one of `verify_ref` and `jwks_uri`. There is no fallback between them. A private-key-only client does not need `client_secret`.

These keys verify assertions from the client. The OP's own [signing keys](/products/access/next/modules/oidc/signing-keys/), configured through `sign_ref`, continue to sign tokens issued by Access.

## Send an assertion

Send a form-encoded POST to the advertised token endpoint with the normal grant parameters and:

* `client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer`
* `client_assertion=<signed JWT>`

The JWT must contain:

* `iss` and `sub`: both equal the configured `client_id`. If `client_id` is also sent as a form parameter, it must match.
* `aud`: contains the exact externally advertised token endpoint URL. Proxy headers do not change the expected audience.
* `exp`: expires within five minutes. Access allows 30 seconds of clock skew around expiry.
* `jti`: a unique, non-empty identifier of at most 256 characters. Generate a new identifier for every request, including retries.

Optional `iat` and `nbf` must not be more than 30 seconds in the future. If `iat` is supplied, the interval from `iat` to `exp` must not exceed five minutes. Use `kid` for asymmetric signatures; omission is allowed only when exactly one usable verification key exists. Headers that supply keys or URLs (`jwk`, `jku`, `x5c`, `x5u`) and unsupported critical headers are rejected.

JWT authentication is available for authorization code, refresh token, client credentials and device code requests at the token endpoint. It does not introduce a JWT bearer grant and does not change authentication on introspection or device authorization endpoints.

## Cache and rotate remote keys

Access fetches keys on first use, shares concurrent fetches for the same registered URL within an OP, and caches successful results for five minutes. After the first fetch, background refresh starts shortly before cache expiry. Normal requests using a known key read the cache.

An unknown `kid` can trigger an earlier refresh. Fetches for a URL are limited to one attempt per 30 seconds, including failed attempts. During an outage, already cached keys remain usable only until their existing expiry. Expired keys are never accepted. Fetches have a five-second deadline, a 256 KiB response limit and a maximum of 32 keys.

Publish both the current and next public keys before switching the client's signing key. Allow time for the cache to refresh. Retain the previous key while assertions signed with it can still arrive. For `verify_ref`, configure both entries and apply the updated configuration before switching the client; remove the old reference after the overlap period.

Only public network destinations are allowed by default. If a client hosts JWKS on a private network, explicitly allow its exact hostname on the OP:

```json
{
  "client_assertion": {
    "jwks_private_hosts": ["client.internal.example.org"]
  }
}
```

This permission does not disable TLS verification. JVM trust configuration must trust the endpoint's certificate chain.

## Configure replay protection

Access reserves a replay marker after validating the signature and claims and before continuing the grant. The marker is scoped by OP issuer, client ID and `jti`, and retained until the assertion expiry plus clock skew, subject to the selected store's restart behavior. Storage failures reject authentication.

### Memory (default)

For one Access node, replay protection uses process-local memory by default. No replay storage configuration or directory is required. You can also select it explicitly with `client_assertion.replay_store.type` set to `memory`.

Markers are shared across providers in the same JVM and survive configuration reloads. They are lost when Access restarts, so a previously used assertion can be accepted again after restart if it is still valid. Choose `file` or `jdbc` when replay protection must survive restarts.

The memory store holds at most 100,000 active markers across the process. Expired markers are removed on subsequent requests. When full, it rejects new assertions until space becomes available; it never evicts an unexpired marker to accept a new assertion.

### Persistent directory (one node)

Select `file` explicitly to retain markers across restarts:

```json
{
  "client_assertion": {
    "replay_store": {
      "type": "file"
    }
  }
}
```

The file store uses `${system.customer_home}/data/oidc-client-assertions`. An optional `client_assertion.replay_store.directory` overrides it with an absolute path. The directory must be writable and retained across restarts and configuration reloads. Do not delete it while assertions may still be valid. It stores hashed identifiers and expiry times, with a capacity of 100,000 active markers per directory.

### Shared database (multiple nodes)

For multiple nodes serving the same OP, configure all nodes with the same issuer and shared JDBC destination:

```json
{
  "client_assertion": {
    "replay_store": {
      "type": "jdbc",
      "destination": "shared-oidc-database"
    }
  }
}
```

The destination refers to an existing platform JDBC configuration. `schema` optionally selects an existing database schema. Access creates `fid_oidc_client_assertion` if it does not exist. The database account needs create-table permission for initial setup, then select, insert and delete permissions. A primary key arbitrates concurrent requests across nodes. Expired entries are cleaned up periodically. Clustered Vert.x deployments reject memory and file modes; independently deployed Access nodes must also use shared JDBC storage.

## Configure through Management Center

Deploy matching Access, management-access and Management Center builds that support this configuration before enabling JWT authentication. Older Access builds do not enforce the new policy fields. Do not roll back only Access while leaving a JWT policy active; restore a previously approved client configuration and matching component versions together.

The provider's **Client authentication** settings are used only for RPs that enable `client_secret_jwt` or `private_key_jwt`. Configure allowed methods and verification keys on each RP.

1. Open the relying party and select **Client authentication**.
2. Select **Choose allowed methods**, then the required methods. Existing configurations initially show their existing behavior.
3. For shared-secret methods, enter the client secret. For `private_key_jwt`, choose **Keystore certificates** or **Remote JWKS URL**. Add overlapping certificate references with their external key IDs when needed.
4. On the provider's **Client authentication** tab, keep **Memory (one node, default)** or select a persistent directory or shared database as required. Configure any explicitly permitted private JWKS hosts on the same tab.
5. Save and apply the configuration. Reload the relying party to confirm the selected methods and key source. Management Center protects shared secrets and keystore passwords when saving.

Changing to private-key-only authentication removes the unused shared secret. Switching the verification source removes the previous source. Keep an overlap of allowed methods only while migrating the client.

## Troubleshoot authentication

Authentication failures return `invalid_client` without exposing assertions or credentials. Check client ID, selected method, algorithm, key ID, audience, clock synchronization and replay storage. Each retry needs a new assertion and `jti`. Malformed forms, duplicate parameters and missing grant parameters return `invalid_request`.

Discovery advertises the union of the configured RP authentication methods and JWT algorithms. JWT methods are not advertised when no RP enables them. OIDC trace logging omits raw request bodies, credential-bearing headers, token responses and redirect locations.
