Implementing API Gateway Authentication With YARP in .NET
Learn to build a secure API gateway in .NET using YARP. Add authentication, per-route authorization policies, and pass user identity to backend services.
One guard at the front gate
Think about a big housing society in your city. There are ten towers inside. Now imagine every single tower had its own guard who checked your ID card, wrote your name in a register, and asked where you were going. That is a lot of guards, and each one might check in a slightly different way. Some might forget to check at all.
A smarter society puts one main gate at the front. A single guard there checks your ID once. If you are allowed in, the guard waves you through and even tells the tower "this is Ravi from flat 304, he is fine." Now the towers can relax a little. The hard check already happened at the front gate.
An API gateway is exactly that front gate for your software. Instead of every backend service checking the caller again and again, you put one gateway in front. It checks who you are (authentication) and what you are allowed to do (authorization) in one place. In the .NET world, a wonderful tool for building this gateway is YARP.
YARP stands for Yet Another Reverse Proxy. It is a free, open-source library from Microsoft, built right on top of ASP.NET Core. Because it is just an ASP.NET Core app underneath, you can use the same authentication and authorization tools you already know. In this guide we will build a secure gateway step by step, in simple words.
What is a reverse proxy?
A proxy stands in the middle of a conversation. A reverse proxy sits in front of your servers and speaks on their behalf. The client talks to the proxy. The proxy decides which backend should answer, forwards the request, and sends the reply back.
The client never talks to the Orders, Users, or Payments services directly. It only knows the gateway. This gives you one tidy place to add security, logging, rate limiting, and routing rules.
How a request flows through the gateway
Steps
Client
Sends request with a token
Authenticate
Gateway reads the token
Authorize
Checks the route policy
Forward
YARP proxies to the service
Backend
Service does the work
Setting up YARP
First, create a normal ASP.NET Core empty web app. Then add the YARP package. It is free and open source under the MIT license, so you can ship it to production with no fees. This is different from some other popular .NET libraries such as MediatR and MassTransit, which have moved to commercial licensing. YARP has not.
dotnet new web -n ApiGateway
cd ApiGateway
dotnet add package Yarp.ReverseProxyNow wire it up in Program.cs. The two key lines are AddReverseProxy() to register the services and MapReverseProxy() to handle the requests.
var builder = WebApplication.CreateBuilder(args);
// Read the proxy routes and clusters from configuration.
builder.Services
.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
// This single line turns the app into a working reverse proxy.
app.MapReverseProxy();
app.Run();That is a complete, working reverse proxy already. It has no security yet, but it can forward requests. The routing rules live in configuration, which we look at next.
Routes and clusters in plain words
YARP uses two simple ideas:
- A route describes which incoming requests to match, for example "any path starting with
/orders." - A cluster describes where to send them, for example "the Orders service running at
https://localhost:7001."
A route points at a cluster. Here is a basic appsettings.json with two routes.
{
"ReverseProxy": {
"Routes": {
"orders-route": {
"ClusterId": "orders-cluster",
"Match": { "Path": "/orders/{**catch-all}" }
},
"users-route": {
"ClusterId": "users-cluster",
"Match": { "Path": "/users/{**catch-all}" }
}
},
"Clusters": {
"orders-cluster": {
"Destinations": {
"d1": { "Address": "https://localhost:7001/" }
}
},
"users-cluster": {
"Destinations": {
"d1": { "Address": "https://localhost:7002/" }
}
}
}
}
}The pattern /orders/{**catch-all} means "match /orders and everything under it." So a call to GET /orders/42 would be matched by orders-route and forwarded to the Orders cluster. Note how we wrap route patterns like /orders/{**catch-all} in backticks when we mention them in normal text, because the curly braces would otherwise confuse the page.
A cluster can have more than one destination. When it does, YARP load-balances across them. That is a bonus you get for free, but our focus today is security.
Adding authentication
Authentication answers the question "who is this caller?" In a token-based world, the client sends a JWT bearer token in the Authorization header. The gateway reads that token, checks the signature, and builds a user identity from it.
Because YARP runs on ASP.NET Core, you add authentication the exact same way you would in any API. Register the JWT bearer handler, then turn on the middleware.
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
// Tell the gateway how to read and trust JWT tokens.
builder.Services
.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://your-identity-server";
options.Audience = "api-gateway";
});
// Register named authorization policies the routes can use.
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("admin-only", policy =>
policy.RequireRole("Admin"));
});
var app = builder.Build();
// Order matters: authenticate, then authorize, then proxy.
app.UseAuthentication();
app.UseAuthorization();
app.MapReverseProxy();
app.Run();Two details matter a lot here:
- Order of middleware.
UseAuthenticationmust come beforeUseAuthorization, and both must come beforeMapReverseProxy. The gateway needs to know the user before it decides whether to forward the request. - The Authority and Audience. These tell the gateway which identity provider issued the token and who the token is meant for. If the token does not match, it is rejected.
Per-route authorization policies
Now the fun part. Authentication tells you who the caller is. Authorization decides whether that caller may use a given route. With YARP you attach an authorization policy to each route, right in the configuration. No code change needed to switch a route's rules.
You add an AuthorizationPolicy value to the route. It can be one of three kinds of value:
| Value | Meaning |
|---|---|
| A policy name | Use a policy you registered with AddAuthorization, like admin-only. |
"default" | Require an authenticated user using the default policy. |
"anonymous" | Skip authorization checks. Good for public routes. |
Here is the same config from before, now with policies added. The login route is public, the orders route needs a signed-in user, and the admin route needs the admin-only policy.
{
"ReverseProxy": {
"Routes": {
"login-route": {
"ClusterId": "auth-cluster",
"AuthorizationPolicy": "anonymous",
"Match": { "Path": "/auth/{**catch-all}" }
},
"orders-route": {
"ClusterId": "orders-cluster",
"AuthorizationPolicy": "default",
"Match": { "Path": "/orders/{**catch-all}" }
},
"admin-route": {
"ClusterId": "admin-cluster",
"AuthorizationPolicy": "admin-only",
"Match": { "Path": "/admin/{**catch-all}" }
}
},
"Clusters": {
"auth-cluster": {
"Destinations": { "d1": { "Address": "https://localhost:7000/" } }
},
"orders-cluster": {
"Destinations": { "d1": { "Address": "https://localhost:7001/" } }
},
"admin-cluster": {
"Destinations": { "d1": { "Address": "https://localhost:7003/" } }
}
}
}
}A big strength here: you can change these policy names and reload them without restarting the proxy. YARP watches the config and applies updates live. That makes it easy to lock down a route quickly if something goes wrong.
Notice the two different rejections. A missing or invalid token returns 401 Unauthorized ("I do not know who you are"). A valid user without the right role returns 403 Forbidden ("I know who you are, but you cannot do this").
Passing the user identity to the backend
Here is a question students often ask. The gateway checked the user. Does the backend service have to check again? And how does the backend even know who the user is?
The good news: YARP forwards the original credentials by default. Cookies, bearer tokens, and API keys flow through to the destination service inside the normal request headers. So the Orders service receives the same Authorization: Bearer ... header, reads the same token, and learns the same user identity.
Identity flows to the backend
Steps
Client
Sends Bearer token
Gateway
Verifies, then forwards token
Backend
Reads same token, verifies again
Should the backend trust the gateway blindly and skip its own check? No. A safer rule is defense in depth: the gateway rejects obvious bad traffic early, and each service still verifies the caller itself. That way, if a service is ever reachable by another path, it is still protected.
Sometimes you want the gateway to swap the public token for an internal one before forwarding. For example, the outside world uses one token, but your internal network uses a different header. You can do that with a request transform.
builder.Services
.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.AddTransforms(context =>
{
// Add an internal header before the request leaves the gateway.
context.AddRequestTransform(transformContext =>
{
var user = transformContext.HttpContext.User;
var name = user.Identity?.Name ?? "anonymous";
transformContext.ProxyRequest.Headers.Add("X-Forwarded-User", name);
return ValueTask.CompletedTask;
});
});This adds an X-Forwarded-User header carrying the signed-in user's name. The backend can read it for logging or lightweight checks. Just remember: a forwarded header is only as trustworthy as the network. Keep your gateway-to-service traffic private, and still verify real tokens for anything important.
Gateway versus per-service checks
It helps to compare the two places you could enforce security. Both have a role.
| Where | What it does well | Watch out for |
|---|---|---|
| At the gateway | One place for rules, blocks bad traffic early, less load on services | Do not treat it as the only wall |
| In each service | Defense in depth, protects services reachable by other paths | Easy to forget or do inconsistently |
The best setups use both. The gateway is your front gate that turns away the obvious troublemakers. Each service is the locked door behind it. Together they give you a calm, layered defense.
A quick mental model of the whole flow
Let us walk one real request end to end. A client wants to read order 42, so it calls GET /orders/42 with a bearer token.
- The request hits the YARP gateway.
UseAuthenticationreads the token and builds the user.- The route
orders-routehas policydefault, soUseAuthorizationchecks that the user is signed in. - If the user is valid, YARP forwards the request to the Orders cluster, token included.
- The Orders service verifies the token again and returns the order.
- YARP sends the response back to the client.
If the token was missing, step 3 would stop everything with a 401, and the Orders service would never be bothered. That early rejection is the whole point of the gateway.
Common mistakes to avoid
- Wrong middleware order. If
MapReverseProxyruns beforeUseAuthorization, your policies are skipped. Authenticate and authorize first. - Forgetting public routes. A health check or login endpoint behind a
defaultpolicy will reject everyone, including your monitoring tools. Mark those routesanonymous. - Trusting forwarded headers as proof of identity. Headers like
X-Forwarded-Userare convenient but can be faked if your network is open. Verify real tokens for sensitive actions. - Skipping service-side checks. The gateway is a helper, not your only guard. Keep verification in the services too.
Quick recap
- An API gateway is one front gate for many services. It checks the caller once, so each service does not have to repeat the hard work.
- YARP is Microsoft's free, open-source reverse proxy for .NET. It is built on ASP.NET Core, so you reuse the authentication and authorization you already know.
- Add the
Yarp.ReverseProxypackage, register it withAddReverseProxy(), and serve it withMapReverseProxy(). - Routes match incoming paths; clusters point at backend destinations.
- Turn on
UseAuthenticationandUseAuthorizationbeforeMapReverseProxy. Order matters. - Set a route's
AuthorizationPolicyto a policy name,"default"(require a signed-in user), or"anonymous"(public). You can change these and reload without restarting. - YARP forwards the original token to the backend by default, so services can verify the user too. Use defense in depth: check at the gateway and in each service.
References and further reading
- YARP Authentication and Authorization — Microsoft Learn
- Get started with YARP — Microsoft Learn
- YARP Configuration Files — Microsoft Learn
- RouteConfig.AuthorizationPolicy Property — Microsoft Learn API docs
- dotnet/yarp sample appsettings.json — GitHub
Related Posts
API Key Authentication in ASP.NET Core: The Secure Way
Learn how to add API key authentication to your ASP.NET Core API the right way. Use an AuthenticationHandler, hash keys, compare safely, and follow 2026 security best practices, with diagrams and code.
Authentication and Authorization Best Practices in ASP.NET Core
A friendly guide to authentication and authorization in ASP.NET Core for .NET 10 — JWT, cookies, claims, roles, policies, and security best practices with diagrams.
Rate Limiting in ASP.NET Core: A Simple, Complete Guide
Learn rate limiting in ASP.NET Core with simple examples. Understand fixed window, sliding window, token bucket, and concurrency limiters, with diagrams, code, and real-world advice on which to pick.
YARP as an API Gateway in .NET: A Beginner's Guide
Learn how to use YARP as an API gateway in .NET 10. Routes, clusters, load balancing, health checks, auth, and transforms explained in simple, friendly steps.
Implementing an API Gateway for Microservices With YARP
Learn to build an API gateway for microservices with YARP in .NET 10. Routes, clusters, auth, rate limits, and transforms explained in simple steps.
YARP vs Nginx: A Quick Performance Comparison for .NET
A simple, friendly look at YARP vs Nginx as a reverse proxy: how each one works, real benchmark numbers, tuning tips, and how to pick the right one.