Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sample with JWT #68

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions scenarios/jwt_authentication/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# :point_right: JWT Authentication to Event Grid

| [Create the Client Certificate](#lock-create-the-client-certificate) | [Configure Event Grid Namespaces](#triangular_ruler-configure-event-grid-namespaces) | [Configure Mosquitto](#fly-configure-mosquitto) | [Run the Sample](#game_die-run-the-sample) |

This scenario showcases how to authenticate to Azure Event Grid via JWT authentication using MQTT 5. This scenario is identical to `getting_started` in functionality.

The sample provides step by step instructions on how to perform following tasks:

- Create the resources including client, topic spaces, permission bindings
- Use $all client group, which is the default client group with all the clients in a namespace, to authorize publish and subscribe access in permission bindings
- Create a custom role assignment on the Azure Portal to access Event Grid via Json Web Token (JWT) authentication.
- Create a JWT, which is used to authenticate to Event Grid.
- Connect with MQTT 5.0.0
- Configure connection settings such as KeepAlive and CleanSession
- Publish messages to a topic
- Subscribe to a topic to receive messages

To keep the scenario simple, a single client called "sample_client" publishes and subscribes to MQTT messages on topics shown in the table.

|Client|Role|Operation|Topic/Topic Filter|
|------|----|---------|------------------|
|sample_client|publisher|publish|sample/topic1|
|sample_client|subscriber|subscribe|sample/+|

## Prerequisites
This sample involves configuring Event Grid per the specifications in [getting_started](../getting_started). If that sample has not already been set up and run, it should be done before moving onto this one.

## :lock: Configure the Json Web Token and AAD Role Assignments

1. Modify the following JSON snippet by adding an Azure subscription Id:

```json
{
"properties": {
"roleName": "Event Grid Pub-Sub",
"description": "communicate with Event Grid.",
"assignableScopes": [
"/subscriptions/<YOUR SUBSCRIPTION ID HERE>"
],
"permissions": [
{
"actions": [],
"notActions": [],
"dataActions": [
"Microsoft.EventGrid/*"
],
"notDataActions": []
}
]
}
}
```
2. Copy the modified snippet and save it locally.
3. In the Azure portal, go to your Resource Group that contains Event Grid and open the Access control (IAM) page.
4. Click Add and then click Add custom role. This opens the custom roles editor.
5. On the `Basics` tab, select `Start from JSON`, and upload the modified JSON file you saved locally.
6. Select the `Review and Create` tab and then `Create`.
7. **NOTE:** It is possible that your Azure account may not have room for more custom role assignments. In this instance the current workaround is to create a free Azure account and complete this process while logged in from there.

## :triangular_ruler: Configure Event Grid Namespaces (Skip if [getting_started](../getting_started) has already been properly configured)

Ensure to create an Event Grid namespace by following the steps in [setup](../setup). Event Grid namespace requires registering the client, and the topic spaces to authorize the publish/subscribe permissions.

### Create the Client (Skip if [getting_started](../getting_started) has already been properly configured)

We will use the SubjectMatchesAuthenticationName validation scheme for `sample_client`. Instructions for how to do this can be found in [getting_started](../getting_started). If this has already been done once, it does not have to be done again (unless using a different Azure account).

### Create topic spaces and permission bindings
Run the commands to create the "samples" topic space, and the two permission bindings that provide publish and subscribe access to $all client group on the samples topic space. As for above, the instructions to do this are part of [getting_started](../getting_started) and do not have to be repeated if they have already been done in the Azure account being used to run this sample.

## :game_die: Run the Sample

All samples are designed to be executed from the root scenario folder.

### dotnet

To build the dotnet sample run:

```bash
# from folder scenarios/jwt_authenticaton
dotnet build dotnet/jwt_authentication.sln
```

To run the dotnet sample:

```bash
dotnet/jwt_authentication/bin/Debug/net7.0/jwt_authentication
```

## Connecting over WebSocket
To connect using WebSockets, modify client's `ConnectAsync()` call as follows:
```csharp
MqttClientConnectResult connAck = await mqttClient!.ConnectAsync(new MqttClientOptionsBuilder()
.WithClientId("sample_client")
//.WithTcpServer(hostname, 8883)
.WithWebSocketServer(b => b.WithUri($"{hostname}:443/mqtt"))
.WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V500)
.WithAuthentication("OAUTH2-JWT", Encoding.UTF8.GetBytes(jwt.Token))
.WithTlsOptions(new MqttClientTlsOptions() { UseTls = true })
.Build());
```

Note that it is required to use port 443 for websocket connections. To learn more about this flow visit the [documentation](https://learn.microsoft.com/azure/event-grid/mqtt-support#connection-flow).
25 changes: 25 additions & 0 deletions scenarios/jwt_authentication/dotnet/jwt_authentication.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "jwt_authentication", "jwt_authentication\jwt_authentication.csproj", "{64CD6647-A322-4F5C-AFD1-3B657CE65FA5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{64CD6647-A322-4F5C-AFD1-3B657CE65FA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{64CD6647-A322-4F5C-AFD1-3B657CE65FA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{64CD6647-A322-4F5C-AFD1-3B657CE65FA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{64CD6647-A322-4F5C-AFD1-3B657CE65FA5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7A2246F6-BDDC-4173-AD14-FD2DC0F879D0}
EndGlobalSection
EndGlobal
39 changes: 39 additions & 0 deletions scenarios/jwt_authentication/dotnet/jwt_authentication/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Extensions;
using System.Text;
using Azure.Identity;
using Azure.Core;

// Create client
IMqttClient mqttClient = new MqttFactory().CreateMqttClient(MqttNetTraceLogger.CreateTraceLogger());
string hostname = "<Event Grid Mqtt Hostname Here>";

// Create JWT
var defaultCredential = new DefaultAzureCredential();

// Sets the audience field of the JWT to Event Grid
var tokenRequestContext = new TokenRequestContext(new string[] { "https://eventgrid.azure.net/" });
AccessToken jwt = defaultCredential.GetToken(tokenRequestContext);
patilsnr marked this conversation as resolved.
Show resolved Hide resolved

// Required to use port 8883: https://learn.microsoft.com/azure/event-grid/mqtt-support
MqttClientConnectResult connAck = await mqttClient!.ConnectAsync(new MqttClientOptionsBuilder()
.WithClientId("sample_client")
.WithTcpServer(hostname, 8883)
.WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V500)
.WithAuthentication("OAUTH2-JWT", Encoding.UTF8.GetBytes(jwt.Token))
.WithTlsOptions(new MqttClientTlsOptions() { UseTls = true })
.Build());

Console.WriteLine($"Client Connected: {mqttClient.IsConnected} with CONNACK: {connAck.ResultCode}");

mqttClient.ApplicationMessageReceivedAsync += async m => await Console.Out.WriteAsync(
$"Received message on topic: '{m.ApplicationMessage.Topic}' with content: '{m.ApplicationMessage.ConvertPayloadToString()}'\n\n");

MqttClientSubscribeResult suback = await mqttClient.SubscribeAsync("sample/+", MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce);
suback.Items.ToList().ForEach(s => Console.WriteLine($"subscribed to '{s.TopicFilter.Topic}' with '{s.ResultCode}'"));

MqttClientPublishResult puback = await mqttClient.PublishStringAsync("sample/topic1", "hello world!", MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce);
Console.WriteLine(puback.ReasonString);

Console.ReadLine();
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.10.1" />
<PackageReference Include="MQTTnet" Version="4.3.1.873" />
</ItemGroup>

<ItemGroup>
<Reference Include="MQTTnet.Client.Extensions">
<HintPath>..\..\..\..\mqttclients\dotnet\MQTTnet.Client.Extensions\bin\Debug\net7.0\MQTTnet.Client.Extensions.dll</HintPath>
</Reference>
</ItemGroup>

</Project>