简体   繁体   中英

React SPA + .NET CORE + AuthorizationHandler

My react application sits behind .net core 2.2

I have it protected via AzureAD

// Sign-in users with the Microsoft identity platform
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("ISINPClient", options));

I add Authorization policy like below. My Auth handler checks if the user has groups in their claims against the ones loaded from the app.settings

itemArray = Configuration.GetSection("AllowedGroups").Get<string[]>();
    services.AddMvc(options =>
                {
                    var policy = new AuthorizationPolicyBuilder()
                        .RequireAuthenticatedUser()
                        .AddRequirements(new GroupAccessRequirement(itemArray))
                        .Build();
                    options.Filters.Add(new AuthorizeFilter(policy));
                }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Here is the Authorizationhanlder

    public class GroupAccessRequirement : AuthorizationHandler<GroupAccessRequirement>, IAuthorizationRequirement
{
    private string[] _groups { get; set; }
    public GroupAccessRequirement(string[] groups)
    {
        _groups = groups;
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, GroupAccessRequirement requirement)
    {

        if (_groups == null)
            context.Succeed(requirement);


        foreach (var group in _groups)
        {
            if (context.User.HasClaim(claim => claim.Value == group))
            {
                context.Succeed(requirement);
            }
        }

        return Task.CompletedTask;
    }
}

In order to force .net core to authenticate with Azure, i have this code below

app.Use(async (context, next) =>
            {
                if (context.User.Identity.IsAuthenticated == false && context.Request.Path != "/signin-oidc")
                {
                    await context.ChallengeAsync(AzureADDefaults.AuthenticationScheme);
                }
                else
                {
                    await next.Invoke();
                }
            });

So at this point i am authenticated.

  1. But i need to force call GroupAccessRequirement to make sure user has group authorization to proceed to SPA.

  2. If you user doesn't have permission which page to show ? SPA or .NET access denied?

Question: How to validate AuthorizationHandler before showing SPA and which Access Denied page to show in SPA or .NET core?

Thank you.

Not sure whether the issue is resolved but I'd like to add my two cents.

  • AddAzureAD most does two things for you - adding OpenIdConnect as the challenge scheme and cookie as the default scheme. Unauthenticated users will be auto directed to OpenIdConnect (AAD authentication) and once done all other client/server authentication/authorization is via cookie.
  • Because of #1, for your authorization scenario if your group policy is not met, ForbidAsync will happen in the authorization service and user will be directed to the AccessDeniedPath specified in the cookie scheme.
  • Since AddAzureAD doesn't give you an opportunity to configure the cookie scheme parameters, AccessDeniedPath will be the default value - /Account/AccessDenied if I remember correctly.

If you want to customize that behavior, likely you'll have to:

  1. Don't use AddAzureAD, instead configure your own OpenIdConnect scheme (challenge) and cookie scheme (default) via AddOpenIdConnect and AddCookie.
  2. Change CookieAuthenticationOptions.AccessDeniedPath to your own custom page.
  3. Configure your SPA home page to require authorization - eg move it from static files to a Razor page.

You might have to pay attention to your api controllers after #2, since you want to return HTTP 403 instead of 302 to the access denied page. Asp.net core tries to return 403 for AJAX requests but based on my experience it might not be enough. You can customize it by setting your own OnRedirectToAccessDenied event - eg return 403 if request URL starts with /api/.

One last thing, relying on getting group membership from the id token might not be a good idea - AAD will send a place holder instead of the real group if a user has many group memberships. The best option is to request for an access token and check against Microsoft Graph API (not AAD Graph which is dying). But it does bring another issue, your app will have to have scopes like Directory.Read.All which requires admin consent. :(

I wish there is a simpler option. Happy to hear more from others.

All in all, I think for SPA application it's better to do authentication from SPA itself. Microsoft has the MSAL.js library which can help with this. It's not perfect but works in most cases. There are many examples - I have some code on github using it as well. It also contains some code to "carry over" asp.net core authorization policies to the client for React app to consume for better user experience. Feel free to check it out.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM