> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-mintlify-fbfa8bee.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Guide to JWT-based authentication and ephemeral users in ClickHouse Cloud

# JWT Authentication

export const CloudOnlyBadge = () => {
  return <div className="cloudBadge">
            <div className="cloudIcon">
            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path fillRule="evenodd" clipRule="evenodd" d="M5.33395 12.6667H12.3739C13.6593 12.6667 14.7073 11.6187 14.7073 10.3334C14.7073 9.04804 13.6593 8.00004 12.3739 8.00004H12.0839V7.33337C12.0839 5.12671 10.2906 3.33337 8.08395 3.33337C6.09928 3.33337 4.45395 4.78537 4.14195 6.68204C2.55728 6.76271 1.29395 8.06204 1.29395 9.66671C1.29395 11.3234 2.63728 12.6667 4.29395 12.6667H5.33395Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
            </svg>
        </div>
            {'ClickHouse Cloud only'}
        </div>;
};

ClickHouse can authenticate users using JSON Web Tokens (JWTs). Unlike other external authenticators such as [LDAP](/concepts/features/security/external-authenticators/ldap) or [Kerberos](/concepts/features/security/external-authenticators/kerberos), JWT authentication does not verify the identity of pre-existing users. Instead, it dynamically creates **ephemeral users** from the claims embedded in each token. These users exist only in memory, receive access rights derived from token claims, and are automatically removed after the token expires.

This makes JWT authentication fundamentally different from password-based or certificate-based methods: there is no `CREATE USER ... IDENTIFIED WITH jwt` statement, and attempting it raises an exception. JWT users are fully managed by the token lifecycle.

<h2 id="overview">
  Overview
</h2>

The authentication flow works as follows:

1. A client presents a signed JWT via one of the supported transport mechanisms (HTTP `Authorization: Bearer` header, the TCP native protocol, or the gRPC `jwt` field).
2. ClickHouse validates the token signature.
3. Required claims (`exp`, `iat`, `iss`, `sub`, `aud`) are verified.
4. An ephemeral user is created in memory with access rights derived from the `clickhouse:grants` and `clickhouse:roles` token claims, intersected with a permission limit.
5. When the token expires, a background garbage collection task removes the user.

<h2 id="token-claims">
  Token claims
</h2>

<h3 id="required-claims">
  Required claims
</h3>

Every JWT presented to ClickHouse must contain the following claims:

| Claim | Description                                                                    |
| ----- | ------------------------------------------------------------------------------ |
| `alg` | Signing algorithm (header claim). Supported values: `HS256`, `RS256`, `ES256`. |
| `exp` | Expiration time. Sets the ephemeral user's `valid_until`.                      |
| `iat` | Issued-at time. Used to prevent replay of older tokens for the same identity.  |
| `iss` | Issuer. Matched against the provider's expected issuer.                        |
| `sub` | Subject. Becomes part of the generated username.                               |
| `aud` | Audience. Matched against the provider's expected audience.                    |

The `kid` (key ID) header claim is also required when JWKS-based key resolution is used.

<Info>
  **JWKS mode supports RSA keys only**

  While static-key providers accept any of `HS256`, `RS256`, or `ES256`, JWKS-based providers only accept JWKs whose `kty` is `RSA` (i.e., tokens signed with `RS256`). Tokens signed with HMAC (`HS256`) or EC (`ES256`) keys cannot be verified against a JWKS endpoint and will be rejected.
</Info>

<h3 id="other-recognized-claims">
  Other recognized claims
</h3>

| Claim | Description                                                                                        |
| ----- | -------------------------------------------------------------------------------------------------- |
| `nbf` | Not-before time. This claim is not required, but if present, tokens are rejected before this time. |
| `jti` | Reserved. Accepted in tokens but not currently validated or used.                                  |

<h3 id="optional-claims">
  Optional claims
</h3>

| Claim                                                                                                                      | Default name        | Description                                                                                                                                         |
| -------------------------------------------------------------------------------------------------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Grants                                                                                                                     | `clickhouse:grants` | A JSON array of SQL `GRANT` fragments, e.g. `["SELECT ON db.*", "INSERT ON db.table1"]`. Each element is parsed as the body of a `GRANT` statement. |
| Roles                                                                                                                      | `clickhouse:roles`  | A JSON array of role names to assign, e.g. `["analyst", "reader"]`.                                                                                 |
| The default claim names can be remapped to custom claim names if your identity provider uses different naming conventions. |                     |                                                                                                                                                     |

<h3 id="example-token-header-and-payload">
  Example token header and payload
</h3>

```json theme={null}
{
  "alg": "RS256",
  "kid": "my-key-id"
}
```

```json theme={null}
{
  "iss": "https://idp.example.com",
  "sub": "jane.doe",
  "aud": "my-clickhouse-cluster",
  "exp": 1719504000,
  "iat": 1719500400,
  "clickhouse:grants": ["SELECT ON analytics.*", "INSERT ON analytics.events"],
  "clickhouse:roles": ["analyst"]
}
```

<h2 id="ephemeral-user-behavior">
  Ephemeral user behavior
</h2>

JWT users differ from regular ClickHouse users in several important ways.

<h3 id="identity-and-naming">
  Identity and naming
</h3>

Each JWT user receives a deterministic UUID computed from the `iss`, `sub`, and `aud` claims. This UUID is **stable** across logins. A user who logs in multiple times with different tokens (but the same issuer, subject, and audience) always gets the same UUID.

The username, however, is **volatile**. It is constructed as:

```text theme={null}
JWT::<issuer>::<audience>::<subject>::<claims_hash>
```

The `<claims_hash>` portion changes whenever the `clickhouse:roles` or `clickhouse:grants` claims change. This means that tokens with different role or grant sets produce different usernames even for the same identity.

<h3 id="access-rights">
  Access rights
</h3>

Effective access rights are computed as:

```text theme={null}
effective_rights = permission_limit ∩ (token_grants ∪ token_roles)
```

Where `permission_limit` is the set of access rights held by a reference role or user configured as the upper bound. Rights requested by the token that exceed the limit are silently dropped.

<h3 id="token-freshness">
  Token freshness
</h3>

ClickHouse tracks the `iat` (issued-at) claim of the most recently authenticated token for each stable identity. If a token with an `iat` equal to or older than the stored value is presented, the server reuses the existing ephemeral user without re-evaluating claims. This prevents older tokens from downgrading a user's permissions.

<h3 id="lifetime-and-garbage-collection">
  Lifetime and garbage collection
</h3>

Ephemeral users are created when a token is first authenticated and removed by a background garbage collection task after `valid_until` (derived from `exp`) passes. The GC interval is controlled by the `gc_interval` parameter (default: 5 minutes).

Between GC runs, expired users may still be visible in `system.users` but can no longer authenticate.

<h3 id="persistent-access-assignments">
  Persistent access assignments
</h3>

Because the UUID is stable, you can assign settings profiles, quotas, row policies, and column masking policies to a JWT user using SQL statements. These assignments persist in the access control storage (on disk or in ZooKeeper) and survive token expiry and re-authentication.

Reference the user by their current username:

```sql theme={null}
ALTER SETTINGS PROFILE my_profile ADD TO 'JWT::ClickHouse::my-service-id::jane.doe::<claims-hash>';
```

<Note>
  The username and UUID for a given identity can be found in the `name` and `id` columns of `system.users` while the user is active.
</Note>

Note that `ALTER USER` does not work on JWT users directly, as they are read-only. To assign settings profiles, quotas, or policies, use the `ALTER SETTINGS PROFILE`, `ALTER QUOTA`, or `ALTER ROW POLICY` statements as shown above.

<h2 id="differences-from-regular-users">
  Differences from regular users
</h2>

| Feature                               | JWT users                                             | Regular users                   |
| ------------------------------------- | ----------------------------------------------------- | ------------------------------- |
| Creation                              | Automatic from token claims                           | `CREATE USER` statement         |
| Storage                               | In-memory only (ephemeral)                            | Disk, ZooKeeper, or config file |
| `CREATE USER ... IDENTIFIED WITH jwt` | Not supported (raises exception)                      | All other auth types supported  |
| `ALTER USER` / `DROP USER`            | Not supported                                         | Supported                       |
| Backup and restore                    | Not included                                          | Included                        |
| Username                              | Auto-generated, volatile                              | Administrator-chosen, fixed     |
| UUID                                  | Deterministic from `iss`+`sub`+`aud`                  | Random at creation time         |
| Lifetime                              | Bounded by token `exp`                                | Until explicitly dropped        |
| Access rights                         | Derived from token claims, capped by permission limit | Explicitly granted via `GRANT`  |
| Host restrictions                     | Per-provider network configuration                    | Per-user `HOST` clause          |
| Settings profiles                     | Assignable by UUID (persistent)                       | Directly configurable           |
| Quotas and row policies               | Assignable by UUID (persistent)                       | Directly configurable           |
| Default roles                         | Not configurable                                      | Configurable                    |

<h2 id="sql-security-definer-views">
  SQL SECURITY DEFINER views
</h2>

When an ephemeral JWT user creates a view with `SQL SECURITY DEFINER`, the server automatically creates a persistent shadow copy of the user to serve as the view's definer. This shadow user:

* Has the name `<original_jwt_username>:definer`
* Has `NO_AUTHENTICATION` (cannot be used to log in)
* Retains the same access rights as the original JWT user at the time the view was created

This ensures that the view continues to function after the ephemeral user's token expires and the original user is garbage-collected.

<h2 id="client-usage">
  Client usage
</h2>

<h3 id="passing-token-directly">
  Passing a token directly
</h3>

Use the `--jwt` flag with `clickhouse-client` to authenticate with a pre-obtained token:

```bash theme={null}
clickhouse-client --host your-instance.clickhouse.cloud --secure --jwt '<your_jwt_token>'
```

<Note>
  The `--jwt` flag is mutually exclusive with `--user`. When `--jwt` is specified, the username is derived from the token.
</Note>

<h3 id="http-interface">
  HTTP interface
</h3>

Send the token as a Bearer token in the `Authorization` header:

```bash theme={null}
curl -H 'Authorization: Bearer <your_jwt_token>' \
    'https://your-instance.clickhouse.cloud:8443/?query=SELECT+currentUser()'
```

<Warning>
  Always send JWTs over HTTPS. A Bearer token sent over plain HTTP is exposed to anyone on the network path and is equivalent to leaking the credential.
</Warning>

<h3 id="oauth2-device-code-login">
  OAuth2 device code login
</h3>

The `clickhouse-client` supports an interactive OAuth2 device code flow via the `--login` flag. For ClickHouse Cloud endpoints, the client automatically performs token exchange to obtain a ClickHouse-specific JWT. Tokens are refreshed transparently during the session. When a new token is obtained, the client reconnects automatically.

```bash theme={null}
clickhouse-client --host your-instance.clickhouse.cloud --login
```

<h2 id="clickhouse-cloud-built-in">
  ClickHouse Cloud built-in JWT authenticator
</h2>

Every ClickHouse Cloud service comes with a predefined JWT authenticator that is used by SQL Console and the `clickhouse-client` `--login` flow. This authenticator is configured with:

| Parameter        | Value                                               |
| ---------------- | --------------------------------------------------- |
| `iss` (issuer)   | `ClickHouse`                                        |
| `aud` (audience) | The service UUID (visible in the Cloud console URL) |
| `sub` (subject)  | Your ClickHouse Cloud account email address         |

The built-in authenticator has a permission limit set to the `default_role` role and the `default` user. This means the effective rights of any JWT user are intersected with the grants held by those two entities, so a token can never escalate privileges beyond what `default_role` and `default` are allowed to do.

You do not need to configure anything to use this authenticator. It is provisioned automatically when the service is created.

<h2 id="interserver-communication">
  Interserver communication
</h2>

When a query is forwarded to another shard or replica, the JWT token is included in the interserver protocol. The remote node re-authenticates the token independently, creating its own ephemeral user.

<h2 id="troubleshooting">
  Troubleshooting
</h2>

* **No access rights granted:** The referenced role or user may lack the required grants. Ensure the roles referenced in the `clickhouse:roles` exist and include the appropriate grants.
* **Token rejected:** Verify that `iss`, `aud`, and the signing algorithm in your token match what the JWT provider expects. If JWKS is used, ensure the token's `kid` matches a key in the provider's key set.
* **User disappears between queries:** Ephemeral users are removed after token expiry. Use a client that supports token refresh (e.g., `--login` mode) for long-running sessions.
* **`CREATE USER ... IDENTIFIED WITH jwt` fails:** This is expected. JWT users cannot be created via DDL. They are managed entirely by the token lifecycle.
