How to configure Blazor app to return index.html only for authenticated users

Viewed 1753

We have existing ASP.NET Core application(.NET 5) which uses angular as UI framework.
We created a Blazor WASM client library which we want to use in this application alongside with already existing angular framework.

Following documentations this is how we configured it in Startup.Configure method to "serve" balzor app from the dedicated directory "blazor-app" in wwwroot.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...
    app.UseBlazorFrameworkFiles("/blazor-app");
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
        endpoints.MapFallbackToFile("blazor-app/index.html");
    });
}

How we can configure Blazor app, so it's index.html is returned only for authenticated users?

For example something like this?

[Authorize]
public class ClientController
{
    public IActionResult ClientApp()
    {
        // returns Blazor app index.html
    }
}
      
3 Answers

There are a couple of approaches to doing authentication before serving the view. I decided to do it by ASP.Net Session management.

Add session service to your application:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddDistributedMemoryCache();
    services.AddSession(options =>
    {
        options.IdleTimeout = TimeSpan.FromSeconds(3600); // an hour
        options.Cookie.HttpOnly = true;
        options.Cookie.IsEssential = true;
    });
   
}

Then use the session in your app:

app.UseRouting();
// Call UseSession after UseRouting and before MapRazorPages and MapDefaultControllerRoute
app.UseSession();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

Now, before serving the view check if the user is authenticated or not:

public IActionResult Index()
{
    string KEY = "Authentication-Key";
    if (string.IsNullOrEmpty(HttpContext.Session.GetString(KEY))|| 
        !IsValid(HttpContext.Session.GetString(KEY))){
        // Redirect to {Action = Index} of {Controller = Login}
        return RedirectToAction("Index", "Login"); 
    }
    return View(); // consider that this is the blazor view
}
private bool IsValid(string KEY)
{
    // Implement your validation mechanism
    
    return KEY == "THIS IS THE AUTH KEY";
}

Let's assume that the user is redirected to the Login page after it has been detected not authenticated and again got authenticated and receives a key for authentication:

string KEY = "Authentication-Key";
HttpContext.Session.SetString(KEY, "THIS IS THE AUTH KEY");

Finally, when she/he returns to the Index page (ex: blazor view) either manually or automatically, the view is served.

Index.html is an entry point to your app. If you want to have different UI for authorized and unauthorized users (ex no menu), you should provide different layouts. Below code is part of App.razor. The key is to have an additional layout component (UnauthorizedLayout), which will be shown if the user is not authorized. This layout does not contain any elements in it, and its sole purpose is to host the login view.

AuthenticationService is just an example how to get a reference to user, you can use any method you prefer.

<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
    @if (AuthenticationService.User == null)
    {
        <AppRouteView RouteData="@routeData" DefaultLayout="@typeof(UnauthorizedLayout)" />
    }
    else
    {
        <AppRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    }
    @*<FocusOnNavigate RouteData="@routeData" Selector="h1" />*@
</Found>
<NotFound>
    ...
</NotFound>

UnathorizedLayout.razor:

@inherits LayoutComponentBase

<div id="container">
    @Body
</div>
<AuthorizeView>
<Authorized>
    <main class="main" >

        <div class="container-fluid" data-layout="container">

            <nav class="navbar navbar-light navbar-vertical navbar-expand-xl">

                
                <NavMenu></NavMenu>
            </nav>
            <div class="content">
                <!-- navbar -->
                <Toolbar></Toolbar>

                <!-- content -->
                @Body

                <!-- footer -->
                <Footer></Footer>
            </div>
        </div>

    </main>
</Authorized>
<NotAuthorized>
   <RedirectToLogin />

</NotAuthorized>

RedirectToLogin

    @inject NavigationManager Navigation
@code {
    [CascadingParameter]
    private Task<AuthenticationState> AuthenticationStateTask { get; set; }

    protected override async Task OnInitializedAsync()
    {
        var authenticationState = await AuthenticationStateTask;

        if (authenticationState?.User?.Identity is null || !authenticationState.User.Identity.IsAuthenticated)
        {
            Navigation.NavigateTo("/Login", true);
        }
    }
}
Related