Securing APIs with YARP: Authentication and Authorization in .NET 8 Minimal APIs

leandroveiga

Leandro Veiga

Posted on September 15, 2024

Securing APIs with YARP: Authentication and Authorization in .NET 8 Minimal APIs

As microservices and API-driven architectures continue to rise in popularity, securing these services becomes increasingly crucial. One of the key features developers need in their API gateways is the ability to manage authentication and authorization.

In this post, we’ll explore how you can implement authentication and authorization using YARP (Yet Another Reverse Proxy) in a .NET 8 Minimal API setup. We'll show you how to handle tokens like OAuth 2.0 and JWT, ensuring that your API proxy acts as a secure gateway between your clients and downstream services.

Why Implement Authentication and Authorization in YARP?

When building a centralized API proxy, you often want to manage authentication and authorization in a unified way. By handling these tasks at the proxy level, you can:

  • Centralize Security: Apply consistent security policies across multiple APIs.
  • Simplify Backend Services: Let the proxy handle authentication and authorization, so backend services can focus on business logic.
  • Token Forwarding: Forward user tokens to downstream APIs, maintaining authorization across services.

Let’s explore how to set this up with YARP in a .NET 8 Minimal API.

Step 1: Setting Up Your YARP Proxy

First, we need to set up a basic YARP proxy in a .NET 8 Minimal API. Create a new project and add the YARP package:

dotnet new web -n AuthYarpProxy
cd AuthYarpProxy
dotnet add package Yarp.ReverseProxy
Enter fullscreen mode Exit fullscreen mode

In the Program.cs file, configure the basic YARP proxy:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddReverseProxy()
    .LoadFromMemory(GetRoutes(), GetClusters());

var app = builder.Build();

app.MapReverseProxy();
app.Run();

static IEnumerable<RouteConfig> GetRoutes()
{
    return new[]
    {
        new RouteConfig
        {
            RouteId = "secured-route",
            ClusterId = "secured-cluster",
            Match = new RouteMatch
            {
                Path = "/api/{**catch-all}"
            }
        }
    };
}

static IEnumerable<ClusterConfig> GetClusters()
{
    return new[]
    {
        new ClusterConfig
        {
            ClusterId = "secured-cluster",
            Destinations = new Dictionary<string, DestinationConfig>
            {
                { "api", new DestinationConfig { Address = "https://your-backend-api.com/" } }
            }
        }
    };
}
Enter fullscreen mode Exit fullscreen mode

This sets up a simple YARP proxy that forwards requests to a backend API.

Step 2: Adding Authentication with OAuth 2.0 and JWT

To add authentication, we’ll use OAuth 2.0 and JWT tokens. Let’s assume you are using an identity provider like IdentityServer or Azure AD to issue tokens.

Install Required Packages

We need to add authentication support in .NET:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
Enter fullscreen mode Exit fullscreen mode

Configuring JWT Authentication

Now, configure the YARP proxy to authenticate requests using the Authorization header with a JWT. Add this configuration in Program.cs:

builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        options.Authority = "https://your-identity-provider.com";
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false // depending on your setup
        };
    });

builder.Services.AddAuthorization();
Enter fullscreen mode Exit fullscreen mode

This tells .NET to use JWT Bearer authentication and validate the tokens issued by your identity provider. The Authority should point to your token provider (e.g., IdentityServer, Azure AD).

Securing the YARP Proxy

Next, enforce authentication for requests coming to your YARP proxy. You can do this by applying authentication and authorization middleware:

app.UseAuthentication();
app.UseAuthorization();

app.MapReverseProxy().RequireAuthorization();
Enter fullscreen mode Exit fullscreen mode

This ensures that any request to your proxy must include a valid JWT token in the Authorization header.

Step 3: Forwarding Tokens to Downstream APIs

In many cases, you need to pass the authentication token received from the client to your downstream API. This is crucial when the backend APIs also require authorization based on the same token.

To forward the token, modify the YARP request headers using transforms:

builder.Services.AddReverseProxy()
    .LoadFromMemory(GetRoutes(), GetClusters())
    .AddTransforms(transforms =>
    {
        transforms.AddRequestTransform(async context =>
        {
            if (context.HttpContext.User.Identity.IsAuthenticated)
            {
                // Extract the JWT token from the incoming request
                var token = await context.HttpContext.GetTokenAsync("access_token");

                // Add the token to the outgoing request headers
                if (!string.IsNullOrEmpty(token))
                {
                    context.ProxyRequest.Headers.Authorization =
                        new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
                }
            }
        });
    });
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • GetTokenAsync("access_token"): This retrieves the JWT token from the incoming request.
  • context.ProxyRequest.Headers.Authorization: Adds the token to the outgoing request, so the downstream API can validate it.

Step 4: Testing Authentication and Token Forwarding

With authentication and token forwarding in place, it’s time to test the setup.

You can use a tool like curl to make a request with a JWT token:

curl -H "Authorization: Bearer YOUR_TOKEN_HERE" http://localhost:5000/api/some-endpoint
Enter fullscreen mode Exit fullscreen mode

This request will be authenticated by the YARP proxy, and the token will be forwarded to the downstream API.

Step 5: Custom Authorization Policies

You can also define custom authorization policies to control access to specific routes or actions based on claims, roles, or other token attributes.

For example, you can define a policy that requires a specific role:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
        policy.RequireClaim("role", "admin"));
});

app.MapReverseProxy().RequireAuthorization("AdminOnly");
Enter fullscreen mode Exit fullscreen mode

This ensures that only users with the admin role can access the proxied API.

Conclusion

By integrating authentication and authorization in your YARP proxy, you can create a secure gateway that validates incoming requests and forwards them to backend services. With support for OAuth 2.0, JWT tokens, and token forwarding, YARP enables you to centralize security for your APIs, simplifying the architecture of your backend services.

YARP’s flexibility with authentication transforms makes it a powerful tool for building secure and scalable API gateways.

💖 💪 🙅 🚩
leandroveiga
Leandro Veiga

Posted on September 15, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related