Skip to main content

OAuth 2.0 Authentication Backend

Overview

This RabbitMQ authentication/authorisation backend plugin lets applications (clients) and users authenticate and authorize using JWT-encoded OAuth 2.0 access tokens.

This guide covers

How it works

The OAuth 2 plugin must be enabled (or pre-configured) before it can be used, like all other plugins:

rabbitmq-plugins enable rabbitmq_auth_backend_oauth2

Then it must be specified as one of the authN and authZ backends. It can be one of the backends or the only one backend, like in the example below:

# note that the module name begins with a "rabbit_", not "rabbitmq_", like in the name
# of the plugin
auth_backends.1 = rabbit_auth_backend_oauth2

Next, let's take a look at the workflows the OAuth 2 plugin supports.

Authorization Workflow

This plugin does not communicate with any OAuth 2.0 provider. It decodes an access token provided by the client and authorises a user based on the data stored in the token.

The token can be any JWT token which contains the scope and aud fields. The way the token was issued (such as what grant type was used) is outside of the scope of this plugin.

Prerequisites

To use this plugin, all RabbitMQ nodes must be

  1. configured to use the rabbit_auth_backend_oauth2 backend.
  2. configured with a resource service ID (resource_server_id) that matches the scope prefix (e.g. rabbitmq in rabbitmq.read:*/*).
  3. configured with a signing key used by RabbitMQ to validate the JWT token signatures.

JWT Tokens presented to RabbitMQ for authentication must

  1. be digitally signed with either a symmetric or asymmetric key.
  2. have a value in the aud field that matches resource_server_id value.

Authorization Flow

  1. Client requests an access_token from the OAuth 2.0 provider,
  2. Token scope returned by OAuth 2.0 provider must include RabbitMQ resource scopes that follow a convention used by this plugin: configure:%2F/foo means "configure permissions for 'foo' in vhost '/'") (scope field can be changed using extra_scopes_source in advanced.config file.
  3. Client passes the token as password when connecting to a RabbitMQ node. The username field is ignored.
  4. The translated permissions are stored as part of the authenticated connection state and used the same way permissions from RabbitMQ's internal database would be used.

Usage

The plugin needs a signing key to be configured in order to verify the token's signature. This is the signing key used by the OAuth 2.0 provider to sign the tokens. RabbitMQ supports two types of signing keys: symmetric and asymmetric.

The examples given below uses Cloud Foundry UAA as OAuth 2.0 provider.

To get the signing key from the OAuth 2.0 provider UAA, use the token_key endpoint or uaac (the uaac signing key command).

The following fields are required: kty, value, alg, and kid.

Assuming UAA reports the following signing key information:

uaac signing key
kty: RSA
e: AQAB
use: sig
kid: a-key-ID
alg: RS256
value: -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2dP+vRn+Kj+S/oGd49kq
6+CKNAduCC1raLfTH7B3qjmZYm45yDl+XmgK9CNmHXkho9qvmhdksdzDVsdeDlhK
IdcIWadhqDzdtn1hj/22iUwrhH0bd475hlKcsiZ+oy/sdgGgAzvmmTQmdMqEXqV2
B9q9KFBmo4Ahh/6+d4wM1rH9kxl0RvMAKLe+daoIHIjok8hCO4cKQQEw/ErBe4SF
2cr3wQwCfF1qVu4eAVNVfxfy/uEvG3Q7x005P3TcK+QcYgJxav3lictSi5dyWLgG
QAvkknWitpRK8KVLypEj5WKej6CF8nq30utn15FQg0JkHoqzwiCqqeen8GIPteI7
VwIDAQAB
-----END PUBLIC KEY-----
n: ANnT_r0Z_io_kv6BnePZKuvgijQHbggta2i30x-wd6o5mWJuOcg5fl5oCvQjZh15IaPar5oXZLHcw1bHXg5YSiHXCFmnYag83bZ9YY_9tolMK4R9G3eO-YZSnLImfqMv7HYBoAM75pk0JnTKhF6ldgfavShQZqOAIYf-vneMDNax_ZMZdEbzACi3vnWqCByI6JPIQju
HCkEBMPxKwXuEhdnK98EMAnxdalbuHgFTVX8X8v7hLxt0O8dNOT903CvkHGICcWr95YnLUouXcli4BkAL5JJ1oraUSvClS8qRI-Vino-ghfJ6t9LrZ9eRUINCZB6Ks8Igqqnnp_BiD7XiO1c

it will translate into the following configuration (in the advanced RabbitMQ config format):

[
%% ...
%% backend configuration
{rabbitmq_auth_backend_oauth2, [
{resource_server_id, <<"my_rabbit_server">>},
%% UAA signing key configuration
{key_config, [
{signing_keys, #{
<<"a-key-ID">> => {pem, <<"-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2dP+vRn+Kj+S/oGd49kq
6+CKNAduCC1raLfTH7B3qjmZYm45yDl+XmgK9CNmHXkho9qvmhdksdzDVsdeDlhK
IdcIWadhqDzdtn1hj/22iUwrhH0bd475hlKcsiZ+oy/sdgGgAzvmmTQmdMqEXqV2
B9q9KFBmo4Ahh/6+d4wM1rH9kxl0RvMAKLe+daoIHIjok8hCO4cKQQEw/ErBe4SF
2cr3wQwCfF1qVu4eAVNVfxfy/uEvG3Q7x005P3TcK+QcYgJxav3lictSi5dyWLgG
QAvkknWitpRK8KVLypEj5WKej6CF8nq30utn15FQg0JkHoqzwiCqqeen8GIPteI7
VwIDAQAB
-----END PUBLIC KEY-----">>}
}}
]}
]}
].

If a symmetric key is used, the configuration will look like this:

[
{rabbitmq_auth_backend_oauth2, [
{resource_server_id, <<"my_rabbit_server">>},
{key_config, [
{signing_keys, #{
<<"a-key-ID">> => {map, #{<<"kty">> => <<"MAC">>,
<<"alg">> => <<"HS256">>,
<<"value">> => <<"my_signing_key">>}}
}}
]}
]},
].

The key set can also be retrieved dynamically from a URL serving a JWK Set. In that case, the configuration will look like this:

[
{rabbitmq_auth_backend_oauth2, [
{resource_server_id, <<"my_rabbit_server">>},
{key_config, [
{jwks_url, <<"https://my-jwt-issuer/jwks.json">>}
]}
]},
].

NOTE: jwks_url takes precedence over signing_keys if both are provided.

Variables Configurable in rabbitmq.conf

KeyDocumentation
auth_oauth2.resource_server_idThe Resource Server ID
auth_oauth2.resource_server_typeThe Resource Server Type
auth_oauth2.additional_scopes_keyConfigure the plugin to also look in other fields (maps to additional_rabbitmq_scopes in the old format).
auth_oauth2.scope_prefixConfigure prefix for all scopes. Default value is auth_oauth2.resource_server_id followed by the dot . character.
auth_oauth2.preferred_username_claimsList of JWT claims to look for username associated to the token separated by commas.
auth_oauth2.default_keyID of the default signing key.
auth_oauth2.signing_keysPaths to signing key files.
auth_oauth2.jwks_urlThe URL of key server. According to the JWT Specification key server URL must be https.
auth_oauth2.https.cacertfilePath to a file containing PEM-encoded CA certificates. The CA certificates are used during key server peer verification.
auth_oauth2.https.depthThe maximum number of non-self-issued intermediate certificates that may follow the peer certificate in a valid certification path. Default is 10.
auth_oauth2.https.peer_verificationShould peer verification be enabled. Available values: verify_none, verify_peer. Default is verify_none. It is recommended to configure verify_peer. Peer verification requires a certain amount of setup and is more secure.
auth_oauth2.https.fail_if_no_peer_certUsed together with auth_oauth2.https.peer_verification = verify_peer. When set to true, TLS connection will be rejected if client fails to provide a certificate. Default is false.
auth_oauth2.https.hostname_verificationEnable wildcard-aware hostname verification for key server. Available values: wildcard, none. Default is none.
auth_oauth2.algorithmsRestrict the usable algorithms.
auth_oauth2.verify_audVerify token's aud.

For example:

Configure with key files

auth_oauth2.resource_server_id = new_resource_server_id
auth_oauth2.additional_scopes_key = my_custom_scope_key
auth_oauth2.preferred_username_claims.1 = username
auth_oauth2.preferred_username_claims.2 = user_name
auth_oauth2.default_key = id1
auth_oauth2.signing_keys.id1 = test/config_schema_SUITE_data/certs/key.pem
auth_oauth2.signing_keys.id2 = test/config_schema_SUITE_data/certs/cert.pem
auth_oauth2.algorithms.1 = HS256
auth_oauth2.algorithms.2 = RS256

Configure with key server

auth_oauth2.resource_server_id = new_resource_server_id
auth_oauth2.jwks_url = https://my-jwt-issuer/jwks.json
auth_oauth2.https.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
auth_oauth2.https.peer_verification = verify_peer
auth_oauth2.https.depth = 5
auth_oauth2.https.fail_if_no_peer_cert = true
auth_oauth2.https.hostname_verification = wildcard
auth_oauth2.algorithms.1 = HS256
auth_oauth2.algorithms.2 = RS256

Resource Server ID and scope prefix

OAuth 2.0 tokens use scopes to communicate what set of permissions particular client has been granted. The scopes are free form strings.

By default, resource_server_id followed by the dot (.) character is the prefix used for scopes to avoid scope collisions (or unintended overlap). However, in some environments, it is not possible to use resource_server_id as the prefix for all scopes. For these environments, there is a new setting called scope_prefix which overrides the default scope prefix. Empty strings are allowed.

Given the below configuration, the scope associated to the permission read:*/* is api://read:*/*.

...
auth_oauth2.scope_prefix = api://
...

Token validation

When RabbitMQ receives a JWT token, it validates it before accepting it.

Must be digitally signed

The token must carry a digital signature and optionally a kid header attribute which identifies the key RabbitMQ should use to validate the signature.

Must not be expired

RabbitMQ uses this field exp (exp) to validate the token if present. It contains the expiration time after which the JWT MUST NOT be accepted for processing.

Audience must have/match the resource_server_id

The aud (Audience) identifies the recipients and/or resource_server of the JWT. By default, RabbitMQ uses this field to validate the token although you can deactivate it by setting verify_aud to false. When it set to true, this attribute must either match the resource_server_id setting or in case of a list, it must contain the resource_server_id.

Scope-to-Permission Translation

Scopes are translated into permission grants to RabbitMQ resources for the provided token.

The current scope format is <permission>:<vhost_pattern>/<name_pattern>[/<routing_key_pattern>] where

  • <permission> is an access permission (configure, read, or write)
  • <vhost_pattern> is a wildcard pattern for vhosts token has access to.
  • <name_pattern> is a wildcard pattern for resource name
  • <routing_key_pattern> is a wildcard pattern for routing key in topic authorization

Wildcard patterns are strings with optional wildcard symbols * that match any sequence of characters.

Wildcard patterns match as following:

  • * matches any string
  • foo* matches any string starting with a foo
  • *foo matches any string ending with a foo
  • foo*bar matches any string starting with a foo and ending with a bar

There can be multiple wildcards in a pattern:

  • start*middle*end
  • *before*after*

To use special characters like *, %, or / in a wildcard pattern, the pattern must be URL-encoded.

These are the typical permissions examples:

  • read:*/*(read:*/*/*) - read permissions to any resource on any vhost
  • write:*/*(write:*/*/*) - write permissions to any resource on any vhost
  • read:vhost1/*(read:vhost1/*/*) - read permissions to any resource on the vhost1 vhost
  • read:vhost1/some* - read permissions to all the resources, starting with some on the vhost1 vhost
  • write:vhost1/some*/routing* - topic write permissions to publish to an exchange starting with some with a routing key starting with routing
  • read:*/*/* and write:*/*/* - queue binding permissions required to bind a queue on a topic exchange with any routing key

See the wildcard matching test suite and scopes test suite for more examples.

Scopes, by default, are prefixed with resource_server_id followed by the dot (.) character if scope_prefix is not configured. For example, if resource_server_id is "my_rabbit", a scope to enable read from any vhost will be my_rabbit.read:*/*.

If scope_prefix is configured then scopes are prefixed as follows: <scope_prefix><permission>. For example, if scope_prefix is api:// and the permission is read:*/* the scope would be api://read:*/*

Topic Exchange scopes

The previous section explained, in detail, how permissions are mapped to scopes. This section explains more specifically what scopes you need in order to operate on Topic Exchanges.

To bind and/or unbind a queue to/from a Topic Exchange, you need to have the following scopes:

  • write permission on the queue and routing key -> rabbitmq.write:<vhost>/<queue>/<routingkey>

e.g. rabbitmq.write:*/*/*

  • read permission on the exchange and routing key -> rabbitmq.write:<vhost>/<exchange>/<routingkey>

e.g. rabbitmq.read:*/*/*

To publish to a Topic Exchange, you need to have the following scope:

  • write permission on the exchange and routing key -> rabbitmq.write:<vhost>/<exchange>/<routingkey>

e.g. rabbitmq.write:*/*/*

OAuth 2.0 authorisation backend supports variable expansion when checking permission on topics. It supports JWT claims whose value is a plain string, plus the vhost variable.

For example, a user connected with the token below to the vhost prod should have a write permission on all exchanges starting with x-prod-, and any routing key starting with u-bob-:

{
"sub" : "bob",
"scope" : [ "rabbitmq.write:*/q-{vhost}-*/u-{sub}-*" ]
}

Using a different token field for the Scope

By default the plugin will look for the scope key in the token, you can configure the plugin to also look in other fields using the extra_scopes_source setting. Values format accepted are scope as string or list

[
{rabbitmq_auth_backend_oauth2, [
{resource_server_id, <<"my_rabbit_server">>},
{extra_scopes_source, <<"my_custom_scope_key">>},
...
]}
]},
].

Token sample:

{
"exp": 1618592626,
"iat": 1618578226,
"aud" : ["my_id"],
...
"scope_as_string": "my_id.configure:*/* my_id.read:*/* my_id.write:*/*",
"scope_as_list": ["my_id.configure:*/*", "my_id.read:*/*", my_id.write:*/*"],
...
}

Using Tokens with Clients

A client must present a valid access_token acquired from an OAuth 2.0 provider (such as UAA) as the password in order to authenticate with RabbitMQ.

To learn more about OAuth 2.0 clients, see the OAuth 2.0 client specification.

Scope and Tags

Users in RabbitMQ can have tags associated with them. Tags are used to control access to the management plugin.

In the OAuth context, tags can be added as part of the scope, using a format like <resource_server_id>.tag:<tag>. For example, if resource_server_id is "my_rabbit", a scope to grant access to the management plugin with the monitoring tag will be my_rabbit.tag:monitoring.

Preferred username claims

The username associated with the token must be available to RabbitMQ so that this username is displayed in the RabbitMQ Management UI. By default, RabbitMQ searches for the sub claim first, and if it is not found, RabbitMQ uses the client_id.

Most authorization servers return the user's GUID in the sub claim instead of the user's username or email address, anything the user can relate to. When the sub claim does not carry a user-friendly username, you can configure one or several claims to extract the username from the token.

Example advanced.config configuration:

  ...
{rabbitmq_auth_backend_oauth2, [
{resource_server_id, <<"rabbitmq">>},
{preferred_username_claims, [<<"user_name">>,<<"email">>]},
...

In the example configuration, RabbitMQ searches for the user_name claim first and if it is not found, RabbitMQ searches for the email. If these are not found, RabbitMQ uses its default lookup mechanism which first looks for sub and then client_id.

Token Expiration and Refresh

On an existing connection the token can be refreshed by the update-secret AMQP 0.9.1 method. Please check your client whether it supports this method. (Eg. see documentation of the Java client.) Otherwise the client has to disconnect and reconnect to use a new token.

If the latest token expires on an existing connection, after a limited time the broker will refuse all operations (but it won't disconnect).

Rich Authorization Request

The Rich Authorization Request extension provides a way for OAuth clients to request fine-grained permissions during an authorization request. It moves away from the concept of scopes that are text labels and instead defines a more sophisticated permission model.

RabbitMQ supports JWT tokens compliant with the extension. Below is a sample example section of JWT token:

{
"authorization_details": [
{
"type" : "rabbitmq",
"locations": ["cluster:finance/vhost:production-*"],
"actions": [ "read", "write", "configure" ]
},
{
"type" : "rabbitmq",
"locations": ["cluster:finance", "cluster:inventory" ],
"actions": ["administrator" ]
}
]
}

The token above contains two permissions under the attribute authorization_details. Both permissions are meant for RabbitMQ servers with resource_server_type set to rabbitmq. This field identifies RabbitMQ-specific permissions.

The first permission grants read, write and configure permissions to any queue and/or exchange on any virtual host whose name matches the pattern production-*, and that reside in clusters whose resource_server_id contains the string finance. The cluster attribute's value is also a regular expression. To match exactly the string finance, use ^finance$.

The second permission grants the administrator user tag in two clusters, finance and inventory. Other supported user tags as management, policymaker and monitoring.

Type field

In order for a RabbitMQ node to accept a permission, its value must match that node's resource_server_type setting value. A JWT token may have permissions for multiple resource types.

Locations field

The locations field can be either a string containing a single location or a Json array containing zero or many locations.

A location consists of a list of key-value pairs separated by forward slash / character. Here is the format:

cluster:<resource_server_id_pattern>[/vhost:<vhost_pattern>][/queue:<queue_name_pattern>|/exchange:<exchange_name_pattern][/routing-key:<routing_key_pattern>]

Any string separated by / which does not conform to <key>:<value> is ignored. For instance, if your locations start with a prefix, e.g. vrn/cluster:rabbitmq, the vrn pattern part is ignored.

The supported location's attributed are:

  • cluster: This is the only mandatory attribute. It is a wildcard pattern which must match RabbitMQ's resource_server_id otherwise the location is ignored.
  • vhost: This is the virtual host you are granting access to. It also a wildcard pattern. If not specified, * will be used.
  • queue|exchange: queue or exchange name pattern. The location grants the permission to a set of queues (or exchanges) that match it. One location can only specify either queue or exchange but not both. If not specified, * will be used
  • routing-key: this is the routing key pattern the location grants the permission to. If not specified, * will be used

For more information about wildcard patterns, check the section Scope-to-Permission Translation.

Actions field

The actions field can be either a string containing a single action or a Json array containing zero or many actions.

The supported actions map to either RabbitMQ permissions:

  • configure
  • read
  • write

Or RabbitMQ user tags:

  • administrator
  • monitoring
  • management
  • policymaker

Rich-Permission to Scope translation

Rich Authorization Request permissions are translated into JWT token scopes that use the aforementioned convention using the following algorithm:

For each location found in the locations where the cluster attribute matches the current RabbitMQ server's resource_server_id:

  • For each location found in the locations field where the cluster attribute matches the current RabbitMQ node's resource_server_id, the plugin extracts the vhost, queue or exchange and routing_key attributes from the location. If the location does not have any of those attributes, the default value of * is assumed. Out of those values, the following scope suffix will be produced:

    scope_suffix = <vhost>/<queue>|<exchange>/<routing-key>
  • For each action found in the actions field:

    if the action is not a known user tag, the following scope is produced out of it:

      scope = <resource_server_id>.<action>:<scope_suffix>

    For known user tag actions, the following scope is produced:

      scope = <resource_server_id>.<action>

The plugin produces permutations of all actions by all locations that match the node's configured resource_server_id.

In the following RAR example

{
"authorization_details": [
{ "type" : "rabbitmq",
"locations": ["cluster:finance/vhost:primary-*"],
"actions": [ "read", "write", "configure" ]
},
{ "type" : "rabbitmq",
"locations": ["cluster:finance", "cluster:inventory" ],
"actions": ["administrator" ]
}
]
}

if RabbitMQ node's resource_server_id is equal to finance, the plugin will compute the following sets of scopes:

  • finance.read:primary-*/*/*
  • finance.write:primary-*/*/*
  • finance.configure:primary-*/*/*
  • finance.tag:administrator

Examples

The RabbitMQ OAuth 2.0 Auth Backend Examples contains many example configuration files which can be used to set up several OAuth 2.0 providers, including UAA, Auth0, and Azure, and issue tokens, which can be used to access RabbitMQ resources.