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



This guide explains how a native MQTT client proves *who it is* to the KubeMQ MQTT connector and
*what it may do&#x2A; once connected. MQTT carries the credential in the standard &#x2A;*`Password`** field of
the CONNECT packet — there is no MQTT-specific auth flag — so any off-the-shelf MQTT 3.1.1 / 5.0
client authenticates by setting username/password the way it already does.

<Callout type="info">
  On a stock dev broker, authentication is **off** — every CONNECT succeeds, the `Password` field is
  ignored, and ACL checks are skipped. So the examples clone-and-run with no credentials. To turn
  auth on, configure an auth provider at the **server** level (there is no MQTT-specific auth env
  var); see [Auth & security](/connectors/reference/auth-and-security).
</Callout>

## Password-as-JWT [#password-as-jwt]

The connector uses **password-as-JWT** authentication. The MQTT `Password` field carries a KubeMQ
JWT; the `Username` field is accepted but is **display-only** — it is stored for the connections
endpoint and never checked against the token.

| MQTT CONNECT field | Role                              | Notes                                                                                              |
| ------------------ | --------------------------------- | -------------------------------------------------------------------------------------------------- |
| `Password`         | **The credential** — a KubeMQ JWT | Must be set when auth is on; the MQTT spec requires `PasswordFlag=true` when a password is present |
| `Username`         | Display / audit label             | Surfaced on the connections endpoint; never validated against the token                            |
| `ClientID`         | **The identity**                  | Becomes the KubeMQ `ClientID`; used for ACL checks and the `$reply/<clientID>/...` reply namespace |

A CONNECT therefore looks like this — the JWT goes in the **password** slot, not the username:

```text
CONNECT
  ClientID:     my-client          ← the identity
  Username:     alice              ← display label only
  Password:     <KubeMQ JWT>       ← the actual credential
  PasswordFlag: true
```

Authentication is checked **once, at CONNECT time only**. There is no mid-connection token
recheck — revoking a token does **not** disconnect an already-authenticated session.

## Connecting with a JWT [#connecting-with-a-jwt]

Put the JWT in the **password** slot; the username is cosmetic. The same code works against an
auth-disabled broker (the server simply does not validate the token).

<Tabs groupId="language" items="['Go','Python','JavaScript']">
  <Tab value="Go">
    ```go
    // paho.mqtt.golang (MQTT 3.1.1) — username is display-only, password is the KubeMQ JWT.
    opts := mqtt.NewClientOptions().
        AddBroker("tcp://broker:1883").
        SetClientID("my-client").
        SetUsername("alice").                       // display-only
        SetPassword(os.Getenv("KUBEMQ_MQTT_JWT"))   // KubeMQ JWT token
    client := mqtt.NewClient(opts)
    if token := client.Connect(); token.Wait() && token.Error() != nil {
        log.Fatalf("connect (bad/expired JWT? auth-enabled broker?): %v", token.Error())
    }
    ```
  </Tab>

  <Tab value="Python">
    ```python
    # paho-mqtt (MQTT 5.0) — username is display-only, password is the KubeMQ JWT.
    import os
    import paho.mqtt.client as mqtt
    from paho.mqtt.enums import CallbackAPIVersion

    client = mqtt.Client(
        callback_api_version=CallbackAPIVersion.VERSION2,
        client_id="my-client",
        protocol=mqtt.MQTTv5,
    )
    client.username_pw_set("alice", os.environ["KUBEMQ_MQTT_JWT"])  # (display, JWT)
    client.connect("broker", 1883, keepalive=30)
    ```
  </Tab>

  <Tab value="JavaScript">
    ```typescript
    // mqtt.js (MQTT 5.0) — username is display-only, password is the KubeMQ JWT.
    import * as mqtt from "mqtt";

    const client = mqtt.connect("tcp://broker:1883", {
      protocolVersion: 5,
      clientId: "my-client",
      username: "alice",                      // display-only
      password: process.env.KUBEMQ_MQTT_JWT,  // KubeMQ JWT token
      clean: true,
    });
    ```
  </Tab>
</Tabs>

## CONNACK reason codes [#connack-reason-codes]

What the broker returns at CONNECT time depends on whether auth is enabled and whether the JWT is
valid:

| Scenario                                                | Packet  | Reason code                  |
| ------------------------------------------------------- | ------- | ---------------------------- |
| Auth disabled — any password accepted                   | CONNACK | `0x00` success               |
| Auth enabled — valid JWT                                | CONNACK | `0x00` success               |
| Auth enabled — empty password (or `PasswordFlag=false`) | CONNACK | `0x86` bad username/password |
| Auth enabled — invalid JWT or bad signature             | CONNACK | `0x86` bad username/password |

When auth is enabled, a CONNECT with an empty `Password` is refused immediately with `0x86`; the
connection is never established. Clients must always supply the token in the `Password` field.

## ACL authorization [#acl-authorization]

Once connected, **every publish and subscribe** is checked against the KubeMQ ACL. Rules are
evaluated per `(pattern, channel, read/write)` tuple, against the identity derived from the
`ClientID`.

| Outcome             | Packet | Reason code           |
| ------------------- | ------ | --------------------- |
| Allowed (publish)   | PUBACK | `0x00`                |
| Allowed (subscribe) | SUBACK | `0x01` (granted QoS)  |
| Denied on publish   | PUBACK | `0x87` not authorized |
| Denied on subscribe | SUBACK | `0x87` not authorized |

Two namespace rules sit alongside the ACL:

* **`$reply/<clientID>/<suffix>`** — a client's **own** reply namespace is **always allowed**,
  regardless of ACL rules. This is the local topic an RPC requester subscribes to for responses;
  see [Topic mapping](/connectors/mqtt/concepts/topic-mapping).
* **Other `$`-prefixed topics** (except `$share/`) are **denied by default**.

<Callout type="warn">
  Authorization is checked **at CONNECT for the password, then at each publish/subscribe for the
  ACL** — but never again on an open subscription. A denied publish returns PUBACK `0x87`; treat it
  as a permission error, not a transient failure to retry.
</Callout>

## Open (no-auth) default [#open-no-auth-default]

When no auth provider is configured the broker is **open**: the `Password` field is ignored, every
CONNECT succeeds, and ACL checks are skipped. This matches KubeMQ's gRPC and REST parity behaviour
and is the mode every example in the docs assumes.

To enable authentication, configure the KubeMQ server with an auth provider (the shared
server-level auth block) — &#x2A;*no MQTT-specific environment variable controls auth.** Because it is a
shared setting, the same JWT model applies across all KubeMQ connectors; see
[Auth & security](/connectors/reference/auth-and-security).

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

| You want…                                   | Do this                                                                     |
| ------------------------------------------- | --------------------------------------------------------------------------- |
| Clone-and-run on a stock dev broker         | Connect with no credentials (auth is off)                                   |
| Authenticate with a KubeMQ identity         | Put the **JWT in the `Password` field**; `Username` is cosmetic             |
| Set a stable identity for ACL / RPC replies | Set a meaningful &#x2A;*`ClientID`** — it is the identity, not the username |
| Diagnose a rejected CONNECT                 | Look for CONNACK `0x86` (bad/empty JWT)                                     |
| Diagnose a rejected publish/subscribe       | Look for PUBACK / SUBACK `0x87` (ACL deny)                                  |

## Related [#related]

<Cards>
  <Card title="TLS and WebSocket" href="/connectors/mqtt/how-to/tls-and-websocket" description="Encrypt the credential in transit — tls:// on 8883 and ws:// on 8083, both carrying the same password-as-JWT CONNECT." />

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

  <Card title="Topic mapping" href="/connectors/mqtt/concepts/topic-mapping" description="The per-client $reply reply namespace and the prefix grammar the ACL is checked against." />
</Cards>
