
YARP (Yet Another Reverse Proxy) is a reverse proxy library for .NET that has been built with configurability in mind, and there are a ton of customisation options available for developers.
In my previous articles, I explored Getting started with YARP and Transforming requests and responses with YARP.
In this article, I will cover some of the key route configuration scenarios that you will most likely need to know about when using YARP for the first time.
Setup
Before continuing, I recommend that you check out my Getting started with YARP post, where I walk through the creation of your first YARP project. The getting started post will help provide you with a basic understanding of YARP before getting into its configuration aspects.
Having said that, as long as you have an ASP.NET Core project with the YARP NuGet package installed, that should be all you need to follow along.
Let’s jump in!
Configuration
The most common way to configure YARP is via a configuration section within the appsettings.json file. One of the cool things about the YARP configuration system is that the internal configuration is updated automatically when the configuration source settings are changed, without needing to restart the application.
As a reminder from my original YARP post, this is the basic Program.cs code needed to register YARP as the reverse proxy for the web application and load the configuration.
var builder = WebApplication.CreateBuilder(args); builder.Services.AddReverseProxy() .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); var app = builder.Build(); app.MapReverseProxy(); app.Run();
The key part is the call to the LoadFromConfig method, which loads the YARP configuration from the “ReverseProxy” config section defined within the appsettings.json file.
Exact match
One of the simplest possible YARP configurations is shown below, where a specific route path is matched exactly.
{ "ReverseProxy": { "Routes": { "todos": { "ClusterId": "jsonplaceholder", "Match": { "Path": "/todos" } } }, "Clusters": { "jsonplaceholder": { "Destinations": { "default": { "Address": "https://jsonplaceholder.typicode.com/" } } } } } }
With the above configuration in place, only requests with a path that is exactly “/todos” will be handled by the route. Due to the corresponding Cluster configuration, the incoming request will be routed to “https://jsonplaceholder.typicode.com/todos”.
Although I’m using the term “exactly”, YARP is pretty forgiving with slashes in the path, so any of the following values for the “Path” property will have the same effect.
- /todos
- /todos/
- todos
Assuming an application URL of https://localhost:7101, the above paths will match requests to both https://localhost:7101/todos and https://localhost:7101/todos/ without any issues.
A request to any other path will result in a HTTP 404 (Not Found) status code being returned to the client.
Catch-all
Here is a basic catch-all configuration, similar to the one covered in the Getting started with YARP post.
{ "ReverseProxy": { "Routes": { "demo": { "ClusterId": "jsonplaceholder", "Match": { "Path": "{**catch-all}" } } }, "Clusters": { "jsonplaceholder": { "Destinations": { "default": { "Address": "https://jsonplaceholder.typicode.com/" } } } } } }
With the above configuration, there is a single “demo” route that is configured to forward all requests to the “jsonplaceholder” cluster, i.e. a single API backend in this case.
The {**catch-all} string is a special pattern that matches anything. While in this example, the route matches any incoming request, we could amend the pattern to something like “/todos/{**catch-all}” to match paths that start with “/todos/” and are followed by anything else, e.g. “/todos/1”.
Note that the two asterisks (*) characters inside the curly braces are the important part of the pattern string. Other strings such as “{**remainder}” or “{**any-string}” will have the same effect as “{**catch-all}”. You can explore how ASP.NET Core route templates are defined in more detail here.
Since YARP supports configuring as many routes as desired, it can be useful to have a catch-all route included at the end of the list of routes that will handle anything that isn’t matched by a more specific pattern.
Hosts
You can configure a specific route to only match when the hostname of the client making the request matches one of a set of host names.
{ "ReverseProxy": { "Routes": { "demo": { "ClusterId": "jsonplaceholder", "Match": { "Path": "{**catch-all}", "Hosts": [ "localhost:7101", "www.third-party.com" ] } } }, "Clusters": { "jsonplaceholder": { "Destinations": { "default": { "Address": "https://jsonplaceholder.typicode.com/" } } } } } }
In the above configuration, the “Hosts” array within the “Match” object specifies two host names that the incoming request must originate from: either “localhost:7101” or “www.third-party.com”.
If a “Hosts” array is not specified, any hostname is allowed for the route.
Methods
It’s also possible to match specific HTTP methods, such as GET, HEAD, POST, etc.
{ "ReverseProxy": { "Routes": { "demo": { "ClusterId": "jsonplaceholder", "Match": { "Path": "{**catch-all}", "Methods": [ "POST", "PUT" ] } } }, "Clusters": { "jsonplaceholder": { "Destinations": { "default": { "Address": "https://jsonplaceholder.typicode.com/" } } } } } }
In the above example, the configured route will only match when a HTTP PUT or POST request is made.
In this minimal case, since no other routes exist that can match incoming requests, a HTTP 405 (Method Not Allowed) status code will be returned to the client when a regular GET request is made.
Headers
Routes can be matched via the presence of HTTP headers.
{ "ReverseProxy": { "Routes": { "demo": { "ClusterId": "jsonplaceholder", "Match": { "Path": "{**catch-all}", "Headers": [ { "Name": "Accept", "Values": [ "application/json", "application/xml" ], "IsCaseSensitive": true } ] } } }, "Clusters": { "jsonplaceholder": { "Destinations": { "default": { "Address": "https://jsonplaceholder.typicode.com/" } } } } } }
Given the above configuration, the request must include an “Accept” header with a value of either “application/json” or “application/xml” for the route to be matched. The matching is case-insensitive in this example.
The Header object can also include a “Mode” property with one of the following values.
"ExactHeader""HeaderPrefix""Exists""Contains""NotContains""NotExists"
The “HeaderPrefix” Mode can be useful for ‘Starts With’ style Header value checks.
Authorization Policy
An Authorisation Policy can be specified via the “AuthorizationPolicy” route configuration property.
{ "ReverseProxy": { "Routes": { "demo": { "ClusterId": "jsonplaceholder", "AuthorizationPolicy": "Bearer", "Match": { "Path": "{**catch-all}" } } }, "Clusters": { "jsonplaceholder": { "Destinations": { "default": { "Address": "https://jsonplaceholder.typicode.com/" } } } } } }
In the above configuration, the “AuthorizationPolicy” has been set to “Bearer”. This means that an Authorisation Policy named “Bearer” will be enforced when the route is matched.
You’ll likely have seen code similar to the following before, where an Authorisation Policy is registered as part of the ASP.NET Core Startup logic.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(); builder.Services.AddAuthorization(options => { options.AddPolicy("Bearer", policy => { policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme); policy.RequireAuthenticatedUser(); }); });
In this example, the “Bearer” Authorisation Policy requires a user to be authenticated via a JWT Bearer token. If the Authorisation Policy requirements are not met for the route, a HTTP 401 (Unauthorized) status code will be returned to the client.
Note that the above code is not fully functional and is provided for illustration purposes only.
It is important to be aware that the YARP configuration will fail to load if the Authorisation Policy specified for the route does not exist. This is an important safety feature.
Metadata
The “Metadata” property can be used to store a list of key-value pairs that are accessible to custom extensions.
{ "ReverseProxy": { "Routes": { "demo": { "ClusterId": "jsonplaceholder", "Match": { "Path": "{**catch-all}" }, "Metadata": { "Environment": "Production", "TenantId": "Acme", "Version": "1.0" } } }, "Clusters": { "jsonplaceholder": { "Destinations": { "default": { "Address": "https://jsonplaceholder.typicode.com/" } } } } } }
Three example key-value pairs have been included in the above example, and these can be inspected when the reverse proxy is handling requests and responses for the route to make processing decisions.
I recommend checking out the Request Transforms section of my previous post, where you can see an example of accessing metadata within an ITransformProvider implementation.
Transforms
The last configuration aspect I’m going to cover in this post is “Transforms”.
YARP provides a range of built-in request transforms that can be applied to the Route configuration. All of these are documented on the YARP Request transforms page.
Below is an example of applying the “PathPattern” request transform.
{ "ReverseProxy": { "Routes": { "todos": { "ClusterId": "jsonplaceholder", "Match": { "Path": "/api/todos/{**remainder}" }, "Transforms": [ { "PathPattern": "/todos/{**remainder}" } ] } }, "Clusters": { "jsonplaceholder": { "Destinations": { "default": { "Address": "https://jsonplaceholder.typicode.com/" } } } } } }
In this final example, the route will match requests that start with “/api/todos” and route these to the JSONPlaceholder API. However, the path will be transformed according to the specified pattern, changing the “/api/todos” path to “/todos”, resulting in the request being forwarded to “https://jsonplaceholder.typicode.com/todos”.
Summary
In this article, I provided examples of the most common YARP route configurations that you will likely need to know about when you start using YARP as your reverse proxy of choice.
I covered how requests can be matched based on exact matches, patterns, hostnames, methods, and headers.
I also looked at how authorisation policies can be applied to routes, how to attach metadata to routes, and how to transform routes via configuration.
While I’ve focused on the most commonly used aspects of YARP route configuration in this article, there are lots of additional options and flexibility available, and Cluster configuration is a whole other aspect. Nonetheless, I hope that you’ve found the examples useful for helping you configure YARP routes according to your requirements.


Comments