Password or token.
Same account. No client changes.
A RabbitMQ auth backend that accepts a pre-issued 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 signature with the configured key (HS/RS/ES, per the token's alg), checks exp/nbf, and takes sub as the identity.One file, five lines
Enable the chain, then point the plugin at the issuer's signing key — both in the file you already edit. 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 = internal # symmetric secret for HS256/384/512 auth_aoptoken.key_file = /etc/rabbitmq/token.key # or a public key for RS*/ES*: # auth_aoptoken.public_key_file = /etc/rabbitmq/token-public.pem
Settings can also go in advanced.config as Erlang terms, which wins where the two overlap. Prefer the _file forms for secrets — rabbitmq.conf tends to end up in configuration management and backups.
Settings and defaults
| setting | default | when not configured |
|---|---|---|
key / key_base64 / key_file | none | HS* tokens refused, warning logged |
public_key / public_key_base64 / public_key_file | none | RS* / ES* tokens refused, warning logged |
audience | none | the aud claim is not checked |
leeway_seconds | 0 | no clock-skew tolerance for exp/nbf |
accept_bare_jwt | false | only token:-prefixed credentials are treated as tokens |
emit_events | true | set to false to stop publishing audit events |
There is no built-in key and no fallback secret or path. With no key configured the plugin refuses every token and leaves the rest of the chain alone, so internal password authentication keeps working.
% AMQP password field, unchanged: token:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcHAxIn0.<hs256-sig> % payload decodes to: {"sub":"app1"}
What it does, precisely
Verifies, doesn't allow-list
The algorithm comes from the token header: HMAC with a shared secret, or RSA/ECDSA with a public key — Pulsar's tokenSecretKey and tokenPublicKey modes. Any validly signed token works, including ones issued later. alg: none is rejected.
Claims are enforced
Expiry and not-before are honoured when present, with optional clock-skew leeway, and an expected audience can be required. Checked only after the signature verifies.
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.
Audit every token
Each verified token is published as an internal event: who authenticated, from where, prefixed or bare, and when the token expires. Read it through rabbitmq_event_exchange.
Install
- Download the
.ezfrom the latest release — pick the-otpNNmatching your broker's Erlang major version — and drop it intoplugins/. (Or build it yourself with the standard RabbitMQ plugin toolchain.) - Enable it:
rabbitmq-plugins enable rabbitmq_auth_backend_aoptoken - Set the key and the auth chain in
rabbitmq.conf(see above). - Restart the node, then verify with both a password client and a token client on the same account.