rabbit_auth_backend_aoptoken
RabbitMQ authentication plugin · Erlang

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.

Verified RabbitMQ 3.11–4.1 Auth HS256 · sub claim OTP 25–29 License MIT
The problem

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.

How it works

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.

client sends → username + secret  (AMQP PLAIN — mode not declared)
1
rabbit_auth_backend_internal
Hashes the secret with the user's salt and compares to the stored password.
match → password
2
rabbit_auth_backend_aoptoken (this plugin)
Sees the token: prefix, verifies the JWT's HS256 signature with the configured key, takes sub as the identity.
valid → token
authorization → internal
Whichever backend authenticated, vhost and resource permissions come from the identity's existing internal grants.
delegated
no backend accepted
Wrong password and no valid token signature.
refused
Configuration

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 = internal
advanced.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"}
Details

What it does, precisely

signature, not a list

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.

identity = sub

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.

authn only

Leaves authorization alone

The plugin authenticates; permissions stay in RabbitMQ's internal database. Import your definitions as usual — nothing about vhosts or grants changes.

key stays server-side

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.

Getting started

Install

  1. Build the plugin as an .ez against your broker's version with the standard RabbitMQ plugin toolchain, and drop it into plugins/.
  2. Enable it: rabbitmq-plugins enable rabbitmq_auth_backend_aoptoken
  3. Set key_file in advanced.config and the auth chain in rabbitmq.conf (see above).
  4. Restart the node, then verify with both a password client and a token client on the same account.