4

I am working on setting up a skeleton of custom policy based authorization which will have a set of business rules to authorized the currently logged on user. But, currently the skeleton always ends up with 401 Unauthorized.

Here is my code,

public class MyAuthorizationRequirement : IAuthorizationRequirement
{
    public MyAuthorizationRequirement()
    {       
    }
}

public class MyAuthorizationHandler : AuthorizationHandler<MyAuthorizationRequirement>
{
    public MyAuthorizationHandler()
    {
    }

    protected override void Handle(AuthorizationContext context, MyAuthorizationRequirement requirement)
    {
        context.Succeed(requirement);
    }
}

And following in the Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IAuthorizationHandler, MyAuthorizationHandler>()
        .AddAuthorization(options =>
        {
            options.AddPolicy("MyAuthorization",
                                policy => policy.Requirements.Add(new MyAuthorizationRequirement()));
        });
    }

And this is how I use it in my HomeController (MVC 6)

[Authorize(Policy = "MyAuthorization")]
public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

When I don't put the Authorize attribute, the Index view renders fine. But, when I include the Authorize attribute, I just receive the blank view. And when I check the developer tools (Network) I get the following behind the scene details.

Request URL:http://localhost:51129/
Request Method:GET
Status Code:401 Unauthorized
Remote Address:[::1]:51129

The breakpoints to the constructors of my requirement and handler classes are invoked, but the breakpoints to the Handle method of Handler class, and Index method of Controller class never get invoked.

adem caglin
  • 17,749
  • 8
  • 44
  • 67
Mukesh Bhojwani
  • 1,896
  • 3
  • 17
  • 32

1 Answers1

4

That is because the AuthorizeFilter added to the pipeline for every [Authorize] attribute requires users to be authenticated.

If you look at the source code, you will see that even without calling any policy it is making sure the user is authenticated:

// Note: Default Anonymous User is new ClaimsPrincipal(new ClaimsIdentity())
if (httpContext.User == null ||
    !httpContext.User.Identities.Any(i => i.IsAuthenticated) ||
    !await authService.AuthorizeAsync(httpContext.User, context, Policy))
{
    context.Result = new ChallengeResult(Policy.AuthenticationSchemes.ToArray());
}

That condition httpContext.User.Identities.Any(i => i.IsAuthenticated) will be false for anonymous users.

  • There is also a DefaultPolicy in the AuthorizationOptions that verifies the user is authenticated. You can set that as null in the AddAuthorization configuration, but even in this case the AuthorizeFilter above will make sure the user is authenticated

The easiest way just for you to try your code would be adding an authenticated anonymous user, so any anonymous user gets assigned a ClaimsPrincipal that is authenticated (as it has a GenericIdentity instead of an empty ClaimsIdentity):

//Add this before app.UseMvc
app.Use(async (context, next) =>
{
    if (!context.User.Identities.Any(i => i.IsAuthenticated))
    {
        //Assign all anonymous users the same generic identity, which is authenticated
        context.User = new ClaimsPrincipal(new GenericIdentity("anonymous"));
    }
    await next.Invoke();

});

In a real app you will probably have some authentication framework where users can authenticate themselves, so you wouldn't have this problem.

Otherwise you might need to play with the application conventions, and replace the AuthorizeFilter with your own tweaked implementation that doesn't require authenticated users, this answer goes in that direction.

Community
  • 1
  • 1
Daniel J.G.
  • 31,175
  • 7
  • 96
  • 106
  • thank you so much for spending your valuable time in providing me your perfect guideline. That's one of the finest answer I have ever received. It works perfect. – Mukesh Bhojwani May 18 '16 at 05:23
  • In my instance I have a full fledged ASP4.5 website, that has everything with respect to authentication & stuff, this new ASP Core website has been added to put new pages in more advanced technology. User will navigate back & forth between these two websites, and I want to make sure he is authenticated, and authorized. Right now my plan is to read cookie "ASP.NET_SessionId" from Request and pass back to business logic to authorize. – Mukesh Bhojwani May 18 '16 at 05:23
  • No worries, I'm glad I could help! I am also learning ASP Core myself so it's also in my benefit :) I see, once you finish building the new site you will have proper authentication and won't face this problem – Daniel J.G. May 18 '16 at 09:43
  • I doubt, both use different HttpContexts, ASP 4.5 uses System.Web.HttpContext, and ASP Core uses Microsoft.AspNet.Http.HttpContext. There is a class library project which is common across both websites, through the core webiste if I invoke functions from the class library project, and suppose it uses context, then it always get HttpContext.Current as null. – Mukesh Bhojwani May 18 '16 at 10:26
  • I mean, different implementations of the authentication, but in both sites you could end up populating the principal/identity object – Daniel J.G. May 18 '16 at 10:28
  • I am using asp.net core 1.1.2 and have the exact issue described above. However, this proposal does not work: I still get an error 401 all the time. Is there a way to debug step by step (with VS2017) in asp.net core code easily? Or increase the framework verbosity? I would like to understand which flow causes this error 401. – CanardMoussant Aug 03 '17 at 11:46