Integrating HiveMQ with Okta

Written by Georg Held

Category: HiveMQ IoT Security HiveMQ ESE Okta JWT OAuth

Published: November 27, 2019


The 1.4.0 release of our HiveMQ Enterprise Security Extension (ESE) adds support for JSON Web Tokens (JWT). This new feature lets you integrate HiveMQ into OAuth 2 / OpenID Connect deployments.

There are many providers of JWT authentication and authorization infrastructure that offer cloud and on premise solutions. We like Okta because it is one of the fastest to setup while still enabling in-depth customization.

Tooling and Requirements

Most of the work in this tutorial can be done with a modern web browser. Here are the additional tools that you will need:

  1. HiveMQ and the Enterprise Security Extension
  2. The MQTT CLI
  3. A command line HTTP client, we use HTTPie

Set up Okta

Okta is a cloud-based identity and access management platform. It supports the OAuth 2/OpenID Connect standards for token-based authentication and authorization in the web. HiveMQ ESE uses these standards, transposes them into the MQTT world, and integrates them into your enterprise MQTT deployment.

Get an Okta Developer Account

First, head over to developer.okta.com and select Create Free Account.

Okta developer landing page

After you enter your account information, accept the terms and conditions and select Get Started.

Okta account setup

Now, you should be redirected to the Okta developer console of your newly-created organisation.

Okta developer console dashboard

Note your organisational URL in the top right corner (directly under the red Upgrade button). This URL is unique to your Okta account and the basis for a lot of the API calls later in this tutorial. The URL should look somewhat like https://dev-123456.okta.com. Later, this URL is referred to as [OKTA DOMAIN].

Set Up an Application in Okta

Next, we set up an application in Okta. The application is the abstraction in the Okta developer console for a service that accepts tokens for authentication and/or authorization.

Switch to the Applications tab and select Add Application. The application creation wizard opens. Here, you can choose the type of application that you want to create. The application types are intrinsically linked to the different OAuth authentication flows that allow the user to generate tokens. The application type also determines the granularity of trust that a user can be granted.

For now, we will use a very simple setup and select the Service category.

Okta application type choice and selection

Select Next and enter a name for the application. For this tutorial, we enter ESE Service App. To confirm the configuration, select Done.

Now, you are redirected to the settings page of the newly created application. Here, two pieces of information are important: the Client ID and Client Secret. This information is sensitive for you setup. Copy both items somewhere and keep them safe. In this tutorial, we will refer to them as [OKTA CLIENT ID] and [OKTA CLIENT SECRET].

Okta application Client ID and Client secret

Configure the Authorization Server

In the next step, we adapt the default authorization server for our purposes.

Select the API > Authorization Servers tab. On this tab, you can see the authorization APIs that are active. Right now, only the default server should be active. To open the settings page of the default authorization server, select the pencil icon.

Default authorization server settings

To customize the default settings, select Edit. We change the Audience field to HiveMQ. After you press Save, note the Metatdata URI which is equal to [OKTA DOMAIN]/oauth2/default/.well-known/oauth-authorization-server. You will need this URI later when we configure the ESE realm.

Remain in the default server configuration and switch to the Scopes tab. To create a new scope, select Add Scope. Set the name of the new scope to mqtt and enable the Set as a default scope setting.

Okta MQTT custom scope creation

To verify your configuration, go to the Token Preview tab.

Enter the application name into the OAuth/OIDC client field and select Client Credentials for the Grant type. After you press Preview Token, you should see something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "ver": 1,
  "jti": "AT.OEtGJ_3L8O-g1-lGXlqdIXFSZJOsg99BqS0hzw0fpAk",
  "iss": "[OKTA DOMAIN]/oauth2/default",
  "aud": "HiveMQ",
  "iat": 1574689268,
  "exp": 1574692868,
  "cid": "0oa1xrpohhFiomar0357",
  "scp": [
    "mqtt"
  ],
  "sub": "0oa1xrpohhFiomar0357"
}

Note that the audience (aud) claim in line 5 is set to HiveMQ and the scopes (scp) claim in lines 9 - 11 contains the mqtt scope.

The setup on the Okta side is now complete. We continue with the configuration of our HiveMQ deployment.

Configure and Install HiveMQ ESE

If you have not already done so, now is the time to download and install the following:

  1. HiveMQ (once installed, stop HiveMQ again)
  2. The Enterprise Security Extension

There is no need to set up a SQL database for ESE. We will authenticate with JWTs.

Open the enterprise-security-extension.xml and change the default configuration to:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8" ?>
<enterprise-security-extension
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="enterprise-security-extension.xsd"
        version="1">
    <realms>
        <jwt-realm>
            <name>Okta</name>
            <enabled>true</enabled>
            <configuration>
                <jwks-endpoint>[OKTA DOMAIN]/oauth2/default/v1/keys</jwks-endpoint>
                <introspection-endpoint>[OKTA DOMAIN]/oauth2/default/v1/introspect</introspection-endpoint>
                <simple-auth>
                    <username>[OKTA CLIENT ID]</username>
                    <password>[OKTA CLIENT SECRET]</password>
                </simple-auth>
            </configuration>
        </jwt-realm>
    </realms>
    <pipelines>
        <listener-pipeline name="okta-pipeline" listener="ALL">
            <jwt-authentication-manager>
                <realm>Okta</realm>
                <jwt-validation>
                    <reserved-claims>
                        <aud>HiveMQ</aud>
                        <scope alt="scp">mqtt</scope>
                    </reserved-claims>
                </jwt-validation>
            </jwt-authentication-manager>
            <allow-all-authorization-manager/>
        </listener-pipeline>
    </pipelines>
</enterprise-security-extension>

Similar to how an Okta application is an abstraction for a token accepting service a ESE realm is an abstraction for an external trust-source. The configuration of the JWT realm with name Okta in lines 7 - 18 must match the parameters from the Okta authorization server and application.

You can always query (with a HTTP GET request) for the endpoints against the Metadata URI and copy-paste them:

1
http [OKTA DOMAIN]/oauth2/default/.well-known/oauth-authorization-server

The next part of the ESE configuration, lines 21 - 32, is a listener pipeline. A listener pipeline usually corresponds to a single listener in the HiveMQ config. For this tutorial we use the keyword ALL to tell the ESE that this pipeline should apply to every listener.

A ESE pipeline always consists of two steps, one for authentication and one for authorization. We use the configured Okta realm for the authentication step with the help of a JWT authentication manager. The authorization step utilizes the very permissive allow-all authorization manager, that grants the authenticated MQTT client the full scope of the MQTT broker.

The example configuration already uses one of the more advanced features: the reserved claims validation. The ESE will only accept JWTs as valid that have the aud claim set to HiveMQ and contain the mqtt scope. Of course, the basic necessities of a cryptographic signature and token expiry date are also validated.

Now, restart HiveMQ and wait for the HiveMQ log to show an output like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
-------------------------------------------------------------------------

                  _    _  _              __  __   ____
                 | |  | |(_)            |  \/  | / __ \
                 | |__| | _ __   __ ___ | \  / || |  | |
                 |  __  || |\ \ / // _ \| |\/| || |  | |
                 | |  | || | \ V /|  __/| |  | || |__| |
                 |_|  |_||_|  \_/  \___||_|  |_| \___\_\

-------------------------------------------------------------------------

  HiveMQ Start Script for Linux/Unix v1.10

-------------------------------------------------------------------------

  HIVEMQ_HOME: /Users/gheld/Work/servers/hivemq-4.2.1

  JAVA_OPTS:  -Djava.net.preferIPv4Stack=true -noverify --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED --add-exports java.base/jdk.internal.misc=ALL-UNNAMED -Djava.security.egd=file:/dev/./urandom -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -XX:+CrashOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/gheld/Work/servers/hivemq-4.2.1/heap-dump.hprof

  JAVA_VERSION: 11

-------------------------------------------------------------------------

...
2019-11-25 15:57:20,055 INFO  - Starting HiveMQ Enterprise Security Extension.
2019-11-25 15:57:20,404 INFO  - Access log is written to /Users/gheld/Work/servers/hivemq-4.2.1/log/access/access.log.
2019-11-25 15:57:20,409 INFO  - Started HiveMQ Enterprise Security Extension successfully in 354ms.
2019-11-25 15:57:20,409 INFO  - Extension "HiveMQ Enterprise Security Extension" version 1.4.0 started successfully.

This concludes the configuration of HiveMQ and the ESE.

Authenticate Clients with JWTs

Now, it is time to authenticate some MQTT clients. We use the mqtt-cli in shell mode for this:

1
2
mqtt sh
mqtt>

First, we try to connect without a valid token:

1
2
3
mqtt> con -i no-token-client
CONNECT failed as CONNACK contained an Error Code: NOT_AUTHORIZED.
mqtt>

Because the MQTT client did not provide a valid JWT, the connection attempt was not successful.

Now, we use the client_credentials authentication flow to generate a JWT from Okta. The flow is very simple and only requires a single HTTP POST request against the token endpoint. This request must contain the [OKTA CLIENT ID] and the [OKTA CLIENT SECRET] as HTTP simple auth header and grant_type client_credentials application/x-www-form-urlencoded in the body.

1
2
3
http -a [OKTA CLIENT ID]:[OKTA CLIENT SECRET] \
     -f POST [OKTA DOMAIN]/oauth2/default/v1/token \
     grant_type=client_credentials

In the response body, we receive a JSON object that contains the needed access token.

1
2
3
4
5
6
{
    "access_token": "eyJraWQiOiJlQzkzUTlJX0F4UjNGWkphVWdnNmxtWTlGVHAtWlNkQTJtQy10ZzZBWFVRIiwiYWxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULjdyUnlBV3VZSUFxN3ltN3AyVEpjRDdvUUVlZGk5V29hSEJFNU9BUW9TMjQiLCJpc3MiOiJodHRwczovL2Rldi01NTkwMTMub2t0YS5jb20vb2F1dGgyL2RlZmF1bHQiLCJhdWQiOiJIaXZlTVEiLCJpYXQiOjE1NzQ2OTgxMDAsImV4cCI6MTU3NDcwMTcwMCwiY2lkIjoiMG9hMXhycG9oaEZpb21hcjAzNTciLCJzY3AiOlsibXF0dCJdLCJzdWIiOiIwb2ExeHJwb2hoRmlvbWFyMDM1NyJ9.H8fwikW54CumDKfOqUuEVeJwyEhPCJPgqFlwhgsZSjoOrc4W-xldNXQNZhcoSRRCZLyRpsEYiTRysbUD52lng3jkN5qQATLzoADwRr1Hhb9w6wDPeP-FDpGT8UptXpZvoh9ctjXZLZn4UtLJOZZo7KH9kqSiVLruvTiWuN-F6AOhIxegfVLQTR_5VYyffziQPjPpYvOwngWyk_LZlrfFOLQa2ab5Qn9CUBnXJOuocJ6JUzUJEL51WvxJflMPNOTQqz1CRdidvpIQ0Sq_pckwTYJYsXHBpHjaYGGS2iJ5QccnLqc_2T3tJam0I09T7oryekFAwBP5f4y07PIM-aPexQ",
    "expires_in": 3600,
    "scope": "mqtt",
    "token_type": "Bearer"
}

You can inspect the token over at jwt.io and use it to authenticate a MQTT client:

1
2
mqtt> con -i token-client -u token-user -pw eyJraWQiOiJlQzkzUTlJX0F4UjNGWkphVWdnNmxtWTlGVHAtWlNkQTJtQy10ZzZBWFVRIiwiYWxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULjdyUnlBV3VZSUFxN3ltN3AyVEpjRDdvUUVlZGk5V29hSEJFNU9BUW9TMjQiLCJpc3MiOiJodHRwczovL2Rldi01NTkwMTMub2t0YS5jb20vb2F1dGgyL2RlZmF1bHQiLCJhdWQiOiJIaXZlTVEiLCJpYXQiOjE1NzQ2OTgxMDAsImV4cCI6MTU3NDcwMTcwMCwiY2lkIjoiMG9hMXhycG9oaEZpb21hcjAzNTciLCJzY3AiOlsibXF0dCJdLCJzdWIiOiIwb2ExeHJwb2hoRmlvbWFyMDM1NyJ9.H8fwikW54CumDKfOqUuEVeJwyEhPCJPgqFlwhgsZSjoOrc4W-xldNXQNZhcoSRRCZLyRpsEYiTRysbUD52lng3jkN5qQATLzoADwRr1Hhb9w6wDPeP-FDpGT8UptXpZvoh9ctjXZLZn4UtLJOZZo7KH9kqSiVLruvTiWuN-F6AOhIxegfVLQTR_5VYyffziQPjPpYvOwngWyk_LZlrfFOLQa2ab5Qn9CUBnXJOuocJ6JUzUJEL51WvxJflMPNOTQqz1CRdidvpIQ0Sq_pckwTYJYsXHBpHjaYGGS2iJ5QccnLqc_2T3tJam0I09T7oryekFAwBP5f4y07PIM-aPexQ
test-client@localhost>

If you look into the ESE access log you will see a failed connection attempt followed by a successful one:

1
2
3
4
cat log/access/access.log
2019-11-25 15:50:24,774 UTC - authentication-failed - Client failed authentication: ID no-token-client, IP 127.0.0.1, reason "unknown authentication key or wrong authentication secret".
2019-11-25 16:17:53,309 UTC - authentication-succeeded - Client succeeded authentication: ID token-client, IP 127.0.0.1.
2019-11-25 16:17:53,327 UTC - authorization-succeeded - Client succeeded authorization: ID token-client, IP 127.0.0.1, permissions [Permission{topicFilter='#', qos=[0, 1, 2], activity=[publish, subscribe], retainedPublishAllowed=true, sharedSubscribeAllowed=true, sharedGroup='', from='allow-all'}].

Conclusion and Next Steps

It is very easy to secure access to your MQTT broker with a cloud-based OAuth 2/OpenID Connect solution.

Here are some possible next steps:

About Georg Held

Georg is a software developer at HiveMQ. He is the main developer of the HiveMQ Enterprise Security Extension.
Contact Georg

HiveMQ ESE 1.4 released