asp.net-coreauthenticationasp.net-identityblazor-server-sideasp.net-authorization

I need a simple example of a login page in blazor server app without to see or access (in the urls string) any other elements


I will shorten my question. xD

So, I use VS2022, C#.

  1. Created blazorserverapp with individual accounts using existing VS 2022 template - OK.

  2. DB migration to my postgres DB using EF - done.

  3. Login into created website - OK.

  4. Login/logout works - OK.

Questions:

  1. How can I avoid seeing all elements on the created blazorserver app webpage while I am not logged in?

  2. How can I avoid accessing of the webpage elements through entering of the url, for example: https://localhost:7164/counter while I am not logged in?

What has to be changed in the code to get the showing/accessing (while not logged in) forbidden?

A simple empty (white) page without any elements, except a little window with user/password fields a and a button inside and in the middle of that empty login page, would be nice.

But something like that, which is actually simple, is not available anywhere – I looked for that for weeks in google…

Everybody is showing/presenting a blazorserver login (individual accounts, authentication to SQL Server or - for me ideally - PostgreSQL) example, but: you can see/access all elements while you are not logged in.

Can somebody provide example of that, pls?

Thx best

P.S.: Sorry for my bad English (I am from Austria)

That is my MainLayout.razor:

@inherits LayoutComponentBase

<PageTitle>msgsolutions</PageTitle>
<div class="page">
<div class="sidebar">
    <NavMenu />
</div>
<main>
    <div class="top-row px-4 auth">
        <LoginDisplay />
        <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
    </div>
    <article class="content px-4">
        @Body
    </article>
</main>

And here my Index.razor:

@page "/"

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />

This is Shared/LoginDisplay.razor:

<AuthorizeView>
<Authorized>
    <a href="Identity/Account/Manage">Hello, @context.User.Identity?.Name!</a>
    <form method="post" action="Identity/Account/LogOut">
        <button type="submit" class="nav-link btn btn-link">Log out</button>
    </form>
</Authorized>
<NotAuthorized>
    <a href="Identity/Account/Register">Register</a>
    <a href="Identity/Account/Login">Log in</a>
</NotAuthorized>

And here Areas/Identity/Pages/shared/_LoginPartial.cshtml:

@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
    <li class="nav-item">
        <a  class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity?.Name!</a>
    </li>
    <li class="nav-item">
        <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="/" method="post">
            <button  type="submit" class="nav-link btn btn-link text-dark">Logout</button>
        </form>
    </li>
}
else
{
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li>
}
</ul>

Nice would be to get this displayed in the middle of a login page. And this login page has to appear first and no other elements have to be shown on this first page until the user is logged in (example):

    <style>


    #login .container #login-row #login-column #login-box {
        margin-top: 120px;
        max-width: 400px;
        height: 280px;
        border: 1px solid #9C9C9C;
        background-color: #EAEAEA;
        margin-left: auto;
        margin-right: auto;
    }

    input[type=submit] {
        background: #00BBFF;
    }

    #login .container #login-row #login-column #login-box #login-form {
        padding: 20px;
    }

        #login .container #login-row #login-column #login-box #login-form #register-link {
            margin-top: -85px;
        }
</style>

<div id="login">
    <h3 class="text-center text-white pt-5">Login form</h3>
    <div class="container">
        <div id="login-row" class="row justify-content-center align-items-center">
            <div id="login-column" class="col-md-6">
                <div id="login-box" class="col-md-12">
                    <form id="login-form" class="form" action="" method="post">
                        <h3 class="text-center text-info">Login</h3>
                        <EditForm Model=@login>

                            <div class="form-group">
                                <label for="Username" class="text-info">Username:</label><br>
                                <InputText @bind-Value=@login.user type="text" name="username" id="username" class="form-control" />

                            </div>
                            <div class="form-group">
                                <label for="Password" class="text-info">Password:</label><br>
                                <InputText @bind-Value=@login.password type="password" name="password" id="password" class="form-control" />

                            </div>
                            <div class="form-group">
                                <input type="submit" name="submit" class="btn btn-primary btn-outline-primary" value="Authenticate" @onclick="@(()=>IsLoginValid())"></input>
                            </div>
                        </EditForm>

                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

Edit (Solved): We were able to manage that using this way: used Index.razor (which contains: "/") and replaced the content with that:

@page "/"
@inject NavigationManager NavigationManager
@inject IJSRuntime jsRuntime
@inject IHttpContextAccessor httpContext
@using System.Net;

<PageTitle>OurLogin</PageTitle>

<style>


    #login .container #login-row #login-column #login-box {
        margin-top: 120px;
        max-width: 400px;
        height: 280px;
        border: 1px solid #9C9C9C;
        background-color: #EAEAEA;
        margin-left: auto;
        margin-right: auto;
    }

    input[type=submit] {
        background: #00BBFF;
    }

    #login .container #login-row #login-column #login-box #login-form {
        padding: 20px;
    }

        #login .container #login-row #login-column #login-box #login-form #register-link {
            margin-top: -85px;
        }
</style>

<div id="login">
    <h3 class="text-center text-white pt-5">Login form</h3>
    <div class="container">
        <div id="login-row" class="row justify-content-center align-items-center">
            <div id="login-column" class="col-md-6">
                <div id="login-box" class="col-md-12">
                    <form id="login-form" class="form" action="" method="post">
                        <h3 class="text-center text-info">Login</h3>
                        <EditForm Model=@login>

                            <div class="form-group">
                                <label for="Username" class="text-info">Username:</label><br>
                                <InputText @bind-Value=@login.user type="text" name="username" id="username" class="form-control" />

                            </div>
                            <div class="form-group">
                                <label for="Password" class="text-info">Password:</label><br>
                                <InputText @bind-Value=@login.password type="password" name="password" id="password" class="form-control" />

                            </div>
                            <div class="form-group">
                                <input type="submit" name="submit" class="btn btn-primary btn-outline-primary" value="Authenticate" @onclick="@(()=>IsLoginValid())"></input>
                            </div>
                        </EditForm>

                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
<p>@login.successmsg</p>
@code
{

    public async Task IsLoginValid()
    {
        string hashedpassword = await hashTask;
        string status = await UserAuth(hashedpassword, login.user);
        string successmsg = "";
        if (status == "Authenticated!")
        {
            successmsg = "Authentication successful! Redirecting...";
            Redirect();

        }
        else if (status == "Not Authenticated!")
        {
            successmsg = "Wrong customer ID, username or password! Please try again!";
        }
        else
        {
            successmsg = status;
        }
        Thread.Sleep(500);
        login.successmsg = successmsg;
    }

    [CascadingParameter]
    public ErrorHandler? ErrorHandler { get; set; }

}

Maybe it helps somebody who could have similar question. So the case can be closed.


Solution

  • I had the same requirement, on a Blazor Server app, and I solved it this way, I hope it will fit your needs. I was inspired by https://askianoor.medium.com/different-ways-of-authorization-in-blazor-redirect-to-login-blazor-46120d629387.

    First, create a distinct layout for the login page, let's call it "LoginLayout.razor"; example:

    <div class="container py-3">
        <link href="css/login.css" rel="stylesheet">
        @Body
        <LoginError visible="false"/>
    </div>
    

    Then, create a login page, using the login layout; let's call it "Login.razor":

    @layout LoginLayout
    @page "/login"
    @attribute [AllowAnonymous]
    
    <div class="text-center">
        ... your logo or whatever
        <EditForm class="form-signin mb-4" Model="@_credential" OnSubmit="@Authenticate">
            <div class="form-group">
                <label for="user-name">User name</label>
                <InputText class="form-control" id="user-name" aria-describedby="emailHelp" placeholder="Indirizzo e-mail" @bind-Value=@_credential.UserName />
            </div>
            <div class="form-group">
                <label for="password">Password</label>
                <InputText type="password" class="form-control" id="password" placeholder="Password" @bind-Value=@_credential.Password />
            </div>
            <div class="form-group">
                <button type="submit" id="login" name="login" class="btn btn-primary">Login</button>
            </div>
            ... other stuff, like "forgot my password", etc...
        </EditForm>
    </div>
    
    @code {
        public class Credential { public string UserName { get; set; } = ""; public string Password { get; set; } = ""; }
        private Credential _credential = new Credential();
    
        private void Authenticate()
        {
            .... perform authentication using _credential fields
        }
    }
    

    Then, prepare a component which purpose is redirecting to the login page, let's call it "RedirectToLogin.razor":

    @inject NavigationManager NavigationManager
    
    @code {
        [CascadingParameter]
        private Task<AuthenticationState>? AuthState { get; set; } = null;
    
        protected override async Task OnInitializedAsync()
        {
            var authState = await AuthState!;
            if (authState?.User?.Identity == null || !authState.User.Identity.IsAuthenticated)
            {
                var returnUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
                if (string.IsNullOrEmpty(returnUrl))
                    NavigationManager.NavigateTo("/login", true);
                else
                    NavigationManager.NavigateTo("/login?returnUrl=" + returnUrl, true);
            }
        }
    }
    

    Finally, most important, modify your App.razor, so that a redirection to the login page is performed, in case the user isn't authenticated:

    <CascadingAuthenticationState>
        <Router AppAssembly="@typeof(App).Assembly">
            <Found Context="routeData">
                <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" >
                    <NotAuthorized>
                        <RedirectToLogin />
                    </NotAuthorized>
                    <Authorizing>
                        <p>Logging in...</p>
                    </Authorizing>
                </AuthorizeRouteView>
                <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>
    </CascadingAuthenticationState>