Search code examples
authenticationasp.net-core.net-coreidentityserver4asp.net-authorization

IdentityServer4 Authorization failed with global authorization policy


I have set up an API that acts both as an authentication / authorization service, and at the same time, exposes some endpoints for data retrieval.

The API is set up with IdentityServer4. I can succesfully retrieve an access token through client credentials. However, when i attempt to call the protected endpoint with the retrieved token, i recieve a 404 not found. The log shows that the request is unauthorized.

I suspect it has something to do with the way I add the authorization policy. It should be applied globally to all controllers, with the default authentication scheme that i apply in AddAuthentication(), as so:

    IdentityModelEventSource.ShowPII = true;

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => 
    {
        options.Authority = "http://localhost";
        options.RequireHttpsMetadata = false;
        options.Audience = "identityService";
    }).AddMvc(options => 
    {
       var policy = new AuthorizationPolicyBuilder()
          .RequireAuthenticatedUser()
          .Build();

       options.Filters.Add(new AuthorizeFilter(policy));
    }
    ...

If if remove the policy, i can call the endpoint in question, without any problems.

What am i doing wrong?

Thanks in advance


Solution

  • There are multiple issues here.

    1. In the client credentials flow there is no user. The token doesn't contain a sub claim. That's why there is no authenticated user. This explains the Unauthorized error, which has HttpStatusCode 401.

    2. The filter forces authorization for all methods. When a method is accessed by an unauthorized user, this will result in a HttpStatusCode 404. This means that certain methods have to remain available for anonymous users. You can exclude methods from global authorization by setting the AllowAnonymous attribute on the lowest level.

    E.g. HomeController:

    [AllowAnonymous]
    public IActionResult Error()
    {
    

    For the client credentials flow you may want to change the filter to something that is part of the token, like a scope:

    var policy = new AuthorizationPolicyBuilder()
                    .RequireClaim("scope", "MyScope").Build();
    

    Though that may not be suitable as a global filter when you are mixing functionality (from IdentityServer and the Api).

    Instead you can configure policies and use attributes. In startup:

    Services.AddAuthorization(option =>
    {
        option.AddPolicy("MyScopePolicy", p => p.RequireScope("MyScope"));
    }
    

    And in the controller:

    [Authorize("MyScopePolicy")]
    [ApiController]
    public class MyController : ControllerBase
    {