How to get URI in Blazor AuthorizationHandler

1.5k views Asked by At

I'm using a custom policy to secure a page in a server-side Blazor app. All is working well except one of my policies requires knowing the query parameters of the request. For example, the URI path is something like https://mywebsite/profile/1234, which is used to view/edit the profile with id=1234. Obviously we only want the user with profileId = 1234 editing this page. How can I check for this in my IAuthorizationHandler?

I tried injecting the HttpContext and reading the request.Query items, but it's just always "/" or "/_blazor", because it's a SPA course. I tried injecting NavigationManager (formerly UriHelper) to get the URI from there, but got an error:

'RemoteNavigationManager' has not been initialized.

I also tried using the Resource parameter to pass the information into my handler. I couldn't find any examples of how to do this, so this is my attempt:

Here is my profile.razor code, where I am limiting access with Policy="CanEditProfile"

@inject NavigationManager  NavigationManager

    <AuthorizeView Policy="CanEditProfile">
        <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>

And my IAuthorizationHandler code:

        public Task HandleAsync(AuthorizationHandlerContext context)
        {
            if (context == null || httpContextAccessor.HttpContext == null) return Task.CompletedTask;

            // try getting path from httpContext
            var path = httpContextAccessor.HttpContext.Request.Path.Value;
            Console.WriteLine($"Path = {path}");  // this is always "/_blazor"

            // try getting path from resource, passed in from blazor page component
            var resource = context.Resource?.ToString();
            Console.WriteLine($"Resource = {resource}");  // this is always null

            var pendingRequirements = context.PendingRequirements.ToList();

            foreach (var requirement in pendingRequirements)
            {
                if (requirement is EditMemberPermission)
                {
                    // if this user is admin, then grant permission
                    var isAdmin = context.User.IsInRole("Admin");
                    if (isAdmin)
                    {
                        context.Succeed(requirement);
                        continue;
                    }

                    // get requested memberId from uri parameter, e.g. /profile/1234
                    var requestedMemberId = // How do I get this?

                    if (IsOwner(context.User, requestedMemberId))
                    {
                        context.Succeed(requirement);
                    }
                }
            }
            return Task.CompletedTask;
        }

Any ideas on how to achieve this? It seems like it would be a common scenario, to secure a page based on which page data (query param "id") the user is trying to access. Many of the examples mention securing a Resource, and show it as an optional parameter, but no examples I could find show actually passing a value and using it. How can you secure a resource if you don't know what the resource is? I thought there might be a way to pass the Resource parameter from the .razor page to the Auth handler, like this, but I haven't gotten that to work either.

<AuthorizeView Policy="CanEditProfile" Resource="<pass url somehow?>" />

Thanks in advance.

1

There are 1 answers

0
Randy Gamage On

I got this working by using this code in my profile.razor:

@page "/profile/{MemberId}"

<AuthorizeView Policy="CanEditProfile" Resource="@MemberId">

... page content

</AuthorizeView>


@code {
    [Parameter]
    public string MemberId { get; set; }
}

This gets the MemberId parameter from the route, and passes it as a Resource to my IAuthorizationHandler. In that handler method, I can fetch it like this:

        public Task HandleAsync(AuthorizationHandlerContext context)
        {
            if (context == null) return Task.CompletedTask;

            // get member id from resource, passed in from blazor page component
            var resource = context.Resource?.ToString();
            var hasParsed = int.TryParse(resource, out int requestedMemberId);
            if (hasParsed)
            {
                // compare the requested memberId to the user's actual claim of memberId
                var isAuthorized = requestedMemberId == context.User.GetMemberIdClaim();
                // now we know if the user is authorized or not, and can act accordingly
            }