21
votes

I have a working netcore 2.2 project where I have implemented a custom policy that checks for API Keys.

In the startup.cs I am adding this policy like this

//Add Key Policy
services.AddAuthorization(options =>
{
    options.AddPolicy("AppKey", policy => policy.Requirements.Add(new AppKeyRequirement()));
});

In my AppKeyRequirement I inherit from AuthorizationHandler and resolve the keys in the incoming requests like this

protected override Task HandleRequirementAsync(AuthorizationHandlerContext authContext, AppKeyRequirement requirement)
{
    var authorizationFilterContext = (AuthorizationFilterContext)authContext.Resource;
    var query = authorizationFilterContext.HttpContext.Request.Query;

    if (query.ContainsKey("key") && query.ContainsKey("app"))
    { // Do stuff

This does not work in netcore 3.1

I am getting the following error:

Unable to cast object of type 'Microsoft.AspNetCore.Routing.RouteEndpoint' to type 'Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext'.

What is the correct way to do this in core 3 and above ?

As pointed out by Kirk Larkin, the correct way in .net 3.0 and above is to inject IHttpContextAccessor into the Auth handler and use that.

My question at this point is how do I inject this ? I cant pass this in startup.cs or at least I am not seeing how.

Any ideas/hints will be much appreciated.

1

1 Answers

33
votes

ASP.NET Core 5.x

Based on the announcement for ASP.NET Core 5.0-preview7 onwards, the Resource property is set to the current HttpContext for the request (when using endpoint routing). This means the following example will work for ASP.NET Core 5.0 onwards, without the need for IHttpContextAccessor:

public class AppKeyAuthorizationHandler : AuthorizationHandler<AppKeyRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext authContext, AppKeyRequirement requirement)
    {
        var httpContext = authContext.Resource as HttpContext;
        var query = httpContext.Request.Query;

        if (query.ContainsKey("key") && query.ContainsKey("app"))
        {
            // ...
        }
    }
}

The RouteEndpoint is still available, using httpContext.GetEndpoint().

ASP.NET Core 3.x

In versions prior to ASP.NET Core 3.0, implementations of IAuthorizationHandler were called during the MVC pipeline. In 3.0 onwards, which uses endpoint-routing (by default), these implementations are called by the authorization middleware (UseAuthorization()). This middleware runs before the MVC pipeline, rather than as part of it.

This change means that AuthorizationFilterContext is no longer passed in to authorization handlers. Instead, it's an instance of RouteEndpoint, which doesn't provide access to the HttpContext.

In your example, you're only using AuthorizationFilterContext to get hold of HttpContext. In 3.0+, inject IHttpContextAccessor into your authorization handler and use that. Here's an example for completeness:

public class AppKeyAuthorizationHandler : AuthorizationHandler<AppKeyRequirement>
{
    private readonly IHttpContextAccessor httpContextAccessor;

    public AppKeyAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }

    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext authContext, AppKeyRequirement requirement)
    {
        var httpContext = httpContextAccessor.HttpContext;
        var query = httpContext.Request.Query;

        if (query.ContainsKey("key") && query.ContainsKey("app"))
        {
            // ...
        }
    }
}

You might also need to register IHttpContextAccessor in ConfigureServices:

services.AddHttpContextAccessor();

See Use HttpContext from custom components for more information about using IHttpContextAccessor.