Home > Back-end >  I want to specify a non-default layout for authentication errors in Blazor
I want to specify a non-default layout for authentication errors in Blazor

Time:03-31

I want to specify the layout in the following error.

  • Route not found.
  • Not authorized.
  • Forbidden.

File details

App.razor

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(App).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    <!-- TODO RZ9999 when Context removed -->
                    <AuthorizeView Context="authenticated">
                        <Authorized>
                            <!-- TODO ErrorLayout -->
                            <Error403/>
                        </Authorized>
                        <NotAuthorized>
                            <!-- TODO ErrorLayout -->
                            <Error401/>
                        </NotAuthorized>
                    </AuthorizeView>
                </NotAuthorized>
            </AuthorizeRouteView>
            <FocusOnNavigate RouteData="@routeData" Selector="h1"/>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(ErrorLayout)">
                <Error404/>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

Error401.razor, Error403.razor, Error404.razor

<PageTitle>Error401</PageTitle>

<h3>Error401</h3>
<PageTitle>Error403</PageTitle>

<h3>Error403</h3>
<PageTitle>Error404</PageTitle>

<h3>Error404</h3>

What I have tried

According to the description in File details, ErrorLayout is used for Error404, but MainLayout is applied for Error401 and Error403.
I tried the following description, but it did not work.

Add @layout

Error401.razor as follows but MainLayoute was applied. Is @layout only valid for those with @page?

@layout ErrorLayout

<PageTitle>Error401</PageTitle>

<h3>Error401</h3>

Add LayoutView

If Error401 and Error403 are children of LayoutView, they will be nested in the MainLayout and ErrorLayout layouts.

<AuthorizeView Context="authenticated">
    <Authorized>
        <LayoutView Layout="@typeof(ErrorLayout)">
            <Error403/>
        </LayoutView>
    </Authorized>
    <NotAuthorized>
        <LayoutView Layout="@typeof(ErrorLayout)">
            <Error401/>
        </LayoutView>
    </NotAuthorized>
</AuthorizeView>

Questions

How do I write a Router to specify the layout in case of authorization and authentication errors?

CodePudding user response:

Is @layout only valid for those with @page?

Yes sort of... You can also add it to a layout to to specify nesting.

FYI: 401 and 403 are both captured by <NotAuthorized>.

Use a component like:

public class RedirectToPage : ComponentBase
{
    [Inject]
    private NavigationManager Navigation { get; set; }

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

    protected override void OnInitialized()
        => Navigation.NavigateTo(Href);
}

You could expand this to include a return address etc.

Inside your App.razor

<NotAuthorized>
@if (context.User.Identity?.IsAuthenticated == true)
{
    <RedirectToPage Href="/error403" />
}
else
{
    <RedirectToPage Href="/error401" />
}
</NotAuthorized>

Create the appropriate error pages using @layout ... of your choosing.

CodePudding user response:

I solved it by creating a custom version of AuthorizeRouteView.
Create a CustomAuthorizeRouteView with the following changes based on the Blazor source AuthorizeRouteView.

public sealed class CustomAuthorizeRouteView : RouteView
{
...
    [Parameter]
    public Type NotAuthorizedLayout { get; set; }
...
    private void RenderContentInNotAuthorizedLayout(RenderTreeBuilder builder, RenderFragment content)
    {
        builder.OpenComponent<LayoutView>(0);
        builder.AddAttribute(1, nameof(LayoutView.Layout), NotAuthorizedLayout);
        builder.AddAttribute(2, nameof(LayoutView.ChildContent), content);
        builder.CloseComponent();
    }

    private void RenderNotAuthorizedInDefaultLayout(RenderTreeBuilder builder, AuthenticationState authenticationState)
    {
        var content = NotAuthorized ?? _defaultNotAuthorizedContent;
        RenderContentInNotAuthorizedLayout(builder, content(authenticationState));
    }
...
}

App.razor

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(App).Assembly">
        <Found Context="routeData">
            <CustomAuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" NotAuthorizedLayout="@typeof(ErrorLayout)">
                <NotAuthorized>
                    @if (context.User.Identity?.IsAuthenticated != true)
                    {
                        <Error401/>
                    }
                    else
                    {
                        <Error403/>
                    }
                </NotAuthorized>
            </CustomAuthorizeRouteView>
            <FocusOnNavigate RouteData="@routeData" Selector="h1"/>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(ErrorLayout)">
                <Error404/>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

With this content, I was able to do what I wanted to do without navigation with the original URL.

  • Related