diff --git a/README.md b/README.md index 37dd42fc..6322cffd 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ UltimateAuth is an open-source auth framework with platform-level capabilities t | Security Expansion | 0.3.0 | MFA, Reauth | 🟡 In Progress | Q1 2027 | | Infrastructure Expansion| 0.4.0 | Redis, Distributed Cache, Password Hashers | 🔜 Planned | Q1 2027 | | Multi-Tenant Expansion | 0.5.0 | Multi tenant management | 🔜 Planned | Q2 2027 | -| Extensibility Expansion | 0.6.0 | Audit, events, hooks | 🔜 Planned | Q2 2027 | +| Extensibility Expansion | 0.6.0 | Audit, Passwordless Login | 🔜 Planned | Q2 2027 | | Performance Expansion | 0.7.0 | Benchmarks, caching | 🔜 Planned | Q3 2027 | | Ecosystem Expansion | 0.8.0 | Migration tools | 🔜 Planned | Q3 2027 | | v1.0 | 1.0.0 | Locked API, align with .NET 12 | 🔜 Planned | Q4 2027 | @@ -62,9 +62,9 @@ UltimateAuth is designed to deliver a simple, consistent developer experience fr | Milestone | Version | Target | |---|---|---| | **Developer Foundation** | 0.1.x | A functional, security-conscious authentication framework with a complete core architecture, extensibility, and a developer-first API. Suitable for development, early adoption, and production use following application-specific evaluation. | -| **Production Expansion** | 0.2.0 | Ready to power real-world applications, particularly small and medium-sized products, with expanded authentication modes, built-in rate limiting, and a proven integration experience. Larger applications can also adopt UltimateAuth when its available capabilities meet their requirements. | -| **Advanced Security & Scale** | 0.4.0 | Extends production capabilities with MFA, reauthentication, distributed infrastructure, and additional security options for applications with more demanding requirements. Builds on the same simple developer experience. | -| **Migration & Adoption** | 0.8.0 | Makes adopting UltimateAuth in existing applications easier through migration tools, guidance, and established integration patterns, without requiring new applications to wait for this milestone. | +| **Production Expansion** | 0.2.x | Ready to power real-world applications, particularly small and medium-sized products, with expanded authentication modes, built-in rate limiting, and a proven integration experience. Larger applications can also adopt UltimateAuth when its available capabilities meet their requirements. | +| **Advanced Security & Scale** | 0.4.x | Extends production capabilities with MFA, reauthentication, distributed infrastructure, and additional security options for applications with more demanding requirements. Builds on the same simple developer experience. | +| **Migration & Adoption** | 0.8.x | Makes adopting UltimateAuth in existing applications easier through migration tools, guidance, and established integration patterns, without requiring new applications to wait for this milestone. | **The UltimateAuth philosophy:** Simple from day one, more capable with every release. Version milestones describe expanding capabilities — not a progression from difficult to easy, or from unusable to usable. @@ -225,25 +225,20 @@ Change `Components/Pages/Home.razor`:

Sign in

- +
- +
- +
- -@code { - private string _identifier = ""; - private string _password = ""; -} ``` ### 7. Protect your pages diff --git a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/LoginPage.razor b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/LoginPage.razor index d512cd87..648aa4bf 100644 --- a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/LoginPage.razor +++ b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/LoginPage.razor @@ -70,15 +70,6 @@ - @*
-
- - Authenticated session -
- -

We recognize your account. Your Identity Lab workspace is ready whenever you are.

-
*@ - - + @if (IsLockedOut) { @@ -174,7 +165,7 @@ diff --git a/docs/content/client-sdk/authentication.md b/docs/content/client-sdk/authentication.md index 5bbb830c..b148a282 100644 --- a/docs/content/client-sdk/authentication.md +++ b/docs/content/client-sdk/authentication.md @@ -895,23 +895,59 @@ The component supports password-based login, PKCE completion through UAuthHub, c ### Basic Usage -A login form can be implemented with the following component: +`UAuthLoginForm` provides a complete, ready-to-use login form out of the box. No additional markup or credential bindings are required. + +```razor + +``` + +That's it. UltimateAuth automatically renders the identifier field, password field, and submit button, and handles the authentication submission workflow. + +**Customizing the Form** + +When you need a custom layout or styling, simply provide your own form content: ```razor @using CodeBeam.UltimateAuth.Client.Blazor - - - + + + - -@code { - private string email = ""; - private string password = ""; -} ``` +UltimateAuth handles the authentication workflow in both cases. Your custom fields only need the correct HTML `name` attributes; there is no need to bind credentials to Blazor component state. + +### Native Credential Submission + +`UAuthLoginForm` reads credentials directly from native browser form fields. It does not accept `Identifier` or `Secret` component parameters. + +When providing custom `ChildContent`, the form must contain enabled input elements with the exact names `Identifier` and `Secret`. + +Credential values should not be bound to Blazor component state solely for submission. This is particularly important in Blazor Server, where interactive component bindings can transmit credential values over the SignalR connection. + +**Important: Do Not Disable Credential Fields During Submission** + +HTML inputs marked as `disabled` are excluded from `FormData` and native form submissions. + +Do not set `Disabled="@isSubmitting"` on the username or password fields. A login preview may succeed, but the subsequent native commit can fail because the credential fields are missing from the submitted form. + +Instead, disable the submit button while leaving the credential inputs enabled. + +If you need to prevent editing during submission, consider `readonly` where supported, while accounting for component behavior, browser autofill, and accessibility. + +**Custom Form Requirements** + +- Use `name="Identifier"` and `name="Secret"` exactly. +- Keep both credential inputs enabled throughout submission. +- Avoid duplicate inputs with these names. +- Use `type="password"` and `autocomplete="current-password"` for password fields. +- Keep custom submit controls inside `UAuthLoginForm`. +- Use `OnSubmittingChanged` to manage loading indicators and submit-button state. + +`UAuthLoginForm` handles the try and commit orchestration. The native commit includes the browser form fields, device information, and antiforgery data required by the configured authentication flow. + The component automatically uses `TryAndCommit` as its default submission mode. When submitted, it: @@ -934,13 +970,13 @@ For example, with MudBlazor: @using CodeBeam.UltimateAuth.Client.Blazor @using CodeBeam.UltimateAuth.Client - + Sign in - - + + - Sign in + Sign in @if (!string.IsNullOrWhiteSpace(error)) { @@ -950,10 +986,15 @@ For example, with MudBlazor: @code { - private string email = ""; - private string password = ""; + private bool _isSubmitting; private string? error; + private Task HandleSubmittingChanged(bool submitting) + { + _isSubmitting = submitting; + return Task.CompletedTask; + } + private Task HandleLoginResult(IUAuthTryResult result) { error = result.IsSuccess ? null : "Unable to sign in."; @@ -973,7 +1014,7 @@ Because the component renders an HTML form internally, place the submit button i Use `SubmitMode` to control how the authentication attempt is executed. ```razor - + ... ``` @@ -995,7 +1036,7 @@ For interactive login forms that need validation feedback, it is generally the r The component exposes an `OnTryResult` callback: ```razor - + ... ``` @@ -1059,9 +1100,9 @@ The same component also supports PKCE login completion when hosted within a supp Set `LoginType` to `Pkce`: ```razor - - - + + + ``` @@ -1085,7 +1126,7 @@ A Blazor WebAssembly application should initiate PKCE using `BeginPkceAsync`; th You can provide an explicit return URL: ```razor - + ... ``` @@ -1103,7 +1144,7 @@ This allows a shared login component to participate in different authentication For advanced integrations, the `Endpoint` parameter allows the form's HTML action endpoint to be overridden: ```razor - + ... ``` @@ -1117,9 +1158,9 @@ Note that `Endpoint` affects the form's HTML action. Try-based submissions invok `UAuthLoginForm` exposes `SubmitAsync`, allowing submission through a component reference. ```razor - - - + + + - -@code { - private string _identifier = ""; - private string _password = ""; -} ``` `UAuthLoginForm` handles the login submission through UltimateAuth's client flow. diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authentication.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authentication.json index 8c7996d0..43f4bb3c 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authentication.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authentication.json @@ -1,7 +1,7 @@ { "Slug": "client-sdk/authentication", "Title": "Authentication", - "Html": "\n\u003Cp\u003EThe UltimateAuth Client SDK provides a high-level API for authentication workflows, including login, PKCE, session validation, refresh, logout, and device-specific session revocation.\u003C/p\u003E\n\u003Cp\u003EThese operations are exposed through \u003Ccode\u003EUAuthClient.Flows\u003C/code\u003E, which implements the \u003Ccode\u003EIFlowClient\u003C/code\u003E contract.\u003C/p\u003E\n\u003Cp\u003EThis guide shows how to use the authentication client in application code, handle structured results, configure interactive behavior, and choose the correct flow for different client environments.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-authentication-client\u0022\u003E1. Accessing the Authentication Client\u003C/h2\u003E\n\u003Cp\u003EIn a Blazor component, inject \u003Ccode\u003EIUAuthClient\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAuthentication operations are available through the \u003Ccode\u003EFlows\u003C/code\u003E property.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.RefreshAsync();\nawait UAuthClient.Flows.ValidateAsync();\nawait UAuthClient.Flows.LogoutAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe relevant methods are defined by \u003Ccode\u003EIFlowClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EMethod\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELoginAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESubmit a direct login flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryLoginAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAttempt login with configurable try/commit behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBeginPkceAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EStart a PKCE authentication flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECompletePkceLoginAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EComplete a PKCE login\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryCompletePkceLoginAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EComplete PKCE with structured try/commit behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERefreshAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERefresh or continue authentication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EValidateAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EValidate current authentication state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogoutAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELog out the current session\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogoutMyDeviceAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke a selected device-associated chain\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogoutMyOtherDevicesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke other devices while retaining the current one\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogoutAllMyDevicesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke all devices for the current user\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EAdministrative device logout operations are also available and covered later in this guide.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-the-correct-login-flow\u0022\u003E2. Choosing the Correct Login Flow\u003C/h2\u003E\n\u003Cp\u003EBefore implementing login, determine whether your application is a trusted server-hosted client or a public client.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EClient environment\u003C/th\u003E\n\u003Cth\u003ERecommended flow\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor Server\u003C/td\u003E\n\u003Ctd\u003EDirect login or supported interactive login flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUAuthHub\u003C/td\u003E\n\u003Ctd\u003EServer-hosted authentication flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor WebAssembly\u003C/td\u003E\n\u003Ctd\u003EPKCE\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBrowser SPA\u003C/td\u003E\n\u003Ctd\u003EPKCE\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPublic native application\u003C/td\u003E\n\u003Ctd\u003EPKCE\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EPublic clients must not rely on embedding a secret or trusting browser-executed code to protect credentials.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth restricts direct credential posting based on the configured client profile and login options.\u003C/p\u003E\n\u003Cp\u003EFor public clients, use the PKCE flow instead of enabling direct credential posting.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022direct-login\u0022\u003E3. Direct Login\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ELoginAsync\u003C/code\u003E performs a direct login submission.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar request = new LoginRequest\n{\n Identifier = \u0026quot;user@example.com\u0026quot;,\n Secret = \u0026quot;password\u0026quot;\n};\n\nawait UAuthClient.Flows.LoginAsync(request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003ETask\u003C/code\u003E, not a structured login result.\u003C/p\u003E\n\u003Cp\u003EIt submits the login payload through the configured navigation-based transport.\u003C/p\u003E\n\u003Cp\u003EDepending on the hosting environment, this may initiate navigation or redirect behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022loginrequest-properties\u0022\u003ELoginRequest Properties\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ELoginRequest\u003C/code\u003E supports the following properties:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential secret\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential type; defaults to \u003Ccode\u003EPassword\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMetadata\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional request metadata\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequestTokens\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EHint to request tokens when supported\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreviewReceipt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional receipt associated with a prior login preview\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EExample:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar request = new LoginRequest\n{\n Identifier = email,\n Secret = password,\n CredentialType = CredentialType.Password,\n RequestTokens = true\n};\n\nawait UAuthClient.Flows.LoginAsync(request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ERequestTokens\u003C/code\u003E is a hint, not a guarantee that tokens will be issued. The server\u0027s authentication mode and policy remain authoritative.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022redirect-after-login\u0022\u003ERedirect After Login\u003C/h3\u003E\n\u003Cp\u003EProvide an explicit return URL when necessary:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.LoginAsync(request, returnUrl: \u0026quot;/dashboard\u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe direct login flow resolves its return URL in the following order:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EThe explicit \u003Ccode\u003EreturnUrl\u003C/code\u003E argument.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ELogin.ReturnUrl\u003C/code\u003E option.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EDefaultReturnUrl\u003C/code\u003E option.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EFor example, client options can specify a default:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.Login.ReturnUrl = \u0026quot;/dashboard\u0026quot;;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe return URL is part of the login workflow. It should not be treated as an authorization grant.\u003C/p\u003E\n\u003Cp\u003EApplications and servers must still enforce their redirect validation rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022interactive-login-with-tryloginasync\u0022\u003E4. Interactive Login with TryLoginAsync\u003C/h2\u003E\n\u003Cp\u003EFor interactive forms, \u003Ccode\u003ETryLoginAsync\u003C/code\u003E provides structured feedback before or during the authentication commit process.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.TryLoginAsync(\n new LoginRequest\n {\n Identifier = email,\n Secret = password\n },\n UAuthSubmitMode.TryAndCommit);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is generally the more useful API for a login form that needs to display validation feedback.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022submit-modes\u0022\u003ESubmit Modes\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthSubmitMode\u003C/code\u003E defines three submission behaviors.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EMode\u003C/th\u003E\n\u003Cth\u003EBehavior\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryOnly\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExecute the try operation and return its result without committing authentication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDirectCommit\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESubmit directly to the commit endpoint without a separate try operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryAndCommit\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETry first, then commit when the try operation succeeds\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese modes control client workflow, not whether the server validates authentication.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDirectCommit does not bypass credential verification or server security requirements.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022tryonly-validate-without-login\u0022\u003ETryOnly: Validate Without Login\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003ETryOnly\u003C/code\u003E when you want structured feedback without establishing an authenticated session.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.TryLoginAsync(\n new LoginRequest\n {\n Identifier = email,\n Secret = password\n },\n UAuthSubmitMode.TryOnly);\n\nif (result.IsSuccess)\n{\n // The try operation succeeded.\n // Authentication has not been committed.\n}\nelse\n{\n // Display the appropriate feedback.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis can be useful for a custom interactive workflow.\u003C/p\u003E\n\u003Cp\u003EA successful try result must not be interpreted as an authenticated application session.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022tryandcommit-interactive-login\u0022\u003ETryAndCommit: Interactive Login\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003ETryAndCommit\u003C/code\u003E to combine structured validation with authentication commit.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.TryLoginAsync(\n new LoginRequest\n {\n Identifier = email,\n Secret = password\n },\n UAuthSubmitMode.TryAndCommit,\n returnUrl: \u0026quot;/dashboard\u0026quot;);\n\nif (!result.IsSuccess)\n{\n // Show validation feedback.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen the try stage succeeds, the client proceeds to the commit operation through its configured transport.\u003C/p\u003E\n\u003Cp\u003EThis can involve navigation, so application code should not assume that execution always continues within the same component instance.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022directcommit-single-commit-submission\u0022\u003EDirectCommit: Single Commit Submission\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.TryLoginAsync(\n new LoginRequest\n {\n Identifier = email,\n Secret = password\n },\n UAuthSubmitMode.DirectCommit);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn this mode, the SDK submits the commit operation directly.\u003C/p\u003E\n\u003Cp\u003EThe method returns a \u003Ccode\u003ETryLoginResult\u003C/code\u003E with \u003Ccode\u003EIsSuccess = true\u003C/code\u003E after the navigation submission completes, but this is not a structured server-side authentication success confirmation.\u003C/p\u003E\n\u003Cp\u003EIf you need structured credential validation feedback, prefer \u003Ccode\u003ETryAndCommit\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-login-results\u0022\u003E5. Handling Login Results\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ETryLoginAsync\u003C/code\u003E returns \u003Ccode\u003ETryLoginResult\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIts available properties include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EMeaning\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsSuccess\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the try operation succeeded\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReason\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional authentication failure reason\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERemainingAttempts\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERemaining attempts, when provided\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELockoutUntilUtc\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELockout expiration, when provided\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequiresMfa\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether additional authentication is required\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreviewReceipt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional receipt for subsequent flow handling\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EExample:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.TryLoginAsync(request, UAuthSubmitMode.TryOnly);\n\nif (!result.IsSuccess)\n{\n if (result.LockoutUntilUtc is not null)\n {\n errorMessage = $\u0026quot;Login temporarily unavailable until {result.LockoutUntilUtc:u}\u0026quot;;\n }\n else\n {\n errorMessage = \u0026quot;Unable to sign in.\u0026quot;;\n }\n\n return;\n}\n\nif (result.RequiresMfa)\n{\n // Continue with the supported additional authentication flow.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDo not expose sensitive internal authentication details unnecessarily.\u003C/p\u003E\n\u003Cp\u003EFor public-facing login forms, generic failure messages may be preferable to displaying every failure reason.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-transport-and-protocol-errors\u0022\u003EHandling Transport and Protocol Errors\u003C/h3\u003E\n\u003Cp\u003EA structured authentication failure is different from a network or protocol failure.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Etry\n{\n var result = await UAuthClient.Flows.TryLoginAsync(request, UAuthSubmitMode.TryAndCommit);\n\n if (!result.IsSuccess)\n {\n errorMessage = \u0026quot;Unable to sign in.\u0026quot;;\n }\n}\ncatch (UAuthTransportException)\n{\n errorMessage = \u0026quot;Authentication service is unavailable.\u0026quot;;\n}\ncatch (UAuthProtocolException)\n{\n errorMessage = \u0026quot;An authentication protocol error occurred.\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse the corresponding UltimateAuth client error namespace when referencing these exception types.\u003C/p\u003E\n\u003Cp\u003EA failed authentication attempt should normally be handled through the structured try result where supported. Transport and protocol exceptions indicate different failure categories.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pkce-authentication\u0022\u003E6. PKCE Authentication\u003C/h2\u003E\n\u003Cp\u003EPKCE provides a proof mechanism for authorization-code-based authentication flows.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports PKCE using an S256 code challenge.\u003C/p\u003E\n\u003Cp\u003EThe SDK generates a cryptographically random verifier, derives its challenge, and coordinates the authorization flow.\u003C/p\u003E\n\u003Cp\u003EPublic clients such as Blazor WebAssembly should use PKCE rather than direct credential posting.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022start-pkce\u0022\u003EStart PKCE\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.BeginPkceAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EYou can provide a return URL:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.BeginPkceAsync(returnUrl: \u0026quot;/dashboard\u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client performs the following operations:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EGenerates a PKCE code verifier.\u003C/li\u003E\n\u003Cli\u003EDerives an S256 code challenge.\u003C/li\u003E\n\u003Cli\u003ESends the authorization request.\u003C/li\u003E\n\u003Cli\u003EReceives an authorization code.\u003C/li\u003E\n\u003Cli\u003EInvokes the configured authorization callback, if any.\u003C/li\u003E\n\u003Cli\u003ENavigates to the configured UAuthHub login flow when automatic redirection is enabled.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe authorization code and verifier are used later to complete the flow.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-pkce\u0022\u003EConfigure PKCE\u003C/h3\u003E\n\u003Cp\u003EThe relevant client options include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.Pkce.Enabled = true;\noptions.Pkce.AutoRedirect = true;\noptions.Pkce.ReturnUrl = \u0026quot;/dashboard\u0026quot;;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EPKCE is enabled by default in \u003Ccode\u003EUAuthClientPkceLoginFlowOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EAutomatic redirection is also enabled by default.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022observe-authorization\u0022\u003EObserve Authorization\u003C/h3\u003E\n\u003Cp\u003EYou can register a callback that runs after the authorization code is received and before the automatic Hub redirect.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.Pkce.OnAuthorized = async response =\u0026gt;\n{\n // Observe the authorization response.\n await Task.CompletedTask;\n};\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis callback is useful for application-specific coordination.\u003C/p\u003E\n\u003Cp\u003EDo not log authorization codes or expose them to unrelated components.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022disable-automatic-redirection\u0022\u003EDisable Automatic Redirection\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.Pkce.AutoRedirect = false;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWith this configuration, \u003Ccode\u003EBeginPkceAsync\u003C/code\u003E still performs the authorization request and invokes \u003Ccode\u003EOnAuthorized\u003C/code\u003E, but it does not automatically navigate to the Hub login endpoint.\u003C/p\u003E\n\u003Cp\u003EThe application becomes responsible for coordinating the remaining flow.\u003C/p\u003E\n\u003Cp\u003EThis is an advanced integration option.\u003C/p\u003E\n\u003Cp\u003EA custom implementation must preserve the authorization code, verifier, and relevant context securely and complete the flow using the supported protocol.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-pkce-login\u0022\u003E7. Complete PKCE Login\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ECompletePkceLoginAsync\u003C/code\u003E submits the PKCE completion request.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar request = new PkceCompleteRequest\n{\n AuthorizationCode = authorizationCode,\n CodeVerifier = codeVerifier,\n Identifier = email,\n Secret = password,\n ReturnUrl = \u0026quot;/dashboard\u0026quot;\n};\n\nawait UAuthClient.Flows.CompletePkceLoginAsync(request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request model includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAuthorizationCode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECode obtained from the authorization step\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECodeVerifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOriginal PKCE verifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin credential\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReturnUrl\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional post-login destination\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHubSessionId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional Hub session context\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe authorization code and verifier must correspond to the same PKCE flow.\u003C/p\u003E\n\u003Cp\u003EDo not generate a new verifier when completing an existing authorization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022interactive-pkce-completion\u0022\u003EInteractive PKCE Completion\u003C/h3\u003E\n\u003Cp\u003EFor structured feedback, use \u003Ccode\u003ETryCompletePkceLoginAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.TryCompletePkceLoginAsync(\n request,\n UAuthSubmitMode.TryAndCommit);\n\nif (!result.IsSuccess)\n{\n // Display PKCE login feedback.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same submit modes are supported:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ETryOnly\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EDirectCommit\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ETryAndCommit\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022try-pkce-without-commit\u0022\u003ETry PKCE Without Commit\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.TryCompletePkceLoginAsync(request, UAuthSubmitMode.TryOnly);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis returns a \u003Ccode\u003ETryPkceLoginResult\u003C/code\u003E without committing a successful login.\u003C/p\u003E\n\u003Cp\u003EThe result includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eresult.IsSuccess;\nresult.Reason;\nresult.RemainingAttempts;\nresult.LockoutUntilUtc;\nresult.RequiresMfa;\nresult.RetryWithNewPkce;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022retrywithnewpkce\u0022\u003ERetryWithNewPkce\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ERetryWithNewPkce\u003C/code\u003E indicates that the client should begin a new PKCE authorization sequence rather than continuing with the existing authorization material.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eif (result.RetryWithNewPkce)\n{\n await UAuthClient.Flows.BeginPkceAsync(\u0026quot;/login\u0026quot;);\n return;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is useful when the current PKCE context can no longer be used.\u003C/p\u003E\n\u003Cp\u003EThe application should not repeatedly resubmit the same invalid authorization code and verifier.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022direct-credential-posting-restrictions\u0022\u003E8. Direct Credential Posting Restrictions\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth restricts direct credential posting according to the configured client profile.\u003C/p\u003E\n\u003Cp\u003EThe client option is:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.Login.AllowCredentialPost = false;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is the default.\u003C/p\u003E\n\u003Cp\u003EIf the current profile is not permitted to post credentials directly and the option has not enabled that capability, direct login methods throw an \u003Ccode\u003EInvalidOperationException\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example, attempting direct login from a public client may produce an error indicating that PKCE is required.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022trusted-server-hosted-clients\u0022\u003ETrusted Server-Hosted Clients\u003C/h3\u003E\n\u003Cp\u003EThe option can be explicitly configured for a trusted server-hosted integration where direct credential posting is appropriate:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.Login.AllowCredentialPost = true;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis must not be used to bypass PKCE requirements in a public browser application.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not enable direct credential posting in production Blazor WebAssembly or SPA clients.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA client profile is an integration descriptor, not cryptographic proof that the caller is trusted.\u003C/p\u003E\n\u003Cp\u003EServer-side security requirements remain authoritative.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-authentication\u0022\u003E9. Refresh Authentication\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ERefreshAsync\u003C/code\u003E requests continuation of the current authentication state.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.RefreshAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003ERefreshResult\u003C/code\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic sealed record RefreshResult\n{\n public bool IsSuccess { get; init; }\n public int Status { get; init; }\n public RefreshOutcome Outcome { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERefresh behavior depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EA refresh operation may update session activity, rotate authentication material, or determine that reauthentication is required.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-refresh-results\u0022\u003EHandling Refresh Results\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.RefreshAsync();\n\nif (result.IsSuccess)\n{\n // Authentication continuation succeeded.\n}\nelse if (result.Outcome == RefreshOutcome.ReauthRequired)\n{\n // The current authentication can no longer continue.\n // Direct the user to the appropriate login flow.\n}\nelse\n{\n // Handle another unsuccessful refresh result.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA \u003Ccode\u003E401 Unauthorized\u003C/code\u003E refresh response is handled as a normal reauthentication outcome.\u003C/p\u003E\n\u003Cp\u003EIt returns:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Enew RefreshResult\n{\n IsSuccess = false,\n Status = 401,\n Outcome = RefreshOutcome.ReauthRequired\n};\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ENetwork and unexpected protocol failures can still produce exceptions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-outcomes\u0022\u003ERefresh Outcomes\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ERefreshOutcome\u003C/code\u003E defines the following values:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOutcome\u003C/th\u003E\n\u003Cth\u003EMeaning\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENone\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo specific semantic outcome\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESuccess\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EGeneric successful refresh\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENoOp\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo refresh state change was necessary\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETouched\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EApplicable session activity was updated\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERotated\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERotation occurred\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReauthRequired\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication continuation is no longer permitted\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.RefreshAsync();\n\nif (!result.IsSuccess)\n{\n return;\n}\n\nswitch (result.Outcome)\n{\n case RefreshOutcome.Rotated:\n // Authentication material was rotated.\n break;\n\n case RefreshOutcome.Touched:\n // Applicable session activity was updated.\n break;\n\n case RefreshOutcome.NoOp:\n // Successful refresh and no state change was required.\n break;\n\n case RefreshOutcome.Success:\n // Refresh succeeded without a more specific outcome.\n break;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022detailed-refresh-diagnostics\u0022\u003EDetailed Refresh Diagnostics\u003C/h3\u003E\n\u003Cp\u003EThe server may return a generic success outcome rather than exposing the internal refresh action.\u003C/p\u003E\n\u003Cp\u003EWhere supported, detailed refresh outcomes can be enabled through server diagnostics:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Diagnostics.EnableRefreshDetails = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis setting is intended for diagnostics and observability.\u003C/p\u003E\n\u003Cp\u003EApplications should not require a specific successful outcome such as \u003Ccode\u003ERotated\u003C/code\u003E or \u003Ccode\u003ETouched\u003C/code\u003E to consider refresh successful.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003EIsSuccess\u003C/code\u003E for the primary success decision.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022manual-and-automatic-refresh\u0022\u003EManual and Automatic Refresh\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ERefreshAsync\u003C/code\u003E has an optional \u003Ccode\u003EisAuto\u003C/code\u003E argument.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003E// Manual refresh\nawait UAuthClient.Flows.RefreshAsync();\n\n// Refresh initiated by automatic coordination\nawait UAuthClient.Flows.RefreshAsync(isAuto: true);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe parameter distinguishes automatic refresh activity for client diagnostics.\u003C/p\u003E\n\u003Cp\u003EIt does not select a different authentication mode or override the server\u0027s refresh policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-refresh-configuration\u0022\u003E10. Automatic Refresh Configuration\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth exposes client-side automatic refresh options through \u003Ccode\u003EAutoRefresh\u003C/code\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.AutoRefresh.Enabled = true;\noptions.AutoRefresh.Interval = TimeSpan.FromMinutes(5);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EEnabled\u003C/code\u003E controls background refresh coordination.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EInterval\u003C/code\u003E specifies the interval used for background refresh attempts.\u003C/p\u003E\n\u003Cp\u003EThe effective defaults and registration behavior depend on the client integration. The options documentation identifies Blazor Server as the environment where background refresh is enabled by default.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-refresh-is-not-a-security-guarantee\u0022\u003EAutomatic Refresh Is Not a Security Guarantee\u003C/h3\u003E\n\u003Cp\u003EAutomatic refresh is a client-side convenience mechanism.\u003C/p\u003E\n\u003Cp\u003EIt does not guarantee that a session will remain valid indefinitely.\u003C/p\u003E\n\u003Cp\u003EAuthentication may still end because of:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EExpiration.\u003C/li\u003E\n\u003Cli\u003ERevocation.\u003C/li\u003E\n\u003Cli\u003EAccount security changes.\u003C/li\u003E\n\u003Cli\u003EInvalid refresh credentials.\u003C/li\u003E\n\u003Cli\u003EServer-side continuation restrictions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe client must remain prepared to handle \u003Ccode\u003EReauthRequired\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022validate-current-authentication\u0022\u003E11. Validate Current Authentication\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EValidateAsync\u003C/code\u003E to ask the server to validate the current authentication state.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.ValidateAsync();\n\nif (result.IsValid)\n{\n // Current authentication is valid.\n}\nelse\n{\n // Authentication is not currently valid.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003EAuthValidationResult\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIts properties include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EState\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECurrent session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsValid\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the state is \u003Ccode\u003EActive\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESnapshot\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional authentication state snapshot\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EChainId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional associated session chain\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERootId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional session root\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBoundDeviceId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional bound device identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-session-context\u0022\u003EInspect Session Context\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.ValidateAsync();\n\nif (result.IsValid \u0026amp;\u0026amp; result.ChainId is { } chainId)\n{\n // Use chainId for supported session-management operations.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ENot every authentication mode necessarily provides all session hierarchy fields.\u003C/p\u003E\n\u003Cp\u003ETreat optional values as optional.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validateasync-versus-ui-authentication-state\u0022\u003EValidateAsync Versus UI Authentication State\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EValidateAsync\u003C/code\u003E performs an explicit server validation operation.\u003C/p\u003E\n\u003Cp\u003EIt should not be called on every UI render merely to determine whether a component should display authenticated content.\u003C/p\u003E\n\u003Cp\u003EFor UI authentication state, use the supported UltimateAuth state management integration, including \u003Ccode\u003EIUAuthStateManager\u003C/code\u003E where appropriate.\u003C/p\u003E\n\u003Cp\u003EExplicit validation is useful when an application needs to check current server-recognized authentication state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003E12. Logout\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ELogoutAsync\u003C/code\u003E performs the current logout flow.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.LogoutAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method uses the configured logout endpoint and navigation transport.\u003C/p\u003E\n\u003Cp\u003EDepending on the client profile and configuration, logout may involve navigation and authentication state changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-button\u0022\u003ELogout Button\u003C/h3\u003E\n\u003Cp\u003EA simple Blazor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@inject IUAuthClient UAuthClient\n\n\u0026lt;button @onclick=\u0026quot;LogoutAsync\u0026quot;\u0026gt;Logout\u0026lt;/button\u0026gt;\n\n@code {\n private async Task LogoutAsync()\n {\n await UAuthClient.Flows.LogoutAsync();\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ELogout is distinct from clearing local UI state.\u003C/p\u003E\n\u003Cp\u003ERemoving a local authentication indicator does not necessarily revoke authoritative server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003EUse the supported logout flow to terminate authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-a-specific-device\u0022\u003E13. Logout a Specific Device\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports revoking the session chain associated with a particular device context.\u003C/p\u003E\n\u003Cp\u003EThe relevant method is:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003ELogoutMyDeviceAsync(LogoutDeviceRequest request)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ELogoutDeviceRequest\u003C/code\u003E requires a \u003Ccode\u003ESessionChainId\u003C/code\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar request = new LogoutDeviceRequest\n{\n ChainId = chainId\n};\n\nvar result = await UAuthClient.Flows.LogoutMyDeviceAsync(request);\n\nif (result.IsSuccess)\n{\n // The requested device logout operation succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis operation targets a session chain, not an arbitrary individual session ID.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022current-device-behavior\u0022\u003ECurrent Device Behavior\u003C/h3\u003E\n\u003Cp\u003EThe returned \u003Ccode\u003ERevokeResult\u003C/code\u003E can indicate whether the affected chain is the current chain.\u003C/p\u003E\n\u003Cp\u003EWhen the operation succeeds and \u003Ccode\u003ECurrentChain\u003C/code\u003E is true, the client publishes a logout-related state event.\u003C/p\u003E\n\u003Cp\u003EThis allows the application to react when the currently authenticated device context has been revoked.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-other-devices\u0022\u003E14. Logout Other Devices\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ELogoutMyOtherDevicesAsync\u003C/code\u003E to revoke other device-associated sessions while retaining the current one.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.LogoutMyOtherDevicesAsync();\n\nif (result.IsSuccess)\n{\n // Other devices were logged out.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is useful for account security pages that provide a \u003Cstrong\u003ELog out other devices\u003C/strong\u003E action.\u003C/p\u003E\n\u003Cp\u003EThe current authentication context is intended to remain active.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for identifying the current session or chain and enforcing the appropriate revocation scope.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-all-devices\u0022\u003E15. Logout All Devices\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ELogoutAllMyDevicesAsync\u003C/code\u003E to revoke all applicable sessions for the current user.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.LogoutAllMyDevicesAsync();\n\nif (result.IsSuccess)\n{\n // All applicable device sessions were revoked.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOn success, the client publishes a logout-related state event.\u003C/p\u003E\n\u003Cp\u003EThis operation is useful when a user suspects account compromise or wants to terminate existing authentication sessions across devices.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, previously issued access tokens may remain cryptographically valid until expiration unless additional revocation enforcement is configured.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-device-logout\u0022\u003E16. Administrative Device Logout\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth also exposes administrative session revocation operations.\u003C/p\u003E\n\u003Cp\u003EThese methods act on a specified user and require appropriate server-side authorization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-one-user-device\u0022\u003ELogout One User Device\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.LogoutUserDeviceAsync(\n userKey,\n new LogoutDeviceRequest\n {\n ChainId = chainId\n });\n\nif (!result.IsSuccess)\n{\n // Handle the failure.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-other-devices-for-a-user\u0022\u003ELogout Other Devices for a User\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.LogoutUserOtherDevicesAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EHere, \u003Ccode\u003Erequest\u003C/code\u003E is a \u003Ccode\u003ELogoutOtherDevicesRequest\u003C/code\u003E identifying the chain to retain.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-all-devices-for-a-user\u0022\u003ELogout All Devices for a User\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.LogoutAllUserDevicesAsync(userKey);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAdministrative methods do not grant administrative privileges by themselves.\u003C/p\u003E\n\u003Cp\u003EThe server must authorize the actor and validate the target user and requested revocation scope.\u003C/p\u003E\n\u003Cp\u003EThese operations are particularly relevant to administration panels and account security workflows.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022building-login-forms-with-uauthloginform\u0022\u003E17. Building Login Forms with UAuthLoginForm\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides \u003Ccode\u003EUAuthLoginForm\u003C/code\u003E, a reusable Blazor component that handles authentication form submission without requiring developers to implement the underlying flow orchestration themselves.\u003C/p\u003E\n\u003Cp\u003EThe component supports password-based login, PKCE completion through UAuthHub, configurable submission modes, structured validation feedback, and customizable UI content.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EYou design the login interface. UltimateAuth handles the authentication submission workflow.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic-usage\u0022\u003EBasic Usage\u003C/h3\u003E\n\u003Cp\u003EA login form can be implemented with the following component:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client.Blazor\n\n\u0026lt;UAuthLoginForm Identifier=\u0026quot;@email\u0026quot; Secret=\u0026quot;@password\u0026quot;\u0026gt;\n \u0026lt;input @bind=\u0026quot;email\u0026quot; placeholder=\u0026quot;Email\u0026quot; /\u0026gt;\n \u0026lt;input @bind=\u0026quot;password\u0026quot; type=\u0026quot;password\u0026quot; placeholder=\u0026quot;Password\u0026quot; /\u0026gt;\n \u0026lt;button type=\u0026quot;submit\u0026quot;\u0026gt;Sign in\u0026lt;/button\u0026gt;\n\u0026lt;/UAuthLoginForm\u0026gt;\n\n@code {\n private string email = \u0026quot;\u0026quot;;\n private string password = \u0026quot;\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe component automatically uses \u003Ccode\u003ETryAndCommit\u003C/code\u003E as its default submission mode.\u003C/p\u003E\n\u003Cp\u003EWhen submitted, it:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EValidates that the identifier and secret are present.\u003C/li\u003E\n\u003Cli\u003ECreates the appropriate authentication request.\u003C/li\u003E\n\u003Cli\u003ECalls the UltimateAuth client flow.\u003C/li\u003E\n\u003Cli\u003EPerforms the configured try/commit behavior.\u003C/li\u003E\n\u003Cli\u003EExposes structured results through \u003Ccode\u003EOnTryResult\u003C/code\u003E.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe example assumes that UltimateAuth\u0027s Blazor services have already been registered and that the hosting environment permits the selected login flow.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022customizing-the-login-ui\u0022\u003ECustomizing the Login UI\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E uses \u003Ccode\u003EChildContent\u003C/code\u003E, allowing developers to use standard HTML elements or their preferred Blazor component library.\u003C/p\u003E\n\u003Cp\u003EFor example, with MudBlazor:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client.Blazor\n@using CodeBeam.UltimateAuth.Client\n\n\u0026lt;UAuthLoginForm Identifier=\u0026quot;@email\u0026quot; Secret=\u0026quot;@password\u0026quot; SubmitMode=\u0026quot;UAuthSubmitMode.TryAndCommit\u0026quot; OnTryResult=\u0026quot;HandleLoginResult\u0026quot;\u0026gt;\n \u0026lt;MudStack Spacing=\u0026quot;3\u0026quot;\u0026gt;\n \u0026lt;MudText Typo=\u0026quot;Typo.h5\u0026quot;\u0026gt;Sign in\u0026lt;/MudText\u0026gt;\n \u0026lt;MudTextField @bind-Value=\u0026quot;email\u0026quot; Label=\u0026quot;Email\u0026quot; Variant=\u0026quot;Variant.Outlined\u0026quot; /\u0026gt;\n \u0026lt;MudTextField @bind-Value=\u0026quot;password\u0026quot; Label=\u0026quot;Password\u0026quot; InputType=\u0026quot;InputType.Password\u0026quot; Variant=\u0026quot;Variant.Outlined\u0026quot; /\u0026gt;\n\n \u0026lt;MudButton ButtonType=\u0026quot;ButtonType.Submit\u0026quot; Variant=\u0026quot;Variant.Filled\u0026quot; Color=\u0026quot;Color.Primary\u0026quot; FullWidth\u0026gt;Sign in\u0026lt;/MudButton\u0026gt;\n\n @if (!string.IsNullOrWhiteSpace(error))\n {\n \u0026lt;MudAlert Severity=\u0026quot;Severity.Error\u0026quot;\u0026gt;@error\u0026lt;/MudAlert\u0026gt;\n }\n \u0026lt;/MudStack\u0026gt;\n\u0026lt;/UAuthLoginForm\u0026gt;\n\n@code {\n private string email = \u0026quot;\u0026quot;;\n private string password = \u0026quot;\u0026quot;;\n private string? error;\n\n private Task HandleLoginResult(IUAuthTryResult result)\n {\n error = result.IsSuccess ? null : \u0026quot;Unable to sign in.\u0026quot;;\n return Task.CompletedTask;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe form controls remain under your control.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth does not require a particular visual design, CSS framework, or component library.\u003C/p\u003E\n\u003Cp\u003EBecause the component renders an HTML form internally, place the submit button inside its child content and avoid nesting another HTML form within it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022submission-modes\u0022\u003ESubmission Modes\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003ESubmitMode\u003C/code\u003E to control how the authentication attempt is executed.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm Identifier=\u0026quot;@email\u0026quot; Secret=\u0026quot;@password\u0026quot; SubmitMode=\u0026quot;UAuthSubmitMode.TryAndCommit\u0026quot;\u0026gt;\n ...\n\u0026lt;/UAuthLoginForm\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EMode\u003C/th\u003E\n\u003Cth\u003EBehavior\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryOnly\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EValidates the authentication attempt without committing login\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryAndCommit\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAttempts authentication and commits on success\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDirectCommit\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESubmits directly to the commit flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ETryAndCommit\u003C/code\u003E is the default.\u003C/p\u003E\n\u003Cp\u003EFor interactive login forms that need validation feedback, it is generally the recommended choice.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EDirectCommit\u003C/code\u003E does not invoke \u003Ccode\u003EOnTryResult\u003C/code\u003E, because it does not perform the structured try operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-authentication-results\u0022\u003EHandling Authentication Results\u003C/h3\u003E\n\u003Cp\u003EThe component exposes an \u003Ccode\u003EOnTryResult\u003C/code\u003E callback:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm Identifier=\u0026quot;@email\u0026quot; Secret=\u0026quot;@password\u0026quot; OnTryResult=\u0026quot;HandleResult\u0026quot;\u0026gt;\n ...\n\u0026lt;/UAuthLoginForm\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe callback receives \u003Ccode\u003EIUAuthTryResult\u003C/code\u003E, allowing the same component to report password login and PKCE completion results.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eprivate Task HandleResult(IUAuthTryResult result)\n{\n if (!result.IsSuccess)\n {\n errorMessage = \u0026quot;Authentication failed.\u0026quot;;\n }\n\n return Task.CompletedTask;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor flow-specific details, inspect the concrete result type:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eprivate Task HandleResult(IUAuthTryResult result)\n{\n if (result is TryLoginResult loginResult)\n {\n if (loginResult.LockoutUntilUtc is not null)\n {\n errorMessage = \u0026quot;Your account is temporarily locked.\u0026quot;;\n }\n else if (loginResult.RequiresMfa)\n {\n // Continue the supported additional authentication workflow.\n }\n else if (!loginResult.IsSuccess)\n {\n errorMessage = \u0026quot;Unable to sign in.\u0026quot;;\n }\n }\n else if (result is TryPkceLoginResult pkceResult)\n {\n if (pkceResult.RetryWithNewPkce)\n {\n errorMessage = \u0026quot;Please restart the authentication flow.\u0026quot;;\n }\n else if (!pkceResult.IsSuccess)\n {\n errorMessage = \u0026quot;Unable to complete authentication.\u0026quot;;\n }\n }\n\n return Task.CompletedTask;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe callback reports the structured try result. It does not replace authentication state management or server-side validation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-uauthloginform-with-pkce\u0022\u003EUsing UAuthLoginForm with PKCE\u003C/h3\u003E\n\u003Cp\u003EThe same component also supports PKCE login completion when hosted within a supported UAuthHub environment.\u003C/p\u003E\n\u003Cp\u003ESet \u003Ccode\u003ELoginType\u003C/code\u003E to \u003Ccode\u003EPkce\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm LoginType=\u0026quot;UAuthLoginType.Pkce\u0026quot; Identifier=\u0026quot;@email\u0026quot; Secret=\u0026quot;@password\u0026quot; SubmitMode=\u0026quot;UAuthSubmitMode.TryAndCommit\u0026quot; OnTryResult=\u0026quot;HandleLoginResult\u0026quot;\u0026gt;\n \u0026lt;input @bind=\u0026quot;email\u0026quot; placeholder=\u0026quot;Email\u0026quot; /\u0026gt;\n \u0026lt;input @bind=\u0026quot;password\u0026quot; type=\u0026quot;password\u0026quot; placeholder=\u0026quot;Password\u0026quot; /\u0026gt;\n \u0026lt;button type=\u0026quot;submit\u0026quot;\u0026gt;Continue\u0026lt;/button\u0026gt;\n\u0026lt;/UAuthLoginForm\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn PKCE mode, the component:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EResolves the relevant UAuthHub session context.\u003C/li\u003E\n\u003Cli\u003ERetrieves the authorization code and code verifier through the configured Hub services.\u003C/li\u003E\n\u003Cli\u003EBuilds a \u003Ccode\u003EPkceCompleteRequest\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EInvokes the corresponding PKCE completion flow.\u003C/li\u003E\n\u003Cli\u003EReports the structured result when a try-enabled submission mode is used.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe application does not need to manually construct the PKCE completion request for this form.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E PKCE mode requires a supported server-side UAuthHub environment. \u003Ccode\u003EUAuthLoginForm\u003C/code\u003E checks the available Hub capabilities and throws when PKCE login is requested in an unsupported client profile.\u003C/p\u003E\n\u003Cp\u003EA Blazor WebAssembly application should initiate PKCE using \u003Ccode\u003EBeginPkceAsync\u003C/code\u003E; the Hub-hosted login form handles the completion stage.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022return-url-handling\u0022\u003EReturn URL Handling\u003C/h3\u003E\n\u003Cp\u003EYou can provide an explicit return URL:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm Identifier=\u0026quot;@email\u0026quot; Secret=\u0026quot;@password\u0026quot; ReturnUrl=\u0026quot;/dashboard\u0026quot;\u0026gt;\n ...\n\u0026lt;/UAuthLoginForm\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe component resolves the effective return URL using the following precedence:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EExplicit \u003Ccode\u003EReturnUrl\u003C/code\u003E parameter.\u003C/li\u003E\n\u003Cli\u003EUAuthHub flow return URL when using PKCE.\u003C/li\u003E\n\u003Cli\u003ECurrent navigation URL.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThis allows a shared login component to participate in different authentication journeys without hardcoding navigation logic into the form.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-login-endpoints\u0022\u003ECustom Login Endpoints\u003C/h3\u003E\n\u003Cp\u003EFor advanced integrations, the \u003Ccode\u003EEndpoint\u003C/code\u003E parameter allows the form\u0027s HTML action endpoint to be overridden:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm Identifier=\u0026quot;@email\u0026quot; Secret=\u0026quot;@password\u0026quot; Endpoint=\u0026quot;/custom-login\u0026quot; SubmitMode=\u0026quot;UAuthSubmitMode.DirectCommit\u0026quot;\u0026gt;\n ...\n\u0026lt;/UAuthLoginForm\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe endpoint is resolved relative to the configured UltimateAuth endpoint base path and multi-tenant settings.\u003C/p\u003E\n\u003Cp\u003ENote that \u003Ccode\u003EEndpoint\u003C/code\u003E affects the form\u0027s HTML action. Try-based submissions invoke the configured \u003Ccode\u003EUAuthClient.Flows\u003C/code\u003E endpoints, so changing \u003Ccode\u003EEndpoint\u003C/code\u003E alone does not redirect those SDK requests to a custom endpoint.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022submit-the-form-programmatically\u0022\u003ESubmit the Form Programmatically\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E exposes \u003Ccode\u003ESubmitAsync\u003C/code\u003E, allowing submission through a component reference.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm @ref=\u0026quot;loginForm\u0026quot; Identifier=\u0026quot;@email\u0026quot; Secret=\u0026quot;@password\u0026quot; AllowEnterKeyToSubmit=\u0026quot;false\u0026quot;\u0026gt;\n \u0026lt;input @bind=\u0026quot;email\u0026quot; placeholder=\u0026quot;Email\u0026quot; /\u0026gt;\n \u0026lt;input @bind=\u0026quot;password\u0026quot; type=\u0026quot;password\u0026quot; /\u0026gt;\n\u0026lt;/UAuthLoginForm\u0026gt;\n\n\u0026lt;button type=\u0026quot;button\u0026quot; @onclick=\u0026quot;SubmitLoginAsync\u0026quot;\u0026gt;\n Sign in\n\u0026lt;/button\u0026gt;\n\n@code {\n private UAuthLoginForm? loginForm;\n\n private string email = \u0026quot;\u0026quot;;\n private string password = \u0026quot;\u0026quot;;\n\n private async Task SubmitLoginAsync()\n {\n if (loginForm is not null)\n {\n await loginForm.SubmitAsync();\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ESubmitAsync\u003C/code\u003E must be called after the component has rendered.\u003C/p\u003E\n\u003Cp\u003EThis is useful when submission is initiated from a custom button, dialog, or application-specific interaction.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022controlling-enter-key-submission\u0022\u003EControlling Enter-Key Submission\u003C/h3\u003E\n\u003Cp\u003EBy default, the component supports Enter-key submission.\u003C/p\u003E\n\u003Cp\u003EYou can disable its built-in hidden submit button:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm Identifier=\u0026quot;@email\u0026quot; Secret=\u0026quot;@password\u0026quot; AllowEnterKeyToSubmit=\u0026quot;false\u0026quot;\u0026gt;\n ...\n\u0026lt;/UAuthLoginForm\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis option controls the component\u0027s built-in Enter-key submit mechanism. Other submit buttons inside the form may still trigger normal browser form submission behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reloading-pkce-context\u0022\u003EReloading PKCE Context\u003C/h3\u003E\n\u003Cp\u003EThe component exposes \u003Ccode\u003EReloadAsync\u003C/code\u003E to reload its Hub credentials and flow state.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm @ref=\u0026quot;loginForm\u0026quot; LoginType=\u0026quot;UAuthLoginType.Pkce\u0026quot; Identifier=\u0026quot;@email\u0026quot; Secret=\u0026quot;@password\u0026quot;\u0026gt;\n ...\n\u0026lt;/UAuthLoginForm\u0026gt;\n\n@code {\n private UAuthLoginForm? loginForm;\n\n private async Task ReloadLoginContextAsync()\n {\n if (loginForm is not null)\n {\n await loginForm.ReloadAsync();\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is an advanced capability intended for scenarios where the associated Hub state changes and the component must refresh its internal context.\u003C/p\u003E\n\u003Cp\u003EIt does not initiate a new PKCE authorization flow by itself.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022component-parameters\u0022\u003EComponent Parameters\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EParameter\u003C/th\u003E\n\u003Cth\u003EType\u003C/th\u003E\n\u003Cth\u003EDefault\u003C/th\u003E\n\u003Cth\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication secret\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELoginType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthLoginType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPassword\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPassword or PKCE login\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESubmitMode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthSubmitMode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryAndCommit\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESubmission strategy\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReturnUrl\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERedirect destination\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEndpoint\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECustom HTML form endpoint\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHubSessionId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHubSessionId?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExplicit Hub session context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAllowEnterKeyToSubmit\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ebool\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBuilt-in Enter-key submit support\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnTryResult\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EEventCallback\u0026lt;IUAuthTryResult\u0026gt;\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u2014\u003C/td\u003E\n\u003Ctd\u003EStructured try result callback\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EChildContent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERenderFragment?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u2014\u003C/td\u003E\n\u003Ctd\u003ECustom form content\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-to-use-uauthloginform\u0022\u003EWhen to Use UAuthLoginForm\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EUAuthLoginForm\u003C/code\u003E when building a Blazor login interface that should integrate directly with UltimateAuth\u0027s supported authentication flows.\u003C/p\u003E\n\u003Cp\u003EIt is particularly useful when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou want to use your own component library without implementing the authentication submission logic.\u003C/li\u003E\n\u003Cli\u003EYou need structured login validation feedback.\u003C/li\u003E\n\u003Cli\u003EYou want to switch between try-only and commit behavior.\u003C/li\u003E\n\u003Cli\u003EYou are building a UAuthHub login page that completes PKCE.\u003C/li\u003E\n\u003Cli\u003EYou want to share login UI across supported Blazor hosting environments.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor application-specific workflows that cannot be expressed through the component, use \u003Ccode\u003EUAuthClient.Flows\u003C/code\u003E directly.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe component handles the login workflow, while your application retains control over presentation, user experience, and surrounding application logic.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-integration-mistakes\u0022\u003E18. Common Integration Mistakes\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022calling-direct-login-from-a-public-client\u0022\u003ECalling Direct Login from a Public Client\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EProblem:\u003C/strong\u003E \u003Ccode\u003ELoginAsync\u003C/code\u003E throws because direct credential posting is disabled.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESolution:\u003C/strong\u003E Use \u003Ccode\u003EBeginPkceAsync\u003C/code\u003E and the supported PKCE completion flow. Do not enable \u003Ccode\u003EAllowCredentialPost\u003C/code\u003E to bypass the public-client restriction.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022treating-tryonly-as-authentication\u0022\u003ETreating TryOnly as Authentication\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EProblem:\u003C/strong\u003E The application receives \u003Ccode\u003EIsSuccess = true\u003C/code\u003E from \u003Ccode\u003ETryOnly\u003C/code\u003E and assumes the user is logged in.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESolution:\u003C/strong\u003E A successful try result does not commit authentication. Use \u003Ccode\u003ETryAndCommit\u003C/code\u003E or the appropriate commit flow.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-directcommit-skips-server-validation\u0022\u003EAssuming DirectCommit Skips Server Validation\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EProblem:\u003C/strong\u003E DirectCommit is interpreted as a faster but less secure authentication mode.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESolution:\u003C/strong\u003E DirectCommit skips the separate client try stage. The server still validates the actual authentication request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-every-refresh-rotates-tokens\u0022\u003EAssuming Every Refresh Rotates Tokens\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EProblem:\u003C/strong\u003E The application requires \u003Ccode\u003ERefreshOutcome.Rotated\u003C/code\u003E for refresh success.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESolution:\u003C/strong\u003E Check \u003Ccode\u003ERefreshResult.IsSuccess\u003C/code\u003E. Rotation, touch, and no-op behavior depend on the authentication mode and refresh strategy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-a-session-id-for-device-logout\u0022\u003EUsing a Session ID for Device Logout\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EProblem:\u003C/strong\u003E A session identifier is passed to a device logout operation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESolution:\u003C/strong\u003E \u003Ccode\u003ELogoutMyDeviceAsync\u003C/code\u003E requires \u003Ccode\u003ELogoutDeviceRequest\u003C/code\u003E with a \u003Ccode\u003ESessionChainId\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-logout-immediately-invalidates-all-jwts\u0022\u003EAssuming Logout Immediately Invalidates All JWTs\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EProblem:\u003C/strong\u003E An application expects every issued JWT access token to become unusable immediately after server-side logout.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESolution:\u003C/strong\u003E Understand the enforcement behavior of SemiHybrid and PureJwt. Local JWT validation may continue accepting previously issued tokens until expiration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-validateasync-on-every-render\u0022\u003EUsing ValidateAsync on Every Render\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EProblem:\u003C/strong\u003E Components repeatedly make validation requests just to display authenticated UI.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESolution:\u003C/strong\u003E Use the appropriate authentication state integration for UI rendering, and reserve explicit validation for operations that require it.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client-sdk/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Inspect and manage session and device information.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E \u2014 Understand token validity, refresh behavior, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/refresh-rotation\u0022\u003ERefresh Rotation \u0026amp; Replay Protection\u003C/a\u003E \u2014 Explore refresh concurrency and reuse security.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Compare authentication behavior across modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E \u2014 Understand device-associated session chains.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EThe UltimateAuth Client SDK provides a high-level API for authentication workflows, including login, PKCE, session validation, refresh, logout, and device-specific session revocation.\u003C/p\u003E\n\u003Cp\u003EThese operations are exposed through \u003Ccode\u003EUAuthClient.Flows\u003C/code\u003E, which implements the \u003Ccode\u003EIFlowClient\u003C/code\u003E contract.\u003C/p\u003E\n\u003Cp\u003EThis guide shows how to use the authentication client in application code, handle structured results, configure interactive behavior, and choose the correct flow for different client environments.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-authentication-client\u0022\u003E1. Accessing the Authentication Client\u003C/h2\u003E\n\u003Cp\u003EIn a Blazor component, inject \u003Ccode\u003EIUAuthClient\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAuthentication operations are available through the \u003Ccode\u003EFlows\u003C/code\u003E property.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.RefreshAsync();\nawait UAuthClient.Flows.ValidateAsync();\nawait UAuthClient.Flows.LogoutAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe relevant methods are defined by \u003Ccode\u003EIFlowClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EMethod\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELoginAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESubmit a direct login flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryLoginAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAttempt login with configurable try/commit behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBeginPkceAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EStart a PKCE authentication flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECompletePkceLoginAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EComplete a PKCE login\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryCompletePkceLoginAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EComplete PKCE with structured try/commit behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERefreshAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERefresh or continue authentication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EValidateAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EValidate current authentication state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogoutAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELog out the current session\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogoutMyDeviceAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke a selected device-associated chain\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogoutMyOtherDevicesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke other devices while retaining the current one\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogoutAllMyDevicesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke all devices for the current user\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EAdministrative device logout operations are also available and covered later in this guide.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-the-correct-login-flow\u0022\u003E2. Choosing the Correct Login Flow\u003C/h2\u003E\n\u003Cp\u003EBefore implementing login, determine whether your application is a trusted server-hosted client or a public client.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EClient environment\u003C/th\u003E\n\u003Cth\u003ERecommended flow\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor Server\u003C/td\u003E\n\u003Ctd\u003EDirect login or supported interactive login flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUAuthHub\u003C/td\u003E\n\u003Ctd\u003EServer-hosted authentication flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor WebAssembly\u003C/td\u003E\n\u003Ctd\u003EPKCE\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBrowser SPA\u003C/td\u003E\n\u003Ctd\u003EPKCE\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPublic native application\u003C/td\u003E\n\u003Ctd\u003EPKCE\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EPublic clients must not rely on embedding a secret or trusting browser-executed code to protect credentials.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth restricts direct credential posting based on the configured client profile and login options.\u003C/p\u003E\n\u003Cp\u003EFor public clients, use the PKCE flow instead of enabling direct credential posting.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022direct-login\u0022\u003E3. Direct Login\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ELoginAsync\u003C/code\u003E performs a direct login submission.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar request = new LoginRequest\n{\n Identifier = \u0026quot;user@example.com\u0026quot;,\n Secret = \u0026quot;password\u0026quot;\n};\n\nawait UAuthClient.Flows.LoginAsync(request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003ETask\u003C/code\u003E, not a structured login result.\u003C/p\u003E\n\u003Cp\u003EIt submits the login payload through the configured navigation-based transport.\u003C/p\u003E\n\u003Cp\u003EDepending on the hosting environment, this may initiate navigation or redirect behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022loginrequest-properties\u0022\u003ELoginRequest Properties\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ELoginRequest\u003C/code\u003E supports the following properties:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential secret\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential type; defaults to \u003Ccode\u003EPassword\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMetadata\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional request metadata\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequestTokens\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EHint to request tokens when supported\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreviewReceipt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional receipt associated with a prior login preview\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EExample:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar request = new LoginRequest\n{\n Identifier = email,\n Secret = password,\n CredentialType = CredentialType.Password,\n RequestTokens = true\n};\n\nawait UAuthClient.Flows.LoginAsync(request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ERequestTokens\u003C/code\u003E is a hint, not a guarantee that tokens will be issued. The server\u0027s authentication mode and policy remain authoritative.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022redirect-after-login\u0022\u003ERedirect After Login\u003C/h3\u003E\n\u003Cp\u003EProvide an explicit return URL when necessary:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.LoginAsync(request, returnUrl: \u0026quot;/dashboard\u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe direct login flow resolves its return URL in the following order:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EThe explicit \u003Ccode\u003EreturnUrl\u003C/code\u003E argument.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ELogin.ReturnUrl\u003C/code\u003E option.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EDefaultReturnUrl\u003C/code\u003E option.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EFor example, client options can specify a default:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.Login.ReturnUrl = \u0026quot;/dashboard\u0026quot;;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe return URL is part of the login workflow. It should not be treated as an authorization grant.\u003C/p\u003E\n\u003Cp\u003EApplications and servers must still enforce their redirect validation rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022interactive-login-with-tryloginasync\u0022\u003E4. Interactive Login with TryLoginAsync\u003C/h2\u003E\n\u003Cp\u003EFor interactive forms, \u003Ccode\u003ETryLoginAsync\u003C/code\u003E provides structured feedback before or during the authentication commit process.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.TryLoginAsync(\n new LoginRequest\n {\n Identifier = email,\n Secret = password\n },\n UAuthSubmitMode.TryAndCommit);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is generally the more useful API for a login form that needs to display validation feedback.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022submit-modes\u0022\u003ESubmit Modes\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthSubmitMode\u003C/code\u003E defines three submission behaviors.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EMode\u003C/th\u003E\n\u003Cth\u003EBehavior\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryOnly\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExecute the try operation and return its result without committing authentication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDirectCommit\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESubmit directly to the commit endpoint without a separate try operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryAndCommit\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETry first, then commit when the try operation succeeds\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese modes control client workflow, not whether the server validates authentication.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDirectCommit does not bypass credential verification or server security requirements.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022tryonly-validate-without-login\u0022\u003ETryOnly: Validate Without Login\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003ETryOnly\u003C/code\u003E when you want structured feedback without establishing an authenticated session.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.TryLoginAsync(\n new LoginRequest\n {\n Identifier = email,\n Secret = password\n },\n UAuthSubmitMode.TryOnly);\n\nif (result.IsSuccess)\n{\n // The try operation succeeded.\n // Authentication has not been committed.\n}\nelse\n{\n // Display the appropriate feedback.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis can be useful for a custom interactive workflow.\u003C/p\u003E\n\u003Cp\u003EA successful try result must not be interpreted as an authenticated application session.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022tryandcommit-interactive-login\u0022\u003ETryAndCommit: Interactive Login\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003ETryAndCommit\u003C/code\u003E to combine structured validation with authentication commit.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.TryLoginAsync(\n new LoginRequest\n {\n Identifier = email,\n Secret = password\n },\n UAuthSubmitMode.TryAndCommit,\n returnUrl: \u0026quot;/dashboard\u0026quot;);\n\nif (!result.IsSuccess)\n{\n // Show validation feedback.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen the try stage succeeds, the client proceeds to the commit operation through its configured transport.\u003C/p\u003E\n\u003Cp\u003EThis can involve navigation, so application code should not assume that execution always continues within the same component instance.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022directcommit-single-commit-submission\u0022\u003EDirectCommit: Single Commit Submission\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.TryLoginAsync(\n new LoginRequest\n {\n Identifier = email,\n Secret = password\n },\n UAuthSubmitMode.DirectCommit);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn this mode, the SDK submits the commit operation directly.\u003C/p\u003E\n\u003Cp\u003EThe method returns a \u003Ccode\u003ETryLoginResult\u003C/code\u003E with \u003Ccode\u003EIsSuccess = true\u003C/code\u003E after the navigation submission completes, but this is not a structured server-side authentication success confirmation.\u003C/p\u003E\n\u003Cp\u003EIf you need structured credential validation feedback, prefer \u003Ccode\u003ETryAndCommit\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-login-results\u0022\u003E5. Handling Login Results\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ETryLoginAsync\u003C/code\u003E returns \u003Ccode\u003ETryLoginResult\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIts available properties include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EMeaning\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsSuccess\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the try operation succeeded\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReason\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional authentication failure reason\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERemainingAttempts\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERemaining attempts, when provided\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELockoutUntilUtc\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELockout expiration, when provided\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequiresMfa\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether additional authentication is required\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreviewReceipt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional receipt for subsequent flow handling\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EExample:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.TryLoginAsync(request, UAuthSubmitMode.TryOnly);\n\nif (!result.IsSuccess)\n{\n if (result.LockoutUntilUtc is not null)\n {\n errorMessage = $\u0026quot;Login temporarily unavailable until {result.LockoutUntilUtc:u}\u0026quot;;\n }\n else\n {\n errorMessage = \u0026quot;Unable to sign in.\u0026quot;;\n }\n\n return;\n}\n\nif (result.RequiresMfa)\n{\n // Continue with the supported additional authentication flow.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDo not expose sensitive internal authentication details unnecessarily.\u003C/p\u003E\n\u003Cp\u003EFor public-facing login forms, generic failure messages may be preferable to displaying every failure reason.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-transport-and-protocol-errors\u0022\u003EHandling Transport and Protocol Errors\u003C/h3\u003E\n\u003Cp\u003EA structured authentication failure is different from a network or protocol failure.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Etry\n{\n var result = await UAuthClient.Flows.TryLoginAsync(request, UAuthSubmitMode.TryAndCommit);\n\n if (!result.IsSuccess)\n {\n errorMessage = \u0026quot;Unable to sign in.\u0026quot;;\n }\n}\ncatch (UAuthTransportException)\n{\n errorMessage = \u0026quot;Authentication service is unavailable.\u0026quot;;\n}\ncatch (UAuthProtocolException)\n{\n errorMessage = \u0026quot;An authentication protocol error occurred.\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse the corresponding UltimateAuth client error namespace when referencing these exception types.\u003C/p\u003E\n\u003Cp\u003EA failed authentication attempt should normally be handled through the structured try result where supported. Transport and protocol exceptions indicate different failure categories.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pkce-authentication\u0022\u003E6. PKCE Authentication\u003C/h2\u003E\n\u003Cp\u003EPKCE provides a proof mechanism for authorization-code-based authentication flows.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports PKCE using an S256 code challenge.\u003C/p\u003E\n\u003Cp\u003EThe SDK generates a cryptographically random verifier, derives its challenge, and coordinates the authorization flow.\u003C/p\u003E\n\u003Cp\u003EPublic clients such as Blazor WebAssembly should use PKCE rather than direct credential posting.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022start-pkce\u0022\u003EStart PKCE\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.BeginPkceAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EYou can provide a return URL:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.BeginPkceAsync(returnUrl: \u0026quot;/dashboard\u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client performs the following operations:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EGenerates a PKCE code verifier.\u003C/li\u003E\n\u003Cli\u003EDerives an S256 code challenge.\u003C/li\u003E\n\u003Cli\u003ESends the authorization request.\u003C/li\u003E\n\u003Cli\u003EReceives an authorization code.\u003C/li\u003E\n\u003Cli\u003EInvokes the configured authorization callback, if any.\u003C/li\u003E\n\u003Cli\u003ENavigates to the configured UAuthHub login flow when automatic redirection is enabled.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe authorization code and verifier are used later to complete the flow.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-pkce\u0022\u003EConfigure PKCE\u003C/h3\u003E\n\u003Cp\u003EThe relevant client options include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.Pkce.Enabled = true;\noptions.Pkce.AutoRedirect = true;\noptions.Pkce.ReturnUrl = \u0026quot;/dashboard\u0026quot;;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EPKCE is enabled by default in \u003Ccode\u003EUAuthClientPkceLoginFlowOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EAutomatic redirection is also enabled by default.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022observe-authorization\u0022\u003EObserve Authorization\u003C/h3\u003E\n\u003Cp\u003EYou can register a callback that runs after the authorization code is received and before the automatic Hub redirect.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.Pkce.OnAuthorized = async response =\u0026gt;\n{\n // Observe the authorization response.\n await Task.CompletedTask;\n};\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis callback is useful for application-specific coordination.\u003C/p\u003E\n\u003Cp\u003EDo not log authorization codes or expose them to unrelated components.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022disable-automatic-redirection\u0022\u003EDisable Automatic Redirection\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.Pkce.AutoRedirect = false;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWith this configuration, \u003Ccode\u003EBeginPkceAsync\u003C/code\u003E still performs the authorization request and invokes \u003Ccode\u003EOnAuthorized\u003C/code\u003E, but it does not automatically navigate to the Hub login endpoint.\u003C/p\u003E\n\u003Cp\u003EThe application becomes responsible for coordinating the remaining flow.\u003C/p\u003E\n\u003Cp\u003EThis is an advanced integration option.\u003C/p\u003E\n\u003Cp\u003EA custom implementation must preserve the authorization code, verifier, and relevant context securely and complete the flow using the supported protocol.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-pkce-login\u0022\u003E7. Complete PKCE Login\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ECompletePkceLoginAsync\u003C/code\u003E submits the PKCE completion request.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar request = new PkceCompleteRequest\n{\n AuthorizationCode = authorizationCode,\n CodeVerifier = codeVerifier,\n Identifier = email,\n Secret = password,\n ReturnUrl = \u0026quot;/dashboard\u0026quot;\n};\n\nawait UAuthClient.Flows.CompletePkceLoginAsync(request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request model includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAuthorizationCode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECode obtained from the authorization step\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECodeVerifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOriginal PKCE verifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin credential\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReturnUrl\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional post-login destination\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHubSessionId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional Hub session context\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe authorization code and verifier must correspond to the same PKCE flow.\u003C/p\u003E\n\u003Cp\u003EDo not generate a new verifier when completing an existing authorization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022interactive-pkce-completion\u0022\u003EInteractive PKCE Completion\u003C/h3\u003E\n\u003Cp\u003EFor structured feedback, use \u003Ccode\u003ETryCompletePkceLoginAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.TryCompletePkceLoginAsync(\n request,\n UAuthSubmitMode.TryAndCommit);\n\nif (!result.IsSuccess)\n{\n // Display PKCE login feedback.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same submit modes are supported:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ETryOnly\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EDirectCommit\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ETryAndCommit\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022try-pkce-without-commit\u0022\u003ETry PKCE Without Commit\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.TryCompletePkceLoginAsync(request, UAuthSubmitMode.TryOnly);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis returns a \u003Ccode\u003ETryPkceLoginResult\u003C/code\u003E without committing a successful login.\u003C/p\u003E\n\u003Cp\u003EThe result includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eresult.IsSuccess;\nresult.Reason;\nresult.RemainingAttempts;\nresult.LockoutUntilUtc;\nresult.RequiresMfa;\nresult.RetryWithNewPkce;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022retrywithnewpkce\u0022\u003ERetryWithNewPkce\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ERetryWithNewPkce\u003C/code\u003E indicates that the client should begin a new PKCE authorization sequence rather than continuing with the existing authorization material.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eif (result.RetryWithNewPkce)\n{\n await UAuthClient.Flows.BeginPkceAsync(\u0026quot;/login\u0026quot;);\n return;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is useful when the current PKCE context can no longer be used.\u003C/p\u003E\n\u003Cp\u003EThe application should not repeatedly resubmit the same invalid authorization code and verifier.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022direct-credential-posting-restrictions\u0022\u003E8. Direct Credential Posting Restrictions\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth restricts direct credential posting according to the configured client profile.\u003C/p\u003E\n\u003Cp\u003EThe client option is:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.Login.AllowCredentialPost = false;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is the default.\u003C/p\u003E\n\u003Cp\u003EIf the current profile is not permitted to post credentials directly and the option has not enabled that capability, direct login methods throw an \u003Ccode\u003EInvalidOperationException\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example, attempting direct login from a public client may produce an error indicating that PKCE is required.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022trusted-server-hosted-clients\u0022\u003ETrusted Server-Hosted Clients\u003C/h3\u003E\n\u003Cp\u003EThe option can be explicitly configured for a trusted server-hosted integration where direct credential posting is appropriate:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.Login.AllowCredentialPost = true;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis must not be used to bypass PKCE requirements in a public browser application.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not enable direct credential posting in production Blazor WebAssembly or SPA clients.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA client profile is an integration descriptor, not cryptographic proof that the caller is trusted.\u003C/p\u003E\n\u003Cp\u003EServer-side security requirements remain authoritative.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-authentication\u0022\u003E9. Refresh Authentication\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ERefreshAsync\u003C/code\u003E requests continuation of the current authentication state.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.RefreshAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003ERefreshResult\u003C/code\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic sealed record RefreshResult\n{\n public bool IsSuccess { get; init; }\n public int Status { get; init; }\n public RefreshOutcome Outcome { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERefresh behavior depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EA refresh operation may update session activity, rotate authentication material, or determine that reauthentication is required.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-refresh-results\u0022\u003EHandling Refresh Results\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.RefreshAsync();\n\nif (result.IsSuccess)\n{\n // Authentication continuation succeeded.\n}\nelse if (result.Outcome == RefreshOutcome.ReauthRequired)\n{\n // The current authentication can no longer continue.\n // Direct the user to the appropriate login flow.\n}\nelse\n{\n // Handle another unsuccessful refresh result.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA \u003Ccode\u003E401 Unauthorized\u003C/code\u003E refresh response is handled as a normal reauthentication outcome.\u003C/p\u003E\n\u003Cp\u003EIt returns:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Enew RefreshResult\n{\n IsSuccess = false,\n Status = 401,\n Outcome = RefreshOutcome.ReauthRequired\n};\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ENetwork and unexpected protocol failures can still produce exceptions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-outcomes\u0022\u003ERefresh Outcomes\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ERefreshOutcome\u003C/code\u003E defines the following values:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOutcome\u003C/th\u003E\n\u003Cth\u003EMeaning\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENone\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo specific semantic outcome\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESuccess\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EGeneric successful refresh\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENoOp\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo refresh state change was necessary\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETouched\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EApplicable session activity was updated\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERotated\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERotation occurred\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReauthRequired\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication continuation is no longer permitted\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.RefreshAsync();\n\nif (!result.IsSuccess)\n{\n return;\n}\n\nswitch (result.Outcome)\n{\n case RefreshOutcome.Rotated:\n // Authentication material was rotated.\n break;\n\n case RefreshOutcome.Touched:\n // Applicable session activity was updated.\n break;\n\n case RefreshOutcome.NoOp:\n // Successful refresh and no state change was required.\n break;\n\n case RefreshOutcome.Success:\n // Refresh succeeded without a more specific outcome.\n break;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022detailed-refresh-diagnostics\u0022\u003EDetailed Refresh Diagnostics\u003C/h3\u003E\n\u003Cp\u003EThe server may return a generic success outcome rather than exposing the internal refresh action.\u003C/p\u003E\n\u003Cp\u003EWhere supported, detailed refresh outcomes can be enabled through server diagnostics:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Diagnostics.EnableRefreshDetails = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis setting is intended for diagnostics and observability.\u003C/p\u003E\n\u003Cp\u003EApplications should not require a specific successful outcome such as \u003Ccode\u003ERotated\u003C/code\u003E or \u003Ccode\u003ETouched\u003C/code\u003E to consider refresh successful.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003EIsSuccess\u003C/code\u003E for the primary success decision.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022manual-and-automatic-refresh\u0022\u003EManual and Automatic Refresh\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ERefreshAsync\u003C/code\u003E has an optional \u003Ccode\u003EisAuto\u003C/code\u003E argument.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003E// Manual refresh\nawait UAuthClient.Flows.RefreshAsync();\n\n// Refresh initiated by automatic coordination\nawait UAuthClient.Flows.RefreshAsync(isAuto: true);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe parameter distinguishes automatic refresh activity for client diagnostics.\u003C/p\u003E\n\u003Cp\u003EIt does not select a different authentication mode or override the server\u0027s refresh policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-refresh-configuration\u0022\u003E10. Automatic Refresh Configuration\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth exposes client-side automatic refresh options through \u003Ccode\u003EAutoRefresh\u003C/code\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.AutoRefresh.Enabled = true;\noptions.AutoRefresh.Interval = TimeSpan.FromMinutes(5);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EEnabled\u003C/code\u003E controls background refresh coordination.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EInterval\u003C/code\u003E specifies the interval used for background refresh attempts.\u003C/p\u003E\n\u003Cp\u003EThe effective defaults and registration behavior depend on the client integration. The options documentation identifies Blazor Server as the environment where background refresh is enabled by default.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-refresh-is-not-a-security-guarantee\u0022\u003EAutomatic Refresh Is Not a Security Guarantee\u003C/h3\u003E\n\u003Cp\u003EAutomatic refresh is a client-side convenience mechanism.\u003C/p\u003E\n\u003Cp\u003EIt does not guarantee that a session will remain valid indefinitely.\u003C/p\u003E\n\u003Cp\u003EAuthentication may still end because of:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EExpiration.\u003C/li\u003E\n\u003Cli\u003ERevocation.\u003C/li\u003E\n\u003Cli\u003EAccount security changes.\u003C/li\u003E\n\u003Cli\u003EInvalid refresh credentials.\u003C/li\u003E\n\u003Cli\u003EServer-side continuation restrictions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe client must remain prepared to handle \u003Ccode\u003EReauthRequired\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022validate-current-authentication\u0022\u003E11. Validate Current Authentication\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EValidateAsync\u003C/code\u003E to ask the server to validate the current authentication state.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.ValidateAsync();\n\nif (result.IsValid)\n{\n // Current authentication is valid.\n}\nelse\n{\n // Authentication is not currently valid.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003EAuthValidationResult\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIts properties include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EState\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECurrent session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsValid\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the state is \u003Ccode\u003EActive\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESnapshot\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional authentication state snapshot\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EChainId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional associated session chain\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERootId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional session root\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBoundDeviceId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional bound device identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-session-context\u0022\u003EInspect Session Context\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.ValidateAsync();\n\nif (result.IsValid \u0026amp;\u0026amp; result.ChainId is { } chainId)\n{\n // Use chainId for supported session-management operations.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ENot every authentication mode necessarily provides all session hierarchy fields.\u003C/p\u003E\n\u003Cp\u003ETreat optional values as optional.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validateasync-versus-ui-authentication-state\u0022\u003EValidateAsync Versus UI Authentication State\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EValidateAsync\u003C/code\u003E performs an explicit server validation operation.\u003C/p\u003E\n\u003Cp\u003EIt should not be called on every UI render merely to determine whether a component should display authenticated content.\u003C/p\u003E\n\u003Cp\u003EFor UI authentication state, use the supported UltimateAuth state management integration, including \u003Ccode\u003EIUAuthStateManager\u003C/code\u003E where appropriate.\u003C/p\u003E\n\u003Cp\u003EExplicit validation is useful when an application needs to check current server-recognized authentication state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003E12. Logout\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ELogoutAsync\u003C/code\u003E performs the current logout flow.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.LogoutAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method uses the configured logout endpoint and navigation transport.\u003C/p\u003E\n\u003Cp\u003EDepending on the client profile and configuration, logout may involve navigation and authentication state changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-button\u0022\u003ELogout Button\u003C/h3\u003E\n\u003Cp\u003EA simple Blazor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@inject IUAuthClient UAuthClient\n\n\u0026lt;button @onclick=\u0026quot;LogoutAsync\u0026quot;\u0026gt;Logout\u0026lt;/button\u0026gt;\n\n@code {\n private async Task LogoutAsync()\n {\n await UAuthClient.Flows.LogoutAsync();\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ELogout is distinct from clearing local UI state.\u003C/p\u003E\n\u003Cp\u003ERemoving a local authentication indicator does not necessarily revoke authoritative server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003EUse the supported logout flow to terminate authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-a-specific-device\u0022\u003E13. Logout a Specific Device\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports revoking the session chain associated with a particular device context.\u003C/p\u003E\n\u003Cp\u003EThe relevant method is:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003ELogoutMyDeviceAsync(LogoutDeviceRequest request)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ELogoutDeviceRequest\u003C/code\u003E requires a \u003Ccode\u003ESessionChainId\u003C/code\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar request = new LogoutDeviceRequest\n{\n ChainId = chainId\n};\n\nvar result = await UAuthClient.Flows.LogoutMyDeviceAsync(request);\n\nif (result.IsSuccess)\n{\n // The requested device logout operation succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis operation targets a session chain, not an arbitrary individual session ID.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022current-device-behavior\u0022\u003ECurrent Device Behavior\u003C/h3\u003E\n\u003Cp\u003EThe returned \u003Ccode\u003ERevokeResult\u003C/code\u003E can indicate whether the affected chain is the current chain.\u003C/p\u003E\n\u003Cp\u003EWhen the operation succeeds and \u003Ccode\u003ECurrentChain\u003C/code\u003E is true, the client publishes a logout-related state event.\u003C/p\u003E\n\u003Cp\u003EThis allows the application to react when the currently authenticated device context has been revoked.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-other-devices\u0022\u003E14. Logout Other Devices\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ELogoutMyOtherDevicesAsync\u003C/code\u003E to revoke other device-associated sessions while retaining the current one.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.LogoutMyOtherDevicesAsync();\n\nif (result.IsSuccess)\n{\n // Other devices were logged out.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is useful for account security pages that provide a \u003Cstrong\u003ELog out other devices\u003C/strong\u003E action.\u003C/p\u003E\n\u003Cp\u003EThe current authentication context is intended to remain active.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for identifying the current session or chain and enforcing the appropriate revocation scope.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-all-devices\u0022\u003E15. Logout All Devices\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ELogoutAllMyDevicesAsync\u003C/code\u003E to revoke all applicable sessions for the current user.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.LogoutAllMyDevicesAsync();\n\nif (result.IsSuccess)\n{\n // All applicable device sessions were revoked.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOn success, the client publishes a logout-related state event.\u003C/p\u003E\n\u003Cp\u003EThis operation is useful when a user suspects account compromise or wants to terminate existing authentication sessions across devices.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, previously issued access tokens may remain cryptographically valid until expiration unless additional revocation enforcement is configured.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-device-logout\u0022\u003E16. Administrative Device Logout\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth also exposes administrative session revocation operations.\u003C/p\u003E\n\u003Cp\u003EThese methods act on a specified user and require appropriate server-side authorization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-one-user-device\u0022\u003ELogout One User Device\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.LogoutUserDeviceAsync(\n userKey,\n new LogoutDeviceRequest\n {\n ChainId = chainId\n });\n\nif (!result.IsSuccess)\n{\n // Handle the failure.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-other-devices-for-a-user\u0022\u003ELogout Other Devices for a User\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.LogoutUserOtherDevicesAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EHere, \u003Ccode\u003Erequest\u003C/code\u003E is a \u003Ccode\u003ELogoutOtherDevicesRequest\u003C/code\u003E identifying the chain to retain.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-all-devices-for-a-user\u0022\u003ELogout All Devices for a User\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.LogoutAllUserDevicesAsync(userKey);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAdministrative methods do not grant administrative privileges by themselves.\u003C/p\u003E\n\u003Cp\u003EThe server must authorize the actor and validate the target user and requested revocation scope.\u003C/p\u003E\n\u003Cp\u003EThese operations are particularly relevant to administration panels and account security workflows.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022building-login-forms-with-uauthloginform\u0022\u003E17. Building Login Forms with UAuthLoginForm\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides \u003Ccode\u003EUAuthLoginForm\u003C/code\u003E, a reusable Blazor component that handles authentication form submission without requiring developers to implement the underlying flow orchestration themselves.\u003C/p\u003E\n\u003Cp\u003EThe component supports password-based login, PKCE completion through UAuthHub, configurable submission modes, structured validation feedback, and customizable UI content.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EYou design the login interface. UltimateAuth handles the authentication submission workflow.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic-usage\u0022\u003EBasic Usage\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E provides a complete, ready-to-use login form out of the box. No additional markup or credential bindings are required.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm /\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThat\u0027s it. UltimateAuth automatically renders the identifier field, password field, and submit button, and handles the authentication submission workflow.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ECustomizing the Form\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EWhen you need a custom layout or styling, simply provide your own form content:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client.Blazor\n\n\u0026lt;UAuthLoginForm\u0026gt;\n \u0026lt;input name=\u0026quot;Identifier\u0026quot; placeholder=\u0026quot;Email\u0026quot; autocomplete=\u0026quot;username\u0026quot; /\u0026gt;\n \u0026lt;input name=\u0026quot;Secret\u0026quot; type=\u0026quot;password\u0026quot; placeholder=\u0026quot;Password\u0026quot; autocomplete=\u0026quot;current-password\u0026quot; /\u0026gt;\n \u0026lt;button type=\u0026quot;submit\u0026quot;\u0026gt;Sign in\u0026lt;/button\u0026gt;\n\u0026lt;/UAuthLoginForm\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUltimateAuth handles the authentication workflow in both cases. Your custom fields only need the correct HTML \u003Ccode\u003Ename\u003C/code\u003E attributes; there is no need to bind credentials to Blazor component state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022native-credential-submission\u0022\u003ENative Credential Submission\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E reads credentials directly from native browser form fields. It does not accept \u003Ccode\u003EIdentifier\u003C/code\u003E or \u003Ccode\u003ESecret\u003C/code\u003E component parameters.\u003C/p\u003E\n\u003Cp\u003EWhen providing custom \u003Ccode\u003EChildContent\u003C/code\u003E, the form must contain enabled input elements with the exact names \u003Ccode\u003EIdentifier\u003C/code\u003E and \u003Ccode\u003ESecret\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ECredential values should not be bound to Blazor component state solely for submission. This is particularly important in Blazor Server, where interactive component bindings can transmit credential values over the SignalR connection.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant: Do Not Disable Credential Fields During Submission\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EHTML inputs marked as \u003Ccode\u003Edisabled\u003C/code\u003E are excluded from \u003Ccode\u003EFormData\u003C/code\u003E and native form submissions.\u003C/p\u003E\n\u003Cp\u003EDo not set \u003Ccode\u003EDisabled=\u0026quot;@isSubmitting\u0026quot;\u003C/code\u003E on the username or password fields. A login preview may succeed, but the subsequent native commit can fail because the credential fields are missing from the submitted form.\u003C/p\u003E\n\u003Cp\u003EInstead, disable the submit button while leaving the credential inputs enabled.\u003C/p\u003E\n\u003Cp\u003EIf you need to prevent editing during submission, consider \u003Ccode\u003Ereadonly\u003C/code\u003E where supported, while accounting for component behavior, browser autofill, and accessibility.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ECustom Form Requirements\u003C/strong\u003E\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUse \u003Ccode\u003Ename=\u0026quot;Identifier\u0026quot;\u003C/code\u003E and \u003Ccode\u003Ename=\u0026quot;Secret\u0026quot;\u003C/code\u003E exactly.\u003C/li\u003E\n\u003Cli\u003EKeep both credential inputs enabled throughout submission.\u003C/li\u003E\n\u003Cli\u003EAvoid duplicate inputs with these names.\u003C/li\u003E\n\u003Cli\u003EUse \u003Ccode\u003Etype=\u0026quot;password\u0026quot;\u003C/code\u003E and \u003Ccode\u003Eautocomplete=\u0026quot;current-password\u0026quot;\u003C/code\u003E for password fields.\u003C/li\u003E\n\u003Cli\u003EKeep custom submit controls inside \u003Ccode\u003EUAuthLoginForm\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EUse \u003Ccode\u003EOnSubmittingChanged\u003C/code\u003E to manage loading indicators and submit-button state.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E handles the try and commit orchestration. The native commit includes the browser form fields, device information, and antiforgery data required by the configured authentication flow.\u003C/p\u003E\n\u003Cp\u003EThe component automatically uses \u003Ccode\u003ETryAndCommit\u003C/code\u003E as its default submission mode.\u003C/p\u003E\n\u003Cp\u003EWhen submitted, it:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EValidates that the identifier and secret are present.\u003C/li\u003E\n\u003Cli\u003ECreates the appropriate authentication request.\u003C/li\u003E\n\u003Cli\u003ECalls the UltimateAuth client flow.\u003C/li\u003E\n\u003Cli\u003EPerforms the configured try/commit behavior.\u003C/li\u003E\n\u003Cli\u003EExposes structured results through \u003Ccode\u003EOnTryResult\u003C/code\u003E.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe example assumes that UltimateAuth\u0027s Blazor services have already been registered and that the hosting environment permits the selected login flow.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022customizing-the-login-ui\u0022\u003ECustomizing the Login UI\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E uses \u003Ccode\u003EChildContent\u003C/code\u003E, allowing developers to use standard HTML elements or their preferred Blazor component library.\u003C/p\u003E\n\u003Cp\u003EFor example, with MudBlazor:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client.Blazor\n@using CodeBeam.UltimateAuth.Client\n\n\u0026lt;UAuthLoginForm SubmitMode=\u0026quot;UAuthSubmitMode.TryAndCommit\u0026quot; OnTryResult=\u0026quot;HandleLoginResult\u0026quot; OnSubmittingChanged=\u0026quot;HandleSubmittingChanged\u0026quot;\u0026gt;\n \u0026lt;MudStack Spacing=\u0026quot;3\u0026quot;\u0026gt;\n \u0026lt;MudText Typo=\u0026quot;Typo.h5\u0026quot;\u0026gt;Sign in\u0026lt;/MudText\u0026gt;\n \u0026lt;MudTextField name=\u0026quot;Identifier\u0026quot; Label=\u0026quot;Email\u0026quot; Variant=\u0026quot;Variant.Outlined\u0026quot; /\u0026gt;\n \u0026lt;MudTextField name=\u0026quot;Secret\u0026quot; Label=\u0026quot;Password\u0026quot; InputType=\u0026quot;InputType.Password\u0026quot; Variant=\u0026quot;Variant.Outlined\u0026quot; /\u0026gt;\n\n \u0026lt;MudButton ButtonType=\u0026quot;ButtonType.Submit\u0026quot; Disabled=\u0026quot;@_isSubmitting\u0026quot; Variant=\u0026quot;Variant.Filled\u0026quot; Color=\u0026quot;Color.Primary\u0026quot; FullWidth\u0026gt;Sign in\u0026lt;/MudButton\u0026gt;\n\n @if (!string.IsNullOrWhiteSpace(error))\n {\n \u0026lt;MudAlert Severity=\u0026quot;Severity.Error\u0026quot;\u0026gt;@error\u0026lt;/MudAlert\u0026gt;\n }\n \u0026lt;/MudStack\u0026gt;\n\u0026lt;/UAuthLoginForm\u0026gt;\n\n@code {\n private bool _isSubmitting;\n private string? error;\n\n private Task HandleSubmittingChanged(bool submitting)\n {\n _isSubmitting = submitting;\n return Task.CompletedTask;\n }\n\n private Task HandleLoginResult(IUAuthTryResult result)\n {\n error = result.IsSuccess ? null : \u0026quot;Unable to sign in.\u0026quot;;\n return Task.CompletedTask;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe form controls remain under your control.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth does not require a particular visual design, CSS framework, or component library.\u003C/p\u003E\n\u003Cp\u003EBecause the component renders an HTML form internally, place the submit button inside its child content and avoid nesting another HTML form within it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022submission-modes\u0022\u003ESubmission Modes\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003ESubmitMode\u003C/code\u003E to control how the authentication attempt is executed.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm SubmitMode=\u0026quot;UAuthSubmitMode.TryAndCommit\u0026quot;\u0026gt;\n ...\n\u0026lt;/UAuthLoginForm\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EMode\u003C/th\u003E\n\u003Cth\u003EBehavior\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryOnly\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EValidates the authentication attempt without committing login\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryAndCommit\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAttempts authentication and commits on success\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDirectCommit\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESubmits directly to the commit flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ETryAndCommit\u003C/code\u003E is the default.\u003C/p\u003E\n\u003Cp\u003EFor interactive login forms that need validation feedback, it is generally the recommended choice.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EDirectCommit\u003C/code\u003E does not invoke \u003Ccode\u003EOnTryResult\u003C/code\u003E, because it does not perform the structured try operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-authentication-results\u0022\u003EHandling Authentication Results\u003C/h3\u003E\n\u003Cp\u003EThe component exposes an \u003Ccode\u003EOnTryResult\u003C/code\u003E callback:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm OnTryResult=\u0026quot;HandleResult\u0026quot;\u0026gt;\n ...\n\u0026lt;/UAuthLoginForm\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe callback receives \u003Ccode\u003EIUAuthTryResult\u003C/code\u003E, allowing the same component to report password login and PKCE completion results.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eprivate Task HandleResult(IUAuthTryResult result)\n{\n if (!result.IsSuccess)\n {\n errorMessage = \u0026quot;Authentication failed.\u0026quot;;\n }\n\n return Task.CompletedTask;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor flow-specific details, inspect the concrete result type:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eprivate Task HandleResult(IUAuthTryResult result)\n{\n if (result is TryLoginResult loginResult)\n {\n if (loginResult.LockoutUntilUtc is not null)\n {\n errorMessage = \u0026quot;Your account is temporarily locked.\u0026quot;;\n }\n else if (loginResult.RequiresMfa)\n {\n // Continue the supported additional authentication workflow.\n }\n else if (!loginResult.IsSuccess)\n {\n errorMessage = \u0026quot;Unable to sign in.\u0026quot;;\n }\n }\n else if (result is TryPkceLoginResult pkceResult)\n {\n if (pkceResult.RetryWithNewPkce)\n {\n errorMessage = \u0026quot;Please restart the authentication flow.\u0026quot;;\n }\n else if (!pkceResult.IsSuccess)\n {\n errorMessage = \u0026quot;Unable to complete authentication.\u0026quot;;\n }\n }\n\n return Task.CompletedTask;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe callback reports the structured try result. It does not replace authentication state management or server-side validation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-uauthloginform-with-pkce\u0022\u003EUsing UAuthLoginForm with PKCE\u003C/h3\u003E\n\u003Cp\u003EThe same component also supports PKCE login completion when hosted within a supported UAuthHub environment.\u003C/p\u003E\n\u003Cp\u003ESet \u003Ccode\u003ELoginType\u003C/code\u003E to \u003Ccode\u003EPkce\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm LoginType=\u0026quot;UAuthLoginType.Pkce\u0026quot; SubmitMode=\u0026quot;UAuthSubmitMode.TryAndCommit\u0026quot; OnTryResult=\u0026quot;HandleLoginResult\u0026quot;\u0026gt;\n \u0026lt;input name=\u0026quot;Identifier\u0026quot; placeholder=\u0026quot;Email\u0026quot; /\u0026gt;\n \u0026lt;input name=\u0026quot;Secret\u0026quot; type=\u0026quot;password\u0026quot; placeholder=\u0026quot;Password\u0026quot; /\u0026gt;\n \u0026lt;button type=\u0026quot;submit\u0026quot;\u0026gt;Continue\u0026lt;/button\u0026gt;\n\u0026lt;/UAuthLoginForm\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn PKCE mode, the component:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EResolves the relevant UAuthHub session context.\u003C/li\u003E\n\u003Cli\u003ERetrieves the authorization code and code verifier through the configured Hub services.\u003C/li\u003E\n\u003Cli\u003EBuilds a \u003Ccode\u003EPkceCompleteRequest\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EInvokes the corresponding PKCE completion flow.\u003C/li\u003E\n\u003Cli\u003EReports the structured result when a try-enabled submission mode is used.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe application does not need to manually construct the PKCE completion request for this form.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E PKCE mode requires a supported server-side UAuthHub environment. \u003Ccode\u003EUAuthLoginForm\u003C/code\u003E checks the available Hub capabilities and throws when PKCE login is requested in an unsupported client profile.\u003C/p\u003E\n\u003Cp\u003EA Blazor WebAssembly application should initiate PKCE using \u003Ccode\u003EBeginPkceAsync\u003C/code\u003E; the Hub-hosted login form handles the completion stage.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022return-url-handling\u0022\u003EReturn URL Handling\u003C/h3\u003E\n\u003Cp\u003EYou can provide an explicit return URL:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm ReturnUrl=\u0026quot;/dashboard\u0026quot;\u0026gt;\n ...\n\u0026lt;/UAuthLoginForm\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe component resolves the effective return URL using the following precedence:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EExplicit \u003Ccode\u003EReturnUrl\u003C/code\u003E parameter.\u003C/li\u003E\n\u003Cli\u003EUAuthHub flow return URL when using PKCE.\u003C/li\u003E\n\u003Cli\u003ECurrent navigation URL.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThis allows a shared login component to participate in different authentication journeys without hardcoding navigation logic into the form.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-login-endpoints\u0022\u003ECustom Login Endpoints\u003C/h3\u003E\n\u003Cp\u003EFor advanced integrations, the \u003Ccode\u003EEndpoint\u003C/code\u003E parameter allows the form\u0027s HTML action endpoint to be overridden:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm Endpoint=\u0026quot;/custom-login\u0026quot; SubmitMode=\u0026quot;UAuthSubmitMode.DirectCommit\u0026quot;\u0026gt;\n ...\n\u0026lt;/UAuthLoginForm\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe endpoint is resolved relative to the configured UltimateAuth endpoint base path and multi-tenant settings.\u003C/p\u003E\n\u003Cp\u003ENote that \u003Ccode\u003EEndpoint\u003C/code\u003E affects the form\u0027s HTML action. Try-based submissions invoke the configured \u003Ccode\u003EUAuthClient.Flows\u003C/code\u003E endpoints, so changing \u003Ccode\u003EEndpoint\u003C/code\u003E alone does not redirect those SDK requests to a custom endpoint.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022submit-the-form-programmatically\u0022\u003ESubmit the Form Programmatically\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E exposes \u003Ccode\u003ESubmitAsync\u003C/code\u003E, allowing submission through a component reference.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm @ref=\u0026quot;loginForm\u0026quot; AllowEnterKeyToSubmit=\u0026quot;false\u0026quot;\u0026gt;\n \u0026lt;input name=\u0026quot;Identifier\u0026quot; placeholder=\u0026quot;Email\u0026quot; /\u0026gt;\n \u0026lt;input name=\u0026quot;Secret\u0026quot; type=\u0026quot;password\u0026quot; /\u0026gt;\n\u0026lt;/UAuthLoginForm\u0026gt;\n\n\u0026lt;button type=\u0026quot;button\u0026quot; @onclick=\u0026quot;SubmitLoginAsync\u0026quot;\u0026gt;\n Sign in\n\u0026lt;/button\u0026gt;\n\n@code {\n private UAuthLoginForm? loginForm;\n\n private async Task SubmitLoginAsync()\n {\n if (loginForm is not null)\n {\n await loginForm.SubmitAsync();\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ESubmitAsync\u003C/code\u003E must be called after the component has rendered.\u003C/p\u003E\n\u003Cp\u003EThis is useful when submission is initiated from a custom button, dialog, or application-specific interaction.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022controlling-enter-key-submission\u0022\u003EControlling Enter-Key Submission\u003C/h3\u003E\n\u003Cp\u003EBy default, the component supports Enter-key submission.\u003C/p\u003E\n\u003Cp\u003EYou can disable its built-in hidden submit button:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm AllowEnterKeyToSubmit=\u0026quot;false\u0026quot;\u0026gt;\n ...\n\u0026lt;/UAuthLoginForm\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis option controls the component\u0027s built-in Enter-key submit mechanism. Other submit buttons inside the form may still trigger normal browser form submission behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reloading-pkce-context\u0022\u003EReloading PKCE Context\u003C/h3\u003E\n\u003Cp\u003EThe component exposes \u003Ccode\u003EReloadAsync\u003C/code\u003E to reload its Hub credentials and flow state.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthLoginForm @ref=\u0026quot;loginForm\u0026quot; LoginType=\u0026quot;UAuthLoginType.Pkce\u0026quot;\u0026gt;\n ...\n\u0026lt;/UAuthLoginForm\u0026gt;\n\n@code {\n private UAuthLoginForm? loginForm;\n\n private async Task ReloadLoginContextAsync()\n {\n if (loginForm is not null)\n {\n await loginForm.ReloadAsync();\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is an advanced capability intended for scenarios where the associated Hub state changes and the component must refresh its internal context.\u003C/p\u003E\n\u003Cp\u003EIt does not initiate a new PKCE authorization flow by itself.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022component-parameters\u0022\u003EComponent Parameters\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EParameter\u003C/th\u003E\n\u003Cth\u003EType\u003C/th\u003E\n\u003Cth\u003EDefault\u003C/th\u003E\n\u003Cth\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELoginType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthLoginType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPassword\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPassword or PKCE login\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESubmitMode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthSubmitMode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryAndCommit\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESubmission strategy\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReturnUrl\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERedirect destination\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEndpoint\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECustom HTML form endpoint\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHubSessionId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHubSessionId?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExplicit Hub session context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAllowEnterKeyToSubmit\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ebool\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBuilt-in Enter-key submit support\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnTryResult\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EEventCallback\u0026lt;IUAuthTryResult\u0026gt;\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u2014\u003C/td\u003E\n\u003Ctd\u003EStructured try result callback\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnSubmittingChanged\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EEventCallback\u0026lt;bool\u0026gt;\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u2014\u003C/td\u003E\n\u003Ctd\u003ESubmission state change callback\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EChildContent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERenderFragment?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u2014\u003C/td\u003E\n\u003Ctd\u003ECustom form content\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-to-use-uauthloginform\u0022\u003EWhen to Use UAuthLoginForm\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EUAuthLoginForm\u003C/code\u003E when building a Blazor login interface that should integrate directly with UltimateAuth\u0027s supported authentication flows.\u003C/p\u003E\n\u003Cp\u003EIt is particularly useful when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou want to use your own component library without implementing the authentication submission logic.\u003C/li\u003E\n\u003Cli\u003EYou need structured login validation feedback.\u003C/li\u003E\n\u003Cli\u003EYou want to switch between try-only and commit behavior.\u003C/li\u003E\n\u003Cli\u003EYou are building a UAuthHub login page that completes PKCE.\u003C/li\u003E\n\u003Cli\u003EYou want to share login UI across supported Blazor hosting environments.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor application-specific workflows that cannot be expressed through the component, use \u003Ccode\u003EUAuthClient.Flows\u003C/code\u003E directly.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe component handles the login workflow, while your application retains control over presentation, user experience, and surrounding application logic.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-integration-mistakes\u0022\u003E18. Common Integration Mistakes\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022calling-direct-login-from-a-public-client\u0022\u003ECalling Direct Login from a Public Client\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EProblem:\u003C/strong\u003E \u003Ccode\u003ELoginAsync\u003C/code\u003E throws because direct credential posting is disabled.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESolution:\u003C/strong\u003E Use \u003Ccode\u003EBeginPkceAsync\u003C/code\u003E and the supported PKCE completion flow. Do not enable \u003Ccode\u003EAllowCredentialPost\u003C/code\u003E to bypass the public-client restriction.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022treating-tryonly-as-authentication\u0022\u003ETreating TryOnly as Authentication\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EProblem:\u003C/strong\u003E The application receives \u003Ccode\u003EIsSuccess = true\u003C/code\u003E from \u003Ccode\u003ETryOnly\u003C/code\u003E and assumes the user is logged in.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESolution:\u003C/strong\u003E A successful try result does not commit authentication. Use \u003Ccode\u003ETryAndCommit\u003C/code\u003E or the appropriate commit flow.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-directcommit-skips-server-validation\u0022\u003EAssuming DirectCommit Skips Server Validation\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EProblem:\u003C/strong\u003E DirectCommit is interpreted as a faster but less secure authentication mode.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESolution:\u003C/strong\u003E DirectCommit skips the separate client try stage. The server still validates the actual authentication request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-every-refresh-rotates-tokens\u0022\u003EAssuming Every Refresh Rotates Tokens\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EProblem:\u003C/strong\u003E The application requires \u003Ccode\u003ERefreshOutcome.Rotated\u003C/code\u003E for refresh success.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESolution:\u003C/strong\u003E Check \u003Ccode\u003ERefreshResult.IsSuccess\u003C/code\u003E. Rotation, touch, and no-op behavior depend on the authentication mode and refresh strategy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-a-session-id-for-device-logout\u0022\u003EUsing a Session ID for Device Logout\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EProblem:\u003C/strong\u003E A session identifier is passed to a device logout operation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESolution:\u003C/strong\u003E \u003Ccode\u003ELogoutMyDeviceAsync\u003C/code\u003E requires \u003Ccode\u003ELogoutDeviceRequest\u003C/code\u003E with a \u003Ccode\u003ESessionChainId\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-logout-immediately-invalidates-all-jwts\u0022\u003EAssuming Logout Immediately Invalidates All JWTs\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EProblem:\u003C/strong\u003E An application expects every issued JWT access token to become unusable immediately after server-side logout.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESolution:\u003C/strong\u003E Understand the enforcement behavior of SemiHybrid and PureJwt. Local JWT validation may continue accepting previously issued tokens until expiration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-validateasync-on-every-render\u0022\u003EUsing ValidateAsync on Every Render\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EProblem:\u003C/strong\u003E Components repeatedly make validation requests just to display authenticated UI.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESolution:\u003C/strong\u003E Use the appropriate authentication state integration for UI rendering, and reserve explicit validation for operations that require it.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client-sdk/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Inspect and manage session and device information.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E \u2014 Understand token validity, refresh behavior, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/refresh-rotation\u0022\u003ERefresh Rotation \u0026amp; Replay Protection\u003C/a\u003E \u2014 Explore refresh concurrency and reuse security.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Compare authentication behavior across modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E \u2014 Understand device-associated session chains.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "accessing-the-authentication-client", @@ -228,6 +228,11 @@ "Text": "Basic Usage", "Level": 1 }, + { + "Id": "native-credential-submission", + "Text": "Native Credential Submission", + "Level": 1 + }, { "Id": "customizing-the-login-ui", "Text": "Customizing the Login UI", @@ -329,6 +334,6 @@ "Level": 0 } ], - "SourceHash": "2A94010322C7A85EC49E84E84F1E73737F414AE2A5FCCED1BAED1E3C3FD11729", + "SourceHash": "6F1CE929DE0E49957E2BF9BA2B7CD4397971D3A91CFB941977BE600C09786639", "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json index 16eb652d..04230bc9 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json @@ -1,7 +1,7 @@ { "Slug": "getting-started/quickstart", "Title": "QuickStart", - "Html": "\n\u003Cp\u003E\u003Cstrong\u003EYour first UltimateAuth application, in just a few steps.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis guide walks you through creating a Blazor Server application, adding UltimateAuth with in-memory storage, and signing in with a sample account.\u003C/p\u003E\n\u003Cp\u003EYou\u0027ll start with a minimal setup. No database configuration, migrations, or custom authentication infrastructure required.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022prerequisites\u0022\u003EPrerequisites\u003C/h2\u003E\n\u003Cp\u003EYou\u0027ll need:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E.NET SDK (8 and higher, latest recommended)\u003C/li\u003E\n\u003Cli\u003EA code editor such as Visual Studio, VS Code, or Rider\u003C/li\u003E\n\u003Cli\u003EBasic familiarity with Blazor\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis guide uses \u003Cstrong\u003EBlazor Web App with Interactive Server rendering\u003C/strong\u003E and UltimateAuth\u0027s in-memory persistence bundle.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-blazor-application\u0022\u003E1. Create a Blazor Application\u003C/h2\u003E\n\u003Cp\u003EOpen a terminal and create a new Blazor Interactive Server project:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet new blazor -n UltimateAuthDemo -int Server -ai\ncd UltimateAuthDemo\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis creates a Blazor Web App with Interactive Server components.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-ultimateauth\u0022\u003E2. Install UltimateAuth\u003C/h2\u003E\n\u003Cp\u003EInstall the Blazor client adapter and the in-memory bundle:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.InMemory.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor this tutorial, also install the sample seed package:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Sample.Seed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe packages serve different purposes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPackage\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EClient.Blazor\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBlazor components, authentication state, and client integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EInMemory.Bundle\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIn-memory implementations for authentication domains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESample.Seed\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDevelopment accounts and sample authorization data\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe in-memory bundle includes the relevant persistence implementations, so you do not need to register each store individually.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-ultimateauth-services\u0022\u003E3. Configure UltimateAuth Services\u003C/h2\u003E\n\u003Cp\u003EOpen \u003Ccode\u003EProgram.cs\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EAdd the following namespaces:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.Blazor;\nusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.InMemory;\nusing CodeBeam.UltimateAuth.Sample.Seed.Extensions;\nusing CodeBeam.UltimateAuth.Server.Extensions;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter creating the builder, register UltimateAuth:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthInMemory();\n\nbuilder.Services.AddUltimateAuthClientBlazor();\n\nbuilder.Services.AddUltimateAuthSampleSeed();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThat\u0027s the core service configuration.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E registers the authentication server.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthInMemory()\u003C/code\u003E provides in-memory stores.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthClientBlazor()\u003C/code\u003E registers the Blazor client integration.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthSampleSeed()\u003C/code\u003E prepares the development seed services.\u003C/p\u003E\n\u003Cp\u003EYou don\u0027t need to call \u003Ccode\u003EAddUltimateAuth()\u003C/code\u003E or \u003Ccode\u003EAddUltimateAuthClient()\u003C/code\u003E separately.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-http-pipeline\u0022\u003E4. Configure the HTTP Pipeline\u003C/h2\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\u003C/code\u003E, configure the application pipeline:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar app = builder.Build();\n\nif (!app.Environment.IsDevelopment())\n{\n app.UseExceptionHandler(\u0026quot;/Error\u0026quot;, createScopeForErrors: true);\n app.UseHsts();\n}\n\napp.UseHttpsRedirection();\napp.UseStaticFiles();\n\napp.UseUltimateAuthWithAspNetCore();\napp.UseAntiforgery();\napp.MapUltimateAuthEndpoints();\n\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveServerRenderMode()\n .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient());\n\nif (app.Environment.IsDevelopment())\n{\n await app.SeedUltimateAuthAsync();\n}\n\napp.Run();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EKeep the \u003Ccode\u003EAddRazorComponents().AddInteractiveServerComponents()\u003C/code\u003E registration generated by the Blazor project template.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EUseUltimateAuthWithAspNetCore()\u003C/code\u003E integrates UltimateAuth\u0027s middleware with ASP.NET Core authentication and authorization.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EMapUltimateAuthEndpoints()\u003C/code\u003E registers the authentication endpoints.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthRoutes()\u003C/code\u003E includes UltimateAuth\u0027s Blazor client route assemblies in endpoint discovery.\u003C/p\u003E\n\u003Cp\u003EThe development-only seed call creates the sample users before the application begins accepting requests.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-the-ultimateauth-script\u0022\u003E5. Add the UltimateAuth Script\u003C/h2\u003E\n\u003Cp\u003EOpen \u003Ccode\u003EComponents/App.razor\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EKeep the existing Blazor script and add UltimateAuth\u0027s JavaScript file:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EHTML\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-html\u0022\u003E\u0026lt;script src=\u0026quot;_content/CodeBeam.UltimateAuth.Client.Blazor/uauth.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EPlace these near the end of the document body.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s Blazor integration uses this script for browser-side functionality.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-usings\u0022\u003E6. Add Usings\u003C/h2\u003E\n\u003Cp\u003EAdd this in \u003Ccode\u003E_Imports.razor\u003C/code\u003E\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@using CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022initialize-the-ultimateauth-application\u0022\u003E7. Initialize the UltimateAuth Application\u003C/h2\u003E\n\u003Cp\u003EOpen \u003Ccode\u003EComponents/Routes.razor\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EReplace its contents with:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthApp UseBuiltInRouter=\u0026quot;true\u0026quot; AppAssembly=\u0026quot;typeof(Program).Assembly\u0026quot; DefaultLayout=\u0026quot;typeof(Layout.MainLayout)\u0026quot; /\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthApp\u003C/code\u003E is the root integration component for UltimateAuth in Blazor.\u003C/p\u003E\n\u003Cp\u003EIt initializes the client runtime, provides authentication state to child components, and coordinates the authenticated session lifecycle.\u003C/p\u003E\n\u003Cp\u003EWith \u003Ccode\u003EUseBuiltInRouter=\u0026quot;true\u0026quot;\u003C/code\u003E, it also provides the application\u0027s router.\u003C/p\u003E\n\u003Cp\u003EYou don\u0027t need to create a separate \u003Ccode\u003ERouter\u003C/code\u003E or \u003Ccode\u003ECascadingAuthenticationState\u003C/code\u003E component for this setup.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-your-login-page\u0022\u003E8. Create Your Login Page\u003C/h2\u003E\n\u003Cp\u003EChange \u003Ccode\u003EComponents/Pages/Home.razor\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@page \u0026quot;/\u0026quot;\n@page \u0026quot;/login\u0026quot;\n@attribute [UAuthLoginPage]\n@inherits UAuthPageBase\n\n\u0026lt;div class=\u0026quot;container mt-5\u0026quot; style=\u0026quot;max-width: 400px;\u0026quot;\u0026gt;\n \u0026lt;h3\u0026gt;Sign in\u0026lt;/h3\u0026gt;\n \u0026lt;UAuthLoginForm Identifier=\u0026quot;@_identifier\u0026quot; Secret=\u0026quot;@_password\u0026quot; ReturnUrl=\u0026quot;/dashboard\u0026quot;\u0026gt;\n \u0026lt;div class=\u0026quot;d-block mb-3\u0026quot;\u0026gt;\n \u0026lt;label\u0026gt;Username\u0026lt;/label\u0026gt;\n \u0026lt;input @bind=\u0026quot;_identifier\u0026quot; class=\u0026quot;form-control w-100\u0026quot; autocomplete=\u0026quot;username\u0026quot; /\u0026gt;\n \u0026lt;/div\u0026gt;\n\n \u0026lt;div class=\u0026quot;d-block mb-3\u0026quot;\u0026gt;\n \u0026lt;label\u0026gt;Password\u0026lt;/label\u0026gt;\n \u0026lt;input type=\u0026quot;password\u0026quot; @bind=\u0026quot;_password\u0026quot; class=\u0026quot;form-control w-100\u0026quot; autocomplete=\u0026quot;current-password\u0026quot; /\u0026gt;\n \u0026lt;/div\u0026gt;\n\n \u0026lt;button class=\u0026quot;btn btn-primary w-100\u0026quot; type=\u0026quot;submit\u0026quot;\u0026gt;Sign in\u0026lt;/button\u0026gt;\n \u0026lt;/UAuthLoginForm\u0026gt;\n\u0026lt;/div\u0026gt;\n\n@code {\n private string _identifier = \u0026quot;\u0026quot;;\n private string _password = \u0026quot;\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E handles the login submission through UltimateAuth\u0027s client flow.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003E[UAuthLoginPage]\u003C/code\u003E identifies this component as the application\u0027s login page.\u003C/p\u003E\n\u003Cp\u003EOnly designate one login page for this purpose.\u003C/p\u003E\n\u003Cp\u003EThe form above is intentionally minimal. You can replace the inputs with your preferred UI components later.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-protected-page\u0022\u003E9. Add a Protected Page\u003C/h2\u003E\n\u003Cp\u003ECreate \u003Ccode\u003EComponents/Pages/Dashboard.razor\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@page \u0026quot;/dashboard\u0026quot;\n@attribute [UAuthAuthorize]\n@inherits UAuthPageBase\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Dashboard\u0026lt;/h3\u0026gt;\n\n\u0026lt;p\u0026gt;You have reached an authenticated page.\u0026lt;/p\u0026gt;\n\u0026lt;UAuthStateView Roles=\u0026quot;Admin\u0026quot;\u0026gt;You have admin privileges.\u0026lt;/UAuthStateView\u0026gt;\n\u0026lt;p\u0026gt;User: @AuthState?.Identity?.DisplayName\u0026lt;/p\u0026gt;\n\n\u0026lt;button @onclick=\u0026quot;Logout\u0026quot; class=\u0026quot;btn btn-primary\u0026quot;\u0026gt;Logout\u0026lt;/button\u0026gt;\n\n@code {\n private async Task Logout()\n {\n await UAuthClient.Flows.LogoutAsync();\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E declares an UltimateAuth authorization requirement for the page.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E Client-side route protection is not a substitute for server-side authorization. Protected APIs and resources must enforce authorization on the server.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022run-the-application\u0022\u003E10. Run the Application\u003C/h2\u003E\n\u003Cp\u003EStart the application:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet run\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOpen the HTTPS address displayed in your terminal, then navigate to:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E/login\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe sample seed package creates two development accounts:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EUsername\u003C/th\u003E\n\u003Cth\u003EPassword\u003C/th\u003E\n\u003Cth\u003ERole\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eadmin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eadmin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAdministrator\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Euser\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Euser\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EStandard user\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ESign in with:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EUsername: admin\nPassword: admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter successful authentication, navigate to:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E/dashboard\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EYou now have a Blazor application using UltimateAuth authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022your-first-ultimateauth-application-is-running\u0022\u003E\uD83C\uDF89 Your First UltimateAuth Application Is Running\u003C/h2\u003E\n\u003Cp\u003EWith this setup, you have:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn authentication server integrated into ASP.NET Core.\u003C/li\u003E\n\u003Cli\u003EIn-memory user, credential, session, token, and authorization storage.\u003C/li\u003E\n\u003Cli\u003EA Blazor authentication client.\u003C/li\u003E\n\u003Cli\u003EA working login form.\u003C/li\u003E\n\u003Cli\u003EAuthentication state initialization.\u003C/li\u003E\n\u003Cli\u003ESession lifecycle coordination.\u003C/li\u003E\n\u003Cli\u003ESample users and authorization data.\u003C/li\u003E\n\u003Cli\u003EA page declaring an authentication requirement.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EYou did not need to create your own password hashing, token issuance, session validation, or authentication middleware implementations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-happened-when-you-logged-in\u0022\u003EWhat Happened When You Logged In?\u003C/h2\u003E\n\u003Cp\u003EFor this Blazor Server scenario, UltimateAuth uses the applicable session-oriented authentication model.\u003C/p\u003E\n\u003Cp\u003EThe login flow validates the credentials and establishes authentication state on the server.\u003C/p\u003E\n\u003Cp\u003EWhere a new session is created, it participates in UltimateAuth\u0027s root, chain, and session lifecycle.\u003C/p\u003E\n\u003Cp\u003EThe Blazor client then coordinates authentication state for the application.\u003C/p\u003E\n\u003Cp\u003EYou can explore these operations in detail later through the Authentication Flows and Session Management documentation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-in-memory-storage\u0022\u003EImportant: In-Memory Storage\u003C/h2\u003E\n\u003Cp\u003EThis QuickStart intentionally uses in-memory persistence.\u003C/p\u003E\n\u003Cp\u003EIt is ideal for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELearning UltimateAuth.\u003C/li\u003E\n\u003Cli\u003ELocal development.\u003C/li\u003E\n\u003Cli\u003ETesting authentication flows.\u003C/li\u003E\n\u003Cli\u003EBuilding prototypes.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\u003Cstrong\u003EIn-memory authentication data is not durable.\u003C/strong\u003E Users, credentials, sessions, and other stored state may be lost when the application restarts.\u003C/p\u003E\n\u003Cp\u003EThe sample credentials are also for development only. Do not deploy the seeded accounts or their example passwords in a production application.\u003C/p\u003E\n\u003Cp\u003EFor production use, configure durable persistence, appropriate secrets and signing keys, HTTPS, and the security settings required by your deployment.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/getting-started/ecosystem\u0022\u003EEcosystem\u003C/a\u003E\u003C/strong\u003E \u2014 Explore packages, integrations, and supported environments.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E\u003C/strong\u003E \u2014 Configure sessions, tokens, login policies, and security behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E\u003C/strong\u003E \u2014 Customize Blazor integration and authentication flows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/auth-flows/overview\u0022\u003EAuthentication Flows\u003C/a\u003E\u003C/strong\u003E \u2014 Understand login, refresh, logout, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/getting-started/real-world-setup#persistent-storage-with-entity-framework-core\u0022\u003EEntity Framework Core Setup\u003C/a\u003E\u003C/strong\u003E \u2014 Move from in-memory storage to persistent authentication data.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003E\u003Cstrong\u003EYour first UltimateAuth application, in just a few steps.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis guide walks you through creating a Blazor Server application, adding UltimateAuth with in-memory storage, and signing in with a sample account.\u003C/p\u003E\n\u003Cp\u003EYou\u0027ll start with a minimal setup. No database configuration, migrations, or custom authentication infrastructure required.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022prerequisites\u0022\u003EPrerequisites\u003C/h2\u003E\n\u003Cp\u003EYou\u0027ll need:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E.NET SDK (8 and higher, latest recommended)\u003C/li\u003E\n\u003Cli\u003EA code editor such as Visual Studio, VS Code, or Rider\u003C/li\u003E\n\u003Cli\u003EBasic familiarity with Blazor\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis guide uses \u003Cstrong\u003EBlazor Web App with Interactive Server rendering\u003C/strong\u003E and UltimateAuth\u0027s in-memory persistence bundle.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-blazor-application\u0022\u003E1. Create a Blazor Application\u003C/h2\u003E\n\u003Cp\u003EOpen a terminal and create a new Blazor Interactive Server project:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet new blazor -n UltimateAuthDemo -int Server -ai\ncd UltimateAuthDemo\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis creates a Blazor Web App with Interactive Server components.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-ultimateauth\u0022\u003E2. Install UltimateAuth\u003C/h2\u003E\n\u003Cp\u003EInstall the Blazor client adapter and the in-memory bundle:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.InMemory.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor this tutorial, also install the sample seed package:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Sample.Seed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe packages serve different purposes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPackage\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EClient.Blazor\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBlazor components, authentication state, and client integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EInMemory.Bundle\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIn-memory implementations for authentication domains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESample.Seed\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDevelopment accounts and sample authorization data\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe in-memory bundle includes the relevant persistence implementations, so you do not need to register each store individually.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-ultimateauth-services\u0022\u003E3. Configure UltimateAuth Services\u003C/h2\u003E\n\u003Cp\u003EOpen \u003Ccode\u003EProgram.cs\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EAdd the following namespaces:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.Blazor;\nusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.InMemory;\nusing CodeBeam.UltimateAuth.Sample.Seed.Extensions;\nusing CodeBeam.UltimateAuth.Server.Extensions;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter creating the builder, register UltimateAuth:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthInMemory();\n\nbuilder.Services.AddUltimateAuthClientBlazor();\n\nbuilder.Services.AddUltimateAuthSampleSeed();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThat\u0027s the core service configuration.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E registers the authentication server.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthInMemory()\u003C/code\u003E provides in-memory stores.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthClientBlazor()\u003C/code\u003E registers the Blazor client integration.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthSampleSeed()\u003C/code\u003E prepares the development seed services.\u003C/p\u003E\n\u003Cp\u003EYou don\u0027t need to call \u003Ccode\u003EAddUltimateAuth()\u003C/code\u003E or \u003Ccode\u003EAddUltimateAuthClient()\u003C/code\u003E separately.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-http-pipeline\u0022\u003E4. Configure the HTTP Pipeline\u003C/h2\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\u003C/code\u003E, configure the application pipeline:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar app = builder.Build();\n\nif (!app.Environment.IsDevelopment())\n{\n app.UseExceptionHandler(\u0026quot;/Error\u0026quot;, createScopeForErrors: true);\n app.UseHsts();\n}\n\napp.UseHttpsRedirection();\napp.UseStaticFiles();\n\napp.UseUltimateAuthWithAspNetCore();\napp.UseAntiforgery();\napp.MapUltimateAuthEndpoints();\n\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveServerRenderMode()\n .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient());\n\nif (app.Environment.IsDevelopment())\n{\n await app.SeedUltimateAuthAsync();\n}\n\napp.Run();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EKeep the \u003Ccode\u003EAddRazorComponents().AddInteractiveServerComponents()\u003C/code\u003E registration generated by the Blazor project template.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EUseUltimateAuthWithAspNetCore()\u003C/code\u003E integrates UltimateAuth\u0027s middleware with ASP.NET Core authentication and authorization.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EMapUltimateAuthEndpoints()\u003C/code\u003E registers the authentication endpoints.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthRoutes()\u003C/code\u003E includes UltimateAuth\u0027s Blazor client route assemblies in endpoint discovery.\u003C/p\u003E\n\u003Cp\u003EThe development-only seed call creates the sample users before the application begins accepting requests.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-the-ultimateauth-script\u0022\u003E5. Add the UltimateAuth Script\u003C/h2\u003E\n\u003Cp\u003EOpen \u003Ccode\u003EComponents/App.razor\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EKeep the existing Blazor script and add UltimateAuth\u0027s JavaScript file:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EHTML\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-html\u0022\u003E\u0026lt;script src=\u0026quot;_content/CodeBeam.UltimateAuth.Client.Blazor/uauth.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EPlace these near the end of the document body.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s Blazor integration uses this script for browser-side functionality.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-usings\u0022\u003E6. Add Usings\u003C/h2\u003E\n\u003Cp\u003EAdd this in \u003Ccode\u003E_Imports.razor\u003C/code\u003E\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@using CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022initialize-the-ultimateauth-application\u0022\u003E7. Initialize the UltimateAuth Application\u003C/h2\u003E\n\u003Cp\u003EOpen \u003Ccode\u003EComponents/Routes.razor\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EReplace its contents with:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthApp UseBuiltInRouter=\u0026quot;true\u0026quot; AppAssembly=\u0026quot;typeof(Program).Assembly\u0026quot; DefaultLayout=\u0026quot;typeof(Layout.MainLayout)\u0026quot; /\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthApp\u003C/code\u003E is the root integration component for UltimateAuth in Blazor.\u003C/p\u003E\n\u003Cp\u003EIt initializes the client runtime, provides authentication state to child components, and coordinates the authenticated session lifecycle.\u003C/p\u003E\n\u003Cp\u003EWith \u003Ccode\u003EUseBuiltInRouter=\u0026quot;true\u0026quot;\u003C/code\u003E, it also provides the application\u0027s router.\u003C/p\u003E\n\u003Cp\u003EYou don\u0027t need to create a separate \u003Ccode\u003ERouter\u003C/code\u003E or \u003Ccode\u003ECascadingAuthenticationState\u003C/code\u003E component for this setup.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-your-login-page\u0022\u003E8. Create Your Login Page\u003C/h2\u003E\n\u003Cp\u003EChange \u003Ccode\u003EComponents/Pages/Home.razor\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@page \u0026quot;/\u0026quot;\n@page \u0026quot;/login\u0026quot;\n@attribute [UAuthLoginPage]\n@inherits UAuthPageBase\n\n\u0026lt;div class=\u0026quot;container mt-5\u0026quot; style=\u0026quot;max-width: 400px;\u0026quot;\u0026gt;\n \u0026lt;h3\u0026gt;Sign in\u0026lt;/h3\u0026gt;\n \u0026lt;UAuthLoginForm ReturnUrl=\u0026quot;/dashboard\u0026quot;\u0026gt;\n \u0026lt;div class=\u0026quot;d-block mb-3\u0026quot;\u0026gt;\n \u0026lt;label\u0026gt;Username\u0026lt;/label\u0026gt;\n \u0026lt;input name=\u0026quot;Identifier\u0026quot; class=\u0026quot;form-control w-100\u0026quot; autocomplete=\u0026quot;username\u0026quot; /\u0026gt;\n \u0026lt;/div\u0026gt;\n\n \u0026lt;div class=\u0026quot;d-block mb-3\u0026quot;\u0026gt;\n \u0026lt;label\u0026gt;Password\u0026lt;/label\u0026gt;\n \u0026lt;input type=\u0026quot;password\u0026quot; name=\u0026quot;Secret\u0026quot; class=\u0026quot;form-control w-100\u0026quot; autocomplete=\u0026quot;current-password\u0026quot; /\u0026gt;\n \u0026lt;/div\u0026gt;\n\n \u0026lt;button class=\u0026quot;btn btn-primary w-100\u0026quot; type=\u0026quot;submit\u0026quot;\u0026gt;Sign in\u0026lt;/button\u0026gt;\n \u0026lt;/UAuthLoginForm\u0026gt;\n\u0026lt;/div\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E handles the login submission through UltimateAuth\u0027s client flow.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003E[UAuthLoginPage]\u003C/code\u003E identifies this component as the application\u0027s login page.\u003C/p\u003E\n\u003Cp\u003EOnly designate one login page for this purpose.\u003C/p\u003E\n\u003Cp\u003EThe form above is intentionally minimal. You can replace the inputs with your preferred UI components later.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-protected-page\u0022\u003E9. Add a Protected Page\u003C/h2\u003E\n\u003Cp\u003ECreate \u003Ccode\u003EComponents/Pages/Dashboard.razor\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@page \u0026quot;/dashboard\u0026quot;\n@attribute [UAuthAuthorize]\n@inherits UAuthPageBase\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Dashboard\u0026lt;/h3\u0026gt;\n\n\u0026lt;p\u0026gt;You have reached an authenticated page.\u0026lt;/p\u0026gt;\n\u0026lt;UAuthStateView Roles=\u0026quot;Admin\u0026quot;\u0026gt;You have admin privileges.\u0026lt;/UAuthStateView\u0026gt;\n\u0026lt;p\u0026gt;User: @AuthState?.Identity?.DisplayName\u0026lt;/p\u0026gt;\n\n\u0026lt;button @onclick=\u0026quot;Logout\u0026quot; class=\u0026quot;btn btn-primary\u0026quot;\u0026gt;Logout\u0026lt;/button\u0026gt;\n\n@code {\n private async Task Logout()\n {\n await UAuthClient.Flows.LogoutAsync();\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E declares an UltimateAuth authorization requirement for the page.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E Client-side route protection is not a substitute for server-side authorization. Protected APIs and resources must enforce authorization on the server.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022run-the-application\u0022\u003E10. Run the Application\u003C/h2\u003E\n\u003Cp\u003EStart the application:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet run\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOpen the HTTPS address displayed in your terminal, then navigate to:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E/login\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe sample seed package creates two development accounts:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EUsername\u003C/th\u003E\n\u003Cth\u003EPassword\u003C/th\u003E\n\u003Cth\u003ERole\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eadmin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eadmin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAdministrator\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Euser\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Euser\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EStandard user\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ESign in with:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EUsername: admin\nPassword: admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter successful authentication, navigate to:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E/dashboard\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EYou now have a Blazor application using UltimateAuth authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022your-first-ultimateauth-application-is-running\u0022\u003E\uD83C\uDF89 Your First UltimateAuth Application Is Running\u003C/h2\u003E\n\u003Cp\u003EWith this setup, you have:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn authentication server integrated into ASP.NET Core.\u003C/li\u003E\n\u003Cli\u003EIn-memory user, credential, session, token, and authorization storage.\u003C/li\u003E\n\u003Cli\u003EA Blazor authentication client.\u003C/li\u003E\n\u003Cli\u003EA working login form.\u003C/li\u003E\n\u003Cli\u003EAuthentication state initialization.\u003C/li\u003E\n\u003Cli\u003ESession lifecycle coordination.\u003C/li\u003E\n\u003Cli\u003ESample users and authorization data.\u003C/li\u003E\n\u003Cli\u003EA page declaring an authentication requirement.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EYou did not need to create your own password hashing, token issuance, session validation, or authentication middleware implementations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-happened-when-you-logged-in\u0022\u003EWhat Happened When You Logged In?\u003C/h2\u003E\n\u003Cp\u003EFor this Blazor Server scenario, UltimateAuth uses the applicable session-oriented authentication model.\u003C/p\u003E\n\u003Cp\u003EThe login flow validates the credentials and establishes authentication state on the server.\u003C/p\u003E\n\u003Cp\u003EWhere a new session is created, it participates in UltimateAuth\u0027s root, chain, and session lifecycle.\u003C/p\u003E\n\u003Cp\u003EThe Blazor client then coordinates authentication state for the application.\u003C/p\u003E\n\u003Cp\u003EYou can explore these operations in detail later through the Authentication Flows and Session Management documentation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-in-memory-storage\u0022\u003EImportant: In-Memory Storage\u003C/h2\u003E\n\u003Cp\u003EThis QuickStart intentionally uses in-memory persistence.\u003C/p\u003E\n\u003Cp\u003EIt is ideal for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELearning UltimateAuth.\u003C/li\u003E\n\u003Cli\u003ELocal development.\u003C/li\u003E\n\u003Cli\u003ETesting authentication flows.\u003C/li\u003E\n\u003Cli\u003EBuilding prototypes.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\u003Cstrong\u003EIn-memory authentication data is not durable.\u003C/strong\u003E Users, credentials, sessions, and other stored state may be lost when the application restarts.\u003C/p\u003E\n\u003Cp\u003EThe sample credentials are also for development only. Do not deploy the seeded accounts or their example passwords in a production application.\u003C/p\u003E\n\u003Cp\u003EFor production use, configure durable persistence, appropriate secrets and signing keys, HTTPS, and the security settings required by your deployment.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/getting-started/ecosystem\u0022\u003EEcosystem\u003C/a\u003E\u003C/strong\u003E \u2014 Explore packages, integrations, and supported environments.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E\u003C/strong\u003E \u2014 Configure sessions, tokens, login policies, and security behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E\u003C/strong\u003E \u2014 Customize Blazor integration and authentication flows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/auth-flows/overview\u0022\u003EAuthentication Flows\u003C/a\u003E\u003C/strong\u003E \u2014 Understand login, refresh, logout, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/getting-started/real-world-setup#persistent-storage-with-entity-framework-core\u0022\u003EEntity Framework Core Setup\u003C/a\u003E\u003C/strong\u003E \u2014 Move from in-memory storage to persistent authentication data.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "prerequisites", @@ -79,6 +79,6 @@ "Level": 0 } ], - "SourceHash": "CB240CCA978FE45AD65EA09C882CE905FF7424CB6531541B1DEDAE55948572FE", + "SourceHash": "D8DF1C0A171AAE73FC58756D8AB78D2B340FC49EADAC831B4B78BAE16B8FD607", "BuilderVersion": "1" } \ No newline at end of file diff --git a/samples/CodeBeam.UltimateAuth.Sample.Shared.Hub/Components/Page/SampleHubHome.razor b/samples/CodeBeam.UltimateAuth.Sample.Shared.Hub/Components/Page/SampleHubHome.razor index da02651b..f2dcb777 100644 --- a/samples/CodeBeam.UltimateAuth.Sample.Shared.Hub/Components/Page/SampleHubHome.razor +++ b/samples/CodeBeam.UltimateAuth.Sample.Shared.Hub/Components/Page/SampleHubHome.razor @@ -38,7 +38,7 @@