Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bypass authentication middleware when not needed in ASP.NET Core

I have the following authentication handler:

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

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var data = await GetDataFromDatabase(); //heavy load
        if(data.IsAuth)
        {
          var identity = new ClaimsIdentity(claims, Scheme.Name);
          var principal = new ClaimsPrincipal(identity);
          var ticket = new AuthenticationTicket(principal, Scheme.Name);

          return Task.FromResult(AuthenticateResult.Success(ticket));
       }
       return Task.FromResult(AuthenticateResult.Failed("Not authenticated"));
    }
}

I register it like this in ConfigureServices:

services.AddAuthentication(options =>
    {
      options.DefaultAuthenticateScheme = CustomAuthenticationSchemeOptions.SchemeName;
      options.DefaultChallengeScheme = CustomAuthenticationSchemeOptions.SchemeName;
    })
    .AddScheme<CustomAuthenticationSchemeOptions, CustomAuthenticationHandler>(CustomAuthenticationSchemeOptions.SchemeName, null);

Then I use it like this in Configure:

app.UseAuthentication();

I have several Controllers with several Actions. Some of those actions should be accessed only after authentication.

I thought that if I use the [Authorize] attribute, I make sure that only authenticated users can access it, so my middleware will be called with that request and the authentication protocol is executed (I guess it would be a very elegant and efficient solution).

public class RegisterController: ControllerBase
{
   public async Task<AsyncResult<int>>> Reg(string name)
   {
     //...
   }
}

[Authorize]
public class DataController: Controller
{
   public async Task<AsyncResult<Data>>> GetData(int dataId)
   {
     //...
   }
}

It seems that I was wrong, as each middleware is called each time when a request arrives.

So if I don't want to search the database after each Action request, I have to filter when the middleware is used.

I see some condition-based solution to use app.UseWhen and test the request path and other cumbersome ways.

Is there a more elegant and efficient way? As I have many actions, I can't create path checking for every one of them.

like image 767
Nestor Avatar asked Jun 10 '19 10:06

Nestor


People also ask

How do I bypass authorization in Web API?

If you want to allow anonymous access you can use the [AllowAnonymous] attribute. This will block access to all methods when a user is not authorized, except the GetData() method which can be called anonymously.

What happens when a terminal middleware is used in the request pipeline?

Each middleware component in the request pipeline is responsible for invoking the next component in the pipeline or short-circuiting the pipeline.

What is difference between authentication and authorization in ASP.NET Core?

Authentication is the process of determining a user's identity. Authorization is the process of determining whether a user has access to a resource. In ASP.NET Core, authentication is handled by the authentication service, IAuthenticationService, which is used by authentication middleware.

What does HttpContext SignInAsync do?

SignInAsync(HttpContext, String, ClaimsPrincipal, AuthenticationProperties) Sign in a principal for the specified scheme.


2 Answers

If you add Authorization to your middleware pipeline this will be the default for all calls to your API. Therefore all calls will act as though they have the [Authorize] attribute applied.

This is normally desirable as it means by default your application is secure and you can't accidently forget an [Authorize] attribute. I'd recommend keeping it like this and simply add the [AllowAnonymous] tag to the controllers or controller actions you want to be public.

If you want to be explicit at all times you simply remove app.UseAuthentication(); You will still be able to use [Authorize] which will trigger your middleware as you have added the service for use. But it will not automatically trigger for all calls.

Additional:

In order to use authorization without having to specify the scheme per call you can set your scheme as a default authorization policy.

services.AddAuthorization(options =>
{
    var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
        CustomAuthenticationSchemeOptions.SchemeName);

    defaultAuthorizationPolicyBuilder = 
        defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();

    options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});

To extend what others have said there are subtle differences between Authentication and Authrorization. Authentication says who a user is, Authorization say what they are allowed to do. All the above simply says is... provided I know who a user is (Is Authenticated) they are allowed to use my actions (Is Authorized). So your default authorization policy is effectively if a user is successfully authenticated.

like image 135
Lee Avatar answered Sep 16 '22 16:09

Lee


I think you are confusing authentication and authorization. From the docs,

Authentication is a process in which a user provides credentials that are then compared to those stored in an operating system, database, app or resource. If they match, users authenticate successfully, and can then perform actions that they're authorized for, during an authorization process. The authorization refers to the process that determines what a user is allowed to do.

What you have created is an authentication handler. Authentication runs regardless of whether there is an authorize attribute, as the purpose of authentication is not only authorization. It is also identification. You should setup authentication in a way that does not cause db load on every request, such as by issuing in a JSON Web Token or an Application Cookie at a selected endpoint such as /account/login. Once you have authentication setup in this manner, you can setup a custom authorization handler or authorization attribute that checks the claims through HttpContext.User.Claims to see whether the User is authorized to access the resource, (and even perform light db activity for this matter, such as looking up user roles.)

This guide outlines how to setup authorization handlers, but you can start by reading up on authorization first.

like image 36
Avin Kavish Avatar answered Sep 20 '22 16:09

Avin Kavish