This blog demonstrates how to use Aspire to set up a solution for developing and deploying an ASP.NET Core web application with Auth0 as the identity provider and a downstream API. The application uses Angular for the frontend and is secured using a Backend-for-Frontend (BFF) architecture.
Code: https://github.com/damienbod/Auth0BffDpopApi
Blogs in this series
- Implement BFF using Auth0, Angular and ASP.NET Core
- Use Aspire to implement and deploy the BFF security architecture
- Implement secure downstream APIs using DPoP and Auth0
Target setup
In this setup, it is planned to implement the recommended authentication for applications and users which uses best practices and recommended authentication flows.

Aspire Setup
Aspire maps all the applications together using a code configuration setup in the AppHost class. This class allows for a development and production setup. The configuration, the connects and other deployment settings can be defined in this class.
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;
var builder = DistributedApplication.CreateBuilder(args);
// Used in the applications when called downstream APIs, etc
const string WEB_APPLICATION = "web-app-bff-service";
const string API_SERVICE = "api-service";
IResourceBuilder<ProjectResource>? webApi = null;
IResourceBuilder<ProjectResource>? webApplication = null;
// Parameters for the web application
var webOidcClientPrivatePem = builder.AddParameter("WebOidcClientPrivatePem", secret: true);
var webOidcClientPublicPem = builder.AddParameter("WebOidcClientPublicPem");
var webDpopClientPrivatePem = builder.AddParameter("WebDpopClientPrivatePem", secret: true);
var webDpopClientPublicPem = builder.AddParameter("WebDpopClientPublicPem");
var webAuth0Authority = builder.AddParameter("WebAuth0Authority");
var webAuth0Audience = builder.AddParameter("WebAuth0Audience");
var webAuth0Domain = builder.AddParameter("WebAuth0Domain");
var webAuth0ClientId = builder.AddParameter("WebAuth0ClientId");
var webAuth0CallbackPath = builder.AddParameter("WebAuth0CallbackPath");
// Parameters for the web API
var apiAuth0Authority = builder.AddParameter("ApiAuth0Authority");
var apiAuth0Audience = builder.AddParameter("ApiAuth0Audience");
var apiAuth0Domain = builder.AddParameter("ApiAuth0Domain");
var apiDeploySwaggerUI = builder.AddParameter("ApiDeploySwaggerUI");
webApi = builder.AddProject<Projects.WebApi>(API_SERVICE)
.WithExternalHttpEndpoints()
.WithEnvironment("Auth0:Authority", apiAuth0Authority)
.WithEnvironment("Auth0:Audience", apiAuth0Audience)
.WithEnvironment("Auth0:Domain", apiAuth0Domain)
.WithEnvironment("DeploySwaggerUI", apiDeploySwaggerUI);
if (builder.Environment.IsDevelopment())
{
var angularFrontend = builder.AddJavaScriptApp("angular", "../bff/ui", "start")
.WithHttpsEndpoint(port: 3000, 4201, env: "BASE_URL");
webApplication =builder.AddProject<Projects.BffAuth0_Server>(WEB_APPLICATION)
.WithExternalHttpEndpoints()
.WithReference(angularFrontend)
.WaitFor(angularFrontend)
.WithReference(webApi)
.WaitFor(webApi)
.WithEnvironment("Auth0:Authority", webAuth0Authority)
.WithEnvironment("Auth0:Audience", webAuth0Audience)
.WithEnvironment("Auth0:Domain", webAuth0Domain)
.WithEnvironment("Auth0:ClientId", webAuth0ClientId)
.WithEnvironment("Auth0:CallbackPath", webAuth0CallbackPath)
.WithEnvironment("OidcClientPrivatePem", webOidcClientPrivatePem)
.WithEnvironment("OidcClientPublicPem", webOidcClientPublicPem)
.WithEnvironment("DpopClientPrivatePem", webDpopClientPrivatePem)
.WithEnvironment("DpopClientPublicPem", webDpopClientPublicPem);
}
else
{
// Hint: to make this work, the deployment pipeline must execute npm run build
// which deploys to the wwwroot folder of the bffauth0-server project.
webApplication = builder.AddProject<Projects.BffAuth0_Server>(WEB_APPLICATION)
.WithExternalHttpEndpoints()
.WithReference(webApi)
.WaitFor(webApi)
.WithEnvironment("WebAuth0Authority", webAuth0Authority)
.WithEnvironment("WebAuth0Audience", webAuth0Audience)
.WithEnvironment("WebAuth0Domain", webAuth0Domain)
.WithEnvironment("WebAuth0ClientId", webAuth0ClientId)
.WithEnvironment("WebAuth0CallbackPath", webAuth0CallbackPath)
.WithEnvironment("WebOidcClientPrivatePem", webOidcClientPrivatePem)
.WithEnvironment("WebOidcClientPublicPem", webOidcClientPublicPem)
.WithEnvironment("WebDpopClientPrivatePem", webDpopClientPrivatePem)
.WithEnvironment("WebDpopClientPublicPem", webDpopClientPublicPem);
}
builder.Build().Run();
Adding Aspire to the projects/applications
Aspire provides a default AppsAspire.ServiceDefaults project which is referenced from each ASP.NET Core project. The Aspire AppHost project can then reference the different apps and is configured in the host project.
Aspire configuration
All configuration properties need to be setup in the AppHost Aspire project which links all the containers and apps together. The routes and paths are automatically set correctly, when the different projects are referenced using the Aspire helper methods.
When using different APIs, the path can be matched using the name of the service from the AppHost file. Then the path gets mapped correctly using Aspire for all deployments.
builder.Services.AddUserAccessTokenHttpClient("dpop-api-client", configureClient: client =>
{
// See App Host for the api-service definition. This is the name of the service in the AppAspireHost project.
client.BaseAddress = new("https+http://api-service");
});
YARP is used in both development and production. The YARP configuration is read through the code configuration can the values are setup using the AppHost from Aspire. The app.settings are used for local development, not for production. This is not required, just how I set this up.
if (builder.Environment.IsDevelopment())
{
// Development
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
}
else
{
// Production
// Support for Aspire and Containers
builder.Services.AddReverseProxy()
.LoadFromMemory(YarpConfigurations.GetProductionRoutes(),
YarpConfigurations.GetProductionClusters(builder.Configuration["DownstreamApiUrl"]!));
}
The WithEnvironment adds the parameters to the different containers as configuration. These values can be used like in any ASP.NET Core application.
Dev setup
The solution uses a backend for frontend architecture. The AddJavaScriptApp method adds a host project for the UI app which maps to the default dev route. This is only used in development, so that aa UI dev can use his or her preferred tools.
Notes
The Auth0 client NuGet client requires app.settings which cannot be changed and these values must be passed in as defined by the Auth0 client NuGet package. The user info endpoint does not work when using a client assertion setup with DPoP.
Links
https://auth0.com/docs/quickstart/webapp/aspnet-core
https://auth0.com/blog/backend-for-frontend-pattern-with-auth0-and-dotnet
https://github.com/damienbod/bff-auth0-aspnetcore-angular
https://github.com/damienbod/DPOP-aspnetcore-idp
https://auth0.com/docs/secure/sender-constraining/demonstrating-proof-of-possession-dpop
https://auth0.com/blog/implementing-dpop-with-auth0
https://auth0.com/docs/quickstart/backend/aspnet-core-webapi#using-dpop-for-enhanced-security

[…] Use Aspire to implement and deploy the security architecture […]
Thanks for sharing this — we included it in DotNetNews here: https://dotnetnews.co/archive/the-net-news-daily-issue-527/
Thanks