Damien Bod’s swiyu series looks at securing Switzerland’s public beta digital identity infrastructure, built around the OpenID for Verifiable Presentations (OpenID4VP) 1.0 specification. This post tackles a problem that shows up in most identity platforms sooner or later. The same container image exposes both the wallet-facing APIs that end users interact with and the management APIs that administrators use to configure the verifier. Left as is, both surfaces sit reachable on the same network path, which is not where you want a management API to be.
The fix here is network isolation, not application-level authorization, and Damien is upfront about that distinction. Once the management APIs sit behind a reverse proxy that forwards only the routes wallets actually need, the admin surface stops being reachable from outside the internal network. Application security for those admin endpoints is left for a follow-up post in the series, since network controls alone do not amount to zero trust.
The Setup
The solution has four moving parts: an identity provider built with ASP.NET Core and Duende, a web application that authenticates against the IDP using OpenID Connect, an API that requires DPoP tokens for access, and the swiyu generic container itself. That container ships both the wallet-facing verifier endpoints and the management APIs in a single image, which is where the isolation problem originates.

Why YARP
YARP, shipped as the Yarp.ReverseProxy NuGet package, is Microsoft’s code-first reverse proxy for .NET. It fits naturally into a .NET Aspire based solution since routes and clusters can be defined directly in code instead of a separate gateway configuration file. For isolating a single container’s endpoints, that keeps the whole proxy definition inside the same codebase and deployment pipeline as everything else.
Teams running many services with complex routing, rate limiting, or WAF requirements will likely outgrow a single YARP proxy and want something like Azure API Management or Application Gateway in front of it. For a solo proxy sitting in front of one container, though, YARP in code is hard to beat for simplicity.
Defining Routes and Clusters in Code
Wiring YARP into an Aspire host works better with code configuration than a static appsettings.json file, because the destination address depends on a value Aspire only resolves at runtime: the container’s actual internal endpoint. Damien wraps this in a YarpConfigurations class that exposes the verifier’s routes and clusters as static methods, so only the deployment-dependent URL needs to be passed in.
public static class YarpConfigurations
{
public static RouteConfig[] GetVerifierRoutes()
{
return
[
new RouteConfig()
{
RouteId = "routeverifier",
ClusterId = "clusterverifier",
AuthorizationPolicy = "Anonymous",
Match = new RouteMatch
{
Path = "/oid4vp/{**catch-all}"
}
}
];
}
public static ClusterConfig[] GetVerifierClusters(string verifier)
{
return
[
new ClusterConfig()
{
ClusterId = "clusterverifier",
Destinations = new Dictionary<string, DestinationConfig>
{
{ "destination1", new DestinationConfig() { Address = $"{verifier}/" } }
},
HttpClient = new HttpClientConfig {
MaxConnectionsPerServer = 10, SslProtocols = SslProtocols.Tls12 }
}
];
}
}
The route configuration matches only /oid4vp/{**catch-all}, the OpenID4VP endpoint prefix that wallets call. Everything else on the swiyu container, including the admin and management endpoints, has no matching route and never gets forwarded through the proxy. AuthorizationPolicy is set to Anonymous here because these are the public wallet-facing endpoints, and nothing sensitive is exposed by leaving them unauthenticated at the proxy layer.
One detail worth flagging in the cluster configuration: MaxConnectionsPerServer is capped at 10 and SslProtocols is pinned to Tls12. Ten connections is a conservative starting point that can become a bottleneck once real production traffic shows up, so it is worth revisiting alongside load testing rather than leaving it at the default used during development.
Registering the Proxy
Wiring the generated routes and clusters into the ASP.NET Core pipeline takes one call beyond the standard reverse proxy registration.
builder.Services.AddReverseProxy()
.LoadFromMemory(YarpConfigurations.GetVerifierRoutes(),
YarpConfigurations.GetVerifierClusters(
builder.Configuration["SwiyuVerifierMgmtUrl"]!));
LoadFromMemory takes the routes and clusters built above and skips the need for a ReverseProxy section in configuration entirely. The verifier’s management URL comes from an Aspire-injected configuration value named SwiyuVerifierMgmtUrl, resolved as an environment variable at container startup. This is a clean pattern once you are already on Aspire, but it does create a hard dependency on that key being present. A missing value here fails with a null reference exception rather than a descriptive error, so guarding against that in production is worth the extra few lines.
Wiring It Together in the App Host
The last piece is telling Aspire which components get external endpoints and which stay internal only.
swiyuVerifier = builder.AddContainer("swiyu-verifier", "ghcr.io/swiyu-admin-ch/swiyu-verifier", "latest")
// ...
.WithHttpEndpoint(port: VERIFIER_PORT, targetPort: 8080, name: HTTP);
swiyuProxy = builder.AddProject<Projects.Swiyu_Endpoints_Proxy>("swiyu-endpoints-proxy")
.WaitFor(swiyuVerifier)
.WithEnvironment("SwiyuVerifierMgmtUrl", swiyuVerifier.GetEndpoint(HTTP))
.WithExternalHttpEndpoints();
identityProvider = builder.AddProject<Projects.Idp_Swiyu_Passkeys_Sts>(IDENTITY_PROVIDER)
.WithExternalHttpEndpoints()
// ...
.WaitFor(swiyuVerifier)
.WaitFor(swiyuProxy);
The swiyu-verifier container never calls WithExternalHttpEndpoints, so Aspire never exposes it outside the internal network regardless of what the proxy does. Only the swiyu-endpoints-proxy project, along with the identity provider, calls WithExternalHttpEndpoints and becomes internet reachable. WaitFor ensures the proxy does not start accepting traffic before its upstream container is ready, which avoids a window of failed requests during a cold start or a redeploy.

What This Actually Protects Against
This setup solves one specific problem well. It stops anyone outside the internal network from reaching the swiyu management APIs directly, since those routes are simply never forwarded by the proxy. That is a meaningful reduction in attack surface for a government-grade identity system, where management operations like key rotation or verifier configuration should never be internet reachable to begin with.
What it does not solve is authorization inside the internal network, and Damien flags this directly in his notes. Once you are inside the network boundary, the swiyu generic container’s management APIs remain wide open with no authentication or authorization applied. Anyone who compromises another workload on the same internal network, or finds a foothold through a misconfigured internal DNS entry, can still reach those APIs freely. Network isolation is a perimeter control, and perimeter controls fail the moment something gets past the perimeter, which is the exact assumption a zero trust architecture is designed not to make.
When to Reach for This Pattern
This approach makes sense when you are already running .NET Aspire and want proxy configuration living in code next to the rest of your orchestration logic, rather than in a separate infrastructure tool. It is a poor fit if you need centralized policy enforcement across many services, rate limiting, or WAF-style protections, since plain YARP does not give you those out of the box.
For an Azure deployment, pairing this same YARP proxy with Azure Container Apps’ built-in ingress controls, private endpoints, and network security groups would add defense in depth beyond what a single proxy project provides on its own. That closes off network paths at the platform level in addition to the application level, which matters for a system handling identity credentials.
The bigger takeaway is architectural, not tied to YARP specifically. Network isolation and application-level security are two separate layers, and this post is honest that it delivers only the first one. Anyone building something similar should plan for both from the outset instead of treating a reverse proxy as the whole security story.
Leave a Reply