Reputation: 109
I want to specify the layout in the following error.
<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>
<PageTitle>Error401</PageTitle>
<h3>Error401</h3>
<PageTitle>Error403</PageTitle>
<h3>Error403</h3>
<PageTitle>Error404</PageTitle>
<h3>Error404</h3>
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.
Error401.razor as follows but MainLayoute was applied. Is @layout only valid for those with @page?
@layout ErrorLayout
<PageTitle>Error401</PageTitle>
<h3>Error401</h3>
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>
How do I write a Router to specify the layout in case of authorization and authentication errors?
Upvotes: 2
Views: 2504
Reputation: 461
just want to add up on @bviala's solution:
I like this approach, but my app has got some paths that don't need authoritzation. For example the "/register" page. All private pages will automatically be redirected to the login page. The public pages all use a different layout (looks and feel of the azure bc2 login).
If you only use on if
statement within the <NotAuthorized>
section the public pages will be rendered with the MainLayout
. If want to render them with the "Login"-layout even, when you are logged in you could use the @layout LoginLayout
in every page or use an if
statement in the <Authorized>
section like in the following example.
This is my LayoutWithAuthorisation.razor
:
@inherits LayoutComponentBase
@inject NavigationManager Navigation
<AuthorizeView>
<Authorized>
@if (IsPublicPage())
{
<LayoutView Layout="typeof(LoginLayout)">
@Body
</LayoutView>
}
else
{
<LayoutView Layout="typeof(MainLayout)">
@Body
</LayoutView>
}
</Authorized>
<NotAuthorized>
<LayoutView Layout="typeof(LoginLayout)">
@if (IsPublicPage())
{
@Body
}
else
{
<RedirectToLogin />
}
</LayoutView>
</NotAuthorized>
<Authorizing>
<LayoutView Layout="typeof(LoginLayout)">
<div class="grid align-center">
<RadzenProgressBarCircular ShowValue="true" Mode="ProgressBarMode.Indeterminate" Size="ProgressBarCircularSize.Large">
<Template></Template>
</RadzenProgressBarCircular>
</div>
</LayoutView>
</Authorizing>
</AuthorizeView>
@code {
string[] _publicPages = ["register", "users/activate"];
private bool IsPublicPage()
{
string uri = Navigation.Uri.Replace(Navigation.BaseUri, string.Empty);
return _publicPages.Any(p => uri.StartsWith(p));
}
}
The Routes.razor
file can now be simple as this:
@rendermode InteractiveServer
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(LayoutWithAuthorisation)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
</Router>
For best practise you can consider injecting the public pages from your appsettings.json file.
Upvotes: 0
Reputation: 184
just want to add up on @edgar_wideman solution:
I think it might work fine in most scenarios, however having two @Body
in the MainLayout was the source of an obscure bug for me.
I'm using AddMsalAuthentication
to auth with ME-ID, and on login completion, the user would become Authorized and the layout would change, but that would retrigger a new login flow.
The solution I used is the following:
DefaultLayout
of AuthorizeRouteView
in App.razor
<PageTitle>Home</PageTitle>
<AuthorizeView>
<Authorized>
<LayoutView Layout="typeof(AppLayout)">
<h1>Hello, world!</h1>
<p>
Welcome to your new app.
</p>
</LayoutView>
</Authorized>
<NotAuthorized>
<LayoutView Layout="typeof(AuthenticationLayout)">
<MudButton Variant="Variant.Filled" Color="Color.Primary" Size="Size.Large" href="authentication/login">
Log in with Entra ID
</MudButton>
</LayoutView>
</NotAuthorized>
</AuthorizeView>
Most likely the Home page is the only page where you need a conditional layout based on auth state, so I find it acceptable.
On the rest of the pages, simply declare the layout you wish to use with @layout YourLayout
Upvotes: 1
Reputation: 382
A much easier way is to set the appropriate layouts in the MainLayout.razor
file. Like this
@inherits LayoutComponentBase
<!-- Page wrapper -->
<AuthorizeView>
<Authorized>
<div class="flex flex-row h-screen bg-slate-50 overflow-hidden">
<!-- Side Bar-->
<SideBar/>
<!-- Content area -->
<div class="">
<!-- site header -->
<HeaderLayout/>
<main>
<div class="px-4 sm:px-6 lg:px-8 py-8 bg-slate-50">
@Body
</div>
</main>
</div>
</div>
</Authorized>
<NotAuthorized>
@Body
</NotAuthorized>
<Authorizing>
<p>Hold up, lemma check you out.</p>
</Authorizing>
</AuthorizeView>
Upvotes: 1
Reputation: 109
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));
}
...
}
<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.
Upvotes: 1
Reputation: 14613
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.
Upvotes: 1