using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
using Owl.reCAPTCHA;
using System;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Account.ExternalProviders;
using Volo.Abp.Account.Public.Web;
using Volo.Abp.Account.Public.Web.Pages.Account;
using Volo.Abp.Account.Security.Recaptcha;
using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims;
using Volo.Abp.Ui.Branding;
using Volo.Saas.Tenants;
namespace Hon.IFS.Admin.Pages.Account
{
[ExposeServices(typeof(CustomLoginModel), typeof(LoginModel))]
public class CustomLoginModel : LoginModel
{
private readonly ITenantRepository _tenantRepository;
public AdminBrandingProvider _adminBrandingProvider;
public DefaultBrandingProvider _defaultBrandingProvider;
public IIdentityUserRepository _identityUserRepository;
public CustomLoginModel(
IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions,
IAccountExternalProviderAppService accountExternalProviderAppService,
ICurrentPrincipalAccessor currentPrincipalAccessor,
IAbpRecaptchaValidatorFactory recaptchaValidatorFactory,
IOptions<IdentityOptions> identityOptions,
IOptionsSnapshot<reCAPTCHAOptions> reCaptchaOptions,
ITenantRepository tenantRepository,
AdminBrandingProvider adminBrandingProvider,
DefaultBrandingProvider defaultBrandingProvider,
IIdentityUserRepository identityUserRepository)
: base(schemeProvider,
accountOptions,
recaptchaValidatorFactory,
accountExternalProviderAppService,
currentPrincipalAccessor,
identityOptions,
reCaptchaOptions)
{
_tenantRepository = tenantRepository;
_adminBrandingProvider = adminBrandingProvider;
_defaultBrandingProvider = defaultBrandingProvider;
_identityUserRepository = identityUserRepository;
}
public override async Task<IActionResult> OnGetAsync()
{
// Decode the entire query string to handle encoded parameters
var decodedQueryString = Uri.UnescapeDataString(Request.QueryString.ToString());
// Log the decoded query string for debugging purposes
System.Diagnostics.Debug.WriteLine(decodedQueryString);
// Parse the decoded query string manually to extract parameters
var queryParams = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(decodedQueryString);
if (queryParams.TryGetValue("tenantKey", out var resolvedTenant))
{
_adminBrandingProvider.SetAppName(resolvedTenant.ToString());
ViewData["sathavva"] = resolvedTenant;
}
return await base.OnGetAsync();
}
public override async Task<IActionResult> OnPostAsync(string action)
{
// Decode the entire query string to handle encoded parameters
var decodedQueryString = Uri.UnescapeDataString(Request.QueryString.ToString());
// Extract the username and password from the posted form data
var username = Request.Form["LoginInput.UserNameOrEmailAddress"].ToString();
var password = Request.Form["LoginInput.Password"].ToString();
// Log the decoded query string for debugging purposes
System.Diagnostics.Debug.WriteLine(decodedQueryString);
// Parse the decoded query string manually to extract parameters
var queryParams = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(decodedQueryString);
// Extract the tenantKey from the query parameters
if (!queryParams.TryGetValue("tenantKey", out var tenantKey))
{
//return BadRequest("Tenant key is not provided in the query parameters.");
}
// var resolvedUser = await _identityUserRepository.FindAsync()
var resolvedTenant = await _tenantRepository.FindByNameAsync(tenantKey);
// var tenantIdOrName = resolvedTenantId.TenantIdOrName;
if (resolvedTenant == null)
{
// Handle the case where the tenant could not be resolved (e.g., return an error view or redirect).
//return NotFound("Tenant could not be resolved.");
}
// Step 3: Switch tenant and call base login logic with error handling
try
{
using (CurrentTenant.Change(resolvedTenant?.Id, resolvedTenant?.Name ?? "Default"))
{
System.Diagnostics.Debug.WriteLine(CurrentTenant?.Name);
// Check ModelState before proceeding (e.g., from data annotations)
if (!ModelState.IsValid)
{
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
System.Diagnostics.Debug.WriteLine("Pre-Login Validation Errors: " + string.Join(", ", errors));
return Page();
}
// Call base method with try-catch for additional safety
var result = await base.OnPostAsync(action);
// Check if base method added errors to ModelState (e.g., invalid credentials)
if (!ModelState.IsValid)
{
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
System.Diagnostics.Debug.WriteLine("Post-Login Errors: " + string.Join(", ", errors));
return Page();
}
return result; // Success: redirect or other action result
}
}
catch (Exception ex)
{
// Handle exceptions from base login or tenant context switching
System.Diagnostics.Debug.WriteLine($"Login Processing Error: {ex.Message}");
ModelState.AddModelError("", "An unexpected error occurred during login. Please try again.");
return Page();
}
//using (CurrentTenant.Change(resolvedTenant?.Id, resolvedTenant?.Name))
//{
// System.Diagnostics.Debug.WriteLine(CurrentTenant?.Name);
// // Call base method and handle its result
// var result = await base.OnPostAsync(action);
// // Check if base method added errors to ModelState
// if (!ModelState.IsValid)
// {
// var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
// System.Diagnostics.Debug.WriteLine("Post-Login Errors: " + string.Join(", ", errors));
// return Page();
// }
// return result;
//}
}
}
}
7 Answer(s)
-
0
hi
You can use
Alerts.Warning(L["Message"]);
.try { //... } catch (Exception ex) { // Handle exceptions from base login or tenant context switching System.Diagnostics.Debug.WriteLine($"Login Processing Error: {ex.Message}"); ModelState.AddModelError("", "An unexpected error occurred during login. Please try again."); Alerts.Warning(L["Message"]); return Page(); }
-
0
public override async Task<IActionResult> OnPostAsync(string action) { // Decode the entire query string to handle encoded parameters var decodedQueryString = Uri.UnescapeDataString(Request.QueryString.ToString()); // Extract the username and password from the posted form data var username = Request.Form["LoginInput.UserNameOrEmailAddress"].ToString(); var password = Request.Form["LoginInput.Password"].ToString(); // Log the decoded query string for debugging purposes System.Diagnostics.Debug.WriteLine(decodedQueryString); // Parse the decoded query string manually to extract parameters var queryParams = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(decodedQueryString); // Extract the tenantKey from the query parameters if (!queryParams.TryGetValue("tenantKey", out var tenantKey)) { //return BadRequest("Tenant key is not provided in the query parameters."); } // var resolvedUser = await _identityUserRepository.FindAsync() var resolvedTenant = await _tenantRepository.FindByNameAsync(tenantKey); // var tenantIdOrName = resolvedTenantId.TenantIdOrName; if (resolvedTenant == null) { // Handle the case where the tenant could not be resolved (e.g., return an error view or redirect). //return NotFound("Tenant could not be resolved."); } // Step 3: Switch tenant and call base login logic with error handling try { using (CurrentTenant.Change(resolvedTenant?.Id, resolvedTenant?.Name ?? "Default")) { System.Diagnostics.Debug.WriteLine(CurrentTenant?.Name); // Check ModelState before proceeding (e.g., from data annotations) if (!ModelState.IsValid) { var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage); System.Diagnostics.Debug.WriteLine("Pre-Login Validation Errors: " + string.Join(", ", errors)); return Page(); } // Call base method with try-catch for additional safety var result = await base.OnPostAsync(action); // Check if base method added errors to ModelState (e.g., invalid credentials) if (!ModelState.IsValid) { var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage); System.Diagnostics.Debug.WriteLine("Post-Login Errors: " + string.Join(", ", errors)); return Page(); } return result; // Success: redirect or other action result } } catch (Exception ex) { // Handle exceptions from base login or tenant context switching System.Diagnostics.Debug.WriteLine($"Login Processing Error: {ex.Message}"); ModelState.AddModelError("", "An unexpected error occurred during login. Please try again."); return Page(); } //using (CurrentTenant.Change(resolvedTenant?.Id, resolvedTenant?.Name)) //{ // System.Diagnostics.Debug.WriteLine(CurrentTenant?.Name); // // Call base method and handle its result // var result = await base.OnPostAsync(action); // // Check if base method added errors to ModelState // if (!ModelState.IsValid) // { // var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage); // System.Diagnostics.Debug.WriteLine("Post-Login Errors: " + string.Join(", ", errors)); // return Page(); // } // return result; //} } } }
Here in the OnPost method. I am not getting Any Exception when I am logging with incorrect password or username
-
0
-
0
I want to customize this login page when I am typing incorrect password, I am not getting any error like "Invalid username or password" Alert and it not catching any exception as well. I am following this documentation https://abp.io/docs/2.8/How-To/Customize-Login-Page-MVC Below I am providing my complete code login.chtml @page @using Hon.IFS.Admin @using Microsoft.AspNetCore.Mvc.Localization @using Volo.Abp.Account.Localization @using Volo.Abp.Account.Settings @using Volo.Abp.Account.Web.Pages.Account @using Volo.Abp.AspNetCore.Mvc.UI.Theming @using Volo.Abp.Identity @using Volo.Abp.Settings @model Hon.IFS.Admin.Pages.Account.CustomLoginModel @inject IHtmlLocalizer<AccountResource> L @inject IThemeManager ThemeManager @inject Volo.Abp.Settings.ISettingProvider SettingProvider @inject AdminBrandingProvider adminBrandingProvider
@{ Layout = ThemeManager.CurrentTheme.GetAccountLayout(); }
@section scripts { <abp-script-bundle name="@typeof(Hon.IFS.Admin.Pages.Account.CustomLoginModel).FullName"> <abp-script src="/Pages/Account/Login.js" /> </abp-script-bundle> <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.5.1.min.js"></script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.unobtrusive-ajax/3.2.6/jquery.unobtrusive-ajax.min.js"></script> }
<style> .card-padding { padding: 0.5rem !important; /* Adjusted back to 0.5rem for consistency */ }
.full-height { min-height: 100vh; /* Kept at 100vh for full height */ display: flex; align-items: flex-start; /* Move content to the top */ padding-top: 50px; /* Adds space from the top to shift content down slightly */ } .static-content { padding-top: 40px; padding-right: 20px; } .red-button { background-color: red !important; border-color: red !important; color: white !important; } .form-check-input.small-switch { width: 2em; /* Adjust width */ height: 1em; /* Adjust height */ }
</style>
<div class="row" style="padding-top: 20px; padding-left: 20px"> <div class="col-12 col-md-6 static-content"> <h2 style="color: red">STAY</h2> <h2 style="color: red">CONNECTED</h2> <h2 style="color: white">STAY</h2> <h2 style="color: white">PROTECTED</h2> </div>
<div class="col-12 col-md-5 align-content-end pe-md-5"> <div style="display: flex; justify-content: center; align-items: center;"> <h3 style="color: white">@adminBrandingProvider.AppName</h3> </div> <div class="card shadow-sm rounded card-padding" style="max-height: 400px; max-width: 600px;"> <div class="card-body p-3"> <h3 class="text-center mb-3">@L["Login"]</h3> <form method="post" class="mt-1"> <div class="mb-1"> <label asp-for="LoginInput.UserNameOrEmailAddress" class="form-label"></label> <input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control form-control-sm" /> <span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span> </div> <div class="mb-2"> <label asp-for="LoginInput.Password" class="form-label"></label> <div class="input-group"> <input type="password" class="form-control form-control-sm" autocomplete="new-password" maxlength="@IdentityUserConsts.MaxPasswordLength" asp-for="LoginInput.Password" /> </div> <span asp-validation-for="LoginInput.Password" class="text-danger"></span> </div> <div class="row"> <div class="col"> <div class="form-check form-switch mb-3"> <input type="checkbox" class="form-check-input" id="LoginInput_RememberMe" name="LoginInput.RememberMe" value="true" /> <label class="form-check-label " for="LoginInput_RememberMe">Remember Me</label> </div> </div> <div class="col text-end"> <a href="@Url.Page("./ForgotPassword", new { returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash })">@L["ForgotPassword"]</a> </div> </div> <div class="d-grid gap-1"> <abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-sm mt-2 red-button">@L["Login"]</abp-button> @if (Model.ShowCancelButton) { <abp-button type="submit" button-type="Secondary" formnovalidate="formnovalidate" name="Action" value="Cancel" class="btn-sm mt-2">@L["Cancel"]</abp-button> } </div> </form> @if (Model.VisibleExternalProviders.Any()) { <div class="mt-1"> <h4>@L["UseAnotherServiceToLogIn"]</h4> <form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post"> @foreach (var provider in Model.VisibleExternalProviders) { <button type="submit" class="btn btn-primary btn-sm 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> </div>
</div>
login.chtml.cs using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Options; using Owl.reCAPTCHA; using System; using System.Linq; using System.Threading.Tasks; using Volo.Abp.Account.ExternalProviders; using Volo.Abp.Account.Public.Web; using Volo.Abp.Account.Public.Web.Pages.Account; using Volo.Abp.Account.Security.Recaptcha; using Volo.Abp.AspNetCore.MultiTenancy; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; using Volo.Abp.MultiTenancy; using Volo.Abp.Security.Claims; using Volo.Abp.Ui.Branding; using Volo.Saas.Tenants;
namespace Hon.IFS.Admin.Pages.Account { [ExposeServices(typeof(CustomLoginModel), typeof(LoginModel))] public class CustomLoginModel : LoginModel { private readonly ITenantRepository _tenantRepository; public AdminBrandingProvider _adminBrandingProvider; public DefaultBrandingProvider _defaultBrandingProvider; public IIdentityUserRepository _identityUserRepository; public CustomLoginModel( IAuthenticationSchemeProvider schemeProvider, IOptions<AbpAccountOptions> accountOptions, IAccountExternalProviderAppService accountExternalProviderAppService, ICurrentPrincipalAccessor currentPrincipalAccessor, IAbpRecaptchaValidatorFactory recaptchaValidatorFactory, IOptions<IdentityOptions> identityOptions, IOptionsSnapshot<reCAPTCHAOptions> reCaptchaOptions, ITenantRepository tenantRepository, AdminBrandingProvider adminBrandingProvider, DefaultBrandingProvider defaultBrandingProvider, IIdentityUserRepository identityUserRepository) : base(schemeProvider, accountOptions, recaptchaValidatorFactory, accountExternalProviderAppService, currentPrincipalAccessor, identityOptions, reCaptchaOptions) { _tenantRepository = tenantRepository; _adminBrandingProvider = adminBrandingProvider; _defaultBrandingProvider = defaultBrandingProvider; _identityUserRepository = identityUserRepository; } public override async Task<IActionResult> OnGetAsync() { // Decode the entire query string to handle encoded parameters var decodedQueryString = Uri.UnescapeDataString(Request.QueryString.ToString());
// Log the decoded query string for debugging purposes System.Diagnostics.Debug.WriteLine(decodedQueryString); // Parse the decoded query string manually to extract parameters var queryParams = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(decodedQueryString); if (queryParams.TryGetValue("tenantKey", out var resolvedTenant)) { _adminBrandingProvider.SetAppName(resolvedTenant.ToString()); ViewData["sathavva"] = resolvedTenant; } return await base.OnGetAsync(); } public override async Task<IActionResult> OnPostAsync(string action) { // Decode the entire query string to handle encoded parameters var decodedQueryString = Uri.UnescapeDataString(Request.QueryString.ToString()); // Extract the username and password from the posted form data var username = Request.Form["LoginInput.UserNameOrEmailAddress"].ToString(); var password = Request.Form["LoginInput.Password"].ToString(); // Log the decoded query string for debugging purposes System.Diagnostics.Debug.WriteLine(decodedQueryString); // Parse the decoded query string manually to extract parameters var queryParams = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(decodedQueryString); // Extract the tenantKey from the query parameters if (!queryParams.TryGetValue("tenantKey", out var tenantKey)) { //return BadRequest("Tenant key is not provided in the query parameters."); } // var resolvedUser = await _identityUserRepository.FindAsync() var resolvedTenant = await _tenantRepository.FindByNameAsync(tenantKey); // var tenantIdOrName = resolvedTenantId.TenantIdOrName; if (resolvedTenant == null) { // Handle the case where the tenant could not be resolved (e.g., return an error view or redirect). //return NotFound("Tenant could not be resolved."); } // Step 3: Switch tenant and call base login logic with error handling try { using (CurrentTenant.Change(resolvedTenant?.Id, resolvedTenant?.Name ?? "Default")) { System.Diagnostics.Debug.WriteLine(CurrentTenant?.Name); // Check ModelState before proceeding (e.g., from data annotations) if (!ModelState.IsValid) { var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage); System.Diagnostics.Debug.WriteLine("Pre-Login Validation Errors: " + string.Join(", ", errors)); return Page(); } // Call base method with try-catch for additional safety var result = await base.OnPostAsync(action); // Check if base method added errors to ModelState (e.g., invalid credentials) if (!ModelState.IsValid) { var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage); System.Diagnostics.Debug.WriteLine("Post-Login Errors: " + string.Join(", ", errors)); // Add custom error message if no specific model errors regarding the username/password ModelState.AddModelError("", "Invalid username or password."); return Page(); } return result; // Success: redirect or other action result } } catch (Exception ex) { // Handle exceptions from base login or tenant context switching System.Diagnostics.Debug.WriteLine($"Login Processing Error: {ex.Message}"); ModelState.AddModelError("", "An unexpected error occurred during login. Please try again."); Alerts.Warning(L["Message"]); return Page(); } //using (CurrentTenant.Change(resolvedTenant?.Id, resolvedTenant?.Name)) //{ // System.Diagnostics.Debug.WriteLine(CurrentTenant?.Name); // // Call base method and handle its result // var result = await base.OnPostAsync(action); // // Check if base method added errors to ModelState // if (!ModelState.IsValid) // { // var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage); // System.Diagnostics.Debug.WriteLine("Post-Login Errors: " + string.Join(", ", errors)); // return Page(); // } // return result; //} } }
}
-
0
hi
Can you share a test project with your custom code with liming.ma@volosoft.com?
Thanks
-
0
@await Component.InvokeAsync(typeof(PageAlertsViewComponent))
it resolved after adding this line in index.chtml
-
0
Great. We also use it in theme layout.