Skip to content

Commit 87329f6

Browse files
authored
Updating workflow collection to allow for use of API Token validation (#1141)
Updating workflow collection to allow for use of API Token validation Signed-off-by: Ryan Lettieri <[email protected]>
1 parent fd7168f commit 87329f6

File tree

5 files changed

+196
-124
lines changed

5 files changed

+196
-124
lines changed

examples/Workflow/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ This Dapr workflow example shows how to create a Dapr workflow (`Workflow`) and
99
- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/)
1010
- [Dapr .NET SDK](https://github.com/dapr/dotnet-sdk/)
1111

12+
13+
## Optional Setup
14+
Dapr workflow, as well as this example program, now support authentication through the use of API tokens. For more information on this, view the following document: [API Token](https://github.com/dapr/dotnet-sdk/docs/api-token.md)
15+
1216
## Projects in sample
1317

1418
This sample contains a single [WorkflowConsoleApp](./WorkflowConsoleApp) .NET project.

examples/Workflow/WorkflowConsoleApp/Program.cs

Lines changed: 120 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,16 @@
4747
using var host = builder.Build();
4848
host.Start();
4949

50-
using var daprClient = new DaprClientBuilder().Build();
50+
DaprClient daprClient;
51+
string apiToken = Environment.GetEnvironmentVariable("DAPR_API_TOKEN");
52+
if (!string.IsNullOrEmpty(apiToken))
53+
{
54+
daprClient = new DaprClientBuilder().UseDaprApiToken(apiToken).Build();
55+
}
56+
else
57+
{
58+
daprClient = new DaprClientBuilder().Build();
59+
}
5160

5261
// Wait for the sidecar to become available
5362
while (!await daprClient.CheckHealthAsync())
@@ -70,136 +79,138 @@
7079
await RestockInventory(daprClient, baseInventory);
7180

7281
// Start the input loop
73-
while (true)
82+
using (daprClient)
7483
{
75-
// Get the name of the item to order and make sure we have inventory
76-
string items = string.Join(", ", baseInventory.Select(i => i.Name));
77-
Console.WriteLine($"Enter the name of one of the following items to order [{items}].");
78-
Console.WriteLine("To restock items, type 'restock'.");
79-
string itemName = Console.ReadLine()?.Trim();
80-
if (string.IsNullOrEmpty(itemName))
81-
{
82-
continue;
83-
}
84-
else if (string.Equals("restock", itemName, StringComparison.OrdinalIgnoreCase))
84+
while (true)
8585
{
86-
await RestockInventory(daprClient, baseInventory);
87-
continue;
88-
}
86+
// Get the name of the item to order and make sure we have inventory
87+
string items = string.Join(", ", baseInventory.Select(i => i.Name));
88+
Console.WriteLine($"Enter the name of one of the following items to order [{items}].");
89+
Console.WriteLine("To restock items, type 'restock'.");
90+
string itemName = Console.ReadLine()?.Trim();
91+
if (string.IsNullOrEmpty(itemName))
92+
{
93+
continue;
94+
}
95+
else if (string.Equals("restock", itemName, StringComparison.OrdinalIgnoreCase))
96+
{
97+
await RestockInventory(daprClient, baseInventory);
98+
continue;
99+
}
89100

90-
InventoryItem item = baseInventory.FirstOrDefault(item => string.Equals(item.Name, itemName, StringComparison.OrdinalIgnoreCase));
91-
if (item == null)
92-
{
93-
Console.ForegroundColor = ConsoleColor.Yellow;
94-
Console.WriteLine($"We don't have {itemName}!");
95-
Console.ResetColor();
96-
continue;
97-
}
101+
InventoryItem item = baseInventory.FirstOrDefault(item => string.Equals(item.Name, itemName, StringComparison.OrdinalIgnoreCase));
102+
if (item == null)
103+
{
104+
Console.ForegroundColor = ConsoleColor.Yellow;
105+
Console.WriteLine($"We don't have {itemName}!");
106+
Console.ResetColor();
107+
continue;
108+
}
98109

99-
Console.WriteLine($"How many {itemName} would you like to purchase?");
100-
string amountStr = Console.ReadLine().Trim();
101-
if (!int.TryParse(amountStr, out int amount) || amount <= 0)
102-
{
103-
Console.ForegroundColor = ConsoleColor.Yellow;
104-
Console.WriteLine($"Invalid input. Assuming you meant to type '1'.");
105-
Console.ResetColor();
106-
amount = 1;
107-
}
110+
Console.WriteLine($"How many {itemName} would you like to purchase?");
111+
string amountStr = Console.ReadLine().Trim();
112+
if (!int.TryParse(amountStr, out int amount) || amount <= 0)
113+
{
114+
Console.ForegroundColor = ConsoleColor.Yellow;
115+
Console.WriteLine($"Invalid input. Assuming you meant to type '1'.");
116+
Console.ResetColor();
117+
amount = 1;
118+
}
108119

109-
// Construct the order with a unique order ID
110-
string orderId = $"{itemName.ToLowerInvariant()}-{Guid.NewGuid().ToString()[..8]}";
111-
double totalCost = amount * item.PerItemCost;
112-
var orderInfo = new OrderPayload(itemName.ToLowerInvariant(), totalCost, amount);
120+
// Construct the order with a unique order ID
121+
string orderId = $"{itemName.ToLowerInvariant()}-{Guid.NewGuid().ToString()[..8]}";
122+
double totalCost = amount * item.PerItemCost;
123+
var orderInfo = new OrderPayload(itemName.ToLowerInvariant(), totalCost, amount);
113124

114-
// Start the workflow using the order ID as the workflow ID
115-
Console.WriteLine($"Starting order workflow '{orderId}' purchasing {amount} {itemName}");
116-
await daprClient.StartWorkflowAsync(
117-
workflowComponent: DaprWorkflowComponent,
118-
workflowName: nameof(OrderProcessingWorkflow),
119-
input: orderInfo,
120-
instanceId: orderId);
125+
// Start the workflow using the order ID as the workflow ID
126+
Console.WriteLine($"Starting order workflow '{orderId}' purchasing {amount} {itemName}");
127+
await daprClient.StartWorkflowAsync(
128+
workflowComponent: DaprWorkflowComponent,
129+
workflowName: nameof(OrderProcessingWorkflow),
130+
input: orderInfo,
131+
instanceId: orderId);
121132

122-
// Wait for the workflow to start and confirm the input
123-
GetWorkflowResponse state = await daprClient.WaitForWorkflowStartAsync(
124-
instanceId: orderId,
125-
workflowComponent: DaprWorkflowComponent);
133+
// Wait for the workflow to start and confirm the input
134+
GetWorkflowResponse state = await daprClient.WaitForWorkflowStartAsync(
135+
instanceId: orderId,
136+
workflowComponent: DaprWorkflowComponent);
126137

127-
Console.WriteLine($"{state.WorkflowName} (ID = {orderId}) started successfully with {state.ReadInputAs<OrderPayload>()}");
138+
Console.WriteLine($"{state.WorkflowName} (ID = {orderId}) started successfully with {state.ReadInputAs<OrderPayload>()}");
128139

129-
// Wait for the workflow to complete
130-
while (true)
131-
{
132-
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
133-
try
140+
// Wait for the workflow to complete
141+
while (true)
134142
{
135-
state = await daprClient.WaitForWorkflowCompletionAsync(
136-
instanceId: orderId,
137-
workflowComponent: DaprWorkflowComponent,
138-
cancellationToken: cts.Token);
139-
break;
140-
}
141-
catch (OperationCanceledException)
142-
{
143-
// Check to see if the workflow is blocked waiting for an approval
144-
state = await daprClient.GetWorkflowAsync(
145-
instanceId: orderId,
146-
workflowComponent: DaprWorkflowComponent);
147-
if (state.Properties.TryGetValue("dapr.workflow.custom_status", out string customStatus) &&
148-
customStatus.Contains("Waiting for approval"))
143+
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
144+
try
149145
{
150-
Console.WriteLine($"{state.WorkflowName} (ID = {orderId}) requires approval. Approve? [Y/N]");
151-
string approval = Console.ReadLine();
152-
ApprovalResult approvalResult = ApprovalResult.Unspecified;
153-
if (string.Equals(approval, "Y", StringComparison.OrdinalIgnoreCase))
154-
{
155-
Console.WriteLine("Approving order...");
156-
approvalResult = ApprovalResult.Approved;
157-
}
158-
else if (string.Equals(approval, "N", StringComparison.OrdinalIgnoreCase))
159-
{
160-
Console.WriteLine("Rejecting order...");
161-
approvalResult = ApprovalResult.Rejected;
162-
}
163-
164-
if (approvalResult != ApprovalResult.Unspecified)
146+
state = await daprClient.WaitForWorkflowCompletionAsync(
147+
instanceId: orderId,
148+
workflowComponent: DaprWorkflowComponent,
149+
cancellationToken: cts.Token);
150+
break;
151+
}
152+
catch (OperationCanceledException)
153+
{
154+
// Check to see if the workflow is blocked waiting for an approval
155+
state = await daprClient.GetWorkflowAsync(
156+
instanceId: orderId,
157+
workflowComponent: DaprWorkflowComponent);
158+
if (state.Properties.TryGetValue("dapr.workflow.custom_status", out string customStatus) &&
159+
customStatus.Contains("Waiting for approval"))
165160
{
166-
// Raise the workflow event to the workflow
167-
await daprClient.RaiseWorkflowEventAsync(
168-
instanceId: orderId,
169-
workflowComponent: DaprWorkflowComponent,
170-
eventName: "ManagerApproval",
171-
eventData: approvalResult);
161+
Console.WriteLine($"{state.WorkflowName} (ID = {orderId}) requires approval. Approve? [Y/N]");
162+
string approval = Console.ReadLine();
163+
ApprovalResult approvalResult = ApprovalResult.Unspecified;
164+
if (string.Equals(approval, "Y", StringComparison.OrdinalIgnoreCase))
165+
{
166+
Console.WriteLine("Approving order...");
167+
approvalResult = ApprovalResult.Approved;
168+
}
169+
else if (string.Equals(approval, "N", StringComparison.OrdinalIgnoreCase))
170+
{
171+
Console.WriteLine("Rejecting order...");
172+
approvalResult = ApprovalResult.Rejected;
173+
}
174+
175+
if (approvalResult != ApprovalResult.Unspecified)
176+
{
177+
// Raise the workflow event to the workflow
178+
await daprClient.RaiseWorkflowEventAsync(
179+
instanceId: orderId,
180+
workflowComponent: DaprWorkflowComponent,
181+
eventName: "ManagerApproval",
182+
eventData: approvalResult);
183+
}
184+
185+
// otherwise, keep waiting
172186
}
173-
174-
// otherwise, keep waiting
175187
}
176188
}
177-
}
178189

179-
if (state.RuntimeStatus == WorkflowRuntimeStatus.Completed)
180-
{
181-
OrderResult result = state.ReadOutputAs<OrderResult>();
182-
if (result.Processed)
190+
if (state.RuntimeStatus == WorkflowRuntimeStatus.Completed)
183191
{
184-
Console.ForegroundColor = ConsoleColor.Green;
185-
Console.WriteLine($"Order workflow is {state.RuntimeStatus} and the order was processed successfully ({result}).");
186-
Console.ResetColor();
192+
OrderResult result = state.ReadOutputAs<OrderResult>();
193+
if (result.Processed)
194+
{
195+
Console.ForegroundColor = ConsoleColor.Green;
196+
Console.WriteLine($"Order workflow is {state.RuntimeStatus} and the order was processed successfully ({result}).");
197+
Console.ResetColor();
198+
}
199+
else
200+
{
201+
Console.WriteLine($"Order workflow is {state.RuntimeStatus} but the order was not processed.");
202+
}
187203
}
188-
else
204+
else if (state.RuntimeStatus == WorkflowRuntimeStatus.Failed)
189205
{
190-
Console.WriteLine($"Order workflow is {state.RuntimeStatus} but the order was not processed.");
206+
Console.ForegroundColor = ConsoleColor.Red;
207+
Console.WriteLine($"The workflow failed - {state.FailureDetails}");
208+
Console.ResetColor();
191209
}
192-
}
193-
else if (state.RuntimeStatus == WorkflowRuntimeStatus.Failed)
194-
{
195-
Console.ForegroundColor = ConsoleColor.Red;
196-
Console.WriteLine($"The workflow failed - {state.FailureDetails}");
197-
Console.ResetColor();
198-
}
199210

200-
Console.WriteLine();
211+
Console.WriteLine();
212+
}
201213
}
202-
203214
static async Task RestockInventory(DaprClient daprClient, List<InventoryItem> inventory)
204215
{
205216
Console.WriteLine("*** Restocking inventory...");

src/Dapr.Workflow/Dapr.Workflow.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
<PackageReference Include="Microsoft.DurableTask.Worker.Grpc" Version="1.0.*" />
1818
</ItemGroup>
1919

20+
<ItemGroup>
21+
<Compile Include="..\Shared\DaprDefaults.cs" />
22+
</ItemGroup>
23+
2024
<ItemGroup>
2125
<ProjectReference Include="..\Dapr.Client\Dapr.Client.csproj" />
2226
<ProjectReference Include="..\Dapr.AspNetCore\Dapr.AspNetCore.csproj" />

src/Dapr.Workflow/WorkflowServiceCollectionExtensions.cs

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,20 @@
1414
namespace Dapr.Workflow
1515
{
1616
using System;
17+
using Grpc.Net.Client;
1718
using Microsoft.DurableTask.Client;
1819
using Microsoft.DurableTask.Worker;
1920
using Microsoft.Extensions.DependencyInjection;
2021
using Microsoft.Extensions.DependencyInjection.Extensions;
22+
using System.Net.Http;
23+
using Dapr;
2124

2225
/// <summary>
2326
/// Contains extension methods for using Dapr Workflow with dependency injection.
2427
/// </summary>
2528
public static class WorkflowServiceCollectionExtensions
2629
{
30+
2731
/// <summary>
2832
/// Adds Dapr Workflow support to the service collection.
2933
/// </summary>
@@ -57,7 +61,18 @@ public static IServiceCollection AddDaprWorkflow(
5761

5862
if (TryGetGrpcAddress(out string address))
5963
{
60-
builder.UseGrpc(address);
64+
var daprApiToken = DaprDefaults.GetDefaultDaprApiToken();
65+
if (!string.IsNullOrEmpty(daprApiToken))
66+
{
67+
var client = new HttpClient();
68+
client.DefaultRequestHeaders.Add("Dapr-Api-Token", daprApiToken);
69+
builder.UseGrpc(CreateChannel(address, client));
70+
}
71+
else
72+
{
73+
builder.UseGrpc(address);
74+
}
75+
6176
}
6277
else
6378
{
@@ -85,7 +100,18 @@ public static IServiceCollection AddDaprWorkflowClient(this IServiceCollection s
85100
{
86101
if (TryGetGrpcAddress(out string address))
87102
{
88-
builder.UseGrpc(address);
103+
var daprApiToken = DaprDefaults.GetDefaultDaprApiToken();
104+
if (!string.IsNullOrEmpty(daprApiToken))
105+
{
106+
var client = new HttpClient();
107+
client.DefaultRequestHeaders.Add("Dapr-Api-Token", daprApiToken);
108+
builder.UseGrpc(CreateChannel(address, client));
109+
}
110+
else
111+
{
112+
builder.UseGrpc(address);
113+
}
114+
89115
}
90116
else
91117
{
@@ -104,13 +130,13 @@ static bool TryGetGrpcAddress(out string address)
104130
// 1. DaprDefaults.cs uses 127.0.0.1 instead of localhost, which prevents testing with Dapr on WSL2 and the app on Windows
105131
// 2. DaprDefaults.cs doesn't compile when the project has C# nullable reference types enabled.
106132
// If the above issues are fixed (ensuring we don't regress anything) we should switch to using the logic in DaprDefaults.cs.
107-
string? daprEndpoint = Environment.GetEnvironmentVariable("DAPR_GRPC_ENDPOINT");
133+
var daprEndpoint = DaprDefaults.GetDefaultGrpcEndpoint();
108134
if (!String.IsNullOrEmpty(daprEndpoint)) {
109135
address = daprEndpoint;
110136
return true;
111137
}
112138

113-
string? daprPortStr = Environment.GetEnvironmentVariable("DAPR_GRPC_PORT");
139+
var daprPortStr = Environment.GetEnvironmentVariable("DAPR_GRPC_PORT");
114140
if (int.TryParse(daprPortStr, out int daprGrpcPort))
115141
{
116142
// There is a bug in the Durable Task SDK that requires us to change the format of the address
@@ -126,6 +152,33 @@ static bool TryGetGrpcAddress(out string address)
126152
address = string.Empty;
127153
return false;
128154
}
155+
156+
static GrpcChannel CreateChannel(string address, HttpClient client)
157+
{
158+
159+
GrpcChannelOptions options = new() { HttpClient = client};
160+
var daprEndpoint = DaprDefaults.GetDefaultGrpcEndpoint();
161+
if (!String.IsNullOrEmpty(daprEndpoint)) {
162+
return GrpcChannel.ForAddress(daprEndpoint, options);
163+
}
164+
165+
var daprPortStr = Environment.GetEnvironmentVariable("DAPR_GRPC_PORT");
166+
if (int.TryParse(daprPortStr, out int daprGrpcPort))
167+
{
168+
// If there is no address passed in, we default to localhost
169+
if (String.IsNullOrEmpty(address))
170+
{
171+
// There is a bug in the Durable Task SDK that requires us to change the format of the address
172+
// depending on the version of .NET that we're targeting. For now, we work around this manually.
173+
#if NET6_0_OR_GREATER
174+
address = $"http://localhost:{daprGrpcPort}";
175+
#else
176+
address = $"localhost:{daprGrpcPort}";
177+
#endif
178+
}
179+
180+
}
181+
return GrpcChannel.ForAddress(address, options);
182+
}
129183
}
130184
}
131-

0 commit comments

Comments
 (0)