Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
296 views
in Technique[技术] by (71.8m points)

c# - Enforce Global Authorization Requirement in Blazor Server

I have a Blazor server app in which I've implemented authentication/authorization using ASP.Net Core Identity. My goal is to require authorization globally. If a user browses to any route, valid or not, and they haven't signed in, they are to be redirected to the login page.

The process currently only works partially. Lets say I'm running the app and I haven't signed in yet. I'm getting three different responses depending on which action I take.

  • If the browser is directed to the main route, I'm automatically redirected to the login page so we're good there.

  • If I try to browse directly to another valid route, a blank screen is displayed and I am not redirected.

  • If I try to browse to an invalid route, part of the nav bar is displayed and the child component is blank. Still no redirect.

I've tried to handle this a couple of different ways. Here's where I'm at so far.

App.razor

<CascadingAuthenticationState>
    <CascadingBlazoredModal>
        <Router AppAssembly="@typeof(Program).Assembly">
            <Found Context="routeData">
                <AuthorizeView>
                    <Authorized>
                        <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
                    </Authorized>
                    <NotAuthorized>
                        <CascadingAuthenticationState>
                            <RedirectToLogin></RedirectToLogin>
                        </CascadingAuthenticationState>
                    </NotAuthorized>
                </AuthorizeView>
            </Found>
            <NotFound>
                <CascadingAuthenticationState>
                    <LayoutView Layout="@typeof(MainLayout)">
                        <p>Sorry, there's nothing at this address.</p>
                    </LayoutView>
                </CascadingAuthenticationState>
            </NotFound>
        </Router>
    </CascadingBlazoredModal>
</CascadingAuthenticationState>

_Imports.razor

@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using Blazored.Modal
@using Blazored.Modal.Services
@attribute [Authorize]

RedirectToLogin.razor

@using System.Security.Claims
@inject NavigationManager Navigation

@code {
    [CascadingParameter] private Task<AuthenticationState> AuthenticationState { get; set; }

    protected override async Task OnInitializedAsync()
    {
        ClaimsPrincipal authenticationState = (await AuthenticationState).User;

        if (!authenticationState.Identity.IsAuthenticated)
        {
            var returnUrl = Navigation.ToBaseRelativePath(Navigation.Uri);

            if (String.IsNullOrWhiteSpace(returnUrl))
            {
                Navigation.NavigateTo("/identity/account/login", true);
                
            }

        }
    }
}

_LoginPartial.cshtml

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Identity
@attribute [AllowAnonymous]
@inject SignInManager<UserModel> SignInManager
@inject UserManager<UserModel> UserManager
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<ul class="navbar-nav">
    @if (SignInManager.IsSignedIn(User))
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a>
        </li>
        <li class="nav-item">
            <form class="form-inline" asp-area="Identity" asp-page="/Identity/Account/Logout" asp-route-returnUrl="/" method="post">
                <button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
            </form>
        </li>
    }
    else
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
        </li>
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
        </li>
    }
</ul>

I'm alright with adding an @attribute[Authorize] to each razor page if that's what it takes. Most of them will probably get one anyways once I introduce roles. I definitely want the default action for an unauthenticated user to be to redirect to the login page. What am I missing?

question from:https://stackoverflow.com/questions/65902213/enforce-global-authorization-requirement-in-blazor-server

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Not sure why you write ASP.Net Identity 4. You probably mean Asp.Net Core Identity, right ?

I'm alright with adding an @attribute[Authorize] to each razor page if that's what it takes

But you already have @attribute[Authorize]in the _Imports.razor file, which makes adding this directive to each razor file unnecessary.

I'm not sure I understand the issue. If your _Imports.razor file contains @attribute[Authorize], then unauthorized user is automatically redirected to the login page...

The RedirectToLogin component should only contain code to perform the redirection. It should not contain code to verify if the user is authenticated or not. Code that redirect to the RedirectToLogin component should contain the code that performs this checking. In your case, from AuthorizeRouteView.NotAuthorized Here's how your code should look like:

<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
              
                    @if (!context.User.Identity.IsAuthenticated)
                    {
                       var returnUrl =
               NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
                <RedirectToLogin ReturnUrl="@returnUrl" />

                        
                    }
                    else
                    {
                        <p>You are not authorized to access this resource.</p>
                    }
                </NotAuthorized>
            </AuthorizeRouteView>

Note that when the user is <NotAuthorized>, we check whether he's authenticated or not, if he's not authenticated, we render the RedirectToLogin component whose code is :

@inject NavigationManager NavigationManager

@code{
    
    [Parameter]
    public string ReturnUrl { get; set; }
    protected override void OnInitialized()
    {
        ReturnUrl = "~/" + ReturnUrl;
        NavigationManager.NavigateTo($"Identity/Account/Login?returnUrl= 
             {ReturnUrl}", forceLoad:true);
    }
}

However, if the user is authenticated, we display the message:

You are not authorized to access this resource. As you may realize by now, a user may be authenticated and still not authorized to access a given resource.

The following is the complete code that should be in your App.razor file

@inject NavigationManager NavigationManager

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    @if (!context.User.Identity.IsAuthenticated)
                        {
                           var returnUrl =
                   NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
                    <RedirectToLogin ReturnUrl="@returnUrl" />

                            
                        }
                        else
                        {
                            <p>You are not authorized to access this resource. 
                            </p>
                        }
                    </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

Note that the CascadingAuthenticationState should be used once only, and not as you did.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...