# Authenticate using mutual TLS

Leveraging the mutual Transport Layer Security (mTLS) encrypted protocol for client authentication ensures a secure and seamless identity experience that meets [FAPI 2.0 (Financial-grade API)](https://openid.net/specs/fapi-security-profile-2_0.html) requirements. This approach overcomes the vulnerability of compromising a client identity by mutually validating client and server with X.509 certificates on the network level and preventing token theft through certificate-based binding. For more information, refer to [RFC 8705 (OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens)](https://www.rfc-editor.org/rfc/rfc8705.html).

Note
This guide describes the integration steps for a simple client authentication flow, for example, when a client needs to obtain a client access token to authorize subsequent API calls. For more use cases, see [Next steps](#next-steps).

## How it works

In user authentication and client authentication scenarios, mutual client and Mosaic certificate authentication occurs during the TLS handshake before any data is sent over HTTPS. During the handshake, Mosaic verifies the certificate signature to confirm proof of ownership (that the client holds the private key for the presented certificate). After the handshake completes, when Mosaic processes the request, it validates that the certificate matches the client ID specified in the request.

- For self-signed certificates, Mosaic validates against the JWKS provided in the client settings.
- For CA-signed certificates, Mosaic validates against the full certificate chain previously provided in the client settings—from the end-entity (client) certificate back to a trusted root.


For example, a client requests a token ([Step 3](#step-3-authenticate-client)) using mTLS authentication. Upon the receiving the client access token, your app validates it ([Step 4](#step-4-validate-tokens)) and then uses this token to authorize the call.


```mermaid
sequenceDiagram
  participant C as Client
  participant M as Mosaic

  Note over C, M: Network level: TLS handshake
  C -->> M: Client certificate signature
  M -->> M: Validate certificate signature
  M -->> C: Mosaic certificate signature
  C -->> C: Validate certificate signature
   Note over C, M: Service level (HTTPS)
  C ->> M: Get client token: /oidc/token
  M ->> C: Token
  C -->> C: Validate token
  C ->> M: Request, authorized with token
  M ->> C: Return result
```

## Step 1: Obtain certificates

Use strong cryptography to secure the client authentication process. Mosaic enables you to authenticate using self-signed or CA-signed certificates.

details
summary
b
CA-signed certificates
Leverage your Private Key Infrastructure (PKI) to create a certificate chain of trust. This chain should include your client certificate (end-entity certificate) signed by at least one intermediate certificate, which is in turn signed by a self-signed, trusted root Certificate Authority (CA).

Export a client certificate, a private key, and a certificate chain. For successful mTLS authentication with Mosaic, they should comply with the following requirements:

- The certificate chain has to be in the PEM format.
- The certificate chain has to be complete and include at least one intermediate certificate and a root certificate. Certificates in the chain should be ordered from the lowest in the hierarchy to the root CA, for example, intermediate certificate 2 --> intermediate certificate 1 --> root CA certificate.
- The root CA certificate has to be self-signed, i.e., the certifcate's `issuer=CN` and `subject=CN` must match to prove that the certificate is a root certificate and not an intermediate.
- The client certificate must be issued by the first certificate in the chain (intermediate issuer).
- The client certificate must include an Extended Key Usage (EKU) extension that explicitly allows it to be used for `clientAuth` (`OID 1.3.6.1.5.5.7.3.2`).
- The client certificate must include a Key Usage (KU) extension with the following values that explicitly defines the certificate purpose: `digitalSignature`.
- (Optional) The client certificate can include Subject Distinguished Name (Subject DN) attributes: Common name (`CN`), Organization (`O`), Organizational Unit (`OU`), Country (`C`), State (`ST`), Locality (`L`), and Email. These attributes extracted from the client certificate will be compared against the expected values in the client settings ([Step 2](#step-2-submit-certificate-to-mosaic)), if provided.


Below is an example of the certificate chain:


```bash
-----BEGIN CERTIFICATE-----
MIIFJDCCBAygAwIBAgIUEeLI1epi5t/raeHK/6UnMwZ8DP4wDQYJKoZIhvcNAQEL
....
KoPFmnhBOjTlcEB2azJOV3jYCJqImLY
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIE6jCCA9KgAwIBAgIUV49I69r0HAlOsD4WXv1ROrut4T8wDQYJKoZIhvcNAQEL
...
U98pDI9m81HgbDmq4gCl8Y+cylJr0ycmcBDXTkIHWqpVGKFOMqL9MPbIgC1SLnYU
-----END CERTIFICATE-----
```

details
summary
b
Self-signed certificates
Start by exporting a key and self-signed certificate in your preferred format. Then, convert the public certificate into the JWK format, for example, using the Node.js `jose` library. The JWKS includes information about the key type, key usage, etc. For details on JWKS structure, see [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517). The keys are generated and converted once during initial configuration but later you can reissue new keys if necessary.

Below is an example of JWKS:


```json
{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "kid": "CpTM4iGliMoklgafvDXA4TbclcynyD_wMgjOfhiCUUE",
      "x5c": ["MIIDazCCAlOgAwIBAgIURndmlRmyo9snXN45B..."],
      "alg": "RS256",
      "e": "AQAB",
      "n": "vXoSLHWtv_t7f78rvKGPkLDuc-9MkzvLiWf-iUfQm..."
    }
  ]
}
```

| Field | Description |
|  --- | --- |
| `kty` | Key type (RSA) |
| `use` | Signature use |
| `kid` | Key ID |
| `x5c` | X.509 cert(s) |
| `alg` | Signing algorithm |
| `n`, `e` | RSA key details |


## Step 2: Submit certificate to Mosaic

Configure your Mosaic client to use mTLS as an authentication method and provide the public certificate or certificate chain. It will be used to prove the client identity to the server.

- **For OIDC implementations**: from the Admin Portal under **Applications**, click your application and proceed to the OIDC client settings to update the authentication method to **mTLS (self-signed)** or **mTLS (CA-signed)**. If you don't already have an application, you'll need to create one first (see [Create application](/guides/user/create_new_application)).
  - For **self-signed mTLS**, provide the JWKS you've generated in [Step 1](#step-1-obtain-certificates).
  - For **CA-signed mTLS**, provide a certificate chain (in the PEM format) you've generated in [Step 1](#step-1-obtain-certificates). Optionally, define **Subject DN** attributes (Common name (`CN`), Organization (`O`), and others) to enforce certificate validation against these values.


FAPI 2.0 compliance
Consider enabling "Enforce FAPI 2.0 compliance" when creating a client. See [Manage clients](/guides/user/manage_clients)

- **If using SSO Service**: from the Admin Portal under **SSO Service**, navigate to **Service Definition** > **Client groups** and proceed to the OIDC client settings to update the authentication method to **mTLS (self-signed)** or **mTLS (CA-signed)**, and submit a certificate. To configure SSO management clients, navigate to **Service Definition** > **Management clients**.
  - For **self-signed mTLS**, provide the JWKS you've generated in [Step 1](#step-1-obtain-certificates).
  - For **CA-signed mTLS**, provide a certificate chain (in the PEM format) you've generated in [Step 1](#step-1-obtain-certificates). Optionally, define **Subject DN** attributes (Common name (`CN`), Organization (`O`), and others) to enforce certificate validation against these values.


Tip
You can rotate keys whenever needed. Update the certificate in the OIDC client configuration in the Admin Portal.

## Step 3: Authenticate client

Authenticate a client and obtain a client access token by sending a POST request like the one below to the `/oidc/token` endpoint, along with the parameters listed below.

| Field | Description |
|  --- | --- |
| `cert` | A client certificate file (e.g., client-cert.pem). |
| `key` | A client private key file (e.g., client-key.pem). |
| `client_id` | Client ID. Can be obtained from client settings in the Mosaic Admin Portal. |
| `grant_type` | Should be set to `client_credentials`. |



```js
import https from 'https';
import fs from 'fs';
import fetch from 'node-fetch';

// Load mTLS credentials
const agent = new https.Agent({
  cert: fs.readFileSync('CLIENT_CERTIFICATE_FILE'),
  key: fs.readFileSync('CLIENT_PRIVATE_KEY_FILE'),
  rejectUnauthorized: true
});

async function run() {
  const formData = new URLSearchParams({
    client_id: 'CLIENT_ID',
    grant_type: 'client_credentials'
  });

  const resp = await fetch(
    'https://api.transmitsecurity.io/cis/oidc/token',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: formData.toString(),
      agent // Enables mTLS
    }
  );

  const data = await resp.text();
  console.log(data);
}

run();
```

details
summary
b
Authenticating clients using self-signed certificates with Java and other JVM languages
When using mTLS with self-signed certificates, some programming languages may fail during the TLS handshake. This happens because the server sends a list of acceptable Certificate Authorities (CAs), and certain implementations—such as Java, Scala, and Go—won't send a client certificate if it isn't signed by one of those CAs.

To work around this, you need to explicitly force the client to send the certificate regardless of the server's CA list.

Go
Use the `GetClientCertificate` callback to always return the certificate. This ignores the server's acceptable CA list:


```go
if forceSend {
    // Force send: use GetClientCertificate to always return the cert
    // This ignores the server's acceptable CA list
    tlsConfig = &tls.Config{
        RootCAs: caCertPool,
        GetClientCertificate: func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
            return &cert, nil
        },
    }
} else {
    // Default behavior: Go checks acceptable CAs and won't send if no match
    tlsConfig = &tls.Config{
        Certificates: []tls.Certificate{cert},
        RootCAs:      caCertPool,
    }
}
```

Java
Wrap `X509ExtendedKeyManager` and pass `null` for the issuers parameter to bypass the CA list check:


```java
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, password);

KeyManager[] managers = kmf.getKeyManagers();

for (int i = 0; i < managers.length; i++) {
    if (managers[i] instanceof X509ExtendedKeyManager) {
        X509ExtendedKeyManager delegate = (X509ExtendedKeyManager) managers[i];
        managers[i] = new X509ExtendedKeyManager() {
            @Override
            public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
                // Pass 'null' to ignore the server's provided CA list
                return delegate.chooseClientAlias(keyType, null, socket);
            }

            @Override
            public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
                // Pass 'null' to ignore the server's provided CA list
                return delegate.chooseEngineClientAlias(keyType, null, engine);
            }

            // Standard delegation for all other methods
            @Override public String[] getClientAliases(String k, Principal[] i) { return delegate.getClientAliases(k, i); }
            @Override public String[] getServerAliases(String k, Principal[] i) { return delegate.getServerAliases(k, i); }
            @Override public String chooseServerAlias(String k, Principal[] i, Socket s) { return delegate.chooseServerAlias(k, i, s); }
            @Override public String chooseEngineServerAlias(String k, Principal[] i, SSLEngine e) { return delegate.chooseEngineServerAlias(k, i, e); }
            @Override public X509Certificate[] getCertificateChain(String a) { return delegate.getCertificateChain(a); }
            @Override public PrivateKey getPrivateKey(String a) { return delegate.getPrivateKey(a); }
        };
    }
}
```

## Step 4: Validate tokens

The `/oidc/token` response includes a client access token. Tokens must be validated as described [here](/guides/user/validate_tokens). If you enable token binding, the generated token will be bound to a certificate (`cnf` claim). Validate the token signatures using the public key retrieved from the JWKS endpoint:


```http
  https://api.transmitsecurity.io/cis/oidc/jwks
```

Note
Cache a response returned by `/oidc/jwks` for further reuse to avoid reaching API rate limits and prevent latency issues. Signing keys don't change often. Yet, if token validation fails due to a signature mismatch, try updating the cache first and then revalidating the token signature.

## Step 5: Check client certificate revocation

To enhance certificate validation during mTLS authentication, you can configure Mosaic to check the revocation status of client certificates using **OCSP (Online Certificate Status Protocol)** (for more about certificate revocation checking, see [our dedicated article](/guides/user/ocsp_revocation)). This allows Mosaic to verify whether a certificate has been revoked by the Certificate Authority (CA) before completing the authentication.

To enable OCSP revocation checking, in the client settings (**Advanced > Client Settings > mTLS (CA-signed)**):

1. Toggle **OCSP** to enable certificate revocation checks.
2. (Optional) Enter a **Responder URI** to override the default source. If left empty, Mosaic extracts the URI from the certificate’s AIA extension.
3. (Optional) Upload the **OCSP responder’s certificate** (PEM format). The certificate **must** include the `OCSP Signing` extended key usage (`OID 1.3.6.1.5.5.7.3.9`) so Mosaic can validate the OCSP response signature. If not provided, Mosaic attempts to use a certificate from the OCSP response or the client chain.
4. Choose the fallback behavior for cases where the revocation status is unknown (e.g., network error, timeout):
  - **Allow authentication** (default)
  - **Block authentication**


Note
In high-assurance environments or when enforcing strict PKI policies, it is recommended to set the fallback behavior to **Block Authentication** to prevent access when the certificate’s revocation status is unknown.

## Next steps

When authentication with mTLS is enabled, a certificate and key should be used in all calls that typically leverage client secrets, including:

- Obtaining client access tokens with `/oidc/token`
- Obtaining user access tokens with `/oidc/token`
- Initiating a [PAR request](/guides/user/auth_oidc_par) with `/oidc/request`
- [Login](/guides/user/auth_oidc) a user with `/oidc/auth`
- Initiating a [backchannel flow](/guides/user/auth_ciba) with `/oidc/backchannel`
- Initiating a [device flow](/guides/user/auth_device) with `/oidc/device/auth`
- Revoking a token `/oidc/token/revocation`
- etc.


Note
For implementation details, see the respective guides or API reference. The steps mentioned in this guide remain relevant for these integrations as well.

For example, below are sample requests leveraging mTLS in the PAR and CIBA flows:

PAR with PKCE

```js
import https from 'https';
import fs from 'fs';
import fetch from 'node-fetch';

// Load mTLS credentials
const agent = new https.Agent({
  cert: fs.readFileSync('CLIENT_CERTIFICATE_FILE'),
  key: fs.readFileSync('CLIENT_PRIVATE_KEY_FILE'),
  rejectUnauthorized: true
});

async function run() {
  const formData = {
    client_id: 'CLIENT_ID',
    redirect_uri: 'REDIRECT_URI',
    response_type: 'code',
    scope: 'openid',
    code_challenge: 'HASHED_CODE_VERIFIER',
    code_challenge_method: 'S256'
  };

  const resp = await fetch(
    `https://api.transmitsecurity.io/cis/oidc/request`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: new URLSearchParams(formData).toString(),
      agent // Enables mTLS
    }
  );

  const data = await resp.text();
  console.log(data);
}

run();
```

CIBA

```js
import https from 'https';
import fs from 'fs';
import fetch from 'node-fetch';

// Load mTLS credentials
const agent = new https.Agent({
  cert: fs.readFileSync('CLIENT_CERTIFICATE_FILE'),
  key: fs.readFileSync('CLIENT_PRIVATE_KEY_FILE'),
  rejectUnauthorized: true
});

async function run() {
  const formData = {
    client_id: 'CLIENT_ID',
    scope: 'openid',
    login_hint: 'LOGIN HINT',
    binding_message: 'MESSAGE'
  };

  const resp = await fetch(
    `https://api.transmitsecurity.io/cis/oidc/backchannel`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: new URLSearchParams(formData).toString(),
      agent // Enables mTLS
    }
  );

  const data = await resp.text();
  console.log(data);
}

run();
```

style

    section article ol li {
        margin-top: 6px !important;
    }

    th {
      min-width: 155px;
    }