In the following statement, the AsBffApiEndpoint()
adds an attribute to all endpoints. Then there is a middleware specifically looking for that attribute and if present will check for an antiforgery header to be present.
endpoints.MapControllers().RequireAuthorization().AsBffApiEndpoint();
I need to be able to bypass that check on all GET endpoints. Most importantly, this is third party library, hence I have no control over the implementation.
I have try many things without success. Last attempt was to add a middleware custom middleware app.Use(...)
and if the attribute was present, then remove it. However that's not possible since the list of metadata is readonly
. Then, my last hope is to find a way to add same attribute -to all GET- with a flag false
which ignores the check. In other words, all AsBffApiEndpoint()
does is decorate an endpoint with [BffApi]
attribute. This attribute ignores antiforery headers if use like this [BffApi(false)]
. I know the solution is hacky because I will end up with something like this.
[BffApi]
[BffApi(false)]
//endpoint definition here
The good news is they get the endpoint metadata ordered endpoint.Metadata.GetOrderedMetadata<BffApiAttribute>()
. Meaning as long as [BffApi(false)]
takes priority in the list I should be good.
I found the solution and didn't involve adding a second attribute but to extend the builder rather. This way I could modify the endpoints metadata, which at that point are mutable.
public static TBuilder AsBffApiEndpointBypassAntiforgeryOnGET<TBuilder>(this TBuilder builder, string routePrefix) where TBuilder : IEndpointConventionBuilder
{
builder.Add(endpointBuilder =>
{
var getAttribute = endpointBuilder.Metadata.FirstOrDefault(m => m.GetType() == typeof(HttpGetAttribute)) as HttpGetAttribute;
var routeAttribute = endpointBuilder.Metadata.FirstOrDefault(m => m.GetType() == typeof(RouteAttribute)) as RouteAttribute;
if (getAttribute != null && routeAttribute != null && routeAttribute.Template.StartsWith(routePrefix))
{
endpointBuilder.Metadata.Add(new BffApiAttribute(false));
}
else
{
endpointBuilder.Metadata.Add(new BffApiAttribute(true));
}
});
return builder;
}
Then in Startup.cs
app.UseEndpoints(endpoints =>
{
endpoints.MapBffManagementEndpoints();
endpoints.MapControllers().RequireAuthorization().AsBffApiEndpointBypassAntiforgeryOnGET("api/Foo");
});
A more generic implementation would be the following.
public static IEndpointConventionBuilder AddConditionalMetadata(this IEndpointConventionBuilder builder, Func<EndpointBuilder, bool> evalEndpoint, Action<EndpointBuilder> onEvalTrue, Action<EndpointBuilder> onEvalFalse)
{
builder.Add(endpointBuilder =>
{
if (evalEndpoint.Invoke(endpointBuilder))
{
onEvalTrue.Invoke(endpointBuilder);
}
else
{
onEvalFalse.Invoke(endpointBuilder);
}
});
return builder;
}
This way you expose the function to evaluate and the actions to perform. Then, your Startup.cs
will change to this.
app.UseEndpoints(endpoints =>
{
endpoints.MapBffManagementEndpoints();
endpoints.MapControllers().RequireAuthorization().AddConditionalMetadata(
evalEndpoint: (endpointBuilder) =>
{
var routeAttribute = endpointBuilder.Metadata.FirstOrDefault(m => m.GetType() == typeof(RouteAttribute)) as RouteAttribute;
var getAttribute = endpointBuilder.Metadata.FirstOrDefault(m => m.GetType() == typeof(HttpGetAttribute)) as HttpGetAttribute;
return getAttribute != null && routeAttribute != null && routeAttribute.Template.StartsWith("api/Foo");
},
onEvalTrue: (endpointBuilder) => { endpointBuilder.Metadata.Add(new BffApiAttribute(false)); },
onEvalFalse: (endpointBuilder) => { endpointBuilder.Metadata.Add(new BffApiAttribute()); });
});
I hope this can help someone else looking for the same thing.