Edit

Share via


Tutorial - Authenticate and authorize MQTT clients based on client certificates with event handlers

In this tutorial, you'll learn how to write a .NET web server to authenticate and authorize MQTT clients.

Prerequisites

  • An Azure account with an active subscription. If you don't have an Azure account, you can create an account for free.
  • An Azure Web PubSub service (must be Standard tier or above).
  • A client certificate in PEM format.
  • .NET Runtime installed.
  • Node.js

Deploy Azure Web PubSub service

Here are the Bicep/Azure Resource Manager templates to deploy an Azure Web PubSub service with client certificate authentication enabled and event handlers configured.

We configure the connect event handler to tell the service the webhook endpoint for authenticating and authorizing clients. We set it to tunnel:///MqttConnect. tunnel:// is a special syntax leveraging the awps-tunnel tool to expose your local auth server to public network. /MqttConnect is the endpoint that will be exposed by your local auth server.

We enable client certificate authentication via the property tls.clientCertEnabled so that the client certificate is sent to your server in the connect event.

Also note that anonymousConnectPolicy needs to be set to allow so clients no longer need to send access tokens.

param name string
param hubName string = 'hub1'
param eventHandlerUrl string = 'tunnel:///MqttConnect'
param location string = resourceGroup().location

resource awps 'Microsoft.SignalRService/WebPubSub@2023-03-01-preview' = {
  name: name
  location: location
  sku: {
    name: 'Standard_S1'
    tier: 'Standard'
    size: 'S1'
    capacity: 1
  }
  properties: {
    tls: {
      clientCertEnabled: true
    }
  }
}

resource hub 'Microsoft.SignalRService/WebPubSub/hubs@2023-03-01-preview' = {
  parent: awps
  name: '${hubName}'
  properties: {
    eventHandlers: [
      {
        urlTemplate: eventHandlerUrl
        userEventPattern: '*'
        systemEvents: [
          'connect'
        ]
      }
    ]
    anonymousConnectPolicy: 'allow'
  }
}

Set up auth server

We've provided an auth server sample here. Please download the project.

Let's take a look at the project structure:

- mqttAuthServer
  - Models
    - MqttConnectEventRequest.cs
    - ...
  - MqttAuthServer.csproj
  - Program.cs

The Models directory contains all the model files to describe the request and response body of MQTT connect event. The Program.cs contains the logic to handle MQTT connect event, including parsing the client certificate contents from request, validating the certificates, and authorizing the client.

The following code snippet is the main logic of handling connect event request:

    var request = await httpContext.Request.ReadFromJsonAsync<MqttConnectEventRequest>();
    var certificates = request.ClientCertificates.Select(cert => GetCertificateFromPemString(cert.Content));
    // Simulate Logic to validate client certificate
    if (!request.Query.TryGetValue("failure", out _))
    {
        // As a demo, we just accept all client certificates and grant the clients with permissions to publish and subscribe to all the topics when the query parameter "success" is present.
        await httpContext.Response.WriteAsJsonAsync(new MqttConnectEventSuccessResponse()
        {
            Roles = ["webpubsub.joinLeaveGroup", "webpubsub.sendToGroup"]
        });
    }
    else
    {
        // If you want to reject the connection, you can return a MqttConnectEventFailureResponse
        var mqttCodeForUnauthorized = request.Mqtt.ProtocolVersion switch
        {
            4 => 5, // UnAuthorized Return Code in Mqtt 3.1.1
            5 => 0x87, // UnAuthorized Reason Code in Mqtt 5.0
            _ => throw new NotSupportedException($"{request.Mqtt.ProtocolVersion} is not supported.")
        };
        httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
        await httpContext.Response.WriteAsJsonAsync(new MqttConnectEventFailureResponse(new MqttConnectEventFailureResponseProperties()
        {
            Code = mqttCodeForUnauthorized,
            Reason = "Invalid Certificate"
        }
        ));
    }

To run the project, execute the following command in the root directory.

dotnet run

Expose server endpoint to public network

Download and install awps-tunnel

The tool runs on Node.js version 16 or higher.

npm install -g @azure/web-pubsub-tunnel-tool

Use the service connection string and run

export WebPubSubConnectionString="<your connection string>"
awps-tunnel run --hub {hubName} --upstream http://localhost:{portExposedByYourAuthServer}

Implement MQTT clients

We will implement the client side in Node.js.

Initialize a Node.js project with the following command.

npm init

Install the mqtt module.

npm install mqtt

Create a new file named index.js, and add the following code to the file.

const mqtt = require('mqtt');

var client = mqtt.connect(`wss://{serviceName}.webpubsub.azure.com/clients/mqtt/hubs/{hubName}`,
    {
        clientId: "client1",
        cert: `-----BEGIN CERTIFICATE-----
{Complete the certificate here}
-----END CERTIFICATE-----`,
        key: `-----BEGIN PRIVATE KEY-----
{Complete the private key here}
-----END PRIVATE KEY-----`,
        protocolVersion: 5,
    });
client.on("connect", (connack) => {
    console.log("connack", connack);
});
client.on("error", (err) => {
    console.log(err);
});

Update the index.js:

  • Update the {serviceName} and {hubName} variable according to the resources you created.
  • Complete the client certificate and key in the file.

Then you're able to run the project with command

node index.js

If everything works well, you'll be able to see a successful CONNACK response printed in the console.

connack Packet {
  cmd: 'connack',
  retain: false,
  qos: 0,
  dup: false,
  length: 2,
  topic: null,
  payload: null,
  sessionPresent: false,
  returnCode: 0
}

To simulate the certificate validation failure, append a failure query to the connection URL as this

var client = mqtt.connect(`wss://{serviceName}.webpubsub.azure.com/clients/mqtt/hubs/{hubName}?failure=xxx`,

And rerun the client, you'll be able to see an unauthorized CONNACK response.

Next step

Now that you have known that how to authenticate and authorize MQTT clients e2e. Next, you can check our event handler protocol for MQTT clients.