Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Blazor redirect to login if user is not authenticated

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:

  • If an anonymous user asks for any page, then he will be redirected to the login page

Better

  • a user must be authenticated for using this app

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?

like image 684
Leonardo Lurci Avatar asked Mar 24 '20 23:03

Leonardo Lurci


People also ask

How do I redirect in Blazor?

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.

Is authenticated Blazor?

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.

Is Blazor slower?

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.


9 Answers

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

like image 126
Brett Avatar answered Oct 02 '22 05:10

Brett


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 .

like image 42
mend0k Avatar answered Oct 02 '22 05:10

mend0k


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.

enter image description here

enter image description here

enter image description here

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:

  • Use the Authorize attribute directive in the _Imports.razor file:
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
  • Add the Authorize attribute to each Razor component in the Pages folder.

I added the code to _Imports.razor but then I only received a white screen for content:

enter image description here

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.

enter image description here

With this solution I could also see the default You are logged out. message.

enter image description here

like image 37
Ogglas Avatar answered Oct 01 '22 05:10

Ogglas


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;
like image 45
ssamko Avatar answered Sep 30 '22 05:09

ssamko


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.

like image 26
Cornelis Avatar answered Oct 02 '22 05:10

Cornelis


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.

like image 28
Siegfried Niedinger Avatar answered Oct 01 '22 05:10

Siegfried Niedinger


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.

like image 44
Sipke Schoorstra Avatar answered Oct 01 '22 05:10

Sipke Schoorstra


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>
like image 42
Fredy Avatar answered Oct 04 '22 05:10

Fredy


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 
    }
}
like image 1
Ekus Avatar answered Oct 04 '22 05:10

Ekus