How to implement custom authorization filter for Blazor page

Viewed 5690

Blazor server-side, .NET Core 3.1.x Look over the examples on authorization, I am trying to get a solution for a custom authorization filter/attribute. I simply need to check the user identity during Authorization.

https://docs.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1

At the top of a Blazor page, after @page

@attribute [MyAuthFilter]

The filter. OnAuthorization never gets hit however.

public class MyAuthFilter: AuthorizeAttribute,IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var httpContext = context.HttpContext;


        // get user name
        string userName = httpContext.User.Identity.Name;

        // todo - call method to check user access
        // check against list to see if access permitted
        //context.Result = new UnauthorizedResult();

    }
}
2 Answers

The following code snippet describes how you can perform the authorization process, and how and where to display content for authorized users. You can built your own component based on the code shown here:

Profile.razor

@page "/profile"
@page "/profile/{id}"


<AuthorizeView Policy="CanEditProfile" Resource="@ID">
    <NotAuthorized>
       <h2 class="mt-5">You are not authorized to view this page</h2>
    </NotAuthorized>
<Authorized>
    <div class="container my-profile">
        <h2>My Profile</h2>
        --- Place here all the content you want your user to view ----
    </div>
 </Authorized>
</AuthorizeView>

@code {

    [Parameter]
    public string ID { get; set; }
}  

ProfileHandler.cs

public class ProfileHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        if (context.User != null)
        {
            var pendingRequirements = context.PendingRequirements.ToList();

            foreach (var requirement in pendingRequirements)
            {
                if (requirement is ProfileOwnerRequirement)
                {
                    // get profile id from resource, passed in from blazor 
                    //  page component
                    var resource = context.Resource?.ToString();
                    var hasParsed = int.TryParse(resource, out int 
                                                       profileID);
                    if (hasParsed)
                    {

                        if (IsOwner(context.User, profileID))
                        {
                            context.Succeed(requirement);
                        }
                    }
                }
            }

        }
        return Task.CompletedTask;
    }
    private bool IsOwner(ClaimsPrincipal user, int profileID)
    {
        // compare the requested memberId to the user's actual claim of 
        // memberId
        //  var isAuthorized = context.User.GetMemberIdClaim();
        // now we know if the user is authorized or not, and can act 
        // accordingly

        var _profileID = user.GetMemberIDClaim();


        return _profileID == profileID;
     }

 }

ProfileOwnerRequirement.cs

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

 }

Startup class

services.AddSingleton<IAuthorizationHandler, ProfileHandler>();

        services.AddAuthorization(config =>
        {
            config.AddPolicy("CanEditProfile", policy =>
                policy.Requirements.Add(new ProfileOwnerRequirement()));
        });

Hope this helps!

If you just want to validade one kind of requirement you can simplify @enet response replaceing ProfileHandler.cs with this:

public class ProfileHandler : AuthorizationHandler<ProfileOwnerRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ProfileOwnerRequirement requirement)
    {
        if (context.User.Identity.IsAuthenticated)
        {               
            var resource = context.Resource?.ToString();
            
            var hasParsed = int.TryParse(resource, out int 
                                                   profileID);
            if (hasParsed)
            {
                 if (IsOwner(context.User, profileID))
                 {
                        context.Succeed(requirement);
                 }
            }                                
        }

        return Task.CompletedTask;
    }

    private bool IsOwner(ClaimsPrincipal user, int profileID)
    {        
        var _profileID = user.GetMemberIDClaim();

        return _profileID == profileID;
     }
}

With HandleRequirementAsync, it only gets called once while if you use HandleAsync, it gets called multiple times, of course for simple validations makes no difference but if you have complex validations they will be run multiple times.

Related