How to customize the login page of an ABP Blazor application

Introduction

In this article, I will show you how to customize the login page of a Blazor APB application

Source Code

The sample application has been developed with Blazor as UI framework and SQL Server as database provider.

The source code of the completed application is available on GitHub.

Requirements

The following tools are needed to be able to run the solution.

  • .NET 8.0 SDK
  • VsCode, Visual Studio 2022 or another compatible IDE
  • ABP CLI version 8.0.0

Development

Creating a new Application

  • Install or update the ABP CLI:
dotnet tool install -g Volo.Abp.Cli || dotnet tool update -g Volo.Abp.Cli
  • Use the following ABP CLI command to create a new Blazor ABP application:
abp new AbpBlazorCustomizeLoginPage -u blazor -o AbpBlazorCustomizeLoginPage

Open & Run the Application

  • Open the solution in Visual Studio (or your favorite IDE).
  • Run the AbpBlazorCustomizeLoginPage.DbMigrator application to apply the migrations and seed the initial data.
  • Run the AbpBlazorCustomizeLoginPage.HttpApi.Host application to start the server-side.
  • Run the AbpBlazorCustomizeLoginPage.Blazor application to start the Blazor UI project.

Create a CustomLoginModel

  • Create a folder structure Pages/Account in the HttpApi.Host project of your application.
  • Add a CustomLoginModel.cs class to the Account folder.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Volo.Abp.Account.Web;
using Volo.Abp.Account.Web.Pages.Account;

namespace AbpBlazorCustomizeLoginPage.HttpApi.Host.Pages.Account
{
  public class CustomLoginModel : LoginModel
  {
    public CustomLoginModel(IAuthenticationSchemeProvider schemeProvider, IOptions<AbpAccountOptions> accountOptions, IOptions<IdentityOptions> identityOptions, IdentityDynamicClaimsPrincipalContributorCache contributorCache)
        : base(schemeProvider, accountOptions, identityOptions, contributorCache) { }
  }
}
  • Add a Login.cshtml file to the Account folder.
@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling

@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@using Volo.Abp.Account.Settings
@using Volo.Abp.Settings

@model AbpBlazorCustomizeLoginPage.HttpApi.Host.Pages.Account.CustomLoginModel

@inject IHtmlLocalizer<AccountResource> L
@inject Volo.Abp.Settings.ISettingProvider SettingProvider

<div class="card text-center mt-3 shadow-sm rounded">
    <div class="card-body abp-background p-5">
        <img class="mb-4" src="https://raw.githubusercontent.com/bartvanhoey/AbpBlazorCustomizeLoginPage/main/~/images/abp-logo-light.svg" alt="ABP logo" width="115" height="55">
        <h4>@L["Login"]</h4>
        @if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
        {
            <strong>
                @L["AreYouANewUser"]
                <a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})" class="text-decoration-none">@L["Register"]</a>
            </strong>
        }
        @if (Model.EnableLocalLogin)
        {
            <form method="post" class="mt-4 text-left">
                <input asp-for="ReturnUrl" />
                <input asp-for="ReturnUrlHash" />
                <div class="form-group">
                    <input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control" placeholder="Username" />
                    <span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span>
                </div>
                <div class="form-group">
                    <input asp-for="LoginInput.Password" class="form-control" placeholder="Password"/>
                    <span asp-validation-for="LoginInput.Password" class="text-danger"></span>
                </div>
                <abp-row>
                    <abp-column>
                        <abp-input asp-for="LoginInput.RememberMe" class="mb-4" />
                    </abp-column>
                    <abp-column class="text-right">
                        <a href="@Url.Page("./ForgotPassword", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})">@L["ForgotPassword"]</a>
                    </abp-column>
                </abp-row>
                <abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-block btn-lg mt-3">@L["Login"]</abp-button>
                @if (Model.ShowCancelButton)
                {
                    <abp-button type="submit" button-type="Secondary" formnovalidate="formnovalidate" name="Action" value="Cancel" class="btn-block btn-lg mt-3">@L["Cancel"]</abp-button>
                }
            </form>
        }
        @if (Model.VisibleExternalProviders.Any())
        {
            <div class="mt-2">
                <h5>@L["OrLoginWith"]</h5>
                <form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
                    <input asp-for="ReturnUrl" />
                    <input asp-for="ReturnUrlHash" />
                    @foreach (var provider in Model.VisibleExternalProviders)
                    {
                        <button type="submit" class="btn btn-primary m-1" name="provider" value="@provider.AuthenticationScheme" title="@L["GivenTenantIsNotAvailable", provider.DisplayName]">@provider.DisplayName</button>
                    }
                </form>
            </div>
        }
        @if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any())
        {
            <div class="alert alert-warning">
                <strong>@L["InvalidLoginRequest"]</strong>
                @L["ThereAreNoLoginSchemesConfiguredForThisClient"]
            </div>
        }
    </div>
</div>

Add some custom styles and images to the HttpApi.Host project

  • add a file login.css to the wwwroot folder of the HttpApi.Host project.
.abp-background {
    background-color:  #e90052 !important;
}
  • Open file AbpBlazorCustomizeLoginPageHttpApiHostModule.cs and update the ConfigureBundles() method.
 private void ConfigureBundles()
 {
    Configure<AbpBundlingOptions>(options =>
    {
        options.StyleBundles.Configure(
            LeptonXLiteThemeBundles.Styles.Global,
            bundle =>
            {
                bundle.AddFiles("/global-styles.css");
                bundle.AddFiles("/login.css");
            }
        );
    });
}
  • add an assets/images folder to the wwwroot folder of the HttpApi.Host project and copy/paste the abp logo in the images folder. You can find a copy of the logo here.

Start both the Blazor and the HttpApi.Host project to run the application

Et voilà! This is the result.

Blazor Up and Running with customized login page

You can now modify the login page, add your custom styles, custom images, etc.

Find more about ASP.NET Core (MVC/Razor Pages) User Interface Customization Guide here.

Get the source code on GitHub.

Enjoy and have fun!

Sturla 173 weeks ago

How do I accomplish this with a separate identiyserver?

GDUnit 153 weeks ago

It works if you take the same steps but within the .IdentityServer project instead of HttpApi.Host

327992883@qq.com 50 weeks ago

How to use custom login page with layered AuthServer using LeptonX Theme

2ristpanel 34 weeks ago

Hi Bart ,

I reproduced your solution and it's working fine , the only problem i have is with localizing RememberMe checkbox label .

i tried using "label" attribute but it's giving me this in my browser:

<abp-column> <abp-input label="@L["RememberMe"]" asp-for="LoginInput.RememberMe" class="mb-4" /> </abp-column>

"Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString",

how do you solve this?

njkhanh 20 weeks ago

When i run with debug it's working. But when i publish build it, custom page not change