# Authentication (/connectors/rabbitmq/how-to/authentication)



The KubeMQ RabbitMQ connector authenticates over **SASL PLAIN only**. The non-obvious part: the
**password carries a KubeMQ JWT**, and the **username is cosmetic**. When authentication is
disabled (the dev default), any credentials are accepted, so the examples clone-and-run with
`guest:guest`.

<Callout type="info">
  On a stock dev broker, authentication is **off** and any credentials are accepted — `guest:guest`
  completes a full round-trip. SASL **PLAIN** with a KubeMQ JWT in the password is the one
  credentialed form that also runs on a stock broker. To encrypt the JWT on the wire, use the TLS
  listener (`amqps://:5671`) — see [TLS and mTLS](/connectors/rabbitmq/how-to/tls-and-mtls).
</Callout>

## SASL PLAIN is the only mechanism [#sasl-plain-is-the-only-mechanism]

The connector advertises and accepts **only the `PLAIN` mechanism**. A `connection.start-ok`
carrying any other mechanism (`AMQPLAIN`, `EXTERNAL`, …) is rejected with `503 command-invalid`
(`"unsupported SASL mechanism … only PLAIN is supported"`).

The PLAIN response is parsed as the standard triple `authzid \x00 authcid \x00 passwd`.

## Password = KubeMQ JWT, username = cosmetic [#password--kubemq-jwt-username--cosmetic]

The username slot is informational; the password slot carries the credential. Most AMQP 0-9-1
clients accept a `(username, password)` pair in the connection URL — put the JWT in the
**password**:

```text
amqp://<username-ignored>:<KubeMQ-JWT>@host:5672/<vhost>
        └─ cosmetic ─┘  └─ authenticated ─┘
```

| Field        | Role                                                                                                                           |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| **Username** | Cosmetic. Recorded for audit and `user-id` checks; **ignored for authorization**.                                              |
| **Password** | The **KubeMQ JWT** — passed to the auth service.                                                                               |
| **Identity** | `ClientID` from the JWT's claims. This single `ClientID` covers both connector-level and channel-level (Casbin) authorization. |

The JWT is validated **at connect time only** — there is no mid-connection expiry enforcement.
On auth failure the connector sends `connection.close(403)` and emits an `auth.failure` audit
event.

<Callout type="warn">
  **The JWT travels in the SASL PLAIN password in cleartext at the AMQP layer.** Production
  deployments that use authentication MUST use the &#x2A;*TLS listener (`amqps://:5671`)**, otherwise the
  JWT is exposed on the wire. See [TLS and mTLS](/connectors/rabbitmq/how-to/tls-and-mtls) and
  [Auth & security](/connectors/reference/auth-and-security).
</Callout>

Put the JWT in the password slot regardless of client library:

<Tabs groupId="language" items="['Go','Python','Java','JavaScript','C#','Ruby','Rust']">
  <Tab value="Go">
    ```go
    // amqp091-go — username is cosmetic, password is the KubeMQ JWT.
    conn, err := amqp.Dial(fmt.Sprintf(
        "amqp://audit-user:%s@broker:5672/", os.Getenv("KUBEMQ_AMQP_JWT")))
    if err != nil {
        log.Fatalf("dial (bad/expired JWT? auth-disabled broker?): %v", err)
    }
    defer conn.Close()
    ```
  </Tab>

  <Tab value="Python">
    ```python
    # pika — credentials: username audit-only, password is the KubeMQ JWT.
    creds = pika.PlainCredentials("audit-user", os.environ["KUBEMQ_AMQP_JWT"])
    params = pika.ConnectionParameters(host="broker", port=5672, credentials=creds)
    conn = pika.BlockingConnection(params)
    ```
  </Tab>

  <Tab value="Java">
    ```java
    // amqp-client — setUsername is cosmetic; setPassword carries the KubeMQ JWT.
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("broker");
    factory.setPort(5672);
    factory.setUsername("audit-user");
    factory.setPassword(System.getenv("KUBEMQ_AMQP_JWT"));
    Connection connection = factory.newConnection();
    ```
  </Tab>

  <Tab value="JavaScript">
    ```typescript
    // amqplib — username audit-only, password is the KubeMQ JWT.
    const connection = await amqp.connect(
      `amqp://audit-user:${process.env.KUBEMQ_AMQP_JWT}@broker:5672/`);
    ```
  </Tab>

  <Tab value="C#">
    ```csharp
    // RabbitMQ.Client — UserName is cosmetic; Password carries the KubeMQ JWT.
    var factory = new ConnectionFactory
    {
        HostName = "broker",
        Port = 5672,
        UserName = "audit-user",
        Password = Environment.GetEnvironmentVariable("KUBEMQ_AMQP_JWT"),
    };
    using var connection = factory.CreateConnection();
    ```
  </Tab>

  <Tab value="Ruby">
    ```ruby
    # bunny — username audit-only, password is the KubeMQ JWT.
    conn = Bunny.new(
      host: "broker", port: 5672,
      user: "audit-user", password: ENV.fetch("KUBEMQ_AMQP_JWT"))
    conn.start
    ```
  </Tab>

  <Tab value="Rust">
    ```rust
    // lapin — username audit-only, password is the KubeMQ JWT.
    let uri = format!("amqp://audit-user:{}@broker:5672/",
        std::env::var("KUBEMQ_AMQP_JWT")?);
    let conn = Connection::connect(&uri, ConnectionProperties::default()).await?;
    ```
  </Tab>
</Tabs>

## Auth disabled — the dev default [#auth-disabled--the-dev-default]

When authentication is disabled (the default on a dev broker), **any credentials are accepted** —
`guest:guest` completes a full round-trip. The `ClientID` is then derived from the client's
sanitized `connection_name`:

* `amqp-{connection_name}` if a connection name was provided, otherwise
* `amqp-{uuid8}`.

## The `user-id` property [#the-user-id-property]

When authentication is **enabled** and the `user-id` property is set on `basic.publish`, it MUST
equal the JWT's `ClientID`; a mismatch is rejected with `406 precondition-failed`. When auth is
disabled, `user-id` passes through unvalidated.

## Authorization (Casbin) [#authorization-casbin]

When auth is enabled, every channel operation is checked **per-channel** against a Casbin policy
using the connection's `ClientID`:

| Operation                                   | Casbin permission           | On denial                                                                                                                      |
| ------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| **Publish**                                 | **Write**, per routed queue | The denied queue is **silently removed** from the routed set + an `amqp.publish.denied` audit. The publish does **not** error. |
| **Consume**                                 | **Read**                    | `403 access-refused`.                                                                                                          |
| **`queue.declare` / `queue.delete` / bind** | **Write**                   | `403 access-refused`.                                                                                                          |

When authorization is disabled, all operations are allowed.

<Callout type="warn">
  **Publish denial is silent.** Because a denied queue is removed from the routed set rather than
  rejected, a publish to a partially-denied fanout still succeeds for the *allowed* queues. Use
  `mandatory=true` if you need a `312 NO_ROUTE` when **nothing** routed — see
  [Reliability](/connectors/rabbitmq/how-to/reliability).
</Callout>

### Example policy [#example-policy]

```json
{"ClientID":"amqp-authz-allowed","Channel":"amqp.default.*","Read":true,"Write":true}
```

A client with this policy can declare, publish, and consume under `amqp.default.*`. A client
without **Read** on a channel gets `403` on consume; a client without **Write** has its publishes
silently dropped for that queue.

## Quick decision guide [#quick-decision-guide]

| You want…                           | Do this                                                                                           |
| ----------------------------------- | ------------------------------------------------------------------------------------------------- |
| Clone-and-run on a stock dev broker | Connect with any credentials (`guest:guest`)                                                      |
| Authenticate with a KubeMQ identity | SASL **PLAIN**, JWT in the **password** slot; username is audit-only                              |
| Encrypt the JWT on the wire         | Use `amqps://:5671` — see [TLS and mTLS](/connectors/rabbitmq/how-to/tls-and-mtls)                |
| Diagnose a permission failure       | A `403 access-refused` on consume means no **Read**; silently-dropped publishes mean no **Write** |

## Related [#related]

<Cards>
  <Card title="TLS and mTLS" href="/connectors/rabbitmq/how-to/tls-and-mtls" description="amqps:// on port 5671 and mutual TLS — encrypt the JWT that travels in the SASL PLAIN password." />

  <Card title="Auth & security" href="/connectors/reference/auth-and-security" description="The shared JWT model, TLS/mTLS, and security concepts across KubeMQ connectors." />

  <Card title="Error codes" href="/connectors/rabbitmq/reference/error-codes" description="The 403, 406, and 503 AMQP codes the connector returns on authentication and authorization failures." />
</Cards>
