JWT Authorization

Hi,

While integrating HiveMQ + Enterprise security extension, I could effectively Authenticate a client using ory hydra locally.

Now, the next steps is to validate claims based on different clients (clientId) for a specific resource (one as a publisher and another one as subscriber).

Could you please guys provide a documentation how to configure scopes by different clients without using sql database? It is not clear even in the examples how to manage jwt authorization client credentials and I was wondering if there is a way for doing this.

From the example as follow, a jwt token was given to a client with scope = subscribe but in the configuration file was defined as “subscribe publish”. It doesn’t match, but I am not sure how to configure this per client or for accepting 3 types of scope:

  • subscribe
  • publish
  • subscribe publish
2023-11-22 10:04:55,124 DEBUG - An invalid JWT with jti 78415e1f-65c4-412b-a73a-0c03f52cf742 was sent. The "scp":"[subscribe]" does not match "[subscribe, publish]" ("[subscribe, publish]" before substitution).
2023-11-22 10:04:55,124 DEBUG - Client failed authentication: ID 0518693c-45f3-481b-83cd-a9ad6eb74551, IP 127.0.0.1, reason "unknown authentication key or wrong authentication secret".
2023-11-22 10:05:00,119 DEBUG - Client '0518693c-45f3-481b-83cd-a9ad6eb74551' with ip 127.0.0.1 could not be authenticated
    <pipelines>
        <listener-pipeline listener="ALL">
            <jwt-authentication-manager>
                <realm>jwt-provider</realm>
                <jwt-validation>
                    <reserved-claims>
                        <sub>${mqtt-clientid}</sub>
                        <scope alt="scp">subscribe publish</scope>
                    </reserved-claims>
                </jwt-validation>
            </jwt-authentication-manager>
            <allow-all-authorization-manager/>
        </listener-pipeline>
    </pipelines>

Can you may be help on this @Daria_H matters?

Thanks.

Hi @daniel.dg.gutierrez,

had to fiddle around locally but I think I have a solution for you. FYI: You will need an authorization manager.

Lets go, in <jwt-validation> don’t expect subscribe and publish in the scope, else each client then has to have those scopes. Just use a general scope you expect clients to have so that they are authenticated. For example with

 <scope alt="scp">mqtt</scope>

your client token now just need to have “mqtt” in the scope to be authenticated. But you can still add “role” based scopes like “publish” or “subscribe”.


As per documentation the scopes claim in the token is forwarded to the authorization manager as authorization-role-key.

Here the scope of token I used:
"scope": "subscribe mqtt publish",
and logging the role-key you can see the scope is reflected:
2023-12-06 21:32:15,070 DEBUG - The content of the {{subscribe}}{{mqtt}}{{publish}}

 <authorization-preprocessors>
    <logging-preprocessor>
        <message>The content of the ${authorization-role-key}</message>
        <level>debug</level>
        <name>com.example.logger</name>
    </logging-preprocessor>
</authorization-preprocessors>

Now you need to configure an authorization manager that defines what the client is allowed to do. Good thing you don’t need to have SQL database but can use a file realm instead (I used the file realm from the examples folder).

But as you can see via the access.log (in hivemq/log/access, managed by ESE) my client can connect and has role based permissions that it fetched from the file realm.

2023-12-06 20:32:15,069 UTC - authentication-succeeded - Client succeeded authentication: ID michi, IP 127.0.0.1.
2023-12-06 20:32:15,076 UTC - authorization-succeeded - Client succeeded authorization: ID michi, IP 127.0.0.1, permissions [Permission{topicFilter='topic-3', qos=[0, 1, 2], activity=[subscribe], retainedPublishAllowed=false, sharedSubscribeAllowed=false, sharedGroup='', from='subscribe'}, Permission{topicFilter='topic-1', qos=[0, 1, 2], activity=[publish], retainedPublishAllowed=false, sharedSubscribeAllowed=false, sharedGroup='', from='publish'}, Permission{topicFilter='topic-2', qos=[0, 1, 2], activity=[publish], retainedPublishAllowed=false, sharedSubscribeAllowed=false, sharedGroup='', from='publish'}].

Here my full ESE config, the file realm is from the examples folders of the extension:

<enterprise-security-extension
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="enterprise-security-extension.xsd"
        version="1">
    <realms>
        <file-realm>
            <name>file-realm</name>
            <enabled>true</enabled>
            <configuration>
                <file-path>conf/ese-file-realm.xml</file-path>
            </configuration>
        </file-realm>
        <jwt-realm>
            <name>Okta</name>
            <enabled>true</enabled>
            <configuration>
                <jwks-endpoint>https://dev-1234.okta.com/oauth2/default/v1/keys</jwks-endpoint>
                <introspection-endpoint>https://dev-1234.okta.com/oauth2/default/v1/introspect</introspection-endpoint>
                <simple-auth>
                    <username>asdf</username>
                    <password>qwertz</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>michi</aud>
                        <scope alt="scp">mqtt</scope>
                    </reserved-claims>
                </jwt-validation>
            </jwt-authentication-manager>
            <file-authorization-manager>
                <realm>file-realm</realm>
            </file-authorization-manager>
        </listener-pipeline>
    </pipelines>
</enterprise-security-extension>

Greetings,
Michael from the HiveMQ team

2 Likes

Hi @michael_w

Thank you for the reply and example. That works fine to me, and clarifies more how to use authorization manager pipeline. My only wondering after testing my custom example is, when defining a user within in the ese-file-realm.xml is that the value mqtt/users/user/name corresponds to clientId from the sub claim instead of username specified in the connect ack package.

Is there any workaround for this? because while using Ory Hydra for instance, it generates UUID as client identifier and that is not the user name itself.

Best regards,
Daniel.

I try to respond to how I understand your question.

As you can see in the image of my first response:

Information from the sub claim is used to populate the authorization-key ESE Variables.

And the authorization-key is used to fetch the matching user from a file realm.

So by default ESE will use the sub claim to look for matching users. But you can change this by using a copy preprocessor to overwrite the sub claim in the authorization-key with the MQTT client id, which is conveniently provided by ESE as a constant ESE variable.

Add this to your ESE config:

<authorization-preprocessors>
  <copy-string-to-string-preprocessor>
      <from>mqtt-clientid</from> <!--  is a constant that is provided by ESE -->
      <to>authorization-key</to>
  </copy-string-to-string-preprocessor>
</authorization-preprocessors>

Greetings,
Michael from the HiveMQ team

1 Like

This is neat,

Glad ESE variables can be used as exchange this values to the next pipeline. Useful by using mqtt-username variable and specified same as user name value.

Thank you again for the big help,
Daniel.