drewhayes.dev

Authentication and Authorization of Dapr Requests for .NET Web API


Authentication and Authorization of Dapr Requests for .NET Web API

The Problem

When a .NET Web API is deployed to Azure Container Apps as a container app, how can we authenticate and authorize requests coming from Dapr? Additionally, how can the application be setup with minimal impact to local development and testing?

Background

TL;DR: Configuring .NET Web API Auth

Dapr can be configured to pass a token (APP_API_TOKEN) in every request header to the attached application. Read more on Dapr token configuration in the Dapr docs.

Azure Container Apps using Dapr will automatically assign the APP_API_TOKEN when a new revision of a container app is created. The token is provided to both the container app and the container app’s Dapr sidecar. Read more on Microsoft’s Container Apps docs.

The Dapr .NET SDK does provide a way to authenticate and authorize requests using the Dapr API token. The SDK docs can be found here but do not outline exactly how to do set this up. The code for the SDK can be found on github, which helped me figure out which methods to call.

Configuring .NET Web API Auth

Before we get started, this article assumes that you can create a .NET Web API project with Dapr running as a sidecar, configured in docker-compose. Check out my article Dapr Sidecar with docker-compose to see an example of an application with this configuration.

Check out the repo which contains all of the code referenced in this post! In this example, there is an API SampleBFF, which publishes messages using the Dapr SDK. There is a second API SampleProcessingService, which subscribes to messages using the Dapr SDK. The SampleProcessingService will be configured to authenticate and authorize requests from Dapr.

An end to end workflow showing Sample BFF publishing messages and Sample Processing Service subscribing with auth configured

1. Initial Solution Configuration

Create a solution with a .NET Web API project. Configure the application to run with docker-compose.

This docker-compose example file sets up each API with the associated Dapr sidecar, as well as a Redis container which will be used as a message broker for the Dapr pubsub component.

2. Add and Configure Dapr SDK in .NET Web API

Install the Dapr.AspNetCore package from nuget.

In Program.cs, add the following lines:

builder.Services.AddControllers().AddDapr(); // Adds a DaprClient singleton for use in the app
// ... More setup code

var app = builder.Build();

app.UseRouting();
app.UseCloudEvents();
app.MapSubscribeHandler();

Add pubsub.yml to the DaprComponents folder.

3. Publish Messages with Dapr SDK

In SampleBff.InitiateBackgroundProcessingController, here’s a sample endpoint which publishes 10 messages.

private readonly DaprClient _client;
// The client is injected with the AddDapr() due to method call in Program.cs
public InitiateBackgroundProcessingController(DaprClient client) 
{ 
  _client = client;
}

[HttpPost]
public async Task<IActionResult> Post()
{
  for (int i = 0; i < 10; i++)
  {
    var message = new MessageFromBff($"Message from BFF number {i}", DateTime.UtcNow);
    await _client.PublishEventAsync("pubsub", "message-from-bff", message);
  }

  return Accepted();
}

4. Subscribe to Messages with Dapr SDK

In SampleProcessingService.MessageFromBffController, here’s a sample endpoint which subscribes to the message-from-bff topic.

[HttpPost]
[Topic("pubsub", "message-from-bff")]
public IActionResult Post(MessageFromBff message)
{
  Console.WriteLine(message.Description);
  return Accepted();
}

At this point, there is no authentication or authorization setup in the applications. Any user or system can post directly to the MessageFromBffController.

5. Add Dapr Authentication to .NET Web API

For the next steps, I will only be adding authentication and authorization to the SampleProcessingService API. This way, we can still post to SampleBff to publish messages.

In Program.cs, add the following line:

builder.Services.AddAuthentication().AddDapr(); // Adds Dapr authentication

Note: This works for .NET 7. If you’re using .NET 6, you must provide an authentication scheme, so the line will become:

builder.Services.AddAuthentication("Dapr").AddDapr(); // Adds Dapr authentication

6. Add Dapr Authorization in .Net Web API

In Program.cs, add the following lines:

// After AddAuthentication
builder.Services.AddAuthorization(options =>
{
  options.AddDapr(); // Adds Dapr authorization
});

// ... More setup code

app.UseAuthorization();
app.UseAuthorization();
// ... More app configurations
app.MapControllers().RequireAuthorization(); // Forces all controllers to require authorization

At this point, run the application, publish messages, and take a look at the Dapr logs. You will most likely see messages that look like this:

time="2023-05-26T02:36:02.08077217Z" level=error msg="Error processing Redis message 
1685068562062-0: retriable error returned from app while processing pub/sub event 
658ee441-3262-4ca8-89d9-e8d442598a77, topic: message-from-bff, body: . status code 
returned: 401" app_id=sampleprocessingservice instance=6e20431c34dc scope=dapr.contrib 

7. Update docker-compose with Environment Variables

The final piece of the puzzle is to add the APP_API_TOKEN environment variable in the docker-compose file for the API and its associated Dapr sidecar. For example:

sampleprocessingservice:
    image: ${DOCKER_REGISTRY-}sampleprocessingservice
    build:
      context: .
      dockerfile: SampleProcessingService/Dockerfile
    environment:
      - APP_API_TOKEN=109be48e-8eab-41a9-92a8-496656c9602f
    depends_on: 
      - daprauthredis

  sampleprocessingservice-dapr:
    image: "daprio/daprd:latest"
    command: [ #... commands
    ]
    volumes:
      - "./DaprComponents/:/DaprComponents"
    environment:
      - APP_API_TOKEN=109be48e-8eab-41a9-92a8-496656c9602f

As far as I can tell, the token can be any string as long as they match. I just use randomly generated GUIDs for local development, but I even tried setting the token to HERESATOKENFORYA and authorization still works.

Why This Works with Azure Container Apps

Again, per Microsoft’s documentation, Azure Container Apps will automatically assign the API and the Dapr sidecar the same app API token. Now that the API is configured with Dapr authentication and authorization, endpoints are secured yet allow traffic from Dapr.

Wrapping It Up

Now that the API is setup with authentication and authorization for Dapr requests, the docker-compose configuration allows for local development without unauthorized requests. This setup mirrors the behavior that will exist in Azure Container Apps.

The API can be configured to work with multiple authentication schemes (not just Dapr’s). In the example code, the API is setup to require authorization for all controllers. If you have multiple authentication schemes and want to secure a specific controller for just Dapr, you can do so by decorating the controller with [Authorize(AuthenticationSchemes = "Dapr")].

I hope this helps! Drop me a line if you have any questions.