Authentication
How a STOMP client authenticates to KubeMQ — the CONNECT login/passcode frame, passcode as a KubeMQ JWT, session ids, and per-channel Casbin authorization.
The KubeMQ STOMP connector authenticates a client once, at CONNECT time, using the STOMP
passcode header as a KubeMQ JWT. It then authorizes every SEND and SUBSCRIBE through the
server-wide Casbin authorizer. This guide covers the login / passcode part of the CONNECT frame
and what the connector does with it — for the full handshake (ports, heartbeats, TLS) see
Connectivity and security.
On a stock dev server, both authentication and authorization are off (the server default) — a
nil authenticator means allow-all. So the examples connect unauthenticated: leave passcode
empty (or set any value) and use a freeform login.
The no-auth default
When the server-wide Authentication block is disabled, the connector is wired with a nil
authenticator and skips the auth check entirely — any (or empty) passcode succeeds, and the
derived session id comes from login. This is the server default, so every example connects without
a token.
CONNECT
accept-version:1.2
heart-beat:10000,10000
login:my-app # freeform; only used to derive the session id when there are no claims
passcode: # empty in the default no-auth mode (any value also works)
^@passcode is a KubeMQ JWT — validated at CONNECT only
When authentication is enabled, the connector validates the passcode header as a KubeMQ JWT
during the handshake, before any data frame is processed:
| Aspect | Behavior |
|---|---|
| Credential | The passcode header is a KubeMQ JWT (not a password). |
| When validated | Connect-time only — the connector never re-validates a live connection. |
| Token expiry | A JWT that expires after CONNECT does not drop the live connection; there is no per-operation re-validation. |
| Failure | Audited as auth.failure, then ERROR "authentication failed" (no detail leak) + socket close. |
| Success | Audited as auth.success; the JWT's ClientID claim becomes the session id. |
Auth is connect-time only. Because the JWT is checked only during the handshake, a token that
expires mid-session keeps working until the client disconnects and reconnects. Do not rely on a
STOMP connection drop as a token-expiry signal — it will not happen. To rotate credentials, the
client must reconnect with a fresh passcode.
login derives the session id
The login header is freeform and is used purely to derive the client/session id when JWT
claims are absent. The derivation order is:
claims.ClientID— when authentication is on and the JWT carries a client id.stomp-<sanitized-login>— theloginvalue with characters outside[a-zA-Z0-9_\-.]stripped, truncated to 64 characters.stomp-<uuid8>— a random fallback when there is no usable login.
The derived id is returned to the client as the session header on the CONNECTED frame, and
is what surfaces as client_id in the management API and audit log.
STOMP has no client-id uniqueness rule. Two connections may share the same login (and
therefore the same derived id). The connector keeps all cross-connection state under a private,
always-unique key, so same-login fleets are the normal, supported case — they do not collide.
The host header is accepted and ignored
STOMP 1.1+ defines a host header for vhost selection. The KubeMQ connector accepts any value (or
none) and ignores it — there are no vhost semantics. Sending a host header will not scope,
isolate, or route your traffic.
Authorization — per-channel Casbin
When the server-wide authorizer is enabled, the connector runs a Casbin enforce check on every data frame:
| Frame | Check | Resource | Channel |
|---|---|---|---|
SEND | write | the pattern name (events / store / queues / commands / queries) | the resolved KubeMQ channel |
SUBSCRIBE (queues / events / store) | read | the pattern name | the resolved KubeMQ channel |
SUBSCRIBE to /reply/... | none — exempt | — | — |
The Resource is the pattern name (not the prefix), and the Channel is the resolved KubeMQ
channel after slash→dot mapping — /queue/orders/new enforces resource queues, channel
orders.new. A nil authorizer means allow-all. A denied frame produces ERROR "access denied" +
socket close.
RPC SEND is authorized as commands / queries. A SEND to /command/exec is a write enforce
against resource commands, channel exec. There is no separate authorization step for the reply:
it arrives on a /reply/ subscription, which is connection-local and exempt.
Why /reply/ is exempt
A /reply/<name> subscription is connection-local — it never touches a KubeMQ channel, has no
downstream channel to authorize, and exists only so the RPC bridge can deliver the matching reply on
the same connection. The connector therefore never consults Casbin for reply. This is what
lets an authorized requester receive its command or query reply without a second, separate grant.
Error semantics
Both authentication and authorization failures are terminal: the connector emits an ERROR
frame and closes the socket. The message is sanitized — internal error text never crosses the
wire.
| Condition | Wire message | Closes connection |
|---|---|---|
| JWT validation fails | authentication failed | Yes |
| Casbin denies SEND/SUBSCRIBE | access denied | Yes |
Treat every ERROR frame as terminal in client code.
Related
Connectivity and security
The full CONNECT handshake, ports 61613/61614, heartbeats, TLS, and the connection limit.
Destination mapping
How a destination resolves to the pattern and channel that authorization checks against.
Auth & security
The shared KubeMQ JWT model and TLS/mTLS concepts across connectors.
Was this page helpful?
Ack modes and receipts
STOMP delivery reliability on KubeMQ — the auto, client-individual, and client ack modes, ACK/NACK correlation, the 30s requeue timeout, and RECEIPT frames.
Commands
Requester-only RPC commands over STOMP — the /command/ destination prefix, the 3-step reply-to flow, and the stomp-error failure header on KubeMQ Commands.