KubeMQ
ConnectorsSTOMPHow-to guides

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:

AspectBehavior
CredentialThe passcode header is a KubeMQ JWT (not a password).
When validatedConnect-time only — the connector never re-validates a live connection.
Token expiryA JWT that expires after CONNECT does not drop the live connection; there is no per-operation re-validation.
FailureAudited as auth.failure, then ERROR "authentication failed" (no detail leak) + socket close.
SuccessAudited 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:

  1. claims.ClientID — when authentication is on and the JWT carries a client id.
  2. stomp-<sanitized-login> — the login value with characters outside [a-zA-Z0-9_\-.] stripped, truncated to 64 characters.
  3. 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:

FrameCheckResourceChannel
SENDwritethe pattern name (events / store / queues / commands / queries)the resolved KubeMQ channel
SUBSCRIBE (queues / events / store)readthe pattern namethe 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.

ConditionWire messageCloses connection
JWT validation failsauthentication failedYes
Casbin denies SEND/SUBSCRIBEaccess deniedYes

Treat every ERROR frame as terminal in client code.

Was this page helpful?

On this page