From 1c24c3e25359db8b3fe8d31a7602db78b0d3a631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Sun, 22 Mar 2026 20:40:57 +0300 Subject: [PATCH 01/10] Interactive Login Enhancement --- .../Components/Layout/MainLayout.razor | 68 +++++- .../Components/Layout/MainLayout.razor.cs | 129 ++++++++++- .../Components/Pages/Home.razor | 160 ++++++++------ .../Components/Pages/Home.razor.cs | 125 +++++++++-- .../Components/_Imports.razor | 1 + .../Controllers/HubLoginController.cs | 2 +- .../Program.cs | 7 +- .../wwwroot/app.css | 11 +- .../Components/Pages/Login.razor | 2 +- .../Components/Pages/Login.razor.cs | 16 +- .../Contracts/Login/LoginRequest.cs | 3 +- .../Contracts/Login/LoginResult.cs | 8 + .../Contracts/Login/TryLoginResult.cs | 13 ++ .../Contracts/Pkce/TryPkceLoginRequest.cs | 11 + .../Contracts/Pkce/TryPkceLoginResult.cs | 13 ++ .../Domain/{Pkce => Auth}/AuthArtifact.cs | 0 .../Domain/{Pkce => Auth}/AuthArtifactType.cs | 1 + .../Domain/Auth/LoginPreviewArtifact.cs | 53 +++++ .../Domain/Hub/HubFlowArtifact.cs | 8 + .../Domain/Hub/HubFlowState.cs | 2 + .../Domain/Pkce/HubLoginArtifact.cs | 30 +-- .../Security/AuthenticationSecurityState.cs | 13 +- .../Options/UAuthLoginOptions.cs | 5 +- .../Abstractions/IDeviceResolver.cs | 2 +- .../Auth/ClientProfileReader.cs | 14 +- .../Auth/Context/AuthFlowContextFactory.cs | 6 +- .../Auth/IClientProfileReader.cs | 2 +- .../AspNetCore/UAuthAuthenticationHandler.cs | 2 +- .../Abstractions/ILoginEndpointHandler.cs | 1 + .../Abstractions/IPkceEndpointHandler.cs | 2 + .../Endpoints/LoginEndpointHandler.cs | 202 +++++++++++++++--- .../Endpoints/PkceEndpointHandler.cs | 130 ++++++++++- .../Endpoints/UAuthEndpointRegistrar.cs | 6 + .../Endpoints/ValidateEndpointHandler.cs | 6 +- .../Extensions/DeviceExtensions.cs | 4 +- .../HttpContextRequestExtensions.cs | 34 +++ .../HttpContextReturnUrlExtensions.cs | 22 +- .../Extensions/ServiceCollectionExtensions.cs | 4 +- .../Flows/Login/ILoginOrchestrator.cs | 5 + .../Flows/Login/LoginExecutionMode.cs | 7 + .../Flows/Login/LoginExecutionOptions.cs | 8 + .../Flows/Login/LoginOrchestrator.cs | 66 +++--- .../Flows/Login/LoginPreviewFingerprint.cs | 16 ++ .../ITransportCredentialResolver.cs | 2 +- .../AspNetCore/TransportCredentialResolver.cs | 96 +++------ ...lver.cs => IValidateCredentialResolver.cs} | 4 +- ...olver.cs => ValidateCredentialResolver.cs} | 19 +- .../Infrastructure/Device/DeviceResolver.cs | 18 +- .../Infrastructure/Hub/HubFlowReader.cs | 2 + .../Services/IUAuthFlowService.cs | 7 + .../Services/UAuthFlowService.cs | 21 +- .../Components/UAuthLoginForm.razor | 8 +- .../Components/UAuthLoginForm.razor.cs | 128 ++++++++++- .../Infrastructure/UAuthRequestClient.cs | 22 ++ .../TScripts/uauth.js | 49 +++++ .../wwwroot/uauth.min.js | 2 +- .../Contracts/PkceClientState.cs | 7 - .../Contracts/UAuthSubmitMode.cs | 8 + .../Infrastructure/IUAuthRequestClient.cs | 3 + .../Options/UAuthClientEndpointOptions.cs | 2 + .../Services/Abstractions/IFlowClient.cs | 3 + .../Services/UAuthFlowClient.cs | 131 ++++++++++-- .../Credentials/ChangePasswordTests.cs | 2 - .../Fake/FakeFlowClient.cs | 13 +- .../Helpers/TestAuthRuntime.cs | 1 - .../Server/LoginOrchestratorTests.cs | 18 -- .../Server/RedirectTests.cs | 4 +- .../Sessions/SessionTests.cs | 3 - .../UserIdentifierApplicationServiceTests.cs | 2 - 69 files changed, 1442 insertions(+), 353 deletions(-) create mode 100644 src/CodeBeam.UltimateAuth.Core/Contracts/Login/TryLoginResult.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/TryPkceLoginRequest.cs create mode 100644 src/CodeBeam.UltimateAuth.Core/Contracts/Pkce/TryPkceLoginResult.cs rename src/CodeBeam.UltimateAuth.Core/Domain/{Pkce => Auth}/AuthArtifact.cs (100%) rename src/CodeBeam.UltimateAuth.Core/Domain/{Pkce => Auth}/AuthArtifactType.cs (92%) create mode 100644 src/CodeBeam.UltimateAuth.Core/Domain/Auth/LoginPreviewArtifact.cs create mode 100644 src/CodeBeam.UltimateAuth.Server/Extensions/HttpContext/HttpContextRequestExtensions.cs create mode 100644 src/CodeBeam.UltimateAuth.Server/Flows/Login/LoginExecutionMode.cs create mode 100644 src/CodeBeam.UltimateAuth.Server/Flows/Login/LoginExecutionOptions.cs create mode 100644 src/CodeBeam.UltimateAuth.Server/Flows/Login/LoginPreviewFingerprint.cs rename src/CodeBeam.UltimateAuth.Server/Infrastructure/Credentials/{IFlowCredentialResolver.cs => IValidateCredentialResolver.cs} (71%) rename src/CodeBeam.UltimateAuth.Server/Infrastructure/Credentials/{FlowCredentialResolver.cs => ValidateCredentialResolver.cs} (72%) delete mode 100644 src/client/CodeBeam.UltimateAuth.Client/Contracts/PkceClientState.cs create mode 100644 src/client/CodeBeam.UltimateAuth.Client/Contracts/UAuthSubmitMode.cs diff --git a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Layout/MainLayout.razor b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Layout/MainLayout.razor index d257eb7a..8b7693b0 100644 --- a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Layout/MainLayout.razor +++ b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Layout/MainLayout.razor @@ -1,11 +1,65 @@ -@using CodeBeam.UltimateAuth.Core.Abstractions -@using CodeBeam.UltimateAuth.Server.Infrastructure -@inherits LayoutComponentBase +@inherits LayoutComponentBase +@inject IUAuthClient UAuthClient +@inject ISnackbar Snackbar +@inject NavigationManager Nav -@Body + + + + UltimateAuth + + UAuthHub Sample -
+ + + + + + + + +
+ + + @((state.Identity?.DisplayName ?? "?").Trim() is var n ? (n.Length >= 2 ? n[..2] : n[..1]) : "?") + + +
+
+ + + @state.Identity?.DisplayName + @string.Join(", ", state.Claims.Roles) + + + + + + + + @if (state.Identity?.SessionState is not null && state.Identity.SessionState != SessionState.Active) + { + + + } + +
+
+ + + + +
+ + + + @Body + + + + +
An unhandled error has occurred. - Reload - 🗙 + Reload + 🗙
diff --git a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Layout/MainLayout.razor.cs b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Layout/MainLayout.razor.cs index d9123d59..7242adbd 100644 --- a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Layout/MainLayout.razor.cs +++ b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Layout/MainLayout.razor.cs @@ -1,7 +1,130 @@ -namespace CodeBeam.UltimateAuth.Sample.UAuthHub.Components.Layout +using CodeBeam.UltimateAuth.Client; +using CodeBeam.UltimateAuth.Client.Errors; +using CodeBeam.UltimateAuth.Core.Contracts; +using CodeBeam.UltimateAuth.Core.Domain; +using CodeBeam.UltimateAuth.Core.Errors; +using CodeBeam.UltimateAuth.Sample.UAuthHub.Infrastructure; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace CodeBeam.UltimateAuth.Sample.UAuthHub.Components.Layout; + +public partial class MainLayout { - public partial class MainLayout + [CascadingParameter] + public UAuthState UAuth { get; set; } = default!; + + [CascadingParameter] + public DarkModeManager DarkModeManager { get; set; } = default!; + + private async Task Refresh() { - + await UAuthClient.Flows.RefreshAsync(); + } + + private async Task Logout() + { + await UAuthClient.Flows.LogoutAsync(); + } + + private Color GetBadgeColor() + { + if (UAuth is null || !UAuth.IsAuthenticated) + return Color.Error; + + if (UAuth.IsStale) + return Color.Warning; + + var state = UAuth.Identity?.SessionState; + + if (state is null || state == SessionState.Active) + return Color.Success; + + if (state == SessionState.Invalid) + return Color.Error; + + return Color.Warning; + } + + private void HandleSignInClick() + { + var uri = Nav.ToAbsoluteUri(Nav.Uri); + + if (uri.AbsolutePath.EndsWith("/login", StringComparison.OrdinalIgnoreCase)) + { + Nav.NavigateTo("/login?focus=1", replace: true, forceLoad: true); + return; + } + + GoToLoginWithReturn(); + } + + private async Task Validate() + { + try + { + var result = await UAuthClient.Flows.ValidateAsync(); + + if (result.IsValid) + { + if (result.Snapshot?.Identity.UserStatus == UserStatus.SelfSuspended) + { + Snackbar.Add("Your account is suspended by you.", Severity.Warning); + return; + } + Snackbar.Add($"Session active • Tenant: {result.Snapshot?.Identity?.Tenant.Value} • User: {result.Snapshot?.Identity?.PrimaryUserName}", Severity.Success); + } + else + { + switch (result.State) + { + case SessionState.Expired: + Snackbar.Add("Session expired. Please sign in again.", Severity.Warning); + break; + + case SessionState.DeviceMismatch: + Snackbar.Add("Session invalid for this device.", Severity.Error); + break; + + default: + Snackbar.Add($"Session state: {result.State}", Severity.Error); + break; + } + } + } + catch (UAuthTransportException) + { + Snackbar.Add("Network error.", Severity.Error); + } + catch (UAuthProtocolException) + { + Snackbar.Add("Invalid response.", Severity.Error); + } + catch (UAuthException ex) + { + Snackbar.Add($"UAuth error: {ex.Message}", Severity.Error); + } + catch (Exception ex) + { + Snackbar.Add($"Unexpected error: {ex.Message}", Severity.Error); + } + } + + private void GoToLoginWithReturn() + { + var uri = Nav.ToAbsoluteUri(Nav.Uri); + + if (uri.AbsolutePath.EndsWith("/login", StringComparison.OrdinalIgnoreCase)) + { + Nav.NavigateTo("/login", replace: true); + return; + } + + var current = Nav.ToBaseRelativePath(uri.ToString()); + if (string.IsNullOrWhiteSpace(current)) + current = "home"; + + var returnUrl = Uri.EscapeDataString("/" + current.TrimStart('/')); + Nav.NavigateTo($"/login?returnUrl={returnUrl}", replace: true); } } diff --git a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Pages/Home.razor b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Pages/Home.razor index 219617bf..056cc78a 100644 --- a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Pages/Home.razor +++ b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Pages/Home.razor @@ -1,82 +1,122 @@ @page "/" @page "/login" -@using CodeBeam.UltimateAuth.Client -@using CodeBeam.UltimateAuth.Client.Authentication -@using CodeBeam.UltimateAuth.Client.Diagnostics +@attribute [UAuthLoginPage] +@inherits UAuthFlowPageBase + +@implements IDisposable @using CodeBeam.UltimateAuth.Client.Infrastructure +@using CodeBeam.UltimateAuth.Client.Runtime @using CodeBeam.UltimateAuth.Core.Abstractions @using CodeBeam.UltimateAuth.Core.Contracts -@using CodeBeam.UltimateAuth.Core.Domain -@using CodeBeam.UltimateAuth.Core.Runtime -@using CodeBeam.UltimateAuth.Server.Abstractions -@using CodeBeam.UltimateAuth.Server.Infrastructure -@using CodeBeam.UltimateAuth.Server.Services @using CodeBeam.UltimateAuth.Server.Stores -@inject IUAuthStateManager StateManager +@inject IUAuthClient UAuthClient +@inject IAuthStore AuthStore @inject IHubFlowReader HubFlowReader @inject IHubCredentialResolver HubCredentialResolver -@inject IAuthStore AuthStore @inject IClientStorage BrowserStorage -@inject IUAuthFlowService Flow @inject ISnackbar Snackbar -@inject IFlowCredentialResolver CredentialResolver -@inject IUAuthClient UAuthClient -@inject NavigationManager Nav -@inject IUAuthProductInfoProvider ProductInfo -@inject AuthenticationStateProvider AuthStateProvider -@inject UAuthClientDiagnostics Diagnostics +@inject IUAuthClientProductInfoProvider ClientProductInfoProvider +@inject IDeviceIdProvider DeviceIdProvider +@inject IDialogService DialogService + +@if (_state == null || !_state.IsActive) +{ + + + + + + Access Denied + + This page cannot be accessed directly. + UAuthHub login flows can only be initiated by an authorized client application. + -
- - @if (_state == null || !_state.IsActive) - { - - - - + - Access Denied + + UltimateAuth protects this resource based on your session and permissions. + + + + + return; +} - - This page cannot be accessed directly. - UAuthHub login flows can only be initiated by an authorized client application. - + + + + + - - return; - } - - - Welcome to UltimateAuth! - - - Login - - + - - Programmatic Pkce Login - + + -
+ + Programmatic Login + Login programmatically as admin/admin. + + + + + @* Forgot Password *@ + Don't have an account? SignUp + +
+ + + +
diff --git a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Pages/Home.razor.cs b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Pages/Home.razor.cs index dc0f988e..eeb77706 100644 --- a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Pages/Home.razor.cs +++ b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/Pages/Home.razor.cs @@ -1,4 +1,5 @@ using CodeBeam.UltimateAuth.Client.Contracts; +using CodeBeam.UltimateAuth.Client.Runtime; using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Server.Stores; @@ -18,6 +19,24 @@ public partial class Home private HubFlowState? _state; + private UAuthClientProductInfo? _productInfo; + private MudTextField _usernameField = default!; + + private CancellationTokenSource? _lockoutCts; + private PeriodicTimer? _lockoutTimer; + private DateTimeOffset? _lockoutUntil; + private TimeSpan _remaining; + private bool _isLocked; + private DateTimeOffset? _lockoutStartedAt; + private TimeSpan _lockoutDuration; + private double _progressPercent; + private int? _remainingAttempts = null; + + protected override async Task OnInitializedAsync() + { + _productInfo = ClientProductInfoProvider.Get(); + } + protected override async Task OnParametersSetAsync() { if (string.IsNullOrWhiteSpace(HubKey)) @@ -32,40 +51,74 @@ protected override async Task OnParametersSetAsync() protected override async Task OnAfterRenderAsync(bool firstRender) { - if (!firstRender) + if (string.IsNullOrWhiteSpace(HubKey)) return; - var currentError = await BrowserStorage.GetAsync(StorageScope.Session, "uauth:last_error"); - - if (!string.IsNullOrWhiteSpace(currentError)) + if (_state is null || !_state.Exists) { - Snackbar.Add(ResolveErrorMessage(currentError), Severity.Error); - await BrowserStorage.RemoveAsync(StorageScope.Session, "uauth:last_error"); + await StartNewPkceAsync(); + return; } - var uri = Nav.ToAbsoluteUri(Nav.Uri); - var query = QueryHelpers.ParseQuery(uri.Query); - - if (query.TryGetValue("__uauth_error", out var error)) - { - await BrowserStorage.SetAsync(StorageScope.Session, "uauth:last_error", error.ToString()); - } - - if (string.IsNullOrWhiteSpace(HubKey)) + if (_state.IsExpired) { + await StartNewPkceAsync(); return; } - if (_state is null || !_state.Exists) - return; - - if (_state?.IsActive != true) + if (_state.Error != null) { + Snackbar.Add(ResolveErrorMessage(_state.Error), Severity.Error); + + await Task.Delay(200); // UX await StartNewPkceAsync(); - return; } } + //protected override async Task OnAfterRenderAsync(bool firstRender) + //{ + // if (!firstRender) + // return; + + // var currentError = await BrowserStorage.GetAsync(StorageScope.Session, "uauth:last_error"); + + // if (!string.IsNullOrWhiteSpace(currentError)) + // { + // Snackbar.Add(ResolveErrorMessage(currentError), Severity.Error); + // await BrowserStorage.RemoveAsync(StorageScope.Session, "uauth:last_error"); + // } + + // var uri = Nav.ToAbsoluteUri(Nav.Uri); + // var query = QueryHelpers.ParseQuery(uri.Query); + + // if (query.TryGetValue("__uauth_error", out var error)) + // { + // await BrowserStorage.SetAsync(StorageScope.Session, "uauth:last_error", error.ToString()); + // } + + // if (string.IsNullOrWhiteSpace(HubKey)) + // { + // return; + // } + + // if (_state is null || !_state.Exists) + // return; + + // if (_state?.IsActive != true) + // { + // await StartNewPkceAsync(); + // return; + // } + + // if (_state?.Error != null) + // { + // Snackbar.Add("Error.", Severity.Error); + + // await Task.Delay(300); // UX + // await StartNewPkceAsync(); + // } + //} + // For testing & debugging private async Task ProgrammaticPkceLogin() { @@ -79,15 +132,41 @@ private async Task ProgrammaticPkceLogin() var credentials = await HubCredentialResolver.ResolveAsync(hubSessionId); - var request = new PkceLoginRequest + //var request = new PkceLoginRequest + //{ + // Identifier = "admin", + // Secret = "admin", + // AuthorizationCode = credentials?.AuthorizationCode ?? string.Empty, + // CodeVerifier = credentials?.CodeVerifier ?? string.Empty, + // ReturnUrl = _state?.ReturnUrl ?? string.Empty + //}; + + var request = new TryPkceLoginRequest { Identifier = "admin", Secret = "admin", AuthorizationCode = credentials?.AuthorizationCode ?? string.Empty, CodeVerifier = credentials?.CodeVerifier ?? string.Empty, - ReturnUrl = _state?.ReturnUrl ?? string.Empty + ReturnUrl = _state?.ReturnUrl ?? string.Empty, + HubSessionId = _state?.HubSessionId.Value ?? hubSessionId.Value, }; - await UAuthClient.Flows.CompletePkceLoginAsync(request); + + await UAuthClient.Flows.TryCompletePkceLoginAsync(request); + } + + private void HandleLoginResult(TryLoginResult result) + { + if (!result.Success) + { + Snackbar.Add("Login failed", Severity.Error); + + if (result.RemainingAttempts is not null) + { + // UI update + } + + return; + } } private async Task StartNewPkceAsync() diff --git a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/_Imports.razor b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/_Imports.razor index aada4df3..f530884f 100644 --- a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/_Imports.razor +++ b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Components/_Imports.razor @@ -10,6 +10,7 @@ @using CodeBeam.UltimateAuth.Sample.UAuthHub @using CodeBeam.UltimateAuth.Sample.UAuthHub.Components @using CodeBeam.UltimateAuth.Sample.UAuthHub.Components.Layout +@using CodeBeam.UltimateAuth.Core.Domain @using CodeBeam.UltimateAuth.Client @using CodeBeam.UltimateAuth.Client.Blazor diff --git a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Controllers/HubLoginController.cs b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Controllers/HubLoginController.cs index 71cb29b3..6b6f486d 100644 --- a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Controllers/HubLoginController.cs +++ b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Controllers/HubLoginController.cs @@ -42,7 +42,7 @@ public async Task BeginLogin( hubSessionId: hubSessionId, flowType: HubFlowType.Login, clientProfile: client_profile, - tenant: TenantKeys.System, + tenant: TenantKeys.System, // TODO: Think about multi tenant scenarios returnUrl: return_url, payload: payload, expiresAt: _clock.UtcNow.Add(_options.Hub.FlowLifetime)); diff --git a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Program.cs b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Program.cs index d5c689a8..a745644f 100644 --- a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Program.cs +++ b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/Program.cs @@ -6,7 +6,6 @@ using CodeBeam.UltimateAuth.InMemory; using CodeBeam.UltimateAuth.Sample.UAuthHub.Components; using CodeBeam.UltimateAuth.Sample.UAuthHub.Infrastructure; -using CodeBeam.UltimateAuth.Security.Argon2; using CodeBeam.UltimateAuth.Server.Extensions; using MudBlazor.Services; using MudExtensions.Services; @@ -16,7 +15,11 @@ // Add services to the container. builder.Services.AddRazorComponents() - .AddInteractiveServerComponents(); + .AddInteractiveServerComponents() + .AddCircuitOptions(options => + { + options.DetailedErrors = true; + }); builder.Services.AddControllers(); diff --git a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/wwwroot/app.css b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/wwwroot/app.css index 671b6199..9d58cb9c 100644 --- a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/wwwroot/app.css +++ b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub/wwwroot/app.css @@ -13,7 +13,7 @@ a, .btn-link { } .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { - box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; } .content { @@ -50,15 +50,6 @@ h1:focus { border-color: #929292; } -.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder { - color: var(--bs-secondary-color); - text-align: end; -} - -.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder { - text-align: start; -} - .uauth-stack { min-height: 60vh; max-height: calc(100vh - var(--mud-appbar-height)); diff --git a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/Login.razor b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/Login.razor index 7687854b..49452e66 100644 --- a/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/Login.razor +++ b/samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/Login.razor @@ -73,7 +73,7 @@