# Authentication, Authorization and ClientInitializer

**URL:** https://community.hivemq.com/t/authentication-authorization-and-clientinitializer/317
**Category:** HiveMQ Extension SDK
**Created:** [August 24, 2020, 12:13pm UTC](https://community.hivemq.com/t/authentication-authorization-and-clientinitializer/317 "2020-08-24T12:13:12Z")
**Posts on this page:** 6
**Page:** 1

<div class="post-metadata">

### Author: ![Misiu](https://yyz1.discourse-cdn.com/flex035/user_avatar/community.hivemq.com/misiu/32/75_2.png) [@Misiu](https://community.hivemq.com/u/Misiu)
#### Post date: [August 24, 2020, 12:13pm UTC](https://community.hivemq.com/t/authentication-authorization-and-clientinitializer/317/1 "2020-08-24T12:13:13Z")

</div>

This topic is a continuation of [Store massages in MsSQL](https://community.hivemq.com/t/store-massages-in-mssql/301), but this time about security.

I’m building a simple extension that will read the username and password from the DB.  
I’ve based my code on [https://github.com/hivemq/hivemq-file-rbac-extension](https://github.com/hivemq/hivemq-file-rbac-extension)

I have some questions about authentication and authorization.

Take a look at [https://github.com/hivemq/hivemq-file-rbac-extension/blob/96009fc4a3353857dea8ee1881b06ba265e0481c/src/main/java/com/hivemq/extensions/rbac/FileAuthAuthenticator.java#L97-L101](https://github.com/hivemq/hivemq-file-rbac-extension/blob/96009fc4a3353857dea8ee1881b06ba265e0481c/src/main/java/com/hivemq/extensions/rbac/FileAuthAuthenticator.java#L97-L101)

Here topic permissions are set when doing authentication.  
Does it mean that I can skip authorization and do everything during authentication?

What is the best practice?

Should I add permissions to the authenticator? Like shown here [https://github.com/hivemq/hivemq-file-rbac-extension/blob/96009fc4a3353857dea8ee1881b06ba265e0481c/src/main/java/com/hivemq/extensions/rbac/FileAuthAuthenticator.java#L98](https://github.com/hivemq/hivemq-file-rbac-extension/blob/96009fc4a3353857dea8ee1881b06ba265e0481c/src/main/java/com/hivemq/extensions/rbac/FileAuthAuthenticator.java#L98)?

When can I use `ClientInitializer` for?

My use case is as follows:

- I have users and devices.
- Each user can own any number of devices
- The device can only publish to their channels (channel `d-{device-id}/temp`)
- The user can subscribe to any or all channels that the devices he owns publish. SO if he has two devices he can subscribe to `d-1/temp` and `d-2/temp`, but also to `+/temp`, this will be used for WebSocket connection (to see live data changes)

My first idea was to call the database in the authenticator and store devices id’s in `ConnectionAttributeStore` and validate topics in authorizer, but if I can do everything during authentication then I will be able to avoid creating authorizer.

Also, I’m trying to implement both `SubscriptionAuthorizer` and `PublishAuthorizer`, but IntelliJ shows me an error for `getTopicFilter` function. My code is based on [https://www.hivemq.com/docs/hivemq/4.4/extensions/authorization.html#\_example\_usage\_3](https://www.hivemq.com/docs/hivemq/4.4/extensions/authorization.html#_example_usage_3)

 ![image](https://canada1.discourse-cdn.com/flex035/uploads/hivemq1/original/1X/f125274843e60d4ccc54a9679d7911b24b78c96b.png)

Is the sample in the docs incomplete?  
EDIT:  
the sample in the docs is wrong.  
It should be:  
`if (input.getSubscription().getTopicFilter().startsWith("admin")) {`  
instead of  
`if (input.getTopicFilter().startsWith("admin")) {`

---

<div class="post-metadata">

### Author: ![Misiu](https://yyz1.discourse-cdn.com/flex035/user_avatar/community.hivemq.com/misiu/32/75_2.png) [@Misiu](https://community.hivemq.com/u/Misiu)
#### Post date: [August 24, 2020, 1:17pm UTC](https://community.hivemq.com/t/authentication-authorization-and-clientinitializer/317/2 "2020-08-24T13:17:18Z")

</div>

While trying to implement both `SubscriptionAuthorizer` and `PublishAuthorizer` I can’t get `authorizeSubscribe` to work.  
`authorizePublish` works fine, but the second isn’t.  
@FloLi any ideas why? I’m using Community Edition (if that changes something).

```
package com.test;

import com.hivemq.extension.sdk.api.annotations.NotNull;
import com.hivemq.extension.sdk.api.auth.PublishAuthorizer;
import com.hivemq.extension.sdk.api.auth.SubscriptionAuthorizer;
import com.hivemq.extension.sdk.api.auth.parameter.PublishAuthorizerInput;
import com.hivemq.extension.sdk.api.auth.parameter.PublishAuthorizerOutput;
import com.hivemq.extension.sdk.api.auth.parameter.SubscriptionAuthorizerInput;
import com.hivemq.extension.sdk.api.auth.parameter.SubscriptionAuthorizerOutput;
import com.hivemq.extension.sdk.api.client.parameter.ConnectionAttributeStore;
import com.hivemq.extension.sdk.api.packets.disconnect.DisconnectReasonCode;
import com.hivemq.extension.sdk.api.packets.general.UserProperties;
import com.hivemq.extension.sdk.api.packets.publish.PublishPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Optional;

public class DatabaseAuthorizer implements SubscriptionAuthorizer, PublishAuthorizer {

    private static final @NotNull
    Logger log = LoggerFactory.getLogger(DatabaseAuthorizer.class);

    @Override
    public void authorizeSubscribe(@NotNull SubscriptionAuthorizerInput input, @NotNull SubscriptionAuthorizerOutput output) {

        log.info("authorizeSubscribe");

        final ConnectionAttributeStore attributeStore = input.getConnectionInformation().getConnectionAttributeStore();
        Optional<String> deviceId = attributeStore.getAsString("deviceId");

        if(!deviceId.isPresent()){
            log.info("No deviceId in ConnectionAttributeStore");
        } else
        {
            log.info("DeviceId in ConnectionAttributeStore = '{}'", deviceId.get());
        }

        //allow every Topic Filter starting with "admin"
        if (input.getSubscription().getTopicFilter().startsWith("admin")) {
            output.authorizeSuccessfully();
            return;
        }

        //disallow a shared subscription
        if (input.getSubscription().getTopicFilter().startsWith("$shared")) {
            output.failAuthorization();
            return;
        }

        //get the user properties from the SUBSCRIBE packet
        final UserProperties userProperties = input.getUserProperties();

        //disconnect all clients with a user property "notallowed" set.
        if (userProperties.getFirst("notallowed").isPresent()) {
            //Use a custom reason code and reason string for the server sent DISCONNECT
            output.disconnectClient(DisconnectReasonCode.ADMINISTRATIVE_ACTION, "User property not allowed");
            return;
        }

        //otherwise let the other extension or default permissions decide
        output.nextExtensionOrDefault();
    }

    @Override
    public void authorizePublish(@NotNull PublishAuthorizerInput input, @NotNull PublishAuthorizerOutput output) {

        log.info("authorizePublish");

        //get the PUBLISH packet contents
        final PublishPacket publishPacket = input.getPublishPacket();

        //allow every Topic Filter starting with "admin"
        if (publishPacket.getTopic().startsWith("admin")) {
            output.authorizeSuccessfully();
            return;
        }

        //disallow publishes to forbidden topics
        if (publishPacket.getTopic().startsWith("forbidden")) {
            output.failAuthorization();
            return;
        }

        //get the user properties from the PUBLISH packet
        final UserProperties userProperties = publishPacket.getUserProperties();

        //disconnect all clients with a user property "notallowed" set.
        if (userProperties.getFirst("notallowed").isPresent()) {
            //Use a custom reason code and reason string for the server sent DISCONNECT
            output.disconnectClient(DisconnectReasonCode.ADMINISTRATIVE_ACTION, "User property not allowed");
            return;
        }

        //otherwise let the other extension or default permissions decide
        output.nextExtensionOrDefault();
    }
}

```

When I try to publish I see in log `authorizePublish` but when I try to subscribe I don’t see `authorizeSubscribe`.

I register my class like this:  
`securityRegistry.setAuthorizerProvider(authorizerProviderInput -> new DatabaseAuthorizer());`

---

<div class="post-metadata">

### Author: ![FloLi](https://yyz1.discourse-cdn.com/flex035/user_avatar/community.hivemq.com/floli/32/17_2.png) [@FloLi](https://community.hivemq.com/u/FloLi)
#### Post date: [August 25, 2020, 8:04am UTC](https://community.hivemq.com/t/authentication-authorization-and-clientinitializer/317/3 "2020-08-25T08:04:30Z")

</div>

Hi misiu,

Thanks for the advice for the documentation issue. 🙂

> [@Misiu](#):
>
> Does it mean that I can skip authorization and do everything during authentication?

Yes, thats correct.

> [@Misiu](#):
>
> What is the best practice?

Depends on your usecase.

Simple authentication (username,password) will be called once and for updates you need to reconnect, the `CONNECT` performance will decrease but on the other hand the `PUBLISH` / `SUBSCRIBE` performance would increase if everything will be done in authentication.

Let’s say if your clients do not need live updates for the permissions and they publish/subscribe very often compared to connect/disconnect than authentication would be a good way.

> [@Misiu](#):
>
> When can I use `ClientInitializer` for?

You can use a `ClientInitializer` to add any kind of `Interceptor` like: [Interceptors :: HiveMQ Documentation](https://www.hivemq.com/docs/hivemq/4.4/extensions/interceptors.html#publish-inbound-interceptor)

the code looks totally fine. This should work.  
Do you have any other extension activated that has a SubscriptionAuthorizer?  
This could cause that the other one will never be called.

Kind regards,  
Flo

---

<div class="post-metadata">

### Author: ![Misiu](https://yyz1.discourse-cdn.com/flex035/user_avatar/community.hivemq.com/misiu/32/75_2.png) [@Misiu](https://community.hivemq.com/u/Misiu)
#### Post date: [August 25, 2020, 8:55am UTC](https://community.hivemq.com/t/authentication-authorization-and-clientinitializer/317/4 "2020-08-25T08:55:01Z")

</div>

Thank you for reply.  
I’ve created a new extension and copy/paste code from my old one. Now everything works fine 🙂  
For now I’m only adding log messages in `authorizePublish` and `authorizeSubscribe`  
Now I need to do the logic.

I have one more question, this time more Java-related (as I wrote before I’m not a Java programmer)  
When doing authentication I’m checking for login/pass pair in the database. Then I’m querying for all the device\_id’s that the user owns.  
I’m using them to build topic permissions (similar to file rbac extension).

I’d like to store id’s into `ConnectionAttributeStore` to be able to access the same list in my authorization class.

Here is my code that I’ve added to `onConnect`

```
final ConnectionAttributeStore attributeStore = input.getConnectionInformation().getConnectionAttributeStore();
var list = new ArrayList<String>();
list.add("d-1");
list.add("d-2");
list.add("d3");
attributeStore.put("devices",??);

```

How should I serialize my list to ByteBuffer?

---

<div class="post-metadata">

### Author: ![Misiu](https://yyz1.discourse-cdn.com/flex035/user_avatar/community.hivemq.com/misiu/32/75_2.png) [@Misiu](https://community.hivemq.com/u/Misiu)
#### Post date: [August 25, 2020, 9:14am UTC](https://community.hivemq.com/t/authentication-authorization-and-clientinitializer/317/5 "2020-08-25T09:14:00Z")

</div>

For now, I’m serializing/deserializing with above code, but I’m sure there is a better way to store and read a list of strings in `ConnectionAttributeStore`

```
var list = new ArrayList<String>();
list.add("d-1");
list.add("d-2");
list.add("d3");

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(list);
byte[] bytes = bos.toByteArray();
ByteBuffer buffer = ByteBuffer.wrap(bytes);

ByteArrayInputStream bis = new ByteArrayInputStream(buffer.array());
ObjectInput oi = new ObjectInputStream(bis);
ArrayList<String> devices = (ArrayList<String>) oi.readObject();
```

---

<div class="post-metadata">

### Author: ![Misiu](https://yyz1.discourse-cdn.com/flex035/user_avatar/community.hivemq.com/misiu/32/75_2.png) [@Misiu](https://community.hivemq.com/u/Misiu)
#### Post date: [August 27, 2020, 9:06am UTC](https://community.hivemq.com/t/authentication-authorization-and-clientinitializer/317/6 "2020-08-27T09:06:13Z")

</div>

@FloLi additional question about authentication and topic permissions (continuation of [Store massages in MsSQL](https://community.hivemq.com/t/store-massages-in-mssql/301/5))

currently in `onConnect` I’m using below code to set permissions per user:

```
final ArrayList<TopicPermission> topicPermissions = new ArrayList<>();
for (String deviceId: user.get().getDevices()) {
    final TopicPermission publishPermission = Builders.topicPermission()
            .topicFilter(deviceId+"/settings")
            .activity(TopicPermission.MqttActivity.PUBLISH)
            .type(TopicPermission.PermissionType.ALLOW)
            .build();
    topicPermissions.add(publishPermission);

    final TopicPermission subscribePermission = Builders.topicPermission()
            .topicFilter(deviceId+"/values")
            .activity(TopicPermission.MqttActivity.SUBSCRIBE)
            .type(TopicPermission.PermissionType.ALLOW)
            .build();
    topicPermissions.add(subscribePermission);
}

output.getDefaultPermissions().addAll(topicPermissions);
output.getDefaultPermissions().setDefaultBehaviour(DefaultAuthorizationBehaviour.DENY);

output.authenticateSuccessfully();

```

this works great in basic scenario, but I want to add support for subscribing to `+/values` topic.

Idea is that when user1 subscribes to that topic he will only get messages from devices 1,2 and 3 (because he owns them).

In the linked response you showed that this can be done in `authorizeSubscribe`, but can I set topic permissions in `onConnect`? This way user could subscribe to get values from all devices and the broker would handle sending values from device to correct user.

This would be an awesome feature to have!
