Instead of using the simple HTTP forwarder, you can also use a more feature complete reverse proxy - e.g. Microsoft YARP.
YARP has built-in advanced features, e.g. load balancing, service discovery, session affinity etc. It also has its own extensibility mechanism. The BFF library includes a set of YARP extensions (e.g. token management and anti-forgery protection) so you can get the best of both worlds.
To enable our YARP integration, add a reference to the Duende.BFF.Yarp Nuget package and add the YARP and our service to DI:
services.AddBff();
// adds YARP with BFF extensions
var builder = services.AddReverseProxy()
.AddBffExtensions();
YARP is most commonly configured via a config file. The following simple snippet forwards a local URL to a remote API:
"ReverseProxy": {
"Routes": {
"todos": {
"ClusterId": "cluster1",
"Match": {
"Path": "/todos/{**catch-all}",
}
}
},
"Clusters": {
"cluster1": {
"Destinations": {
"destination1": {
"Address": "https://api.mycompany.com/todos"
}
}
}
}
}
See the Microsoft documentation for the complete configuration schema.
Another option is to configure YARP in code using the in-memory config provider included in the BFF extensions for YARP. The above configuration as code would look like this:
builder.LoadFromMemory(
new[]
{
new RouteConfig()
{
RouteId = "todos",
ClusterId = "cluster1",
Match = new()
{
Path = "/todos/{**catch-all}"
}
}
},
new[]
{
new ClusterConfig
{
ClusterId = "cluster1",
Destinations = new Dictionary<string, DestinationConfig>(StringComparer.OrdinalIgnoreCase)
{
{
"destination1", new()
{
Address = "https://api.mycompany.com/todos"
}
},
}
}
});
Using the BFF extensions, you can benefit from the built-in token management, and attach user or client access tokens automatically to proxied API calls. This is done by adding metadata with the name Duende.Bff.Yarp.TokenType to the route or cluster configuration:
"ReverseProxy": {
"Routes": {
"todos": {
"ClusterId": "cluster1",
"Match": {
"Path": "/todos/{**catch-all}",
},
"Metadata": {
"Duende.Bff.Yarp.TokenType": "User"
}
}
},
// rest omitted
}
The allowed values for the token type are User, Client, UserOrClient
If you are using the code config method, the WithAccessToken extension method achieves the same:
builder.LoadFromMemory(
new[]
{
new RouteConfig()
{
RouteId = "todos",
ClusterId = "cluster1",
Match = new RouteMatch
{
Path = "/todos/{**catch-all}"
}
}.WithAccessToken(TokenType.User)
},
// rest omitted
});
Just like with local APIs, you can also add additional anti-forgery protection to proxied API calls.
You can either add the anti-forgery protection to all YARP routes by adding the AsBffApiEndpoint extension:
endpoints.MapReverseProxy()
.AsBffApiEndpoint();
// or shorter
endpoints.MapBffReverseProxy();
If you need more fine grained control over which routes should enforce the anti-forgery header, you can also annotate the route configuration by adding the Duende.Bff.Yarp.AntiforgeryCheck metadata to the route config:
"ReverseProxy": {
"Routes": {
"todos": {
"ClusterId": "cluster1",
"Match": {
"Path": "/todos/{**catch-all}",
},
"Metadata": {
"Duende.Bff.Yarp.AntiforgeryCheck" : "true"
}
}
},
// rest omitted
}
This is also possible in code:
builder.LoadFromMemory(
new[]
{
new RouteConfig()
{
RouteId = "todos",
ClusterId = "cluster1",
Match = new RouteMatch
{
Path = "/todos/{**catch-all}"
}
}.WithAntiforgeryCheck()
},
// rest omitted
});
You can combine the token management feature with the anti-forgery check.
To enforce the presence of the anti-forgery headers, you need to add a middleware to the YARP pipeline:
endpoints.MapReverseProxy(proxyApp =>
{
proxyApp.UseAntiforgeryCheck();
});