I am trying to develop an app using Blazor WebAssembly and I am wondering about how I can protect my whole application if the user is not authenticated. The behavior I would implement is:
Better
At the moment I've implemented this behavior applying the [Authorize]
attribute to every page, but I would like to centralize it.
I've achieved this goal on Blazor Server Side applying the [Authorize]
attribute inside the _host.razor
component.
Is there a solution even for Blazor Client Side?
You can redirect to a page in Blazor using the Navigation Manager's NavigateTo method. In the following code snippet, it will redirect to the home page when this page gets loaded. Similarly, you can call NavigateTo() method from NavigationManager class anywhere to redirect to another page.
Blazor Server authentication Authentication in SignalR-based apps is handled when the connection is established. Authentication can be based on a cookie or some other bearer token. The built-in AuthenticationStateProvider service for Blazor Server apps obtains authentication state data from ASP.NET Core's HttpContext.
Performance. Blazor projects are slow on the client-side because you have to download the entire dot net runtime along with the necessary DLL libraries on your browser.
There may be slicker ways of doing this, but this is what worked for me:
Assuming you've configured Authentication correctly according to these instructions
In your MainLayout.razor (which is used by default for all components) add a code
block as follows:
@code{
[CascadingParameter] protected Task<AuthenticationState> AuthStat { get; set; }
protected async override Task OnInitializedAsync()
{
base.OnInitialized();
var user = (await AuthStat).User;
if(!user.Identity.IsAuthenticated)
{
NavigationManager.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}");
}
}
}
If the user is not authenticated, we redirect to the built-in The RemoteAuthenticatorView component at the "authentication/" enpoint with the "login" action. This should kick-off authentication
Basically to apply authorization to all pages in BlazorApp.Client, you have to add:
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
...to your _Imports.razor file.
Furthermore, you can add:
@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]
...on pages that don't require authorization.
Also if you wanted to redirect a user to any page, here is something I came up with:
<NotAuthorized>
@if (true) { navMan.NavigateTo("login"); }
</NotAuthorized>
...where navMan is an injected instance of NavigationManager. Here i'm redirecting the user to my Login.razor if they try to access an authorized user only page .
I tried the solution from @Brett and it worked but on redirect back to the page the user came from it ended up saying Authorizing...
, Checking login state...
and then finally Completing login...
and got stuck there. The user then had to click on a link or manually type the previous URL to get back.
Microsoft now has documentation for "Require authorization for the entire app".
https://docs.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/?view=aspnetcore-5.0#require-authorization-for-the-entire-app
According to the documentation you can either:
_Imports.razor
file:@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
I added the code to _Imports.razor
but then I only received a white screen for content:
I then noticed that https://localhost:44123/authentication/login
also gave me a white screen that Shared\RedirectToLogin.razor
normally points to. I then added @attribute [AllowAnonymous]
to Pages\Authentication.razor
and then everything worked as expected and I did not get stuck.
With this solution I could also see the default You are logged out.
message.
You can create your own redirecting component LoginRedirect.razor
.
@attribute [AllowAnonymous]
@inject NavigationManager _navigationManager
@code {
protected override void OnInitialized()
{
_navigationManager.NavigateTo("/login");
}
}
and then just use it in the App.razor
like this:
<NotAuthorized>
<LoginRedirect />
</NotAuthorized>
and dont forget to add using
to that component based on your project structure on the top of the App.razor
like:
@using SolutionName.ProjectName.Pages.MyFolder;
In my trip to get familiar with Blazor, I am following a tutorial and the author has a nice and clean way to solve this problem as well. As everything seems to be a component in Blazor, your login page probably is a component as well. It is at least in the tutorial. So all he does is this:
<NotAuthorized>
<Login />
</NotAuthorized>
And you need to add the correct using to your login component of course.
The downside to this solution is that the url doesn't match the page you see when you aren't logged in.
Just add the following lines to the client ..pages/Index.razor file.
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
This will require the Index page to be authorized on start and force the login redirect as per RedirectToLogin component used in App.Razor.
You could leverage the <NotAuthorizedContent>
template of the <Router>
component as described here:
<CascadingAuthenticationState>
<Router AppAssembly="typeof(Startup).Assembly">
<NotFoundContent>
<p>Sorry, there's nothing at this address.</p>
</NotFoundContent>
<NotAuthorizedContent>
<h1>Sorry</h1>
<p>You're not authorized to reach this page. You may need to log in as a different user.</p>
</NotAuthorizedContent>
<AuthorizingContent>
<p>Please wait...</p>
</AuthorizingContent>
</Router>
</CascadingAuthenticationState>
Replace the contents of <NotAuthorizedContent>
with a custom component called something like RedirectToLogin
whose OnInitializedAsync
checks if the user is logged in, and if not, does the redirect.
Using the asp.net core identity login Page directly
The folloing solution may not hit all whishes. Here you need to make your own razor component for your login. Instead of using the identity Page from asp.net core.
<NotAuthorized>
<Login />
</NotAuthorized>
Better is to use the one from asp.net core. The problem here is, that you get an exception if you're not checking the NavigationManager. Here is the solution without exception.
<NotAuthorized>
@{
try
{
var hasJSRuntime = navManager != null && (navManager.GetType()?.GetProperty("HasAttachedJSRuntime") != null ?
(bool)navManager.GetType().GetProperty("HasAttachedJSRuntime").GetValue(navManager) : true);
if (hasJSRuntime)
{
navManager.NavigateTo("/Identity/Account/Login", true);
}
}
catch
{
<NavLink href="Identity/Account/Login">
Redirect to Login
</NavLink>
}
}
</NotAuthorized>
If your Blazor WASM is hosted inside .net .Server project (you have 3 projects in the solution: .Client, .Server and .Shared), add .RequireAuthorization to:
.Server/Program.cs (not the one in the .Client project)
app.MapFallbackToFile("index.html").RequireAuthorization();
// redirect to login page BEFORE loading Blazor WASM page if auth is missing,
// use when ALL Blazor pages require autentication
// and you don't handle login screens etc. in Blazor but in the backend
Or you can wrap your in app.razor in and add custom LoginRedirect component suggested by @ssamko but make sure you use forceful redirection if the authentication URL is handled by .Server app.
app.razor:
@using Microsoft.AspNetCore.Components.Authorization
@using YourBlazor.Client.Components
<CascadingAuthenticationState>
<AuthorizeView>
<Authorized>
Current user: @context.User.Identity.Name
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</Authorized>
<NotAuthorized>
<LoginRedirect />
</NotAuthorized>
</AuthorizeView>
Components/LoginRedirect.razor (note the forceLoad:true);
@using System.Web
@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]
@inject NavigationManager _navigationManager
@code {
protected override void OnInitialized()
{
_navigationManager.NavigateTo("/authentication/login?returnUrl=" + HttpUtility.UrlEncode(_navigationManager.ToBaseRelativePath(_navigationManager.Uri)), forceLoad:true);
// /authentication controller is handled by hosted .Server project
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With