Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do i need to set a DefaultForbidScheme

In a WebAPI .net core project I have created a Middleware class that validates an api key. From validating it, it retrieves the permissions that the key has (user or admin) within the invoke method.

I pass it through a switch to set the principle like so

GenericIdentity identity = new GenericIdentity("API");
GenericPrincipal principle = null;

//we have a valid api key, so set the role permissions of the key
switch (keyValidatorRes.Role)
{
   case Roles.User:
        principle = new GenericPrincipal(identity, new[] { "User" });  
        context.User = principle;
        break;
    case Roles.Admin:
        principle = new GenericPrincipal(identity, new[] { "Admin" });         
        context.User = principle;
        break;
    default:
        principle = new GenericPrincipal(identity, new[] { "Other" });
        context.User = principle;
        break;
}

On controllers methods I have
[Authorize(Roles = "Admin")]

to validate the roles of an authenticated api key

If the user has the admin principle it goes through as expected. However, if it has a user or other principle then I get an error about

not having a DefaultForbidScheme

I googled around and added Authentication to my startup.cs with a customer scheme

services.AddAuthentication(options=> {
       options.DefaultForbidScheme = "forbidScheme";
       options.AddScheme<AuthSchemeHandle>("forbidScheme", "Handle Forbidden");
});

and created the AuthSchemeHandle

public class AuthSchemeHandle : IAuthenticationHandler
{
    private HttpContext _context;

    public Task<AuthenticateResult> AuthenticateAsync()
    {
        return Task.FromResult(AuthenticateResult.NoResult());
    }

    public Task ChallengeAsync(AuthenticationProperties properties)
    {
        throw new NotImplementedException();
    }

    public Task ForbidAsync(AuthenticationProperties properties)
    {
        return Task.FromResult(AuthenticateResult.Fail("Failed Auth"));
    }

    public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
    {
        _context = context;
        return Task.CompletedTask;
    }
}

Now if the principle does not have Admin it fails without the error but the response that is returned on the API is 200 with no content. I was expecting a 4xx response with the message "Failed Auth"

I am just trying to work out why it is not as expected as although it seems "fixed" I do not understand how it has fixed it.

Is there a better way that I should be doing this?

like image 737
markblue777 Avatar asked Feb 07 '19 16:02

markblue777


1 Answers

why it is not as expected as although it seems "fixed" I do not understand how it has fixed it.

There's no dark magic when the authentication handler calls IAuthenticationHandler.ForbidAsync() method. We have to do relevant things ourself. In short, setting the StatusCode=403 as your need.

public async Task ForbidAsync(AuthenticationProperties properties)
{
    properties = properties ?? new AuthenticationProperties();
    _context.Response.StatusCode = 403;
    // ...
    return Task.CompletedTask;    
}

As a side note, you don't need return a Task.FromResult() as it doesn't care about the result.

Is there a better way that I should be doing this?

The ASP.NET Core Team provides us an abstract class AuthenticationHandler to handle authentication. This abstract class has a built-in implementation for ForbidAsync(AuthenticationProperties properties) (and also for other public methods). So it's much easy to extends this abstract class as below:

public class MyAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public MyAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) 
        : base(options, logger, encoder, clock)
    {
    }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        return AuthenticateResult.NoResult();
    }
}

Finally, add a configuration for authentication service:

services
    .AddAuthentication(options=>{
        options.DefaultAuthenticateScheme = "forbidScheme";
        options.DefaultForbidScheme = "forbidScheme";
        options.AddScheme<MyAuthenticationHandler>("forbidScheme", "Handle Forbidden");
    });

It should work as expected.

like image 62
itminus Avatar answered Oct 13 '22 03:10

itminus