> ## Documentation Index
> Fetch the complete documentation index at: https://docs.streamnative.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Configure JWT authentication

You can configure [JSON Web Token (JWT)](https://jwt.io/introduction) authentication to a Pulsar cluster using asymmetric (RS256) signing. A **private key** signs tokens and a **public key** verifies them on the brokers and proxies, so the signing credential never needs to be distributed to cluster components.

## Before you begin

* Install the following tools.

  * Install [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) v1.16 or higher.
  * Install [pulsarctl](https://github.com/streamnative/pulsarctl).

***

<Warning>
  Keep `my-private.key` strictly controlled. Anyone who holds the private key can issue valid tokens for any subject, including `broker-admin` and `proxy-admin`. Treat it with the same care as a root credential:

  * Store it outside the Kubernetes cluster.
  * Consider storing it in a secrets manager (for example, AWS Secrets Manager, HashiCorp Vault) and only retrieving it when issuing new tokens.
</Warning>

## Generate the RSA256 key pair and tokens

Use `pulsarctl` to generate an RSA256 key pair:

```bash theme={null}
pulsarctl token create-key-pair \
  --output-private-key my-private.key \
  --output-public-key my-public.key
```

Issue tokens for the `broker-admin` and `proxy-admin` subjects using the private key:

```bash theme={null}
pulsarctl token create -a RS256 \
  --private-key-file my-private.key \
  --subject broker-admin
```

```bash theme={null}
pulsarctl token create -a RS256 \
  --private-key-file my-private.key \
  --subject proxy-admin
```

Issue a token for the `client` subject:

```bash theme={null}
pulsarctl token create -a RS256 \
  --private-key-file my-private.key \
  --subject client
```

## Create Kubernetes Secrets for keys and tokens

Only the **public key** goes into Kubernetes. The private key must never be stored as a cluster Secret.

* Create the public key Secret:

  ```bash theme={null}
  kubectl create secret generic token-public-key \
    --from-file=my-public.key \
    -n pulsar
  ```

* Create the `broker-admin` token Secret:

  ```bash theme={null}
  kubectl create secret generic broker-admin \
    --from-literal=token=<broker-admin-token> \
    -n pulsar
  ```

* Create the `proxy-admin` token Secret:

  ```bash theme={null}
  kubectl create secret generic proxy-admin \
    --from-literal=token=<proxy-admin-token> \
    -n pulsar
  ```

Replace `<broker-admin-token>` and `<proxy-admin-token>` with the tokens generated in the previous step.

## Enable JWT authentication for the Pulsar cluster

Add the following configurations to the `PulsarBroker` object:

```yaml theme={null}
spec:
  config:
    clientAuth:
      jwt:
        secret: broker-admin
    custom:
      authenticationEnabled: 'true'
      authenticateOriginalAuthData: 'true'
      authenticationProviders: 'org.apache.pulsar.broker.authentication.AuthenticationProviderToken'
      brokerClientAuthenticationPlugin: 'org.apache.pulsar.client.impl.auth.AuthenticationToken'
      superUserRoles: 'broker-admin, proxy-admin'
      proxyRoles: 'proxy-admin'
      authorizationEnabled: 'true'
      authorizationProvider: 'org.apache.pulsar.broker.authorization.PulsarAuthorizationProvider'
  pod:
    secretRefs:
      - mountPath: /mnt/secrets
        secretName: token-public-key
    vars:
      - name: brokerClientAuthenticationParameters
        valueFrom:
          secretKeyRef:
            name: broker-admin
            key: token
      - name: tokenPublicKey
        value: 'file:///mnt/secrets/my-public.key'
```

* `config.clientAuth`: configures the toolset to mount and use the broker-admin token automatically.
* `config.custom`: sets Pulsar authentication and authorization properties.
* `pod.secretRefs`: mounts the public key Secret as a file inside the pod.
* `pod.vars`: exposes the public key path and broker-admin token as environment variables for Pulsar configuration.

On the `PulsarProxy` object, add the following configurations:

```yaml theme={null}
spec:
  config:
    custom:
      authenticationEnabled: 'true'
      authenticateOriginalAuthData: 'true'
      forwardAuthorizationCredentials: 'true'
      authenticationProviders: 'org.apache.pulsar.broker.authentication.AuthenticationProviderToken'
      brokerClientAuthenticationPlugin: 'org.apache.pulsar.client.impl.auth.AuthenticationToken'
      superUserRoles: 'proxy-admin'
  pod:
    secretRefs:
      - mountPath: /mnt/secrets
        secretName: token-public-key
    vars:
      - name: brokerClientAuthenticationParameters
        valueFrom:
          secretKeyRef:
            name: proxy-admin
            key: token
      - name: tokenPublicKey
        value: 'file:///mnt/secrets/my-public.key'
```

* `config.custom`: sets Pulsar authentication and authorization properties.
* `pod.secretRefs`: mounts the public key Secret as a file inside the pod.
* `pod.vars`: exposes the public key path and broker-admin token as environment variables for Pulsar configuration.

## Connect clients to Pulsar

* Create authorization for the client subject using the `broker-admin` token:

  ```bash theme={null}
  pulsarctl --admin-service-url http://<Your PulsarProxy Endpoint>:8080 \
    --token "<broker-admin-token>" \
    namespaces grant-permission \
    --role client \
    --actions produce \
    --actions consume \
    public/default
  ```

  Expected output:

  ```
  Grant permissions [produce consume] to the client role client to access the namespace public/default successfully
  ```

  <Tip>
    If you are running this command from the toolset pod, `pulsarctl` reads `/pulsar/conf/client.conf` which already has the admin service URL and the broker-admin token configured. You can omit the `--admin-service-url` and `--token` flags:

    ```bash theme={null}
    pulsarctl namespaces grant-permission \
      --role client \
      --actions produce \
      --actions consume \
      public/default
    ```
  </Tip>

* Produce messages with the client token:

  ```bash theme={null}
  bin/pulsar-client \
    --url pulsar://<Your PulsarProxy Endpoint>:6650 \
    --auth-plugin org.apache.pulsar.client.impl.auth.AuthenticationToken \
    --auth-params "token:<client-token>" \
    produce public/default/test -m "test" -n 10
  ```

* Consume messages with the client token:

  ```bash theme={null}
  bin/pulsar-client \
    --url pulsar://<Your PulsarProxy Endpoint>:6650 \
    --auth-plugin org.apache.pulsar.client.impl.auth.AuthenticationToken \
    --auth-params "token:<client-token>" \
    consume public/default/test -s my-subscription -n 10
  ```

***

## Verify the configuration

After the pods restart, confirm the broker is using the public key and not a shared secret:

```bash theme={null}
kubectl exec -n pulsar <broker-pod> -- \
  grep -E 'tokenPublicKey|tokenSecretKey' /pulsar/conf/broker.conf
```

Expected output:

```
tokenSecretKey=
tokenPublicKey=file:///mnt/secrets/my-public.key
```

`tokenSecretKey` must be empty. If it is set, the broker is using symmetric auth instead of RS256.

***

## Configure Pulsar console

The `Console` CRD does not have a native plain-JWT field. Authentication is configured by injecting environment variables directly via `pod.vars` and mounting the public key via `pod.secretRefs`.

A single dedicated service account (`consoleservice`) is used for both the Console backend connection to the broker and the web login. This subject must be in `superUserRoles` on the broker so it can perform all admin operations, including listing clusters.

### Issue the Console service account token

Issue a dedicated RS256 token for the `consoleservice` subject:

```bash theme={null}
pulsarctl token create -a RS256 \
  --private-key-file my-private.key \
  --subject consoleservice
```

Create a Kubernetes Secret that contains both the token and the public key:

```bash theme={null}
kubectl create secret generic consoleservice \
  --from-literal=token=<consoleservice-token> \
  --from-file=my-public.key \
  -n pulsar
```

Replace `<consoleservice-token>` with the token generated above.

### Add the Console service account to the broker

Update the `PulsarBroker` object to include `consoleservice` in `superUserRoles`:

```yaml theme={null}
spec:
  config:
    custom:
      superUserRoles: 'broker-admin, proxy-admin, consoleservice'
```

### Configure the Console CRD

Add the following to the `Console` object:

```yaml theme={null}
spec:
  pod:
    secretRefs:
      - mountPath: /pulsar-manager/keys
        secretName: consoleservice
    vars:
      - name: AUTHENTICATION_NAME
        value: pulsar-jwt
      - name: AUTHENTICATION_PROVIDER
        value: org.apache.pulsar.manager.authentication.PulsarJWTAuthenticationProvider
      - name: AUTHENTICATION_CUSTOM_CLAIM
        value: sub
      - name: SERVICE_ACCOUNT_NAME
        value: jwt
      - name: SERVICE_ACCOUNT_PROVIDER
        value: org.apache.pulsar.manager.serviceAccount.JWTProvider
      - name: AUTH_METHOD_NAME
        value: pulsar-jwt
      - name: AUTH_METHOD_PROVIDER
        value: org.apache.pulsar.manager.authMethod.PulsarJWTAuthMethodProvider
      - name: AUTH_METHOD_CUSTOM_CLAIM
        value: sub
      - name: USERNAME_CLAIM
        value: sub
      - name: JWT_BROKER_TOKEN_MODE
        value: PRIVATE
      - name: JWT_BROKER_PUBLIC_KEY_PATH
        value: /pulsar-manager/keys/my-public.key
      - name: TOKEN
        valueFrom:
          secretKeyRef:
            name: consoleservice
            key: token
      - name: BACKEND_DEFAULT_SUPER_USER_ROLE
        value: consoleservice
```
