Password or token.
Same account. No client changes.
A RabbitMQ auth backend that accepts a pre-issued HS256 JWT — the opaque token:<jwt> style used by Pulsar/AoP brokers — right alongside ordinary username/password auth, for the very same user. Built for teams migrating off a Pulsar-based broker onto native RabbitMQ without touching a single client.
One account can only hold one secret
RabbitMQ's internal backend stores a single password hash per user. After migrating from a Pulsar/AoP broker you have two kinds of clients on the same account — some send a password, some send a bearer token — and the built-in OAuth2 backend rejects the minimal {"sub":"…"} tokens these brokers issue. So the usual advice is "pick one and change the others." You can't always change the others.
internal only breaks token clients
Stored hash matches the password, so the token client — sending token:eyJ… — never matches and is refused. Or the reverse. One side always loses.
with this backend both connect
Password clients authenticate against internal; token clients are verified by signature. Same username, same permissions, no change on the wire.
A backend chain, decided by fall-through
RabbitMQ natively supports chaining authentication backends. The client just sends a username and one secret in the AMQP password field — it never declares which mode it's using. The broker tries each backend in order until one accepts.
token: prefix, verifies the JWT's HS256 signature with the configured key, takes sub as the identity.Two files, four lines
Enable the chain, then point the plugin at the issuer's signing key. Authorization stays with internal, so your existing users, vhosts and permissions are untouched.
rabbitmq.conf# try password first, then verify a token auth_backends.1 = internal auth_backends.2.authn = rabbit_auth_backend_aoptoken auth_backends.2.authz = internaladvanced.config
[
{rabbitmq_auth_backend_aoptoken, [
{key_file, "/etc/rabbitmq/token.key"}
]}
].
token — what the client already sends
% AMQP password field, unchanged: token:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcHAxIn0.<hs256-sig> % payload decodes to: {"sub":"app1"}
What it does, precisely
Verifies, doesn't allow-list
Every token is checked against the signing key with a constant-time HMAC compare. Any validly signed token works — including ones you never captured and ones issued later.
Token's subject is the identity
Authorization resolves against the sub claim, matching how Pulsar/AoP brokers behave — so a client whose login name differs from its token subject still lands on the right permissions.
Leaves authorization alone
The plugin authenticates; permissions stay in RabbitMQ's internal database. Import your definitions as usual — nothing about vhosts or grants changes.
No secret in the build
The signing key is read from a file path at runtime and never embedded in the plugin. Rotate keys and tokens on your own policy.
Install
- Build the plugin as an
.ezagainst your broker's version with the standard RabbitMQ plugin toolchain, and drop it intoplugins/. - Enable it:
rabbitmq-plugins enable rabbitmq_auth_backend_aoptoken - Set key_file in
advanced.configand the auth chain inrabbitmq.conf(see above). - Restart the node, then verify with both a password client and a token client on the same account.