From 81b988437cc87d201996d325a328dc770639677a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Tue, 3 Nov 2026 00:30:06 +0300 Subject: [PATCH 1/2] docs: Fix Broken Links --- docs/content/auth-flows/overview.md | 14 +++++++------- docs/content/client-sdk/authentication.md | 2 +- docs/content/client-sdk/authorization.md | 10 +++++----- docs/content/client-sdk/credentials.md | 4 +--- docs/content/client-sdk/identifiers.md | 8 ++++---- docs/content/client-sdk/session-management.md | 4 ++-- docs/content/client-sdk/user-management.md | 10 +++++----- docs/content/configuration/overview.md | 3 +-- docs/content/fundamentals/flow-based-auth.md | 2 +- docs/content/plugin-domains/credentials.md | 4 ++-- .../wwwroot/docs/auth-flows/overview.json | 4 ++-- .../wwwroot/docs/client-sdk/authentication.json | 4 ++-- .../wwwroot/docs/client-sdk/authorization.json | 4 ++-- .../wwwroot/docs/client-sdk/credentials.json | 4 ++-- .../wwwroot/docs/client-sdk/identifiers.json | 4 ++-- .../docs/client-sdk/session-management.json | 4 ++-- .../wwwroot/docs/client-sdk/user-management.json | 4 ++-- .../wwwroot/docs/configuration/overview.json | 4 ++-- .../wwwroot/docs/fundamentals/flow-based-auth.json | 4 ++-- .../wwwroot/docs/plugin-domains/credentials.json | 4 ++-- 20 files changed, 49 insertions(+), 52 deletions(-) diff --git a/docs/content/auth-flows/overview.md b/docs/content/auth-flows/overview.md index a3565f06..6f76f7cb 100644 --- a/docs/content/auth-flows/overview.md +++ b/docs/content/auth-flows/overview.md @@ -210,9 +210,9 @@ The following pages explain each operation in detail. This overview focuses on t | Flow | Purpose | Practical outcome | Guide | | --- | --- | --- | --- | -| **Login** | Establish authenticated access from acceptable credentials. | Creates a valid authentication session in session-issuing modes; may issue tokens according to policy. | [Login Flow](./login-flow.md) | -| **Refresh** | Continue an existing authentication relationship using renewal authority. | Maintains or renews access according to mode, validation, and configured refresh behavior. | [Refresh Flow](./refresh-flow.md) | -| **Logout** | End access at the applicable session or device scope. | Terminates authentication access and handles configured client cleanup/navigation. | [Logout Flow](./logout-flow.md) | +| **Login** | Establish authenticated access from acceptable credentials. | Creates a valid authentication session in session-issuing modes; may issue tokens according to policy. | [Login Flow](/docs/auth-flows/login-flow) | +| **Refresh** | Continue an existing authentication relationship using renewal authority. | Maintains or renews access according to mode, validation, and configured refresh behavior. | [Refresh Flow](/docs/auth-flows/refresh-flow) | +| **Logout** | End access at the applicable session or device scope. | Terminates authentication access and handles configured client cleanup/navigation. | [Logout Flow](/docs/auth-flows/logout-flow) | These flows are related but not interchangeable. Login establishes access; refresh continues it; logout terminates it. Revocation operations provide stronger invalidation controls for the relevant security scope. @@ -220,9 +220,9 @@ These flows are related but not interchangeable. Login establishes access; refre | Concept | What it contributes | Guide | | --- | --- | --- | -| **Session lifecycle** | Root, chain, and session relationships; creation, continuity, expiry, and invalidation. | [Session Lifecycle](./session-lifecycle.md) | -| **Token behavior** | Access and refresh credentials, validation, renewal, and mode-specific behavior. | [Token Behavior](./token-behavior.md) | -| **Device management** | Device-associated chains, multi-device access, and targeted termination. | [Device Management](./device-management.md) | +| **Session lifecycle** | Root, chain, and session relationships; creation, continuity, expiry, and invalidation. | [Session Lifecycle](/docs/sessions-devices/session-lifecycle) | +| **Token behavior** | Access and refresh credentials, validation, renewal, and mode-specific behavior. | [Token Behavior](/docs/security/token-security) | +| **Device management** | Device-associated chains, multi-device access, and targeted termination. | [Device Management](/docs/sessions-devices/device-management) | ## What this design enables in real applications @@ -246,4 +246,4 @@ This is the larger goal of UltimateAuth's unified model—not to make every auth ## Next step -Start with [Login Flow](/docs/auth-flows/login) to see how UltimateAuth turns validated credentials into controlled authenticated access. +Start with [Login Flow](/docs/auth-flows/login-flow) to see how UltimateAuth turns validated credentials into controlled authenticated access. diff --git a/docs/content/client-sdk/authentication.md b/docs/content/client-sdk/authentication.md index 88ab5fb8..5bbb830c 100644 --- a/docs/content/client-sdk/authentication.md +++ b/docs/content/client-sdk/authentication.md @@ -1263,7 +1263,7 @@ For application-specific workflows that cannot be expressed through the componen ## Next Steps -- [Session Management](/docs/client/session-management) — Inspect and manage session and device information. +- [Session Management](/docs/client-sdk/session-management) — Inspect and manage session and device information. - [Token Security](/docs/security/token-security) — Understand token validity, refresh behavior, and revocation. - [Refresh Rotation & Replay Protection](/docs/security/refresh-rotation) — Explore refresh concurrency and reuse security. - [Authentication Modes](/docs/fundamentals/auth-modes) — Compare authentication behavior across modes. diff --git a/docs/content/client-sdk/authorization.md b/docs/content/client-sdk/authorization.md index 05be2f25..fa74571f 100644 --- a/docs/content/client-sdk/authorization.md +++ b/docs/content/client-sdk/authorization.md @@ -1310,7 +1310,7 @@ if (result.IsSuccess) This pattern works well alongside `UAuthClient.Users` for building a unified user administration interface. -See [User Management](/docs/client/user-management). +See [User Management](/docs/client-sdk/user-management). ## 20. Authorization State Events @@ -1373,7 +1373,7 @@ Revoking a session changes whether an authentication session remains valid. Applications may combine these operations when security policy requires immediate termination of access, but the concepts should remain separate. -See [Session Management](/docs/client/session-management). +See [Session Management](/docs/client-sdk/session-management). ## 22. Multi-Tenant Authorization @@ -1545,7 +1545,7 @@ This allows applications to provide flexible administrative interfaces while kee ## Next Steps - [Policies & Access Control](/docs/plugin-domains/policies-access-control) — Understand application policies and access rules. -- [Authorization Pipeline Internals](/docs/security/authorization-pipeline-internals) — Explore how authorization decisions are evaluated. -- [User Management](/docs/client/user-management) — Manage user accounts and lifecycle. -- [Session Management](/docs/client/session-management) — Manage authenticated sessions and devices. +- [Authorization Pipeline Internals](/docs/security/authorization-pipeline) — Explore how authorization decisions are evaluated. +- [User Management](/docs/client-sdk/user-management) — Manage user accounts and lifecycle. +- [Session Management](/docs/client-sdk/session-management) — Manage authenticated sessions and devices. - [Session Security Model](/docs/security/session-security) — Understand session validity and security boundaries. \ No newline at end of file diff --git a/docs/content/client-sdk/credentials.md b/docs/content/client-sdk/credentials.md index d97431a1..7b665805 100644 --- a/docs/content/client-sdk/credentials.md +++ b/docs/content/client-sdk/credentials.md @@ -1095,9 +1095,7 @@ Use `UAuthPasswordClientValidator` to provide immediate feedback before submitti ```csharp using CodeBeam.UltimateAuth.Client; -var errors = UAuthPasswordClientValidator.Validate( - password, - policy); +var errors = UAuthPasswordClientValidator.Validate(password, policy); if (errors.Count == 0) { diff --git a/docs/content/client-sdk/identifiers.md b/docs/content/client-sdk/identifiers.md index 1fb33760..e0e82737 100644 --- a/docs/content/client-sdk/identifiers.md +++ b/docs/content/client-sdk/identifiers.md @@ -50,7 +50,7 @@ Each identifier has its own value, type, verification state, and primary designa **Identifiers are not profiles.** A user profile contains information such as display name and biography, while identifiers represent managed identity-related values. -For general profile management, see [User Management](/docs/client/user-management). +For general profile management, see [User Management](/docs/client-sdk/user-management). ## 2. Accessing the Identifier Client @@ -1070,8 +1070,8 @@ Applications should distinguish identifier management from session revocation an ## Next Steps -- [User Management](/docs/client/user-management) — Manage user profiles, account status, and lifecycle. -- [Credentials](/docs/client/credentials) — Manage passwords, credentials, and recovery workflows. -- [Authentication](/docs/client/authentication) — Understand login and authentication flows. +- [User Management](/docs/client-sdk/user-management) — Manage user profiles, account status, and lifecycle. +- [Credentials](/docs/client-sdk/credentials) — Manage passwords, credentials, and recovery workflows. +- [Authentication](/docs/client-sdk/authentication) — Understand login and authentication flows. - [Users](/docs/plugin-domains/users) — Explore the user domain architecture. - [Session Security Model](/docs/security/session-security) — Understand authentication security boundaries. \ No newline at end of file diff --git a/docs/content/client-sdk/session-management.md b/docs/content/client-sdk/session-management.md index 9e870348..eb63b166 100644 --- a/docs/content/client-sdk/session-management.md +++ b/docs/content/client-sdk/session-management.md @@ -439,7 +439,7 @@ var result = await UAuthClient.Flows.LogoutMyDeviceAsync( Pass a `SessionChainId`, not an individual `AuthSessionId`. -For complete logout examples, see [Authentication](/docs/client/authentication). +For complete logout examples, see [Authentication](/docs/client-sdk/authentication). ## 11. Build a Device Management Page @@ -792,7 +792,7 @@ These operations should be protected by appropriate permissions and, where requi ## Next Steps -- [Authentication](/docs/client/authentication) — Login, logout, refresh, and authentication state validation. +- [Authentication](/docs/client-sdk/authentication) — Login, logout, refresh, and authentication state validation. - [Session Model & Lifecycle](/docs/sessions-devices/session-lifecycle) — Session roots, chains, lifecycle, and expiration. - [Device Management](/docs/sessions-devices/device-management) — Device-associated authentication contexts. - [Session Security Model](/docs/security/session-security) — Revocation and session security boundaries. \ No newline at end of file diff --git a/docs/content/client-sdk/user-management.md b/docs/content/client-sdk/user-management.md index 694fe263..2290b0d0 100644 --- a/docs/content/client-sdk/user-management.md +++ b/docs/content/client-sdk/user-management.md @@ -16,7 +16,7 @@ A user has a stable identity and may have multiple profiles. Account status cont This guide covers the public `IUserClient` API, including self-service and administrative operations, structured results, pagination, multi-profile support, and important update semantics. -For email addresses, usernames, phone numbers, and identifier verification, see [Identifiers](/docs/client/identifiers). +For email addresses, usernames, phone numbers, and identifier verification, see [Identifiers](/docs/client-sdk/identifiers). ## 1. Accessing the User Client @@ -420,7 +420,7 @@ Verification flags must not be treated as client-controlled proof of identifier The server is responsible for enforcing verification requirements and determining whether a caller is authorized to establish verified identifier state. -For identifier-specific operations and verification workflows, see [Identifiers](/docs/client/identifiers). +For identifier-specific operations and verification workflows, see [Identifiers](/docs/client-sdk/identifiers). ### UserCreateResult @@ -1339,8 +1339,8 @@ Applications should consider retention, recovery, audit requirements, and depend ## Next Steps -- [Identifiers](/docs/client/identifiers) — Manage usernames, email addresses, phone numbers, and verification. -- [Authentication](/docs/client/authentication) — Login, refresh, logout, and authentication state. -- [Session Management](/docs/client/session-management) — Inspect and revoke sessions across devices. +- [Identifiers](/docs/client-sdk/identifiers) — Manage usernames, email addresses, phone numbers, and verification. +- [Authentication](/docs/client-sdk/authentication) — Login, refresh, logout, and authentication state. +- [Session Management](/docs/client-sdk/session-management) — Inspect and revoke sessions across devices. - [Users](/docs/plugin-domains/users) — Understand the user domain and lifecycle architecture. - [Policies & Access Control](/docs/plugin-domains/policies) — Understand authorization enforcement for user operations. \ No newline at end of file diff --git a/docs/content/configuration/overview.md b/docs/content/configuration/overview.md index 30861406..cbea0478 100644 --- a/docs/content/configuration/overview.md +++ b/docs/content/configuration/overview.md @@ -490,7 +490,7 @@ builder.Services.AddUltimateAuthServer(options => Do not enable an unsupported mode merely because it exists in the enum. -For supported modes and their behavior, see [Authentication Modes](/docs/fundamentals/authentication-modes). +For supported modes and their behavior, see [Authentication Modes](/docs/fundamentals/auth-modes). ## 10. Configure Credential Session Policies @@ -836,7 +836,6 @@ The Core-only registration exists for infrastructure scenarios that explicitly c ## Next Steps - [Configuration Overview](/docs/configuration/configuration-overview) — Understand the overall configuration model. -- [Core Options](/docs/configuration/core-options) — Explore the reusable option definitions composed into server configuration. - [Server Options](/docs/configuration/server-options) — Configure authentication and security behavior. - [Client Options](/docs/configuration/client-options) — Configure client profiles, flows, and SDK behavior. - [Advanced Configuration](/docs/configuration/advanced-configuration) — Customize integration, multi-tenancy, and runtime extension points. \ No newline at end of file diff --git a/docs/content/fundamentals/flow-based-auth.md b/docs/content/fundamentals/flow-based-auth.md index ec1af006..9f8ac553 100644 --- a/docs/content/fundamentals/flow-based-auth.md +++ b/docs/content/fundamentals/flow-based-auth.md @@ -234,7 +234,7 @@ Separating these responsibilities prevents transport behavior from becoming auth For example, whether an access token is delivered through a cookie or header should not change the security requirements used to issue it. -The technical components and request pipeline implementing these responsibilities are documented in [Auth Flows](../auth-flows/). +The technical components and request pipeline implementing these responsibilities are documented in [Auth Flows](/docs/auth-flows/overview). ## Flows across client runtimes diff --git a/docs/content/plugin-domains/credentials.md b/docs/content/plugin-domains/credentials.md index a4dfb6c4..67b7bf89 100644 --- a/docs/content/plugin-domains/credentials.md +++ b/docs/content/plugin-domains/credentials.md @@ -306,6 +306,6 @@ The current password implementation provides the operational foundation. The bro - [Users](/docs/plugin-domains/users) — Understand identity ownership, identifiers, and account lifecycle. - [Authorization](/docs/plugin-domains/authorization) — Explore how authenticated identities are evaluated against access policies. -- [Plugin Domains](/docs/plugin-domains) — Review the architecture and responsibilities of Plugin Domains. +- [Plugin Domains](/docs/plugin-domains/overview) — Review the architecture and responsibilities of Plugin Domains. -For the authentication flow that consumes credential verification results, see [Flow-Based Authentication](/docs/fundamentals/flow-based-authentication). \ No newline at end of file +For the authentication flow that consumes credential verification results, see [Flow-Based Authentication](/docs/fundamentals/flow-based-auth). \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/overview.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/overview.json index 4ec11523..3865758a 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/overview.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/overview.json @@ -1,7 +1,7 @@ { "Slug": "auth-flows/overview", "Title": "Auth-Flows Overview", - "Html": "\n\u003Cp\u003EAuthentication is often described as a collection of endpoints: one accepts a password, another renews a token, and another clears a cookie. Those endpoints are useful interfaces, but they are not the security model.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EUltimateAuth treats authentication as a set of controlled flows operating within an explicitly resolved security context.\u003C/strong\u003E A flow represents an intention\u2014establish access, continue it, or terminate it\u2014and the server determines which rules, state, and delivery behavior apply before executing that intention.\u003C/p\u003E\n\u003Cp\u003EThis distinction matters because the same request can have different legitimate outcomes depending on the tenant, authentication mode, client profile, device, current session, and server policy. A flow should not have to guess those values independently.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-flow-is-more-than-an-endpoint\u0022\u003EA flow is more than an endpoint\u003C/h2\u003E\n\u003Cp\u003EAn endpoint receives an HTTP request. A flow describes what the request is attempting to do and provides a consistent context for the operation.\u003C/p\u003E\n\u003Cp\u003EIn UltimateAuth, the HTTP boundary, context resolution, domain orchestration, authorization decisions, persistence, and response delivery have separate responsibilities. This makes authentication behavior easier to reason about without forcing every flow to use exactly the same internal implementation.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003ECore principle:\u003C/strong\u003E Resolve the security context first. Make decisions against authoritative state. Commit only the changes permitted by the flow. Then deliver the outcome in the form appropriate for the client.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-request-to-result-pipeline\u0022\u003EThe request-to-result pipeline\u003C/h2\u003E\n\u003Cp\u003EThe following sequence describes the common \u003Cstrong\u003EHTTP auth-flow boundary\u003C/strong\u003E. Individual flows have additional steps, and their internal order may differ.\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\u003EIncoming HTTP request\n |\n v\nUltimateAuth Middleware Pipeline\n |\n \u002B-- TenantMiddleware\n | - Resolve tenant context\n | - Store tenant context in HttpContext\n |\n \u002B-- SessionResolutionMiddleware\n | - Resolve session identifier\n | - Build SessionContext (or anonymous context)\n |\n \u002B-- UserMiddleware\n | - Resolve user context through IUserAccessor\n |\n |-- ASP.NET Core Authentication and Authorization Middleware (Optional)\n |\n v\nRequest Context Prepared\n |\n v\nEndpoint metadata (AuthFlowType)\n |\n v\nAuthFlowEndpointFilter\n |\n v\nAuthFlow.BeginAsync(...)\n |\n v\nAuthFlowContextFactory\n - Read tenant, session, and user contexts\n - Resolve client profile and effective server options\n - Determine effective authentication mode\n - Resolve primary token kind and response delivery policy\n - Resolve device context and return URL\n - Validate existing session where applicable\n - Reject invalid or unresolved tenant\n |\n v\nAuthFlowContext (request-scoped)\n |\n v\nEndpoint handler and flow-specific validation\n |\n v\nFlow service / orchestrator\n |\n v\nSecurity and authorization decisions\n |\n v\nPermitted state transitions and optional token operations\n |\n v\nResponse delivery, optional redirect, and flow events\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is a \u003Cstrong\u003Econceptual pipeline\u003C/strong\u003E, not a promise that every flow creates a session, issues a token, or dispatches the same events. Nor is there one universal authority method that all flows invoke identically.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-request-identifies-the-flow\u0022\u003E1. A request identifies the flow\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth endpoints can carry \u003Ccode\u003EAuthFlowMetadata\u003C/code\u003E describing the \u003Ccode\u003EAuthFlowType\u003C/code\u003E. The \u003Ccode\u003EAuthFlowEndpointFilter\u003C/code\u003E reads that metadata and invokes \u003Ccode\u003EIAuthFlow.BeginAsync(...)\u003C/code\u003E before the endpoint handler executes.\u003C/p\u003E\n\u003Cp\u003EThis is an important boundary: the endpoint does not need to assemble its own authentication environment from scattered headers, cookies, and settings.\u003C/p\u003E\n\u003Cp\u003EThe filter initializes the flow context; it does \u003Cstrong\u003Enot\u003C/strong\u003E, by itself, validate a password, authorize every operation, or mutate the session store. Those responsibilities belong to the appropriate downstream components.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-server-constructs-authflowcontext\u0022\u003E2. The server constructs \u003Ccode\u003EAuthFlowContext\u003C/code\u003E\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlowContextFactory\u003C/code\u003E resolves the inputs that govern how the operation must behave. The resulting context includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EContext value\u003C/th\u003E\n\u003Cth\u003EWhy it matters\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EFlowType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifies the intended authentication operation.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEstablishes the tenant boundary for state and decisions.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EClientProfile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDescribes the kind of client interacting with the server.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEffectiveMode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDetermines the authentication model actually in force for this request.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESupplies the effective server policy.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryTokenKind\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifies the primary credential/token kind for the effective mode.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDevice\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECarries the resolved device context.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESession\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECarries validated session security context when available.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAuthenticated\u003C/code\u003E / \u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERepresent the resolved authentication identity for this request.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResponse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDescribes effective credential delivery and redirect behavior.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReturnUrlInfo\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECarries parsed navigation intent for applicable browser flows.\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe factory rejects unresolved or invalid tenant context and disallowed authentication modes. When a session context is present, it invokes session validation. For session-authoritative modes such as \u003Ccode\u003EPureOpaque\u003C/code\u003E and \u003Ccode\u003EHybrid\u003C/code\u003E, that validation also affects the resolved authentication state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe context is resolved, not blindly trusted.\u003C/strong\u003E Request-provided information may influence resolution, but the effective mode, policy, identity, and session validity are determined by server-side components.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-context-is-made-available-to-the-handler\u0022\u003E3. The context is made available to the handler\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlow.BeginAsync\u003C/code\u003E stores the created context through the request-scoped \u003Ccode\u003EAuthFlowContextAccessor\u003C/code\u003E. The endpoint handler can then access the same resolved context rather than reconstructing it.\u003C/p\u003E\n\u003Cp\u003EThis creates a shared vocabulary across the HTTP boundary: the handler and downstream services work with the same tenant, mode, device, and response policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-specific-validation-and-orchestration-begin\u0022\u003E4. Flow-specific validation and orchestration begin\u003C/h3\u003E\n\u003Cp\u003EOnce the context exists, the endpoint handler reads and validates the operation-specific request. For example, login needs an identifier and secret; logout needs an applicable session or target scope; refresh needs valid renewal material.\u003C/p\u003E\n\u003Cp\u003EThe handler delegates the actual operation to the relevant flow service or orchestrator. Those components coordinate credential checks, session operations, token operations, and state transitions as appropriate.\u003C/p\u003E\n\u003Cp\u003EThere is no benefit in pretending these operations are identical. \u003Cstrong\u003ETheir shared architecture is the context and security boundary; their individual state transitions remain purpose-specific.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022decisions-are-evaluated-against-authoritative-state\u0022\u003E5. Decisions are evaluated against authoritative state\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth distinguishes between what a client \u003Cem\u003Epresents\u003C/em\u003E and what the system \u003Cem\u003Eknows\u003C/em\u003E.\u003C/p\u003E\n\u003Cp\u003EA cookie, bearer token, device identifier, or user-supplied value is evidence or input\u2014not automatically the source of truth. The relevant authoritative data comes from server-managed user, credential, authentication-security, and session state, subject to the active authentication mode.\u003C/p\u003E\n\u003Cp\u003EFor session-backed flows, the hierarchy 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\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\u003EUser\n \u2514\u2500\u2500 Root\n \u2514\u2500\u2500 Chain (device-associated lifecycle)\n \u2514\u2500\u2500 Session (authentication instance)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe root, chain, and session provide distinct scopes for continuity, validation, and invalidation. Tokens and cookies can represent access to that state, but should not be confused with the underlying lifecycle.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EMode distinction:\u003C/strong\u003E Do not assume that every mode has identical persistence or session-issuance behavior. In particular, \u003Ccode\u003EPureJwt\u003C/code\u003E is not a session-issuance mode in the current session issuer. The effective mode determines which state and proof are authoritative for a given operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022only-permitted-transitions-are-committed\u0022\u003E6. Only permitted transitions are committed\u003C/h3\u003E\n\u003Cp\u003EA successful authentication operation may create a session, rotate or touch existing state, revoke access, or leave persistent state unchanged. The actual effect depends on the flow and mode.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogin verifies credentials and, in session-issuing modes, establishes a session before optionally issuing tokens.\u003C/li\u003E\n\u003Cli\u003ERefresh evaluates existing renewal authority and applies its configured continuation behavior; it is not simply a request to mint another token.\u003C/li\u003E\n\u003Cli\u003ELogout ends access at the relevant scope, while revoke represents irreversible invalidation of the revoked object or hierarchy scope.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EWhere a session command passes through \u003Ccode\u003EUAuthSessionOrchestrator\u003C/code\u003E, \u003Ccode\u003EIAuthAuthority\u003C/code\u003E makes an allow, deny, or challenge decision before the session issuer executes that command. Other operations use their own relevant decision and access orchestration paths.\u003C/p\u003E\n\u003Cp\u003EThis separation helps prevent HTTP transport details from becoming security policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-outcome-is-delivered-according-to-policy\u0022\u003E7. The outcome is delivered according to policy\u003C/h3\u003E\n\u003Cp\u003EA successful domain operation and the HTTP response carrying its outcome are different concerns.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthResponseResolver\u003C/code\u003E derives the effective response configuration from the authentication mode, flow type, client profile, and server options. It binds applicable cookie settings and validates the delivery configuration before the endpoint performs the operation.\u003C/p\u003E\n\u003Cp\u003EThis means a flow can use the same underlying authentication semantics while producing a client-appropriate HTTP outcome\u2014for example, a response with managed cookies or a browser redirect when configured.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ECurrent implementation note:\u003C/strong\u003E \u003Ccode\u003ETokenResponseMode.Body\u003C/code\u003E credential delivery is explicitly unsupported by the response resolver. Do not assume that access or refresh credentials are returned in a JSON response body.\u003C/p\u003E\n\u003Cp\u003EA redirect is a navigation decision, \u003Cstrong\u003Enot\u003C/strong\u003E an authentication or revocation mechanism. It can return a browser to the application after login or logout; the underlying security operation must stand independently of whether the browser follows the redirect.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-authflowcontext-matters\u0022\u003EWhy \u003Ccode\u003EAuthFlowContext\u003C/code\u003E matters\u003C/h2\u003E\n\u003Cp\u003EWithout a shared flow context, individual endpoints tend to make their own assumptions: which tenant is active, whether a cookie represents an authenticated session, which token type is expected, whether a client should receive cookies, and where to navigate afterward.\u003C/p\u003E\n\u003Cp\u003EThose assumptions drift as applications add multiple clients, devices, tenants, or authentication modes.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth instead resolves these questions at the flow boundary. That provides three practical benefits:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003E\u003Cstrong\u003EConsistency.\u003C/strong\u003E The same effective policy is available throughout the operation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESeparation of concerns.\u003C/strong\u003E Endpoint handlers handle HTTP; orchestrators coordinate security-relevant work; response components handle delivery.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EExtensibility.\u003C/strong\u003E New client profiles and flow behaviors can integrate with established boundaries instead of duplicating authentication plumbing.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EA context is not an authorization bypass. Possessing an \u003Ccode\u003EAuthFlowContext\u003C/code\u003E does not imply that the requested operation has been approved.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022source-of-truth-proof-versus-state\u0022\u003ESource of truth: proof versus state\u003C/h2\u003E\n\u003Cp\u003EA useful mental model is to distinguish \u003Cstrong\u003Eproof\u003C/strong\u003E, \u003Cstrong\u003Epolicy\u003C/strong\u003E, and \u003Cstrong\u003Estate\u003C/strong\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcept\u003C/th\u003E\n\u003Cth\u003EExamples\u003C/th\u003E\n\u003Cth\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EProof\u003C/td\u003E\n\u003Ctd\u003EPassword, session identifier, access token, refresh token\u003C/td\u003E\n\u003Ctd\u003EPresents evidence for an operation.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPolicy\u003C/td\u003E\n\u003Ctd\u003EEffective mode, server options, authority decisions\u003C/td\u003E\n\u003Ctd\u003EDetermines what is permitted.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EState\u003C/td\u003E\n\u003Ctd\u003EUser eligibility, credential records, security state, session root/chain/session\u003C/td\u003E\n\u003Ctd\u003EDetermines the authoritative lifecycle and validity where applicable.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDelivery\u003C/td\u003E\n\u003Ctd\u003ECookies, supported response modes, redirect\u003C/td\u003E\n\u003Ctd\u003ECommunicates the result to the client.\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA valid-looking token is not, by itself, permission to ignore revocation, tenant boundaries, session state, or mode-specific validation. Conversely, not every authentication mode is built around a persisted session hierarchy. The authoritative checks must match the configured model.\u003C/p\u003E\n\u003Cp\u003EThis distinction is central to UltimateAuth\u0027s vision: \u003Cstrong\u003Eauthentication is not the possession of a string; it is the outcome of a controlled security process.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-in-flows-at-a-glance\u0022\u003EBuilt-in flows at a glance\u003C/h2\u003E\n\u003Cp\u003EThe following pages explain each operation in detail. This overview focuses on their roles within the shared model.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EFlow\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003Cth\u003EPractical outcome\u003C/th\u003E\n\u003Cth\u003EGuide\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELogin\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEstablish authenticated access from acceptable credentials.\u003C/td\u003E\n\u003Ctd\u003ECreates a valid authentication session in session-issuing modes; may issue tokens according to policy.\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022./login-flow.md\u0022\u003ELogin Flow\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERefresh\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EContinue an existing authentication relationship using renewal authority.\u003C/td\u003E\n\u003Ctd\u003EMaintains or renews access according to mode, validation, and configured refresh behavior.\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022./refresh-flow.md\u0022\u003ERefresh Flow\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELogout\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEnd access at the applicable session or device scope.\u003C/td\u003E\n\u003Ctd\u003ETerminates authentication access and handles configured client cleanup/navigation.\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022./logout-flow.md\u0022\u003ELogout Flow\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese flows are related but not interchangeable. Login establishes access; refresh continues it; logout terminates it. Revocation operations provide stronger invalidation controls for the relevant security scope.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022concepts-shared-by-the-flows\u0022\u003EConcepts shared by the flows\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcept\u003C/th\u003E\n\u003Cth\u003EWhat it contributes\u003C/th\u003E\n\u003Cth\u003EGuide\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESession lifecycle\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERoot, chain, and session relationships; creation, continuity, expiry, and invalidation.\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022./session-lifecycle.md\u0022\u003ESession Lifecycle\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EToken behavior\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAccess and refresh credentials, validation, renewal, and mode-specific behavior.\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022./token-behavior.md\u0022\u003EToken Behavior\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EDevice management\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EDevice-associated chains, multi-device access, and targeted termination.\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022./device-management.md\u0022\u003EDevice Management\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-this-design-enables-in-real-applications\u0022\u003EWhat this design enables in real applications\u003C/h2\u003E\n\u003Cp\u003EConsider an application with a browser front end, a mobile client, and an administrative interface. They may share the same user identity system, but they do not necessarily have the same credential delivery needs or session behavior.\u003C/p\u003E\n\u003Cp\u003EA flow-driven architecture lets the server establish the applicable context first and then run the requested operation under that context. Developers can reason about \u003Cstrong\u003Ewhat authentication action is taking place\u003C/strong\u003E separately from \u003Cstrong\u003Ehow the resulting credentials or navigation are delivered\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EIt also makes security-sensitive behavior easier to locate: tenant and mode resolution occur at the boundary, domain validation happens in the flow, session operations use dedicated infrastructure, and response policy is resolved independently.\u003C/p\u003E\n\u003Cp\u003EThis is the larger goal of UltimateAuth\u0027s unified model\u2014not to make every authentication operation identical, but to make their differences explicit, controlled, and composable.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003EKey takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EAn auth flow is a controlled operation\u003C/strong\u003E, not merely an HTTP endpoint.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ccode\u003EAuthFlowEndpointFilter\u003C/code\u003E initializes the request context\u003C/strong\u003E for endpoints carrying flow metadata.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ccode\u003EAuthFlowContext\u003C/code\u003E resolves tenant, mode, client profile, device, identity/session information, and response policy\u003C/strong\u003E before the handler executes.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EProof is not the source of truth.\u003C/strong\u003E Validation depends on authoritative state and the effective authentication mode.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthority and orchestration are distinct responsibilities.\u003C/strong\u003E The exact checks and state transitions vary by flow.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ECredential delivery and redirects are outcomes\u003C/strong\u003E, not substitutes for security decisions.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ELogin, refresh, and logout share a model without sharing identical execution paths.\u003C/strong\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003ENext step\u003C/h2\u003E\n\u003Cp\u003EStart with \u003Ca href=\u0022/docs/auth-flows/login\u0022\u003ELogin Flow\u003C/a\u003E to see how UltimateAuth turns validated credentials into controlled authenticated access.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EAuthentication is often described as a collection of endpoints: one accepts a password, another renews a token, and another clears a cookie. Those endpoints are useful interfaces, but they are not the security model.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EUltimateAuth treats authentication as a set of controlled flows operating within an explicitly resolved security context.\u003C/strong\u003E A flow represents an intention\u2014establish access, continue it, or terminate it\u2014and the server determines which rules, state, and delivery behavior apply before executing that intention.\u003C/p\u003E\n\u003Cp\u003EThis distinction matters because the same request can have different legitimate outcomes depending on the tenant, authentication mode, client profile, device, current session, and server policy. A flow should not have to guess those values independently.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-flow-is-more-than-an-endpoint\u0022\u003EA flow is more than an endpoint\u003C/h2\u003E\n\u003Cp\u003EAn endpoint receives an HTTP request. A flow describes what the request is attempting to do and provides a consistent context for the operation.\u003C/p\u003E\n\u003Cp\u003EIn UltimateAuth, the HTTP boundary, context resolution, domain orchestration, authorization decisions, persistence, and response delivery have separate responsibilities. This makes authentication behavior easier to reason about without forcing every flow to use exactly the same internal implementation.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003ECore principle:\u003C/strong\u003E Resolve the security context first. Make decisions against authoritative state. Commit only the changes permitted by the flow. Then deliver the outcome in the form appropriate for the client.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-request-to-result-pipeline\u0022\u003EThe request-to-result pipeline\u003C/h2\u003E\n\u003Cp\u003EThe following sequence describes the common \u003Cstrong\u003EHTTP auth-flow boundary\u003C/strong\u003E. Individual flows have additional steps, and their internal order may differ.\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\u003EIncoming HTTP request\n |\n v\nUltimateAuth Middleware Pipeline\n |\n \u002B-- TenantMiddleware\n | - Resolve tenant context\n | - Store tenant context in HttpContext\n |\n \u002B-- SessionResolutionMiddleware\n | - Resolve session identifier\n | - Build SessionContext (or anonymous context)\n |\n \u002B-- UserMiddleware\n | - Resolve user context through IUserAccessor\n |\n |-- ASP.NET Core Authentication and Authorization Middleware (Optional)\n |\n v\nRequest Context Prepared\n |\n v\nEndpoint metadata (AuthFlowType)\n |\n v\nAuthFlowEndpointFilter\n |\n v\nAuthFlow.BeginAsync(...)\n |\n v\nAuthFlowContextFactory\n - Read tenant, session, and user contexts\n - Resolve client profile and effective server options\n - Determine effective authentication mode\n - Resolve primary token kind and response delivery policy\n - Resolve device context and return URL\n - Validate existing session where applicable\n - Reject invalid or unresolved tenant\n |\n v\nAuthFlowContext (request-scoped)\n |\n v\nEndpoint handler and flow-specific validation\n |\n v\nFlow service / orchestrator\n |\n v\nSecurity and authorization decisions\n |\n v\nPermitted state transitions and optional token operations\n |\n v\nResponse delivery, optional redirect, and flow events\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is a \u003Cstrong\u003Econceptual pipeline\u003C/strong\u003E, not a promise that every flow creates a session, issues a token, or dispatches the same events. Nor is there one universal authority method that all flows invoke identically.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-request-identifies-the-flow\u0022\u003E1. A request identifies the flow\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth endpoints can carry \u003Ccode\u003EAuthFlowMetadata\u003C/code\u003E describing the \u003Ccode\u003EAuthFlowType\u003C/code\u003E. The \u003Ccode\u003EAuthFlowEndpointFilter\u003C/code\u003E reads that metadata and invokes \u003Ccode\u003EIAuthFlow.BeginAsync(...)\u003C/code\u003E before the endpoint handler executes.\u003C/p\u003E\n\u003Cp\u003EThis is an important boundary: the endpoint does not need to assemble its own authentication environment from scattered headers, cookies, and settings.\u003C/p\u003E\n\u003Cp\u003EThe filter initializes the flow context; it does \u003Cstrong\u003Enot\u003C/strong\u003E, by itself, validate a password, authorize every operation, or mutate the session store. Those responsibilities belong to the appropriate downstream components.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-server-constructs-authflowcontext\u0022\u003E2. The server constructs \u003Ccode\u003EAuthFlowContext\u003C/code\u003E\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlowContextFactory\u003C/code\u003E resolves the inputs that govern how the operation must behave. The resulting context includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EContext value\u003C/th\u003E\n\u003Cth\u003EWhy it matters\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EFlowType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifies the intended authentication operation.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEstablishes the tenant boundary for state and decisions.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EClientProfile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDescribes the kind of client interacting with the server.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEffectiveMode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDetermines the authentication model actually in force for this request.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESupplies the effective server policy.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryTokenKind\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifies the primary credential/token kind for the effective mode.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDevice\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECarries the resolved device context.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESession\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECarries validated session security context when available.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAuthenticated\u003C/code\u003E / \u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERepresent the resolved authentication identity for this request.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResponse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDescribes effective credential delivery and redirect behavior.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReturnUrlInfo\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECarries parsed navigation intent for applicable browser flows.\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe factory rejects unresolved or invalid tenant context and disallowed authentication modes. When a session context is present, it invokes session validation. For session-authoritative modes such as \u003Ccode\u003EPureOpaque\u003C/code\u003E and \u003Ccode\u003EHybrid\u003C/code\u003E, that validation also affects the resolved authentication state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe context is resolved, not blindly trusted.\u003C/strong\u003E Request-provided information may influence resolution, but the effective mode, policy, identity, and session validity are determined by server-side components.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-context-is-made-available-to-the-handler\u0022\u003E3. The context is made available to the handler\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlow.BeginAsync\u003C/code\u003E stores the created context through the request-scoped \u003Ccode\u003EAuthFlowContextAccessor\u003C/code\u003E. The endpoint handler can then access the same resolved context rather than reconstructing it.\u003C/p\u003E\n\u003Cp\u003EThis creates a shared vocabulary across the HTTP boundary: the handler and downstream services work with the same tenant, mode, device, and response policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-specific-validation-and-orchestration-begin\u0022\u003E4. Flow-specific validation and orchestration begin\u003C/h3\u003E\n\u003Cp\u003EOnce the context exists, the endpoint handler reads and validates the operation-specific request. For example, login needs an identifier and secret; logout needs an applicable session or target scope; refresh needs valid renewal material.\u003C/p\u003E\n\u003Cp\u003EThe handler delegates the actual operation to the relevant flow service or orchestrator. Those components coordinate credential checks, session operations, token operations, and state transitions as appropriate.\u003C/p\u003E\n\u003Cp\u003EThere is no benefit in pretending these operations are identical. \u003Cstrong\u003ETheir shared architecture is the context and security boundary; their individual state transitions remain purpose-specific.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022decisions-are-evaluated-against-authoritative-state\u0022\u003E5. Decisions are evaluated against authoritative state\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth distinguishes between what a client \u003Cem\u003Epresents\u003C/em\u003E and what the system \u003Cem\u003Eknows\u003C/em\u003E.\u003C/p\u003E\n\u003Cp\u003EA cookie, bearer token, device identifier, or user-supplied value is evidence or input\u2014not automatically the source of truth. The relevant authoritative data comes from server-managed user, credential, authentication-security, and session state, subject to the active authentication mode.\u003C/p\u003E\n\u003Cp\u003EFor session-backed flows, the hierarchy 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\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\u003EUser\n \u2514\u2500\u2500 Root\n \u2514\u2500\u2500 Chain (device-associated lifecycle)\n \u2514\u2500\u2500 Session (authentication instance)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe root, chain, and session provide distinct scopes for continuity, validation, and invalidation. Tokens and cookies can represent access to that state, but should not be confused with the underlying lifecycle.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EMode distinction:\u003C/strong\u003E Do not assume that every mode has identical persistence or session-issuance behavior. In particular, \u003Ccode\u003EPureJwt\u003C/code\u003E is not a session-issuance mode in the current session issuer. The effective mode determines which state and proof are authoritative for a given operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022only-permitted-transitions-are-committed\u0022\u003E6. Only permitted transitions are committed\u003C/h3\u003E\n\u003Cp\u003EA successful authentication operation may create a session, rotate or touch existing state, revoke access, or leave persistent state unchanged. The actual effect depends on the flow and mode.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogin verifies credentials and, in session-issuing modes, establishes a session before optionally issuing tokens.\u003C/li\u003E\n\u003Cli\u003ERefresh evaluates existing renewal authority and applies its configured continuation behavior; it is not simply a request to mint another token.\u003C/li\u003E\n\u003Cli\u003ELogout ends access at the relevant scope, while revoke represents irreversible invalidation of the revoked object or hierarchy scope.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EWhere a session command passes through \u003Ccode\u003EUAuthSessionOrchestrator\u003C/code\u003E, \u003Ccode\u003EIAuthAuthority\u003C/code\u003E makes an allow, deny, or challenge decision before the session issuer executes that command. Other operations use their own relevant decision and access orchestration paths.\u003C/p\u003E\n\u003Cp\u003EThis separation helps prevent HTTP transport details from becoming security policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-outcome-is-delivered-according-to-policy\u0022\u003E7. The outcome is delivered according to policy\u003C/h3\u003E\n\u003Cp\u003EA successful domain operation and the HTTP response carrying its outcome are different concerns.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthResponseResolver\u003C/code\u003E derives the effective response configuration from the authentication mode, flow type, client profile, and server options. It binds applicable cookie settings and validates the delivery configuration before the endpoint performs the operation.\u003C/p\u003E\n\u003Cp\u003EThis means a flow can use the same underlying authentication semantics while producing a client-appropriate HTTP outcome\u2014for example, a response with managed cookies or a browser redirect when configured.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ECurrent implementation note:\u003C/strong\u003E \u003Ccode\u003ETokenResponseMode.Body\u003C/code\u003E credential delivery is explicitly unsupported by the response resolver. Do not assume that access or refresh credentials are returned in a JSON response body.\u003C/p\u003E\n\u003Cp\u003EA redirect is a navigation decision, \u003Cstrong\u003Enot\u003C/strong\u003E an authentication or revocation mechanism. It can return a browser to the application after login or logout; the underlying security operation must stand independently of whether the browser follows the redirect.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-authflowcontext-matters\u0022\u003EWhy \u003Ccode\u003EAuthFlowContext\u003C/code\u003E matters\u003C/h2\u003E\n\u003Cp\u003EWithout a shared flow context, individual endpoints tend to make their own assumptions: which tenant is active, whether a cookie represents an authenticated session, which token type is expected, whether a client should receive cookies, and where to navigate afterward.\u003C/p\u003E\n\u003Cp\u003EThose assumptions drift as applications add multiple clients, devices, tenants, or authentication modes.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth instead resolves these questions at the flow boundary. That provides three practical benefits:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003E\u003Cstrong\u003EConsistency.\u003C/strong\u003E The same effective policy is available throughout the operation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESeparation of concerns.\u003C/strong\u003E Endpoint handlers handle HTTP; orchestrators coordinate security-relevant work; response components handle delivery.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EExtensibility.\u003C/strong\u003E New client profiles and flow behaviors can integrate with established boundaries instead of duplicating authentication plumbing.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EA context is not an authorization bypass. Possessing an \u003Ccode\u003EAuthFlowContext\u003C/code\u003E does not imply that the requested operation has been approved.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022source-of-truth-proof-versus-state\u0022\u003ESource of truth: proof versus state\u003C/h2\u003E\n\u003Cp\u003EA useful mental model is to distinguish \u003Cstrong\u003Eproof\u003C/strong\u003E, \u003Cstrong\u003Epolicy\u003C/strong\u003E, and \u003Cstrong\u003Estate\u003C/strong\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcept\u003C/th\u003E\n\u003Cth\u003EExamples\u003C/th\u003E\n\u003Cth\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EProof\u003C/td\u003E\n\u003Ctd\u003EPassword, session identifier, access token, refresh token\u003C/td\u003E\n\u003Ctd\u003EPresents evidence for an operation.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPolicy\u003C/td\u003E\n\u003Ctd\u003EEffective mode, server options, authority decisions\u003C/td\u003E\n\u003Ctd\u003EDetermines what is permitted.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EState\u003C/td\u003E\n\u003Ctd\u003EUser eligibility, credential records, security state, session root/chain/session\u003C/td\u003E\n\u003Ctd\u003EDetermines the authoritative lifecycle and validity where applicable.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDelivery\u003C/td\u003E\n\u003Ctd\u003ECookies, supported response modes, redirect\u003C/td\u003E\n\u003Ctd\u003ECommunicates the result to the client.\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA valid-looking token is not, by itself, permission to ignore revocation, tenant boundaries, session state, or mode-specific validation. Conversely, not every authentication mode is built around a persisted session hierarchy. The authoritative checks must match the configured model.\u003C/p\u003E\n\u003Cp\u003EThis distinction is central to UltimateAuth\u0027s vision: \u003Cstrong\u003Eauthentication is not the possession of a string; it is the outcome of a controlled security process.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-in-flows-at-a-glance\u0022\u003EBuilt-in flows at a glance\u003C/h2\u003E\n\u003Cp\u003EThe following pages explain each operation in detail. This overview focuses on their roles within the shared model.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EFlow\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003Cth\u003EPractical outcome\u003C/th\u003E\n\u003Cth\u003EGuide\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELogin\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEstablish authenticated access from acceptable credentials.\u003C/td\u003E\n\u003Ctd\u003ECreates a valid authentication session in session-issuing modes; may issue tokens according to policy.\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022/docs/auth-flows/login-flow\u0022\u003ELogin Flow\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERefresh\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EContinue an existing authentication relationship using renewal authority.\u003C/td\u003E\n\u003Ctd\u003EMaintains or renews access according to mode, validation, and configured refresh behavior.\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022/docs/auth-flows/refresh-flow\u0022\u003ERefresh Flow\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELogout\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEnd access at the applicable session or device scope.\u003C/td\u003E\n\u003Ctd\u003ETerminates authentication access and handles configured client cleanup/navigation.\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022/docs/auth-flows/logout-flow\u0022\u003ELogout Flow\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese flows are related but not interchangeable. Login establishes access; refresh continues it; logout terminates it. Revocation operations provide stronger invalidation controls for the relevant security scope.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022concepts-shared-by-the-flows\u0022\u003EConcepts shared by the flows\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcept\u003C/th\u003E\n\u003Cth\u003EWhat it contributes\u003C/th\u003E\n\u003Cth\u003EGuide\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESession lifecycle\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERoot, chain, and session relationships; creation, continuity, expiry, and invalidation.\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022/docs/sessions-devices/session-lifecycle\u0022\u003ESession Lifecycle\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EToken behavior\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAccess and refresh credentials, validation, renewal, and mode-specific behavior.\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Behavior\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EDevice management\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EDevice-associated chains, multi-device access, and targeted termination.\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-this-design-enables-in-real-applications\u0022\u003EWhat this design enables in real applications\u003C/h2\u003E\n\u003Cp\u003EConsider an application with a browser front end, a mobile client, and an administrative interface. They may share the same user identity system, but they do not necessarily have the same credential delivery needs or session behavior.\u003C/p\u003E\n\u003Cp\u003EA flow-driven architecture lets the server establish the applicable context first and then run the requested operation under that context. Developers can reason about \u003Cstrong\u003Ewhat authentication action is taking place\u003C/strong\u003E separately from \u003Cstrong\u003Ehow the resulting credentials or navigation are delivered\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EIt also makes security-sensitive behavior easier to locate: tenant and mode resolution occur at the boundary, domain validation happens in the flow, session operations use dedicated infrastructure, and response policy is resolved independently.\u003C/p\u003E\n\u003Cp\u003EThis is the larger goal of UltimateAuth\u0027s unified model\u2014not to make every authentication operation identical, but to make their differences explicit, controlled, and composable.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003EKey takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EAn auth flow is a controlled operation\u003C/strong\u003E, not merely an HTTP endpoint.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ccode\u003EAuthFlowEndpointFilter\u003C/code\u003E initializes the request context\u003C/strong\u003E for endpoints carrying flow metadata.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ccode\u003EAuthFlowContext\u003C/code\u003E resolves tenant, mode, client profile, device, identity/session information, and response policy\u003C/strong\u003E before the handler executes.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EProof is not the source of truth.\u003C/strong\u003E Validation depends on authoritative state and the effective authentication mode.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthority and orchestration are distinct responsibilities.\u003C/strong\u003E The exact checks and state transitions vary by flow.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ECredential delivery and redirects are outcomes\u003C/strong\u003E, not substitutes for security decisions.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ELogin, refresh, and logout share a model without sharing identical execution paths.\u003C/strong\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003ENext step\u003C/h2\u003E\n\u003Cp\u003EStart with \u003Ca href=\u0022/docs/auth-flows/login-flow\u0022\u003ELogin Flow\u003C/a\u003E to see how UltimateAuth turns validated credentials into controlled authenticated access.\u003C/p\u003E\n", "Headings": [ { "Id": "a-flow-is-more-than-an-endpoint", @@ -84,6 +84,6 @@ "Level": 0 } ], - "SourceHash": "A2EB72E76BD9E836BEF7240D2D1FD72BF16C85F7D73C85160CFD908E689DDD4E", + "SourceHash": "9DD0D0650310BF1AD536E53D4BF6A707638ED0914712E64D469349D7E82543BB", "BuilderVersion": "1" } \ No newline at end of file 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 3b4c2c4d..8c7996d0 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/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\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", "Headings": [ { "Id": "accessing-the-authentication-client", @@ -329,6 +329,6 @@ "Level": 0 } ], - "SourceHash": "E08764ABC9FE77E47DEDD39DFB7BC9C174BD3482E71CF20D8547489902D0DC6D", + "SourceHash": "2A94010322C7A85EC49E84E84F1E73737F414AE2A5FCCED1BAED1E3C3FD11729", "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authorization.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authorization.json index 427e1a0d..1ea8da64 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authorization.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authorization.json @@ -1,7 +1,7 @@ { "Slug": "client-sdk/authorization", "Title": "Authorization", - "Html": "\n\u003Cp\u003EUltimateAuth provides a unified client API for managing roles, assigning permissions, controlling user-role relationships, and requesting authorization decisions from the server.\u003C/p\u003E\n\u003Cp\u003EThrough \u003Ccode\u003EUAuthClient.Authorization\u003C/code\u003E, applications can build administrative authorization interfaces and check whether the current user is allowed to perform a particular action.\u003C/p\u003E\n\u003Cp\u003EAuthorization is enforced on the server. Client-side permission checks are useful for improving the user experience, but they do not replace authorization at the endpoint or application-service level.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the current \u003Ccode\u003EIAuthorizationClient\u003C/code\u003E API, practical role management workflows, permission patterns, and secure client integration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-authorization\u0022\u003E1. Understanding Authorization\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth authorization combines several concepts:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcept\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\u003ERoles\u003C/td\u003E\n\u003Ctd\u003ENamed collections of permissions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPermissions\u003C/td\u003E\n\u003Ctd\u003EFine-grained capabilities represented by normalized values\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUser-role assignments\u003C/td\u003E\n\u003Ctd\u003EAssociations between users and roles\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPolicies\u003C/td\u003E\n\u003Ctd\u003EServer-side rules that participate in access decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthorization context\u003C/td\u003E\n\u003Ctd\u003EInformation about the actor, resource, action, and current security state\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA typical authorization relationship looks like this:\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\u003EUser\n |\n \u002B-- Manager\n | |\n | \u002B-- products.read\n | \u002B-- products.create\n | \u002B-- products.update\n |\n \u002B-- Auditor\n |\n \u002B-- reports.read\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA user can have multiple roles, and each role can contain multiple permissions.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Cstrong\u003Ehaving a permission does not automatically guarantee access\u003C/strong\u003E. The server\u0027s authorization pipeline may apply additional requirements, such as resource ownership, session validity, tenant isolation, or application policies.\u003C/p\u003E\n\u003Cp\u003EFor the internal decision model, see \u003Ca href=\u0022/docs/security/authorization-pipeline\u0022\u003EAuthorization Pipeline Internals\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-authorization-client\u0022\u003E2. Accessing the Authorization Client\u003C/h2\u003E\n\u003Cp\u003EInject \u003Ccode\u003EIUAuthClient\u003C/code\u003E into a Blazor 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\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAuthorization operations are available through:\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\u003EUAuthClient.Authorization\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis property exposes \u003Ccode\u003EIAuthorizationClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe interface provides three main groups of operations:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthorization checks:\u003C/strong\u003E Evaluate whether the current user may perform an action.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERole management:\u003C/strong\u003E Create, query, rename, configure, and delete roles.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUser-role management:\u003C/strong\u003E Retrieve, assign, and remove user roles.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe SDK sends requests to the corresponding UltimateAuth endpoints and returns structured \u003Ccode\u003EUAuthResult\u003C/code\u003E values.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-permissions\u0022\u003E3. Understanding Permissions\u003C/h2\u003E\n\u003Cp\u003EA permission is represented by the \u003Ccode\u003EPermission\u003C/code\u003E value 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\u003Evar permission = Permission.From(\u0026quot;products.create\u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe current implementation normalizes permission values by trimming surrounding whitespace and converting the value to lowercase.\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\u003Evar permission = Permission.From(\u0026quot; PRODUCTS.CREATE \u0026quot;);\n\nConsole.WriteLine(permission.Value);\n// products.create\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe constructor rejects null, empty, and whitespace-only values.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-patterns\u0022\u003EPermission Patterns\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth recognizes three important forms:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPattern\u003C/th\u003E\n\u003Cth\u003EExample\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\u003EExact\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eproducts.create\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EA specific permission\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPrefix wildcard\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eproducts.*\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPermissions within the corresponding prefix\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EGlobal wildcard\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E*\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUnrestricted permission matching within the applicable authorization rules\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ECreate them using:\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 exact = Permission.From(\u0026quot;products.create\u0026quot;);\n\nvar prefix = Permission.From(\u0026quot;products.*\u0026quot;);\n\nvar global = Permission.Wildcard;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe type also exposes:\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\u003Epermission.IsWildcard;\npermission.IsPrefix;\npermission.IsValid;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-normalization\u0022\u003EPermission Normalization\u003C/h3\u003E\n\u003Cp\u003EThe following values normalize to the same representation:\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\u003EPermission.From(\u0026quot;Products.Create\u0026quot;);\nPermission.From(\u0026quot;products.create\u0026quot;);\nPermission.From(\u0026quot; PRODUCTS.CREATE \u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAll produce:\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\u003Eproducts.create\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse consistent permission names across application services, policies, and role configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022wildcards-are-powerful\u0022\u003EWildcards Are Powerful\u003C/h3\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\u003EPermission.From(\u0026quot;users.*\u0026quot;)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Emay grant broad access to operations within the \u003Ccode\u003Eusers\u003C/code\u003E permission prefix.\u003C/p\u003E\n\u003Cp\u003EThe global wildcard:\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\u003EPermission.Wildcard\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eis especially sensitive.\u003C/p\u003E\n\u003Cp\u003EAvoid assigning broad wildcard permissions merely to simplify development. Use least-privilege permissions for ordinary application roles.\u003C/p\u003E\n\u003Cp\u003EA wildcard is not a substitute for satisfying independent security policies, authentication requirements, or tenant restrictions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-authorization\u0022\u003E4. Check Authorization\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E evaluates whether the current authenticated user is authorized to perform a specific action against a resource.\u003C/p\u003E\n\u003Cp\u003EIt provides an \u003Cstrong\u003Eon-demand, server-side authorization decision\u003C/strong\u003E based on the current authentication context, permissions, roles, and applicable authorization policies.\u003C/p\u003E\n\u003Cp\u003EUnlike operations that perform protected actions, \u003Ccode\u003ECheckAsync\u003C/code\u003E only evaluates access. It does not execute the requested operation, modify permissions, grant access, or reserve authorization for a future request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-to-use-checkasync\u0022\u003EWhen to Use CheckAsync\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECheckAsync\u003C/code\u003E when your application needs to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDetermine whether an authenticated user can perform an action before presenting it in the UI.\u003C/li\u003E\n\u003Cli\u003EEnable, disable, or hide controls based on the current authorization decision.\u003C/li\u003E\n\u003Cli\u003EExplain why an operation is unavailable, using the returned denial reason where appropriate.\u003C/li\u003E\n\u003Cli\u003EDetermine whether an operation requires reauthentication.\u003C/li\u003E\n\u003Cli\u003EEvaluate resource-specific access when the applicable policies support it.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, an application may check whether the current user can delete a particular order before displaying the Delete button.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-checkasync-does-not-guarantee\u0022\u003EWhat CheckAsync Does Not Guarantee\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EA successful authorization check is not a guarantee that a subsequent operation will succeed.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAuthorization is evaluated against the security context available at the time of the check. That context may change before the actual operation is performed.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn administrator may revoke the user\u0027s permissions.\u003C/li\u003E\n\u003Cli\u003EThe user\u0027s session may expire or become invalid.\u003C/li\u003E\n\u003Cli\u003EThe target resource may be deleted or modified.\u003C/li\u003E\n\u003Cli\u003EThe resource\u0027s ownership or tenant context may change.\u003C/li\u003E\n\u003Cli\u003EAdditional business rules may prevent the operation.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe protected operation must independently enforce authorization using its current context.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ENever use a previous \u003Ccode\u003ECheckAsync\u003C/code\u003E result as proof of authorization for a later request.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E is primarily a developer-experience and user-experience API. It helps applications understand and present authorization decisions without replacing the security enforcement performed by protected endpoints and application services.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECheckAsync\u003C/code\u003E to ask the server whether the current user is authorized to perform an action.\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;read\u0026quot;,\n Resource = \u0026quot;products\u0026quot;\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsAllowed: true })\n{\n // The server authorized this action.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;AuthorizationResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorizationcheckrequest\u0022\u003EAuthorizationCheckRequest\u003C/h3\u003E\n\u003Cp\u003EThe request contains:\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\u003EType\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\u003EAction\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResource\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResource being accessed\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResourceId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional identifier for a specific resource instance\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EBoth \u003Ccode\u003EAction\u003C/code\u003E and \u003Ccode\u003EResource\u003C/code\u003E are required.\u003C/p\u003E\n\u003Cp\u003EThe action and resource values must follow the conventions understood by your server-side authorization configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-a-specific-resource\u0022\u003ECheck a Specific Resource\u003C/h3\u003E\n\u003Cp\u003EFor example, check access to a particular product:\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;update\u0026quot;,\n Resource = \u0026quot;products\u0026quot;,\n ResourceId = productId.ToString()\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EProviding \u003Ccode\u003EResourceId\u003C/code\u003E allows the server to evaluate resource-specific rules where supported by the configured authorization pipeline.\u003C/p\u003E\n\u003Cp\u003EFor example, the application may distinguish between updating any product and updating a product owned by the current user.\u003C/p\u003E\n\u003Cp\u003EDo not assume that sending \u003Ccode\u003EResourceId\u003C/code\u003E automatically enables ownership checking. The relevant server-side policy must implement the required rule.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-authorization-results\u0022\u003E5. Understanding Authorization Results\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E returns an \u003Ccode\u003EAuthorizationResult\u003C/code\u003E with 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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAllowed\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the request was authorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequiresReauthentication\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the decision indicates reauthentication is required\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDenyReason\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional denial reason code\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=\u0022authorization-decisions-vs.http-results\u0022\u003EAuthorization Decisions vs. HTTP Results\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E separates the outcome of the API request from the authorization decision itself.\u003C/p\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003EUAuthResult\u0026lt;AuthorizationResult\u0026gt;\u003C/code\u003E:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthResult.IsSuccess\u003C/code\u003E indicates whether the authorization check request completed successfully.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAuthorizationResult.IsAllowed\u003C/code\u003E indicates whether the requested action is currently authorized.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAuthorizationResult.RequiresReauthentication\u003C/code\u003E indicates whether additional authentication is required.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAuthorizationResult.DenyReason\u003C/code\u003E provides an optional reason for denial.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA denied authorization decision is a valid result of a successful authorization check.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EScenario\u003C/th\u003E\n\u003Cth\u003EHTTP Status\u003C/th\u003E\n\u003Cth\u003EAuthorization Result\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAction allowed\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E200 OK\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAllowed = true\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAction denied\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E200 OK\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAllowed = false\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReauthentication required\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E200 OK\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequiresReauthentication = true\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECaller not authenticated\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E401 Unauthorized\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo authorization decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInvalid request\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E400 Bad Request\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo authorization decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EServer-side evaluation failure\u003C/td\u003E\n\u003Ctd\u003EAppropriate error status\u003C/td\u003E\n\u003Ctd\u003ENo authorization decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EHTTP 200 means the authorization evaluation completed successfully. It does not mean access was granted.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EOnly authenticated callers can use the current \u003Ccode\u003ECheckAsync\u003C/code\u003E endpoint. However, callers do not need permission to perform the action they are checking.\u003C/p\u003E\n\u003Cp\u003EThis distinction is intentional: the purpose of \u003Ccode\u003ECheckAsync\u003C/code\u003E is to determine whether the action is permitted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-denied-decisions\u0022\u003EHandling Denied Decisions\u003C/h3\u003E\n\u003Cp\u003EApplications should distinguish between authorization denial, reauthentication requirements, and request failures.\u003C/p\u003E\n\u003Cp\u003EA denied decision may be used to disable an action or display an appropriate message.\u003C/p\u003E\n\u003Cp\u003EA reauthentication requirement may be used to initiate the application\u0027s supported reauthentication flow.\u003C/p\u003E\n\u003Cp\u003EA failed request must not be interpreted as permission to proceed.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EFail closed:\u003C/strong\u003E If the authorization decision is unavailable, treat the operation as unavailable in the UI and rely on server-side authorization when the actual operation is requested.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-all-decision-outcomes\u0022\u003EHandle All Decision Outcomes\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;delete\u0026quot;,\n Resource = \u0026quot;products\u0026quot;,\n ResourceId = productId.ToString()\n });\n\nif (!result.IsSuccess)\n{\n // The authorization request itself failed.\n}\nelse if (result.Value is { RequiresReauthentication: true })\n{\n // Ask the user to authenticate again.\n}\nelse if (result.Value is { IsAllowed: true })\n{\n // The operation is allowed.\n}\nelse\n{\n // The operation was denied.\n var reason = result.Value?.DenyReason;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn unsuccessful API request and an explicit authorization denial are different outcomes.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003Eresult.IsSuccess == false\u003C/code\u003E means the authorization request did not complete successfully at the API level.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eresult.Value.IsAllowed == false\u003C/code\u003E means the server returned a denial decision.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eresult.Value.RequiresReauthentication == true\u003C/code\u003E indicates that the caller needs to reestablish authentication.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ETreat failed or unavailable authorization checks as \u003Cstrong\u003Enot authorized\u003C/strong\u003E in the UI.\u003C/p\u003E\n\u003Cp\u003ENever grant access because an authorization request failed.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-authorization-checks-in-blazor\u0022\u003E6. Use Authorization Checks in Blazor\u003C/h2\u003E\n\u003Cp\u003EA common use case is controlling whether an action button should be displayed.\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.Authorization.Contracts\n@inject IUAuthClient UAuthClient\n\n@if (_canCreate)\n{\n \u0026lt;button @onclick=\u0026quot;CreateProductAsync\u0026quot;\u0026gt;Create Product\u0026lt;/button\u0026gt;\n}\n\n@code {\n private bool _canCreate;\n\n protected override async Task OnInitializedAsync()\n {\n var result = await UAuthClient.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;create\u0026quot;,\n Resource = \u0026quot;products\u0026quot;\n });\n\n _canCreate = result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsAllowed: true };\n }\n\n private Task CreateProductAsync()\n {\n // Invoke the protected application operation.\n return Task.CompletedTask;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis improves the interface by hiding unavailable actions.\u003C/p\u003E\n\u003Cp\u003EHowever, the endpoint handling product creation must still enforce authorization independently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-client-checks-are-not-security-boundaries\u0022\u003EWhy Client Checks Are Not Security Boundaries\u003C/h3\u003E\n\u003Cp\u003EA user can bypass a hidden button and send a request directly to an endpoint.\u003C/p\u003E\n\u003Cp\u003ETherefore:\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\u003EClient authorization check\n |\n v\nImprove the user experience\n |\n v\nUser submits an operation\n |\n v\nServer independently authorizes the operation\n |\n v\nExecute or deny\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EClient authorization checks should never be the only protection for sensitive operations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-my-roles\u0022\u003E7. Get My Roles\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMyRolesAsync\u003C/code\u003E to retrieve roles assigned to the current authenticated 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.Authorization.GetMyRolesAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n foreach (var role in data.Roles.Items)\n {\n Console.WriteLine(role.Name);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;UserRolesGetResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userrolesgetresult\u0022\u003EUserRolesGetResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser whose roles were requested\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoles\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPaginated collection of role assignments\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EEach entry is a \u003Ccode\u003EUserRoleInfo\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userroleinfo\u0022\u003EUserRoleInfo\u003C/h3\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant associated with the assignment\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssigned user\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssigned role\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole name\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAssignedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssignment timestamp\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=\u0022pagination\u0022\u003EPagination\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.Authorization.GetMyRolesAsync(\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe returned role collection is paginated.\u003C/p\u003E\n\u003Cp\u003EDo not assume that the first page contains all assigned roles.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-my-permissions\u0022\u003E8. Get My Permissions\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMyPermissionsAsync()\u003C/code\u003E to retrieve the permissions assigned to the currently authenticated user through their roles.\u003C/p\u003E\n\u003Cp\u003EUnlike \u003Ccode\u003EGetMyRolesAsync()\u003C/code\u003E, which returns role assignments, this method returns the resulting permission collection.\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.Authorization.GetMyPermissionsAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n foreach (var permission in data.Permissions)\n {\n Console.WriteLine(permission.Value);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method is self-service and does not require administrative permission.\u003C/p\u003E\n\u003Cp\u003EThe user identity and tenant context are derived from the authenticated session. No user identifier or tenant identifier is accepted as a request parameter.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userpermissionsresult\u0022\u003EUserPermissionsResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains the user\u0027s role-derived permissions:\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 UserPermissionsResult\n{\n public required IReadOnlyList\u0026lt;Permission\u0026gt; Permissions { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe returned collection:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECombines permissions from the user\u0027s assigned roles.\u003C/li\u003E\n\u003Cli\u003ERemoves duplicate permissions.\u003C/li\u003E\n\u003Cli\u003EUses deterministic ordering.\u003C/li\u003E\n\u003Cli\u003EPreserves wildcard permissions such as \u003Ccode\u003Eusers.*\u003C/code\u003E and \u003Ccode\u003E*\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EReturns an empty collection when no permissions are assigned.\u003C/li\u003E\n\u003Cli\u003EReflects the current permissions resolved from the configured authorization stores.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, a user assigned multiple roles may receive:\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\u003Ereports.*\nusers.read.admin\nusers.update.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe result does not identify which role contributed each permission.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assigned-permissions-vs.authorization-decisions\u0022\u003EAssigned Permissions vs. Authorization Decisions\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EAssigned permissions are not final authorization decisions.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA user may possess a permission but still be denied access because of an applicable policy, tenant boundary, target restriction, or other security requirement.\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\u003Evar permissions = await UAuthClient.Authorization.GetMyPermissionsAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis retrieves the user\u0027s assigned permissions.\u003C/p\u003E\n\u003Cp\u003ETo determine whether a specific operation is currently authorized, use \u003Ccode\u003ECheckAsync()\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;users.delete.admin\u0026quot;,\n Resource = \u0026quot;users\u0026quot;,\n ResourceId = targetUserKey.Value\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } decision)\n{\n if (decision.IsAllowed)\n {\n // The authorization check allowed this operation.\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEven a successful client-side authorization check does not replace authorization enforcement at the server-side operation boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022wildcard-permissions\u0022\u003EWildcard Permissions\u003C/h3\u003E\n\u003Cp\u003EWildcard permissions are returned without expansion.\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\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\u003Eusers.*\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eremains \u003Ccode\u003Eusers.*\u003C/code\u003E rather than being expanded into individual user-management actions.\u003C/p\u003E\n\u003Cp\u003EThis preserves the stored permission semantics and avoids depending on a potentially incomplete action catalog.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003ESecurity Considerations\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EGetMyPermissionsAsync()\u003C/code\u003E is intended for permission inspection, user interfaces, and administrative transparency.\u003C/p\u003E\n\u003Cp\u003EIt must not be used as the sole mechanism for protecting sensitive operations.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for evaluating authorization policies and enforcing access at the actual execution boundary.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/client-sdk/authorization#4-check-authorization\u0022\u003ECheck Authorization\u003C/a\u003E for policy-aware authorization checks.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-another-users-roles\u0022\u003E9. Get Another User\u0027s Roles\u003C/h2\u003E\n\u003Cp\u003EAdministrative interfaces can retrieve the roles assigned to a specific 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.Authorization.GetUserRolesAsync(userKey,\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n foreach (var role in data.Roles.Items)\n {\n Console.WriteLine($\u0026quot;{role.Name} \u2014 {role.AssignedAt}\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis operation requires appropriate administrative authorization.\u003C/p\u003E\n\u003Cp\u003EThe \u003Ccode\u003EUserKey\u003C/code\u003E identifies the target user, not the current actor.\u003C/p\u003E\n\u003Cp\u003EThe server determines whether the current actor may inspect that user\u0027s role assignments.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022query-roles\u0022\u003E10. Query Roles\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EQueryRolesAsync\u003C/code\u003E to retrieve roles with pagination and optional filtering.\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var role in page.Items)\n {\n Console.WriteLine(role.Name);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;PagedResult\u0026lt;RoleInfo\u0026gt;\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rolequery\u0022\u003ERoleQuery\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ERoleQuery\u003C/code\u003E inherits from \u003Ccode\u003EPageRequest\u003C/code\u003E and adds:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESearch\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional role-name search\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIncludeDeleted\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EInclude deleted roles when supported and authorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPageNumber\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested page\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPageSize\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested page size\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=\u0022search-roles\u0022\u003ESearch Roles\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n Search = \u0026quot;manager\u0026quot;,\n PageNumber = 1,\n PageSize = 10\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022include-deleted-roles\u0022\u003EInclude Deleted Roles\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n IncludeDeleted = true,\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003EIncludeDeleted\u003C/code\u003E only where the application needs administrative access to deleted role records.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for enforcing the query\u0027s permissions and lifecycle rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-roleinfo\u0022\u003E11. Understanding RoleInfo\u003C/h2\u003E\n\u003Cp\u003EEach role returned by the SDK is represented by \u003Ccode\u003ERoleInfo\u003C/code\u003E.\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\u003EType\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\u003EId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole name\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPermissions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIReadOnlyCollection\u0026lt;Permission\u0026gt;\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssigned permissions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreation timestamp\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUpdatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELast update timestamp\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var role in page.Items)\n {\n Console.WriteLine($\u0026quot;Role: {role.Name}\u0026quot;);\n\n foreach (var permission in role.Permissions)\n {\n Console.WriteLine($\u0026quot; {permission.Value}\u0026quot;);\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ERoleId\u003C/code\u003E is a strongly typed identifier rather than a plain \u003Ccode\u003EGuid\u003C/code\u003E.\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\u003ERoleId roleId = RoleId.From(existingGuid);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERoleId\u003C/code\u003E for role management requests.\u003C/p\u003E\n\u003Cp\u003EDo not confuse it with \u003Ccode\u003EUserKey\u003C/code\u003E or other identifier types.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-role\u0022\u003E12. Create a Role\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECreateRoleAsync\u003C/code\u003E to create a role.\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } role)\n{\n Console.WriteLine($\u0026quot;Created: {role.Name}\u0026quot;);\n Console.WriteLine($\u0026quot;Role ID: {role.Id}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;RoleInfo\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERole names must be unique within the relevant tenant according to the server\u0027s role-name rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-role-with-permissions\u0022\u003ECreate a Role with Permissions\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECreateRoleRequest\u003C/code\u003E supports an optional initial permission collection.\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;ProductManager\u0026quot;,\n Permissions =\n [\n Permission.From(\u0026quot;products.read\u0026quot;),\n Permission.From(\u0026quot;products.create\u0026quot;),\n Permission.From(\u0026quot;products.update\u0026quot;)\n ]\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows applications to create and configure a role in one operation.\u003C/p\u003E\n\u003Cp\u003EThe server validates the requested permissions and whether the caller is authorized to create the role.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-read-only-role\u0022\u003ECreate a Read-Only Role\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;Auditor\u0026quot;,\n Permissions =\n [\n Permission.From(\u0026quot;reports.read\u0026quot;),\n Permission.From(\u0026quot;products.read\u0026quot;)\n ]\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse role names that describe the responsibility or capability represented by the role.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rename-a-role\u0022\u003E13. Rename a Role\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERenameRoleAsync\u003C/code\u003E to change a role\u0027s name.\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.Authorization.RenameRoleAsync(\n new RenameRoleRequest\n {\n Id = roleId,\n Name = \u0026quot;SeniorManager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role renamed.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ERenameRoleRequest\u003C/code\u003E requires:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EId\u003C/code\u003E: The role\u0027s \u003Ccode\u003ERoleId\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EName\u003C/code\u003E: The new role name.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe operation may fail if another role already uses the requested name.\u003C/p\u003E\n\u003Cp\u003ERole renaming does not mean that permissions are automatically changed.\u003C/p\u003E\n\u003Cp\u003EAfter a successful rename, the SDK publishes an \u003Ccode\u003EAuthorizationChanged\u003C/code\u003E state event.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022set-role-permissions\u0022\u003E14. Set Role Permissions\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E to configure a role\u0027s permissions.\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.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = roleId,\n Permissions =\n [\n Permission.From(\u0026quot;products.read\u0026quot;),\n Permission.From(\u0026quot;products.create\u0026quot;),\n Permission.From(\u0026quot;products.update\u0026quot;)\n ]\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole to modify\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPermissions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENew permission collection\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=\u0022important-this-is-a-replacement-operation\u0022\u003EImportant: This Is a Replacement Operation\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E replaces the role\u0027s existing permission collection.\u003C/p\u003E\n\u003Cp\u003EIt does not append permissions to the current collection.\u003C/p\u003E\n\u003Cp\u003EFor example, suppose a role currently contains:\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\u003Eproducts.read\nproducts.create\nproducts.update\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ESubmitting:\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\u003EPermissions =\n[\n Permission.From(\u0026quot;products.read\u0026quot;)\n]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Emeans the intended new permission collection contains only:\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\u003Eproducts.read\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe other permissions are removed from the role.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022safely-add-a-permission-to-an-existing-role\u0022\u003ESafely Add a Permission to an Existing Role\u003C/h3\u003E\n\u003Cp\u003ETo preserve existing permissions, first retrieve the role and construct the complete desired collection.\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 query = await UAuthClient.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n Search = \u0026quot;ProductManager\u0026quot;,\n PageNumber = 1,\n PageSize = 50\n });\n\nif (query.IsSuccess \u0026amp;\u0026amp; query.Value is { } page)\n{\n var role = page.Items.FirstOrDefault(x =\u0026gt; x.Name == \u0026quot;ProductManager\u0026quot;);\n\n if (role is not null)\n {\n var permissions = role.Permissions.Append(Permission.From(\u0026quot;products.delete\u0026quot;)).Distinct().ToArray();\n\n var result =\n await UAuthClient.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = role.Id,\n Permissions = permissions\n });\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is a read-modify-write pattern, not an atomic append operation.\u003C/p\u003E\n\u003Cp\u003EIf multiple administrators edit the same role concurrently, the last successful replacement may overwrite another administrator\u0027s changes.\u003C/p\u003E\n\u003Cp\u003EApplications requiring concurrency-safe editing should implement suitable coordination or version checking at the application level.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-a-permission\u0022\u003ERemove a Permission\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 updatedPermissions = role.Permissions.Where(x =\u0026gt; x.Value != \u0026quot;products.delete\u0026quot;).ToArray();\n\nvar result = await UAuthClient.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = role.Id,\n Permissions = updatedPermissions\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAgain, submit the complete desired permission set.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022clear-all-permissions\u0022\u003EClear All Permissions\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.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = roleId,\n Permissions = []\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn empty collection requests that the role have no permissions.\u003C/p\u003E\n\u003Cp\u003EThe server determines whether the operation is permitted.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-a-role-to-a-user\u0022\u003E15. Assign a Role to a User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EAssignRoleToUserAsync\u003C/code\u003E to assign an existing role to a 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.Authorization.AssignRoleToUserAsync(\n new AssignRoleRequest\n {\n UserKey = userKey,\n RoleName = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role assigned.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETarget user\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExisting role name\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe role must exist, and the current actor must be authorized to assign it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-multiple-roles\u0022\u003EAssign Multiple Roles\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\u003Eforeach (var roleName in new[] { \u0026quot;Manager\u0026quot;, \u0026quot;Auditor\u0026quot; })\n{\n var result = await UAuthClient.Authorization.AssignRoleToUserAsync(\n new AssignRoleRequest\n {\n UserKey = userKey,\n RoleName = roleName\n });\n\n if (!result.IsSuccess)\n {\n // Handle the failed assignment.\n break;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach call is a separate operation.\u003C/p\u003E\n\u003Cp\u003EThis example does not provide transactional, all-or-nothing behavior across the entire collection.\u003C/p\u003E\n\u003Cp\u003EFor complex provisioning workflows, consider whether the application needs a server-side orchestration operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-a-role-from-a-user\u0022\u003E16. Remove a Role from a User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERemoveRoleFromUserAsync\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.Authorization.RemoveRoleFromUserAsync(\n new RemoveRoleRequest\n {\n UserKey = userKey,\n RoleName = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role removed.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERemoving a role changes the user\u0027s role assignments.\u003C/p\u003E\n\u003Cp\u003EIt does not delete the role itself.\u003C/p\u003E\n\u003Cp\u003EOther users assigned to that role are unaffected by this particular operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-effective-permissions-may-remain\u0022\u003EImportant: Effective Permissions May Remain\u003C/h3\u003E\n\u003Cp\u003EA user may receive the same permission through multiple roles.\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\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\u003EUser\n |\n \u002B-- Manager\n | \u002B-- products.read\n |\n \u002B-- Auditor\n \u002B-- products.read\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERemoving \u003Ccode\u003EManager\u003C/code\u003E does not necessarily remove the user\u0027s ability to read products because \u003Ccode\u003EAuditor\u003C/code\u003E still provides \u003Ccode\u003Eproducts.read\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ETo determine whether the user may perform an action, rely on the server\u0027s authorization decision rather than inspecting one role assignment in isolation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-a-role\u0022\u003E17. Delete a Role\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EDeleteRoleAsync\u003C/code\u003E to delete a role.\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.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Soft\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request supports a deletion mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hard-delete\u0022\u003EHard Delete\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.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Hard\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe availability and effect of each deletion mode are determined by server-side lifecycle rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022roledeleteresult\u0022\u003ERoleDeleteResult\u003C/h3\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;RoleDeleteResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe response contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDeleted role\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERemovedAssignments\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENumber of user-role assignments removed\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EApplied deletion mode\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeletedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDeletion timestamp\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-the-result\u0022\u003EInspect the Result\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.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Soft\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } deleted)\n{\n Console.WriteLine($\u0026quot;Removed assignments: {deleted.RemovedAssignments}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDeleting a role removes its assignments from users.\u003C/p\u003E\n\u003Cp\u003EThis can affect multiple users at once.\u003C/p\u003E\n\u003Cp\u003EFor that reason, role deletion should be presented as a sensitive administrative operation with appropriate confirmation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-role-management-page\u0022\u003E18. Build a Role Management Page\u003C/h2\u003E\n\u003Cp\u003EThe following Blazor example demonstrates querying roles, creating a role, and deleting a selected role.\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.Authorization.Contracts\n@using CodeBeam.UltimateAuth.Core.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Role Management\u0026lt;/h3\u0026gt;\n\n\u0026lt;input @bind=\u0026quot;_search\u0026quot; placeholder=\u0026quot;Search roles\u0026quot; /\u0026gt;\n\n\u0026lt;button @onclick=\u0026quot;LoadRolesAsync\u0026quot;\u0026gt;Search\u0026lt;/button\u0026gt;\n\n@if (_loading)\n{\n \u0026lt;p\u0026gt;Loading roles...\u0026lt;/p\u0026gt;\n}\nelse\n{\n @foreach (var role in _roles)\n {\n \u0026lt;div @key=\u0026quot;role.Id\u0026quot;\u0026gt;\n \u0026lt;strong\u0026gt;@role.Name\u0026lt;/strong\u0026gt;\n \u0026lt;span\u0026gt;@role.Permissions.Count permissions\u0026lt;/span\u0026gt;\n \u0026lt;button @onclick=\u0026quot;() =\u0026gt; DeleteRoleAsync(role.Id)\u0026quot;\u0026gt;Delete\u0026lt;/button\u0026gt;\n \u0026lt;/div\u0026gt;\n }\n}\n\n\u0026lt;h4\u0026gt;Create Role\u0026lt;/h4\u0026gt;\n\u0026lt;input @bind=\u0026quot;_newRoleName\u0026quot; placeholder=\u0026quot;Role name\u0026quot; /\u0026gt;\n\u0026lt;button @onclick=\u0026quot;CreateRoleAsync\u0026quot; disabled=\u0026quot;@_busy\u0026quot;\u0026gt;Create\u0026lt;/button\u0026gt;\n\n@if (_message is not null)\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private IReadOnlyList\u0026lt;RoleInfo\u0026gt; _roles = [];\n\n private string _search = \u0026quot;\u0026quot;;\n private string _newRoleName = \u0026quot;\u0026quot;;\n private string? _message;\n\n private bool _loading = true;\n private bool _busy;\n\n protected override async Task OnInitializedAsync()\n {\n await LoadRolesAsync();\n }\n\n private async Task LoadRolesAsync()\n {\n _loading = true;\n\n try\n {\n var result = await UAuthClient.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n Search = _search,\n PageNumber = 1,\n PageSize = 50\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n {\n _roles = page.Items;\n }\n else\n {\n _message = \u0026quot;Unable to load roles.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _message = \u0026quot;The authorization service is unavailable.\u0026quot;;\n }\n finally\n {\n _loading = false;\n }\n }\n\n private async Task CreateRoleAsync()\n {\n if (_busy || string.IsNullOrWhiteSpace(_newRoleName))\n return;\n\n _busy = true;\n _message = null;\n\n try\n {\n var result = await UAuthClient.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = _newRoleName\n });\n\n if (result.IsSuccess)\n {\n _newRoleName = \u0026quot;\u0026quot;;\n await LoadRolesAsync();\n }\n else\n {\n _message = \u0026quot;Unable to create role.\u0026quot;;\n }\n }\n finally\n {\n _busy = false;\n }\n }\n\n private async Task DeleteRoleAsync(RoleId roleId)\n {\n // Add a confirmation dialog before deletion in production.\n\n var result = await UAuthClient.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Soft\n });\n\n if (result.IsSuccess)\n {\n await LoadRolesAsync();\n }\n else\n {\n _message = \u0026quot;Unable to delete role.\u0026quot;;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example demonstrates the SDK integration rather than a complete production administration interface.\u003C/p\u003E\n\u003Cp\u003EA production implementation should include pagination, role assignment controls, permission editing, deletion confirmation, and appropriate loading and error handling for every operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-user-role-assignment-interface\u0022\u003E19. Build a User Role Assignment Interface\u003C/h2\u003E\n\u003Cp\u003EAn administrative user details page can display assigned roles and allow administrators to add or remove them.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022load-user-roles\u0022\u003ELoad User Roles\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.Authorization.GetUserRolesAsync(userKey,\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 50\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n var assignedRoles = data.Roles.Items.ToList();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-a-selected-role\u0022\u003EAssign a Selected Role\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.Authorization.AssignRoleToUserAsync(\n new AssignRoleRequest\n {\n UserKey = userKey,\n RoleName = selectedRoleName\n });\n\nif (result.IsSuccess)\n{\n // Reload the user\u0027s assigned roles.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-a-selected-role\u0022\u003ERemove a Selected Role\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.Authorization.RemoveRoleFromUserAsync(\n new RemoveRoleRequest\n {\n UserKey = userKey,\n RoleName = selectedRoleName\n });\n\nif (result.IsSuccess)\n{\n // Reload the user\u0027s assigned roles.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis pattern works well alongside \u003Ccode\u003EUAuthClient.Users\u003C/code\u003E for building a unified user administration interface.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/client/user-management\u0022\u003EUser Management\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-state-events\u0022\u003E20. Authorization State Events\u003C/h2\u003E\n\u003Cp\u003EThe current \u003Ccode\u003EUAuthAuthorizationClient\u003C/code\u003E publishes:\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\u003EUAuthStateEvent.AuthorizationChanged\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eafter successful authorization mutations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EPublishes \u003Ccode\u003EAuthorizationChanged\u003C/code\u003E\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAssignRoleToUserAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERemoveRoleFromUserAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERenameRoleAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeleteRoleAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreateRoleAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EQueryRolesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EGetMyRolesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EGetUserRolesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe event follows the configured client state event handling mode.\u003C/p\u003E\n\u003Cp\u003EIt can be used to refresh navigation menus, role assignment views, or permission-sensitive UI.\u003C/p\u003E\n\u003Cp\u003EHowever, it is a \u003Cstrong\u003Elocal client event\u003C/strong\u003E, not a guarantee that all active clients or distributed caches have immediately observed the change.\u003C/p\u003E\n\u003Cp\u003EFor example, an administrator assigning a role to another user does not automatically mean that user\u0027s open browser receives the administrator\u0027s local event.\u003C/p\u003E\n\u003Cp\u003EApplications requiring cross-client updates should provide an appropriate synchronization mechanism.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-changes-and-active-sessions\u0022\u003E21. Authorization Changes and Active Sessions\u003C/h2\u003E\n\u003Cp\u003ERole and permission changes may affect active users.\u003C/p\u003E\n\u003Cp\u003EHowever, the timing of those changes depends on how authorization data is resolved and cached by the server.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EAn administrator removes a role.\u003C/li\u003E\n\u003Cli\u003EThe server persists the new role assignment state.\u003C/li\u003E\n\u003Cli\u003EA subsequent authorization evaluation uses the effective authorization state available to the server.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EIf the application caches authorization information, changes may not become visible everywhere immediately.\u003C/p\u003E\n\u003Cp\u003EDo not assume that a client event invalidates every active session or refreshes every distributed authorization cache.\u003C/p\u003E\n\u003Cp\u003EFor sensitive operations, authorization should be evaluated against sufficiently current server-side state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022roles-are-not-sessions\u0022\u003ERoles Are Not Sessions\u003C/h3\u003E\n\u003Cp\u003ERemoving a role and revoking a session are different operations.\u003C/p\u003E\n\u003Cp\u003ERemoving a role changes authorization data.\u003C/p\u003E\n\u003Cp\u003ERevoking a session changes whether an authentication session remains valid.\u003C/p\u003E\n\u003Cp\u003EApplications may combine these operations when security policy requires immediate termination of access, but the concepts should remain separate.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/client/session-management\u0022\u003ESession Management\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022multi-tenant-authorization\u0022\u003E22. Multi-Tenant Authorization\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth authorization operations are scoped to the current tenant context.\u003C/p\u003E\n\u003Cp\u003ERole names are unique within the relevant tenant, and role assignments belong to the applicable tenant.\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\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\u003ETenant A\n |\n \u002B-- Manager\n | \u002B-- products.read\n |\n \u002B-- User A\n\nTenant B\n |\n \u002B-- Manager\n | \u002B-- reports.read\n |\n \u002B-- User B\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same role name may represent different permission sets in different tenants.\u003C/p\u003E\n\u003Cp\u003EThe client does not gain cross-tenant access merely by providing another user\u0027s \u003Ccode\u003EUserKey\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe server must enforce tenant isolation and administrative authorization.\u003C/p\u003E\n\u003Cp\u003EWhen implementing multi-tenant administration, do not assume role names or permission configurations are globally shared.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-authorization-errors\u0022\u003E23. Handling Authorization Errors\u003C/h2\u003E\n\u003Cp\u003EAuthorization methods return \u003Ccode\u003EUAuthResult\u003C/code\u003E or \u003Ccode\u003EUAuthResult\u0026lt;T\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-role-creation-errors\u0022\u003EHandle Role Creation Errors\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role created.\n}\nelse if (result.Http.IsConflict)\n{\n // A conflicting role may already exist.\n}\nelse if (result.Http.IsForbidden)\n{\n // Caller lacks permission.\n}\nelse if (result.Http.IsBadRequest)\n{\n // Invalid request.\n}\nelse\n{\n // Handle another failure.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-authorization-check-failures\u0022\u003EHandle Authorization Check Failures\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;update\u0026quot;,\n Resource = \u0026quot;products\u0026quot;\n });\n\nvar allowed = result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsAllowed: true };\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis fail-closed pattern is appropriate when determining whether the UI should expose a protected operation.\u003C/p\u003E\n\u003Cp\u003EUse the server\u0027s structured error information for diagnostics where appropriate.\u003C/p\u003E\n\u003Cp\u003EDo not expose sensitive policy internals unnecessarily.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations-1\u0022\u003E24. Security Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-authorization-is-authoritative\u0022\u003EServer Authorization Is Authoritative\u003C/h3\u003E\n\u003Cp\u003EClient-side checks improve usability but cannot protect an endpoint by themselves.\u003C/p\u003E\n\u003Cp\u003EEvery protected operation must enforce authorization on the server.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022follow-least-privilege\u0022\u003EFollow Least Privilege\u003C/h3\u003E\n\u003Cp\u003EAssign only the permissions a role actually needs.\u003C/p\u003E\n\u003Cp\u003EAvoid using \u003Ccode\u003E*\u003C/code\u003E or broad prefix wildcards for ordinary users.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-administrative-operations\u0022\u003EProtect Administrative Operations\u003C/h3\u003E\n\u003Cp\u003ERole creation, permission replacement, role assignment, and role deletion are privileged operations.\u003C/p\u003E\n\u003Cp\u003EThe server must authorize each mutation independently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022treat-permission-replacement-carefully\u0022\u003ETreat Permission Replacement Carefully\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E replaces the entire permission collection.\u003C/p\u003E\n\u003Cp\u003EAlways inspect the existing role configuration before submitting changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-reauthentication-requirements\u0022\u003EHandle Reauthentication Requirements\u003C/h3\u003E\n\u003Cp\u003EAn authorization decision can indicate \u003Ccode\u003ERequiresReauthentication\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EApplications should handle this state explicitly rather than treating it as an ordinary permission denial.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022respect-tenant-boundaries\u0022\u003ERespect Tenant Boundaries\u003C/h3\u003E\n\u003Cp\u003ERoles and assignments must remain within their authorized tenant context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-not-infer-effective-access-from-one-role\u0022\u003EDo Not Infer Effective Access from One Role\u003C/h3\u003E\n\u003Cp\u003EA user can have multiple roles, overlapping permissions, and additional policy restrictions.\u003C/p\u003E\n\u003Cp\u003EUse the server\u0027s authorization decision when determining whether an action is permitted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022account-for-cached-authorization-state\u0022\u003EAccount for Cached Authorization State\u003C/h3\u003E\n\u003Cp\u003EPermission changes may not propagate instantly to every active client or cached authorization context.\u003C/p\u003E\n\u003Cp\u003EDesign sensitive operations around server-side enforcement and appropriate cache invalidation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022putting-everything-together\u0022\u003E25. Putting Everything Together\u003C/h2\u003E\n\u003Cp\u003EA typical administrative authorization workflow may look like this:\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\u003ECreate role\n |\n v\nConfigure permissions\n |\n v\nAssign role to user\n |\n v\nUser requests protected action\n |\n v\nServer evaluates authorization\n |\n \u002B-- Allow --\u0026gt; Execute operation\n |\n \u002B-- Deny --\u0026gt; Reject operation\n |\n \u002B-- Reauthentication required\n |\n v\n Authenticate again\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUltimateAuth separates role management from authorization enforcement.\u003C/p\u003E\n\u003Cp\u003EThis allows applications to provide flexible administrative interfaces while keeping access decisions under server control.\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/plugin-domains/policies-access-control\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E \u2014 Understand application policies and access rules.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/authorization-pipeline-internals\u0022\u003EAuthorization Pipeline Internals\u003C/a\u003E \u2014 Explore how authorization decisions are evaluated.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/user-management\u0022\u003EUser Management\u003C/a\u003E \u2014 Manage user accounts and lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Manage authenticated sessions and devices.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand session validity and security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth provides a unified client API for managing roles, assigning permissions, controlling user-role relationships, and requesting authorization decisions from the server.\u003C/p\u003E\n\u003Cp\u003EThrough \u003Ccode\u003EUAuthClient.Authorization\u003C/code\u003E, applications can build administrative authorization interfaces and check whether the current user is allowed to perform a particular action.\u003C/p\u003E\n\u003Cp\u003EAuthorization is enforced on the server. Client-side permission checks are useful for improving the user experience, but they do not replace authorization at the endpoint or application-service level.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the current \u003Ccode\u003EIAuthorizationClient\u003C/code\u003E API, practical role management workflows, permission patterns, and secure client integration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-authorization\u0022\u003E1. Understanding Authorization\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth authorization combines several concepts:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcept\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\u003ERoles\u003C/td\u003E\n\u003Ctd\u003ENamed collections of permissions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPermissions\u003C/td\u003E\n\u003Ctd\u003EFine-grained capabilities represented by normalized values\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUser-role assignments\u003C/td\u003E\n\u003Ctd\u003EAssociations between users and roles\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPolicies\u003C/td\u003E\n\u003Ctd\u003EServer-side rules that participate in access decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthorization context\u003C/td\u003E\n\u003Ctd\u003EInformation about the actor, resource, action, and current security state\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA typical authorization relationship looks like this:\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\u003EUser\n |\n \u002B-- Manager\n | |\n | \u002B-- products.read\n | \u002B-- products.create\n | \u002B-- products.update\n |\n \u002B-- Auditor\n |\n \u002B-- reports.read\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA user can have multiple roles, and each role can contain multiple permissions.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Cstrong\u003Ehaving a permission does not automatically guarantee access\u003C/strong\u003E. The server\u0027s authorization pipeline may apply additional requirements, such as resource ownership, session validity, tenant isolation, or application policies.\u003C/p\u003E\n\u003Cp\u003EFor the internal decision model, see \u003Ca href=\u0022/docs/security/authorization-pipeline\u0022\u003EAuthorization Pipeline Internals\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-authorization-client\u0022\u003E2. Accessing the Authorization Client\u003C/h2\u003E\n\u003Cp\u003EInject \u003Ccode\u003EIUAuthClient\u003C/code\u003E into a Blazor 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\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAuthorization operations are available through:\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\u003EUAuthClient.Authorization\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis property exposes \u003Ccode\u003EIAuthorizationClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe interface provides three main groups of operations:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthorization checks:\u003C/strong\u003E Evaluate whether the current user may perform an action.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERole management:\u003C/strong\u003E Create, query, rename, configure, and delete roles.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUser-role management:\u003C/strong\u003E Retrieve, assign, and remove user roles.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe SDK sends requests to the corresponding UltimateAuth endpoints and returns structured \u003Ccode\u003EUAuthResult\u003C/code\u003E values.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-permissions\u0022\u003E3. Understanding Permissions\u003C/h2\u003E\n\u003Cp\u003EA permission is represented by the \u003Ccode\u003EPermission\u003C/code\u003E value 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\u003Evar permission = Permission.From(\u0026quot;products.create\u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe current implementation normalizes permission values by trimming surrounding whitespace and converting the value to lowercase.\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\u003Evar permission = Permission.From(\u0026quot; PRODUCTS.CREATE \u0026quot;);\n\nConsole.WriteLine(permission.Value);\n// products.create\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe constructor rejects null, empty, and whitespace-only values.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-patterns\u0022\u003EPermission Patterns\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth recognizes three important forms:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPattern\u003C/th\u003E\n\u003Cth\u003EExample\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\u003EExact\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eproducts.create\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EA specific permission\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPrefix wildcard\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eproducts.*\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPermissions within the corresponding prefix\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EGlobal wildcard\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E*\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUnrestricted permission matching within the applicable authorization rules\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ECreate them using:\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 exact = Permission.From(\u0026quot;products.create\u0026quot;);\n\nvar prefix = Permission.From(\u0026quot;products.*\u0026quot;);\n\nvar global = Permission.Wildcard;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe type also exposes:\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\u003Epermission.IsWildcard;\npermission.IsPrefix;\npermission.IsValid;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-normalization\u0022\u003EPermission Normalization\u003C/h3\u003E\n\u003Cp\u003EThe following values normalize to the same representation:\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\u003EPermission.From(\u0026quot;Products.Create\u0026quot;);\nPermission.From(\u0026quot;products.create\u0026quot;);\nPermission.From(\u0026quot; PRODUCTS.CREATE \u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAll produce:\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\u003Eproducts.create\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse consistent permission names across application services, policies, and role configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022wildcards-are-powerful\u0022\u003EWildcards Are Powerful\u003C/h3\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\u003EPermission.From(\u0026quot;users.*\u0026quot;)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Emay grant broad access to operations within the \u003Ccode\u003Eusers\u003C/code\u003E permission prefix.\u003C/p\u003E\n\u003Cp\u003EThe global wildcard:\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\u003EPermission.Wildcard\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eis especially sensitive.\u003C/p\u003E\n\u003Cp\u003EAvoid assigning broad wildcard permissions merely to simplify development. Use least-privilege permissions for ordinary application roles.\u003C/p\u003E\n\u003Cp\u003EA wildcard is not a substitute for satisfying independent security policies, authentication requirements, or tenant restrictions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-authorization\u0022\u003E4. Check Authorization\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E evaluates whether the current authenticated user is authorized to perform a specific action against a resource.\u003C/p\u003E\n\u003Cp\u003EIt provides an \u003Cstrong\u003Eon-demand, server-side authorization decision\u003C/strong\u003E based on the current authentication context, permissions, roles, and applicable authorization policies.\u003C/p\u003E\n\u003Cp\u003EUnlike operations that perform protected actions, \u003Ccode\u003ECheckAsync\u003C/code\u003E only evaluates access. It does not execute the requested operation, modify permissions, grant access, or reserve authorization for a future request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-to-use-checkasync\u0022\u003EWhen to Use CheckAsync\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECheckAsync\u003C/code\u003E when your application needs to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDetermine whether an authenticated user can perform an action before presenting it in the UI.\u003C/li\u003E\n\u003Cli\u003EEnable, disable, or hide controls based on the current authorization decision.\u003C/li\u003E\n\u003Cli\u003EExplain why an operation is unavailable, using the returned denial reason where appropriate.\u003C/li\u003E\n\u003Cli\u003EDetermine whether an operation requires reauthentication.\u003C/li\u003E\n\u003Cli\u003EEvaluate resource-specific access when the applicable policies support it.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, an application may check whether the current user can delete a particular order before displaying the Delete button.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-checkasync-does-not-guarantee\u0022\u003EWhat CheckAsync Does Not Guarantee\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EA successful authorization check is not a guarantee that a subsequent operation will succeed.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAuthorization is evaluated against the security context available at the time of the check. That context may change before the actual operation is performed.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn administrator may revoke the user\u0027s permissions.\u003C/li\u003E\n\u003Cli\u003EThe user\u0027s session may expire or become invalid.\u003C/li\u003E\n\u003Cli\u003EThe target resource may be deleted or modified.\u003C/li\u003E\n\u003Cli\u003EThe resource\u0027s ownership or tenant context may change.\u003C/li\u003E\n\u003Cli\u003EAdditional business rules may prevent the operation.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe protected operation must independently enforce authorization using its current context.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ENever use a previous \u003Ccode\u003ECheckAsync\u003C/code\u003E result as proof of authorization for a later request.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E is primarily a developer-experience and user-experience API. It helps applications understand and present authorization decisions without replacing the security enforcement performed by protected endpoints and application services.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECheckAsync\u003C/code\u003E to ask the server whether the current user is authorized to perform an action.\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;read\u0026quot;,\n Resource = \u0026quot;products\u0026quot;\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsAllowed: true })\n{\n // The server authorized this action.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;AuthorizationResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorizationcheckrequest\u0022\u003EAuthorizationCheckRequest\u003C/h3\u003E\n\u003Cp\u003EThe request contains:\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\u003EType\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\u003EAction\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResource\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResource being accessed\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResourceId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional identifier for a specific resource instance\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EBoth \u003Ccode\u003EAction\u003C/code\u003E and \u003Ccode\u003EResource\u003C/code\u003E are required.\u003C/p\u003E\n\u003Cp\u003EThe action and resource values must follow the conventions understood by your server-side authorization configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-a-specific-resource\u0022\u003ECheck a Specific Resource\u003C/h3\u003E\n\u003Cp\u003EFor example, check access to a particular product:\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;update\u0026quot;,\n Resource = \u0026quot;products\u0026quot;,\n ResourceId = productId.ToString()\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EProviding \u003Ccode\u003EResourceId\u003C/code\u003E allows the server to evaluate resource-specific rules where supported by the configured authorization pipeline.\u003C/p\u003E\n\u003Cp\u003EFor example, the application may distinguish between updating any product and updating a product owned by the current user.\u003C/p\u003E\n\u003Cp\u003EDo not assume that sending \u003Ccode\u003EResourceId\u003C/code\u003E automatically enables ownership checking. The relevant server-side policy must implement the required rule.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-authorization-results\u0022\u003E5. Understanding Authorization Results\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E returns an \u003Ccode\u003EAuthorizationResult\u003C/code\u003E with 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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAllowed\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the request was authorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequiresReauthentication\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the decision indicates reauthentication is required\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDenyReason\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional denial reason code\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=\u0022authorization-decisions-vs.http-results\u0022\u003EAuthorization Decisions vs. HTTP Results\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E separates the outcome of the API request from the authorization decision itself.\u003C/p\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003EUAuthResult\u0026lt;AuthorizationResult\u0026gt;\u003C/code\u003E:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthResult.IsSuccess\u003C/code\u003E indicates whether the authorization check request completed successfully.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAuthorizationResult.IsAllowed\u003C/code\u003E indicates whether the requested action is currently authorized.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAuthorizationResult.RequiresReauthentication\u003C/code\u003E indicates whether additional authentication is required.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAuthorizationResult.DenyReason\u003C/code\u003E provides an optional reason for denial.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA denied authorization decision is a valid result of a successful authorization check.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EScenario\u003C/th\u003E\n\u003Cth\u003EHTTP Status\u003C/th\u003E\n\u003Cth\u003EAuthorization Result\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAction allowed\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E200 OK\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAllowed = true\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAction denied\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E200 OK\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAllowed = false\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReauthentication required\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E200 OK\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequiresReauthentication = true\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECaller not authenticated\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E401 Unauthorized\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo authorization decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInvalid request\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E400 Bad Request\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo authorization decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EServer-side evaluation failure\u003C/td\u003E\n\u003Ctd\u003EAppropriate error status\u003C/td\u003E\n\u003Ctd\u003ENo authorization decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EHTTP 200 means the authorization evaluation completed successfully. It does not mean access was granted.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EOnly authenticated callers can use the current \u003Ccode\u003ECheckAsync\u003C/code\u003E endpoint. However, callers do not need permission to perform the action they are checking.\u003C/p\u003E\n\u003Cp\u003EThis distinction is intentional: the purpose of \u003Ccode\u003ECheckAsync\u003C/code\u003E is to determine whether the action is permitted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-denied-decisions\u0022\u003EHandling Denied Decisions\u003C/h3\u003E\n\u003Cp\u003EApplications should distinguish between authorization denial, reauthentication requirements, and request failures.\u003C/p\u003E\n\u003Cp\u003EA denied decision may be used to disable an action or display an appropriate message.\u003C/p\u003E\n\u003Cp\u003EA reauthentication requirement may be used to initiate the application\u0027s supported reauthentication flow.\u003C/p\u003E\n\u003Cp\u003EA failed request must not be interpreted as permission to proceed.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EFail closed:\u003C/strong\u003E If the authorization decision is unavailable, treat the operation as unavailable in the UI and rely on server-side authorization when the actual operation is requested.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-all-decision-outcomes\u0022\u003EHandle All Decision Outcomes\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;delete\u0026quot;,\n Resource = \u0026quot;products\u0026quot;,\n ResourceId = productId.ToString()\n });\n\nif (!result.IsSuccess)\n{\n // The authorization request itself failed.\n}\nelse if (result.Value is { RequiresReauthentication: true })\n{\n // Ask the user to authenticate again.\n}\nelse if (result.Value is { IsAllowed: true })\n{\n // The operation is allowed.\n}\nelse\n{\n // The operation was denied.\n var reason = result.Value?.DenyReason;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn unsuccessful API request and an explicit authorization denial are different outcomes.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003Eresult.IsSuccess == false\u003C/code\u003E means the authorization request did not complete successfully at the API level.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eresult.Value.IsAllowed == false\u003C/code\u003E means the server returned a denial decision.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eresult.Value.RequiresReauthentication == true\u003C/code\u003E indicates that the caller needs to reestablish authentication.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ETreat failed or unavailable authorization checks as \u003Cstrong\u003Enot authorized\u003C/strong\u003E in the UI.\u003C/p\u003E\n\u003Cp\u003ENever grant access because an authorization request failed.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-authorization-checks-in-blazor\u0022\u003E6. Use Authorization Checks in Blazor\u003C/h2\u003E\n\u003Cp\u003EA common use case is controlling whether an action button should be displayed.\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.Authorization.Contracts\n@inject IUAuthClient UAuthClient\n\n@if (_canCreate)\n{\n \u0026lt;button @onclick=\u0026quot;CreateProductAsync\u0026quot;\u0026gt;Create Product\u0026lt;/button\u0026gt;\n}\n\n@code {\n private bool _canCreate;\n\n protected override async Task OnInitializedAsync()\n {\n var result = await UAuthClient.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;create\u0026quot;,\n Resource = \u0026quot;products\u0026quot;\n });\n\n _canCreate = result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsAllowed: true };\n }\n\n private Task CreateProductAsync()\n {\n // Invoke the protected application operation.\n return Task.CompletedTask;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis improves the interface by hiding unavailable actions.\u003C/p\u003E\n\u003Cp\u003EHowever, the endpoint handling product creation must still enforce authorization independently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-client-checks-are-not-security-boundaries\u0022\u003EWhy Client Checks Are Not Security Boundaries\u003C/h3\u003E\n\u003Cp\u003EA user can bypass a hidden button and send a request directly to an endpoint.\u003C/p\u003E\n\u003Cp\u003ETherefore:\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\u003EClient authorization check\n |\n v\nImprove the user experience\n |\n v\nUser submits an operation\n |\n v\nServer independently authorizes the operation\n |\n v\nExecute or deny\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EClient authorization checks should never be the only protection for sensitive operations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-my-roles\u0022\u003E7. Get My Roles\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMyRolesAsync\u003C/code\u003E to retrieve roles assigned to the current authenticated 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.Authorization.GetMyRolesAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n foreach (var role in data.Roles.Items)\n {\n Console.WriteLine(role.Name);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;UserRolesGetResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userrolesgetresult\u0022\u003EUserRolesGetResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser whose roles were requested\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoles\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPaginated collection of role assignments\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EEach entry is a \u003Ccode\u003EUserRoleInfo\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userroleinfo\u0022\u003EUserRoleInfo\u003C/h3\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant associated with the assignment\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssigned user\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssigned role\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole name\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAssignedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssignment timestamp\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=\u0022pagination\u0022\u003EPagination\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.Authorization.GetMyRolesAsync(\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe returned role collection is paginated.\u003C/p\u003E\n\u003Cp\u003EDo not assume that the first page contains all assigned roles.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-my-permissions\u0022\u003E8. Get My Permissions\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMyPermissionsAsync()\u003C/code\u003E to retrieve the permissions assigned to the currently authenticated user through their roles.\u003C/p\u003E\n\u003Cp\u003EUnlike \u003Ccode\u003EGetMyRolesAsync()\u003C/code\u003E, which returns role assignments, this method returns the resulting permission collection.\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.Authorization.GetMyPermissionsAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n foreach (var permission in data.Permissions)\n {\n Console.WriteLine(permission.Value);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method is self-service and does not require administrative permission.\u003C/p\u003E\n\u003Cp\u003EThe user identity and tenant context are derived from the authenticated session. No user identifier or tenant identifier is accepted as a request parameter.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userpermissionsresult\u0022\u003EUserPermissionsResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains the user\u0027s role-derived permissions:\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 UserPermissionsResult\n{\n public required IReadOnlyList\u0026lt;Permission\u0026gt; Permissions { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe returned collection:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECombines permissions from the user\u0027s assigned roles.\u003C/li\u003E\n\u003Cli\u003ERemoves duplicate permissions.\u003C/li\u003E\n\u003Cli\u003EUses deterministic ordering.\u003C/li\u003E\n\u003Cli\u003EPreserves wildcard permissions such as \u003Ccode\u003Eusers.*\u003C/code\u003E and \u003Ccode\u003E*\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EReturns an empty collection when no permissions are assigned.\u003C/li\u003E\n\u003Cli\u003EReflects the current permissions resolved from the configured authorization stores.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, a user assigned multiple roles may receive:\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\u003Ereports.*\nusers.read.admin\nusers.update.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe result does not identify which role contributed each permission.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assigned-permissions-vs.authorization-decisions\u0022\u003EAssigned Permissions vs. Authorization Decisions\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EAssigned permissions are not final authorization decisions.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA user may possess a permission but still be denied access because of an applicable policy, tenant boundary, target restriction, or other security requirement.\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\u003Evar permissions = await UAuthClient.Authorization.GetMyPermissionsAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis retrieves the user\u0027s assigned permissions.\u003C/p\u003E\n\u003Cp\u003ETo determine whether a specific operation is currently authorized, use \u003Ccode\u003ECheckAsync()\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;users.delete.admin\u0026quot;,\n Resource = \u0026quot;users\u0026quot;,\n ResourceId = targetUserKey.Value\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } decision)\n{\n if (decision.IsAllowed)\n {\n // The authorization check allowed this operation.\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEven a successful client-side authorization check does not replace authorization enforcement at the server-side operation boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022wildcard-permissions\u0022\u003EWildcard Permissions\u003C/h3\u003E\n\u003Cp\u003EWildcard permissions are returned without expansion.\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\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\u003Eusers.*\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eremains \u003Ccode\u003Eusers.*\u003C/code\u003E rather than being expanded into individual user-management actions.\u003C/p\u003E\n\u003Cp\u003EThis preserves the stored permission semantics and avoids depending on a potentially incomplete action catalog.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003ESecurity Considerations\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EGetMyPermissionsAsync()\u003C/code\u003E is intended for permission inspection, user interfaces, and administrative transparency.\u003C/p\u003E\n\u003Cp\u003EIt must not be used as the sole mechanism for protecting sensitive operations.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for evaluating authorization policies and enforcing access at the actual execution boundary.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/client-sdk/authorization#4-check-authorization\u0022\u003ECheck Authorization\u003C/a\u003E for policy-aware authorization checks.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-another-users-roles\u0022\u003E9. Get Another User\u0027s Roles\u003C/h2\u003E\n\u003Cp\u003EAdministrative interfaces can retrieve the roles assigned to a specific 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.Authorization.GetUserRolesAsync(userKey,\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n foreach (var role in data.Roles.Items)\n {\n Console.WriteLine($\u0026quot;{role.Name} \u2014 {role.AssignedAt}\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis operation requires appropriate administrative authorization.\u003C/p\u003E\n\u003Cp\u003EThe \u003Ccode\u003EUserKey\u003C/code\u003E identifies the target user, not the current actor.\u003C/p\u003E\n\u003Cp\u003EThe server determines whether the current actor may inspect that user\u0027s role assignments.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022query-roles\u0022\u003E10. Query Roles\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EQueryRolesAsync\u003C/code\u003E to retrieve roles with pagination and optional filtering.\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var role in page.Items)\n {\n Console.WriteLine(role.Name);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;PagedResult\u0026lt;RoleInfo\u0026gt;\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rolequery\u0022\u003ERoleQuery\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ERoleQuery\u003C/code\u003E inherits from \u003Ccode\u003EPageRequest\u003C/code\u003E and adds:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESearch\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional role-name search\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIncludeDeleted\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EInclude deleted roles when supported and authorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPageNumber\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested page\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPageSize\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested page size\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=\u0022search-roles\u0022\u003ESearch Roles\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n Search = \u0026quot;manager\u0026quot;,\n PageNumber = 1,\n PageSize = 10\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022include-deleted-roles\u0022\u003EInclude Deleted Roles\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n IncludeDeleted = true,\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003EIncludeDeleted\u003C/code\u003E only where the application needs administrative access to deleted role records.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for enforcing the query\u0027s permissions and lifecycle rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-roleinfo\u0022\u003E11. Understanding RoleInfo\u003C/h2\u003E\n\u003Cp\u003EEach role returned by the SDK is represented by \u003Ccode\u003ERoleInfo\u003C/code\u003E.\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\u003EType\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\u003EId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole name\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPermissions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIReadOnlyCollection\u0026lt;Permission\u0026gt;\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssigned permissions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreation timestamp\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUpdatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELast update timestamp\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var role in page.Items)\n {\n Console.WriteLine($\u0026quot;Role: {role.Name}\u0026quot;);\n\n foreach (var permission in role.Permissions)\n {\n Console.WriteLine($\u0026quot; {permission.Value}\u0026quot;);\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ERoleId\u003C/code\u003E is a strongly typed identifier rather than a plain \u003Ccode\u003EGuid\u003C/code\u003E.\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\u003ERoleId roleId = RoleId.From(existingGuid);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERoleId\u003C/code\u003E for role management requests.\u003C/p\u003E\n\u003Cp\u003EDo not confuse it with \u003Ccode\u003EUserKey\u003C/code\u003E or other identifier types.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-role\u0022\u003E12. Create a Role\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECreateRoleAsync\u003C/code\u003E to create a role.\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } role)\n{\n Console.WriteLine($\u0026quot;Created: {role.Name}\u0026quot;);\n Console.WriteLine($\u0026quot;Role ID: {role.Id}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;RoleInfo\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERole names must be unique within the relevant tenant according to the server\u0027s role-name rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-role-with-permissions\u0022\u003ECreate a Role with Permissions\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECreateRoleRequest\u003C/code\u003E supports an optional initial permission collection.\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;ProductManager\u0026quot;,\n Permissions =\n [\n Permission.From(\u0026quot;products.read\u0026quot;),\n Permission.From(\u0026quot;products.create\u0026quot;),\n Permission.From(\u0026quot;products.update\u0026quot;)\n ]\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows applications to create and configure a role in one operation.\u003C/p\u003E\n\u003Cp\u003EThe server validates the requested permissions and whether the caller is authorized to create the role.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-read-only-role\u0022\u003ECreate a Read-Only Role\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;Auditor\u0026quot;,\n Permissions =\n [\n Permission.From(\u0026quot;reports.read\u0026quot;),\n Permission.From(\u0026quot;products.read\u0026quot;)\n ]\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse role names that describe the responsibility or capability represented by the role.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rename-a-role\u0022\u003E13. Rename a Role\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERenameRoleAsync\u003C/code\u003E to change a role\u0027s name.\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.Authorization.RenameRoleAsync(\n new RenameRoleRequest\n {\n Id = roleId,\n Name = \u0026quot;SeniorManager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role renamed.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ERenameRoleRequest\u003C/code\u003E requires:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EId\u003C/code\u003E: The role\u0027s \u003Ccode\u003ERoleId\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EName\u003C/code\u003E: The new role name.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe operation may fail if another role already uses the requested name.\u003C/p\u003E\n\u003Cp\u003ERole renaming does not mean that permissions are automatically changed.\u003C/p\u003E\n\u003Cp\u003EAfter a successful rename, the SDK publishes an \u003Ccode\u003EAuthorizationChanged\u003C/code\u003E state event.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022set-role-permissions\u0022\u003E14. Set Role Permissions\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E to configure a role\u0027s permissions.\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.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = roleId,\n Permissions =\n [\n Permission.From(\u0026quot;products.read\u0026quot;),\n Permission.From(\u0026quot;products.create\u0026quot;),\n Permission.From(\u0026quot;products.update\u0026quot;)\n ]\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole to modify\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPermissions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENew permission collection\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=\u0022important-this-is-a-replacement-operation\u0022\u003EImportant: This Is a Replacement Operation\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E replaces the role\u0027s existing permission collection.\u003C/p\u003E\n\u003Cp\u003EIt does not append permissions to the current collection.\u003C/p\u003E\n\u003Cp\u003EFor example, suppose a role currently contains:\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\u003Eproducts.read\nproducts.create\nproducts.update\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ESubmitting:\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\u003EPermissions =\n[\n Permission.From(\u0026quot;products.read\u0026quot;)\n]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Emeans the intended new permission collection contains only:\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\u003Eproducts.read\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe other permissions are removed from the role.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022safely-add-a-permission-to-an-existing-role\u0022\u003ESafely Add a Permission to an Existing Role\u003C/h3\u003E\n\u003Cp\u003ETo preserve existing permissions, first retrieve the role and construct the complete desired collection.\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 query = await UAuthClient.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n Search = \u0026quot;ProductManager\u0026quot;,\n PageNumber = 1,\n PageSize = 50\n });\n\nif (query.IsSuccess \u0026amp;\u0026amp; query.Value is { } page)\n{\n var role = page.Items.FirstOrDefault(x =\u0026gt; x.Name == \u0026quot;ProductManager\u0026quot;);\n\n if (role is not null)\n {\n var permissions = role.Permissions.Append(Permission.From(\u0026quot;products.delete\u0026quot;)).Distinct().ToArray();\n\n var result =\n await UAuthClient.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = role.Id,\n Permissions = permissions\n });\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is a read-modify-write pattern, not an atomic append operation.\u003C/p\u003E\n\u003Cp\u003EIf multiple administrators edit the same role concurrently, the last successful replacement may overwrite another administrator\u0027s changes.\u003C/p\u003E\n\u003Cp\u003EApplications requiring concurrency-safe editing should implement suitable coordination or version checking at the application level.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-a-permission\u0022\u003ERemove a Permission\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 updatedPermissions = role.Permissions.Where(x =\u0026gt; x.Value != \u0026quot;products.delete\u0026quot;).ToArray();\n\nvar result = await UAuthClient.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = role.Id,\n Permissions = updatedPermissions\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAgain, submit the complete desired permission set.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022clear-all-permissions\u0022\u003EClear All Permissions\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.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = roleId,\n Permissions = []\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn empty collection requests that the role have no permissions.\u003C/p\u003E\n\u003Cp\u003EThe server determines whether the operation is permitted.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-a-role-to-a-user\u0022\u003E15. Assign a Role to a User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EAssignRoleToUserAsync\u003C/code\u003E to assign an existing role to a 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.Authorization.AssignRoleToUserAsync(\n new AssignRoleRequest\n {\n UserKey = userKey,\n RoleName = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role assigned.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETarget user\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExisting role name\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe role must exist, and the current actor must be authorized to assign it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-multiple-roles\u0022\u003EAssign Multiple Roles\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\u003Eforeach (var roleName in new[] { \u0026quot;Manager\u0026quot;, \u0026quot;Auditor\u0026quot; })\n{\n var result = await UAuthClient.Authorization.AssignRoleToUserAsync(\n new AssignRoleRequest\n {\n UserKey = userKey,\n RoleName = roleName\n });\n\n if (!result.IsSuccess)\n {\n // Handle the failed assignment.\n break;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach call is a separate operation.\u003C/p\u003E\n\u003Cp\u003EThis example does not provide transactional, all-or-nothing behavior across the entire collection.\u003C/p\u003E\n\u003Cp\u003EFor complex provisioning workflows, consider whether the application needs a server-side orchestration operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-a-role-from-a-user\u0022\u003E16. Remove a Role from a User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERemoveRoleFromUserAsync\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.Authorization.RemoveRoleFromUserAsync(\n new RemoveRoleRequest\n {\n UserKey = userKey,\n RoleName = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role removed.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERemoving a role changes the user\u0027s role assignments.\u003C/p\u003E\n\u003Cp\u003EIt does not delete the role itself.\u003C/p\u003E\n\u003Cp\u003EOther users assigned to that role are unaffected by this particular operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-effective-permissions-may-remain\u0022\u003EImportant: Effective Permissions May Remain\u003C/h3\u003E\n\u003Cp\u003EA user may receive the same permission through multiple roles.\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\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\u003EUser\n |\n \u002B-- Manager\n | \u002B-- products.read\n |\n \u002B-- Auditor\n \u002B-- products.read\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERemoving \u003Ccode\u003EManager\u003C/code\u003E does not necessarily remove the user\u0027s ability to read products because \u003Ccode\u003EAuditor\u003C/code\u003E still provides \u003Ccode\u003Eproducts.read\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ETo determine whether the user may perform an action, rely on the server\u0027s authorization decision rather than inspecting one role assignment in isolation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-a-role\u0022\u003E17. Delete a Role\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EDeleteRoleAsync\u003C/code\u003E to delete a role.\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.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Soft\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request supports a deletion mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hard-delete\u0022\u003EHard Delete\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.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Hard\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe availability and effect of each deletion mode are determined by server-side lifecycle rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022roledeleteresult\u0022\u003ERoleDeleteResult\u003C/h3\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;RoleDeleteResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe response contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDeleted role\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERemovedAssignments\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENumber of user-role assignments removed\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EApplied deletion mode\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeletedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDeletion timestamp\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-the-result\u0022\u003EInspect the Result\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.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Soft\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } deleted)\n{\n Console.WriteLine($\u0026quot;Removed assignments: {deleted.RemovedAssignments}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDeleting a role removes its assignments from users.\u003C/p\u003E\n\u003Cp\u003EThis can affect multiple users at once.\u003C/p\u003E\n\u003Cp\u003EFor that reason, role deletion should be presented as a sensitive administrative operation with appropriate confirmation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-role-management-page\u0022\u003E18. Build a Role Management Page\u003C/h2\u003E\n\u003Cp\u003EThe following Blazor example demonstrates querying roles, creating a role, and deleting a selected role.\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.Authorization.Contracts\n@using CodeBeam.UltimateAuth.Core.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Role Management\u0026lt;/h3\u0026gt;\n\n\u0026lt;input @bind=\u0026quot;_search\u0026quot; placeholder=\u0026quot;Search roles\u0026quot; /\u0026gt;\n\n\u0026lt;button @onclick=\u0026quot;LoadRolesAsync\u0026quot;\u0026gt;Search\u0026lt;/button\u0026gt;\n\n@if (_loading)\n{\n \u0026lt;p\u0026gt;Loading roles...\u0026lt;/p\u0026gt;\n}\nelse\n{\n @foreach (var role in _roles)\n {\n \u0026lt;div @key=\u0026quot;role.Id\u0026quot;\u0026gt;\n \u0026lt;strong\u0026gt;@role.Name\u0026lt;/strong\u0026gt;\n \u0026lt;span\u0026gt;@role.Permissions.Count permissions\u0026lt;/span\u0026gt;\n \u0026lt;button @onclick=\u0026quot;() =\u0026gt; DeleteRoleAsync(role.Id)\u0026quot;\u0026gt;Delete\u0026lt;/button\u0026gt;\n \u0026lt;/div\u0026gt;\n }\n}\n\n\u0026lt;h4\u0026gt;Create Role\u0026lt;/h4\u0026gt;\n\u0026lt;input @bind=\u0026quot;_newRoleName\u0026quot; placeholder=\u0026quot;Role name\u0026quot; /\u0026gt;\n\u0026lt;button @onclick=\u0026quot;CreateRoleAsync\u0026quot; disabled=\u0026quot;@_busy\u0026quot;\u0026gt;Create\u0026lt;/button\u0026gt;\n\n@if (_message is not null)\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private IReadOnlyList\u0026lt;RoleInfo\u0026gt; _roles = [];\n\n private string _search = \u0026quot;\u0026quot;;\n private string _newRoleName = \u0026quot;\u0026quot;;\n private string? _message;\n\n private bool _loading = true;\n private bool _busy;\n\n protected override async Task OnInitializedAsync()\n {\n await LoadRolesAsync();\n }\n\n private async Task LoadRolesAsync()\n {\n _loading = true;\n\n try\n {\n var result = await UAuthClient.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n Search = _search,\n PageNumber = 1,\n PageSize = 50\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n {\n _roles = page.Items;\n }\n else\n {\n _message = \u0026quot;Unable to load roles.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _message = \u0026quot;The authorization service is unavailable.\u0026quot;;\n }\n finally\n {\n _loading = false;\n }\n }\n\n private async Task CreateRoleAsync()\n {\n if (_busy || string.IsNullOrWhiteSpace(_newRoleName))\n return;\n\n _busy = true;\n _message = null;\n\n try\n {\n var result = await UAuthClient.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = _newRoleName\n });\n\n if (result.IsSuccess)\n {\n _newRoleName = \u0026quot;\u0026quot;;\n await LoadRolesAsync();\n }\n else\n {\n _message = \u0026quot;Unable to create role.\u0026quot;;\n }\n }\n finally\n {\n _busy = false;\n }\n }\n\n private async Task DeleteRoleAsync(RoleId roleId)\n {\n // Add a confirmation dialog before deletion in production.\n\n var result = await UAuthClient.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Soft\n });\n\n if (result.IsSuccess)\n {\n await LoadRolesAsync();\n }\n else\n {\n _message = \u0026quot;Unable to delete role.\u0026quot;;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example demonstrates the SDK integration rather than a complete production administration interface.\u003C/p\u003E\n\u003Cp\u003EA production implementation should include pagination, role assignment controls, permission editing, deletion confirmation, and appropriate loading and error handling for every operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-user-role-assignment-interface\u0022\u003E19. Build a User Role Assignment Interface\u003C/h2\u003E\n\u003Cp\u003EAn administrative user details page can display assigned roles and allow administrators to add or remove them.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022load-user-roles\u0022\u003ELoad User Roles\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.Authorization.GetUserRolesAsync(userKey,\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 50\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n var assignedRoles = data.Roles.Items.ToList();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-a-selected-role\u0022\u003EAssign a Selected Role\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.Authorization.AssignRoleToUserAsync(\n new AssignRoleRequest\n {\n UserKey = userKey,\n RoleName = selectedRoleName\n });\n\nif (result.IsSuccess)\n{\n // Reload the user\u0027s assigned roles.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-a-selected-role\u0022\u003ERemove a Selected Role\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.Authorization.RemoveRoleFromUserAsync(\n new RemoveRoleRequest\n {\n UserKey = userKey,\n RoleName = selectedRoleName\n });\n\nif (result.IsSuccess)\n{\n // Reload the user\u0027s assigned roles.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis pattern works well alongside \u003Ccode\u003EUAuthClient.Users\u003C/code\u003E for building a unified user administration interface.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/client-sdk/user-management\u0022\u003EUser Management\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-state-events\u0022\u003E20. Authorization State Events\u003C/h2\u003E\n\u003Cp\u003EThe current \u003Ccode\u003EUAuthAuthorizationClient\u003C/code\u003E publishes:\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\u003EUAuthStateEvent.AuthorizationChanged\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eafter successful authorization mutations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EPublishes \u003Ccode\u003EAuthorizationChanged\u003C/code\u003E\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAssignRoleToUserAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERemoveRoleFromUserAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERenameRoleAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeleteRoleAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreateRoleAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EQueryRolesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EGetMyRolesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EGetUserRolesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe event follows the configured client state event handling mode.\u003C/p\u003E\n\u003Cp\u003EIt can be used to refresh navigation menus, role assignment views, or permission-sensitive UI.\u003C/p\u003E\n\u003Cp\u003EHowever, it is a \u003Cstrong\u003Elocal client event\u003C/strong\u003E, not a guarantee that all active clients or distributed caches have immediately observed the change.\u003C/p\u003E\n\u003Cp\u003EFor example, an administrator assigning a role to another user does not automatically mean that user\u0027s open browser receives the administrator\u0027s local event.\u003C/p\u003E\n\u003Cp\u003EApplications requiring cross-client updates should provide an appropriate synchronization mechanism.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-changes-and-active-sessions\u0022\u003E21. Authorization Changes and Active Sessions\u003C/h2\u003E\n\u003Cp\u003ERole and permission changes may affect active users.\u003C/p\u003E\n\u003Cp\u003EHowever, the timing of those changes depends on how authorization data is resolved and cached by the server.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EAn administrator removes a role.\u003C/li\u003E\n\u003Cli\u003EThe server persists the new role assignment state.\u003C/li\u003E\n\u003Cli\u003EA subsequent authorization evaluation uses the effective authorization state available to the server.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EIf the application caches authorization information, changes may not become visible everywhere immediately.\u003C/p\u003E\n\u003Cp\u003EDo not assume that a client event invalidates every active session or refreshes every distributed authorization cache.\u003C/p\u003E\n\u003Cp\u003EFor sensitive operations, authorization should be evaluated against sufficiently current server-side state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022roles-are-not-sessions\u0022\u003ERoles Are Not Sessions\u003C/h3\u003E\n\u003Cp\u003ERemoving a role and revoking a session are different operations.\u003C/p\u003E\n\u003Cp\u003ERemoving a role changes authorization data.\u003C/p\u003E\n\u003Cp\u003ERevoking a session changes whether an authentication session remains valid.\u003C/p\u003E\n\u003Cp\u003EApplications may combine these operations when security policy requires immediate termination of access, but the concepts should remain separate.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/client-sdk/session-management\u0022\u003ESession Management\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022multi-tenant-authorization\u0022\u003E22. Multi-Tenant Authorization\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth authorization operations are scoped to the current tenant context.\u003C/p\u003E\n\u003Cp\u003ERole names are unique within the relevant tenant, and role assignments belong to the applicable tenant.\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\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\u003ETenant A\n |\n \u002B-- Manager\n | \u002B-- products.read\n |\n \u002B-- User A\n\nTenant B\n |\n \u002B-- Manager\n | \u002B-- reports.read\n |\n \u002B-- User B\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same role name may represent different permission sets in different tenants.\u003C/p\u003E\n\u003Cp\u003EThe client does not gain cross-tenant access merely by providing another user\u0027s \u003Ccode\u003EUserKey\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe server must enforce tenant isolation and administrative authorization.\u003C/p\u003E\n\u003Cp\u003EWhen implementing multi-tenant administration, do not assume role names or permission configurations are globally shared.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-authorization-errors\u0022\u003E23. Handling Authorization Errors\u003C/h2\u003E\n\u003Cp\u003EAuthorization methods return \u003Ccode\u003EUAuthResult\u003C/code\u003E or \u003Ccode\u003EUAuthResult\u0026lt;T\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-role-creation-errors\u0022\u003EHandle Role Creation Errors\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role created.\n}\nelse if (result.Http.IsConflict)\n{\n // A conflicting role may already exist.\n}\nelse if (result.Http.IsForbidden)\n{\n // Caller lacks permission.\n}\nelse if (result.Http.IsBadRequest)\n{\n // Invalid request.\n}\nelse\n{\n // Handle another failure.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-authorization-check-failures\u0022\u003EHandle Authorization Check Failures\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;update\u0026quot;,\n Resource = \u0026quot;products\u0026quot;\n });\n\nvar allowed = result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsAllowed: true };\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis fail-closed pattern is appropriate when determining whether the UI should expose a protected operation.\u003C/p\u003E\n\u003Cp\u003EUse the server\u0027s structured error information for diagnostics where appropriate.\u003C/p\u003E\n\u003Cp\u003EDo not expose sensitive policy internals unnecessarily.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations-1\u0022\u003E24. Security Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-authorization-is-authoritative\u0022\u003EServer Authorization Is Authoritative\u003C/h3\u003E\n\u003Cp\u003EClient-side checks improve usability but cannot protect an endpoint by themselves.\u003C/p\u003E\n\u003Cp\u003EEvery protected operation must enforce authorization on the server.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022follow-least-privilege\u0022\u003EFollow Least Privilege\u003C/h3\u003E\n\u003Cp\u003EAssign only the permissions a role actually needs.\u003C/p\u003E\n\u003Cp\u003EAvoid using \u003Ccode\u003E*\u003C/code\u003E or broad prefix wildcards for ordinary users.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-administrative-operations\u0022\u003EProtect Administrative Operations\u003C/h3\u003E\n\u003Cp\u003ERole creation, permission replacement, role assignment, and role deletion are privileged operations.\u003C/p\u003E\n\u003Cp\u003EThe server must authorize each mutation independently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022treat-permission-replacement-carefully\u0022\u003ETreat Permission Replacement Carefully\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E replaces the entire permission collection.\u003C/p\u003E\n\u003Cp\u003EAlways inspect the existing role configuration before submitting changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-reauthentication-requirements\u0022\u003EHandle Reauthentication Requirements\u003C/h3\u003E\n\u003Cp\u003EAn authorization decision can indicate \u003Ccode\u003ERequiresReauthentication\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EApplications should handle this state explicitly rather than treating it as an ordinary permission denial.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022respect-tenant-boundaries\u0022\u003ERespect Tenant Boundaries\u003C/h3\u003E\n\u003Cp\u003ERoles and assignments must remain within their authorized tenant context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-not-infer-effective-access-from-one-role\u0022\u003EDo Not Infer Effective Access from One Role\u003C/h3\u003E\n\u003Cp\u003EA user can have multiple roles, overlapping permissions, and additional policy restrictions.\u003C/p\u003E\n\u003Cp\u003EUse the server\u0027s authorization decision when determining whether an action is permitted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022account-for-cached-authorization-state\u0022\u003EAccount for Cached Authorization State\u003C/h3\u003E\n\u003Cp\u003EPermission changes may not propagate instantly to every active client or cached authorization context.\u003C/p\u003E\n\u003Cp\u003EDesign sensitive operations around server-side enforcement and appropriate cache invalidation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022putting-everything-together\u0022\u003E25. Putting Everything Together\u003C/h2\u003E\n\u003Cp\u003EA typical administrative authorization workflow may look like this:\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\u003ECreate role\n |\n v\nConfigure permissions\n |\n v\nAssign role to user\n |\n v\nUser requests protected action\n |\n v\nServer evaluates authorization\n |\n \u002B-- Allow --\u0026gt; Execute operation\n |\n \u002B-- Deny --\u0026gt; Reject operation\n |\n \u002B-- Reauthentication required\n |\n v\n Authenticate again\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUltimateAuth separates role management from authorization enforcement.\u003C/p\u003E\n\u003Cp\u003EThis allows applications to provide flexible administrative interfaces while keeping access decisions under server control.\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/plugin-domains/policies-access-control\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E \u2014 Understand application policies and access rules.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/authorization-pipeline\u0022\u003EAuthorization Pipeline Internals\u003C/a\u003E \u2014 Explore how authorization decisions are evaluated.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client-sdk/user-management\u0022\u003EUser Management\u003C/a\u003E \u2014 Manage user accounts and lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client-sdk/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Manage authenticated sessions and devices.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand session validity and security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "understanding-authorization", @@ -364,6 +364,6 @@ "Level": 0 } ], - "SourceHash": "3304F9F304C1A8D3DFDE25BB8369FD2C1265457FAAB5A975F2662FC94FA1FB6D", + "SourceHash": "BCD3447FF02C9E98B5D0102E7B99CAB9C7A205F4EEE828339D99D9BFB9801A2F", "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/credentials.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/credentials.json index 771a8890..989374dd 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/credentials.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/credentials.json @@ -1,7 +1,7 @@ { "Slug": "client-sdk/credentials", "Title": "Credentials", - "Html": "\n\u003Cp\u003EUltimateAuth provides a dedicated client API for managing authentication credentials, including password creation, password changes, credential revocation, account recovery, and administrative credential operations.\u003C/p\u003E\n\u003Cp\u003EThrough \u003Ccode\u003EUAuthClient.Credentials\u003C/code\u003E, applications can implement credential management without manually constructing requests to the underlying endpoints.\u003C/p\u003E\n\u003Cp\u003ECredential management is security-sensitive. UltimateAuth separates authenticated credential changes from recovery workflows and provides configurable session invalidation policies.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the current \u003Ccode\u003EICredentialClient\u003C/code\u003E API, its request and response models, and practical implementation patterns.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-credentials\u0022\u003E1. Understanding Credentials\u003C/h2\u003E\n\u003Cp\u003EA credential is authentication material associated with a user.\u003C/p\u003E\n\u003Cp\u003EPasswords are the primary credential type supported by the current credential management request contracts.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth also defines a broader \u003Ccode\u003ECredentialType\u003C/code\u003E enum for additional authentication mechanisms.\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 enum CredentialType\n{\n Password = 0,\n OneTimeCode = 10,\n EmailOtp = 11,\n SmsOtp = 12,\n Totp = 30,\n Passkey = 100,\n Certificate = 200,\n ApiKey = 210,\n External = 1000\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese enum values do not mean every credential type is currently supported by the credential management API.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EAddCredentialRequest\u003C/code\u003E currently accepts a secret but does not expose a credential type selector.\u003C/p\u003E\n\u003Cp\u003EDo not assume that passkeys, API keys, or external authentication credentials can be created through \u003Ccode\u003EAddMyAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-credential-client\u0022\u003E2. Accessing the Credential Client\u003C/h2\u003E\n\u003Cp\u003EInject \u003Ccode\u003EIUAuthClient\u003C/code\u003E into your Blazor 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\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECredential operations are available through:\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\u003EUAuthClient.Credentials\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003ECredentials\u003C/code\u003E property exposes \u003Ccode\u003EICredentialClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIts operations fall into three groups:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EGroup\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\u003ESelf-service\u003C/td\u003E\n\u003Ctd\u003EAdd, change, and revoke the current user\u0027s credentials\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERecovery\u003C/td\u003E\n\u003Ctd\u003EBegin and complete a credential reset\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAdministration\u003C/td\u003E\n\u003Ctd\u003EManage another user\u0027s credentials\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ERecovery operations are designed to support users who cannot authenticate normally.\u003C/p\u003E\n\u003Cp\u003EAdministrative operations require appropriate server-side authorization.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-your-password\u0022\u003E3. Change Your Password\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EChangeMyAsync\u003C/code\u003E when an authenticated user knows their current password and wants to replace it.\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.Credentials.ChangeMyAsync(\n new ChangeCredentialRequest\n {\n CurrentSecret = currentPassword,\n NewSecret = newPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n{\n // Password changed successfully.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;CredentialChangeResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022changecredentialrequest\u0022\u003EChangeCredentialRequest\u003C/h3\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\u003EType\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\u003ECurrentSecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECurrent password; required for self-service changes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENewSecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENew password\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ECurrentSecret\u003C/code\u003E is nullable in the shared request contract because administrative changes do not require the user\u0027s existing password.\u003C/p\u003E\n\u003Cp\u003EFor self-service password changes, provide the current secret.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialchangeresult\u0022\u003ECredentialChangeResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains:\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\u003EDescription\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 credential change succeeded\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EError\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional operation-level failure\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential type affected by the change\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.Credentials.ChangeMyAsync(\n new ChangeCredentialRequest\n {\n CurrentSecret = currentPassword,\n NewSecret = newPassword\n });\n\nif (!result.IsSuccess)\n{\n errorMessage = \u0026quot;Unable to change your password.\u0026quot;;\n}\nelse if (result.Value is { IsSuccess: false } change)\n{\n errorMessage = change.Error ?? \u0026quot;Password change failed.\u0026quot;;\n}\nelse\n{\n successMessage = \u0026quot;Your password has been changed.\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECheck both the API-level result and the operation-level result when processing credential changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-state-event\u0022\u003EClient State Event\u003C/h3\u003E\n\u003Cp\u003EAfter a successful \u003Ccode\u003EChangeMyAsync\u003C/code\u003E operation, the SDK publishes:\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\u003EUAuthStateEvent.CredentialsChangedSelf\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe event uses the configured client state event handling mode.\u003C/p\u003E\n\u003Cp\u003EIt can help the application refresh authentication-related UI.\u003C/p\u003E\n\u003Cp\u003EIt does not replace the server\u0027s credential or session security checks.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-password-change-form\u0022\u003E4. Build a Password Change Form\u003C/h2\u003E\n\u003Cp\u003EThe following example demonstrates an authenticated password change form in Blazor.\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.Credentials.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Change Password\u0026lt;/h3\u0026gt;\n\n\u0026lt;EditForm Model=\u0026quot;_model\u0026quot; OnValidSubmit=\u0026quot;ChangePasswordAsync\u0026quot;\u0026gt;\n \u0026lt;DataAnnotationsValidator /\u0026gt;\n\n \u0026lt;label\u0026gt;Current password\u0026lt;/label\u0026gt;\n \u0026lt;InputText type=\u0026quot;password\u0026quot; @bind-Value=\u0026quot;_model.CurrentPassword\u0026quot; /\u0026gt;\n\n \u0026lt;label\u0026gt;New password\u0026lt;/label\u0026gt;\n \u0026lt;InputText type=\u0026quot;password\u0026quot; @bind-Value=\u0026quot;_model.NewPassword\u0026quot; /\u0026gt;\n\n \u0026lt;button type=\u0026quot;submit\u0026quot; disabled=\u0026quot;@_busy\u0026quot;\u0026gt;Change password\u0026lt;/button\u0026gt;\n\u0026lt;/EditForm\u0026gt;\n\n@if (!string.IsNullOrWhiteSpace(_message))\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private readonly PasswordChangeModel _model = new();\n private bool _busy;\n private string? _message;\n\n private async Task ChangePasswordAsync()\n {\n if (_busy)\n return;\n\n _busy = true;\n _message = null;\n\n try\n {\n var result = await UAuthClient.Credentials.ChangeMyAsync(\n new ChangeCredentialRequest\n {\n CurrentSecret = _model.CurrentPassword,\n NewSecret = _model.NewPassword\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp;\n result.Value is { IsSuccess: true })\n {\n _model.CurrentPassword = \u0026quot;\u0026quot;;\n _model.NewPassword = \u0026quot;\u0026quot;;\n _message = \u0026quot;Password changed successfully.\u0026quot;;\n }\n else\n {\n _message = \u0026quot;Unable to change password.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _message = \u0026quot;The credential service is unavailable.\u0026quot;;\n }\n finally\n {\n _busy = false;\n }\n }\n\n private sealed class PasswordChangeModel\n {\n [System.ComponentModel.DataAnnotations.Required]\n public string CurrentPassword { get; set; } = \u0026quot;\u0026quot;;\n\n [System.ComponentModel.DataAnnotations.Required]\n public string NewPassword { get; set; } = \u0026quot;\u0026quot;;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example demonstrates the client integration.\u003C/p\u003E\n\u003Cp\u003EA production form should additionally provide password confirmation, appropriate password policy feedback, and safeguards against repeated submissions.\u003C/p\u003E\n\u003Cp\u003ENever log password values or include them in application telemetry.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-credential\u0022\u003E5. Add a Credential\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EAddMyAsync\u003C/code\u003E to add a credential to 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.Credentials.AddMyAsync(\n new AddCredentialRequest\n {\n Secret = password\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } added)\n{\n Console.WriteLine($\u0026quot;Credential ID: {added.Id}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;CredentialAddResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022addcredentialrequest\u0022\u003EAddCredentialRequest\u003C/h3\u003E\n\u003Cp\u003EThe current request contract 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\u003Epublic sealed record AddCredentialRequest\n{\n public required string Secret { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECredential type and source selection are not currently exposed by this request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialaddresult\u0022\u003ECredentialAddResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains:\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\u003EDescription\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 credential was added\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EError\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional failure information\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreated credential ID, when available\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreated credential type, when available\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EAdding a credential is subject to the server\u0027s credential policies.\u003C/p\u003E\n\u003Cp\u003EFor example, the server may reject attempts to create a duplicate or otherwise unsupported credential.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022password-change-versus-password-recovery\u0022\u003E6. Password Change Versus Password Recovery\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between two different workflows.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EWorkflow\u003C/th\u003E\n\u003Cth\u003ETypical situation\u003C/th\u003E\n\u003Cth\u003EAPI\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPassword change\u003C/td\u003E\n\u003Ctd\u003EAuthenticated user knows the current password\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EChangeMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPassword recovery\u003C/td\u003E\n\u003Ctd\u003EUser cannot authenticate using the existing password\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EBeginResetAsync\u003C/code\u003E \u2192 \u003Ccode\u003ECompleteResetAsync\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA password change verifies the existing secret.\u003C/p\u003E\n\u003Cp\u003EPassword recovery uses a separate proof mechanism, such as a reset token or code delivered through a trusted channel.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not use administrative password changes as a replacement for public password recovery.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022begin-password-recovery\u0022\u003E7. Begin Password Recovery\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EBeginResetAsync\u003C/code\u003E to initiate a credential recovery workflow.\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.Credentials.BeginResetAsync(\n new BeginResetCredentialRequest\n {\n Identifier = \u0026quot;john@example.com\u0026quot;,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Token\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } reset)\n{\n Console.WriteLine($\u0026quot;Expires at: {reset.ExpiresAt}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;CredentialBeginResetResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022beginresetcredentialrequest\u0022\u003EBeginResetCredentialRequest\u003C/h3\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\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\u003EUser identifier associated with the recovery request\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 to recover\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResetCodeType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReset proof format\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=\u0022reset-code-types\u0022\u003EReset Code Types\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\u003Epublic enum ResetCodeType\n{\n Token = 0,\n Code = 10\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003EToken\u003C/code\u003E for a token-based recovery workflow.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECode\u003C/code\u003E for a code-based recovery workflow.\u003C/p\u003E\n\u003Cp\u003EThe delivery mechanism is determined by the application\u0027s configured server-side notification implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialbeginresetresult\u0022\u003ECredentialBeginResetResult\u003C/h3\u003E\n\u003Cp\u003EThe result contains:\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 CredentialBeginResetResult\n{\n public DateTimeOffset ExpiresAt { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe reset token or code is not returned in the API response.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis prevents the recovery endpoint from directly exposing the secret needed to take over an account.\u003C/p\u003E\n\u003Cp\u003EThe proof must be delivered through the configured trusted notification channel.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-password-recovery\u0022\u003E8. Complete Password Recovery\u003C/h2\u003E\n\u003Cp\u003EAfter the user receives a reset token or code, call \u003Ccode\u003ECompleteResetAsync\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.Credentials.CompleteResetAsync(\n new CompleteResetCredentialRequest\n {\n Identifier = \u0026quot;john@example.com\u0026quot;,\n CredentialType = CredentialType.Password,\n ResetToken = resetToken,\n NewSecret = newPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n{\n // Password recovery completed.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;CredentialActionResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022completeresetcredentialrequest\u0022\u003ECompleteResetCredentialRequest\u003C/h3\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\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\u003EIdentifier used for the recovery workflow\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 being recovered\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResetToken\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDelivered recovery proof\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENewSecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReplacement secret\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe property is named \u003Ccode\u003EResetToken\u003C/code\u003E even when the selected reset format is a code.\u003C/p\u003E\n\u003Cp\u003EUse the exact contract property name.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialactionresult\u0022\u003ECredentialActionResult\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\u003Epublic sealed record CredentialActionResult\n{\n public bool IsSuccess { get; init; }\n public string? Error { get; init; }\n}\n\u003C/code\u003E\u003C/pre\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\u003Eif (!result.IsSuccess)\n{\n errorMessage = \u0026quot;Unable to complete password recovery.\u0026quot;;\n}\nelse if (result.Value is { IsSuccess: false } action)\n{\n errorMessage = action.Error ?? \u0026quot;Password recovery failed.\u0026quot;;\n}\nelse\n{\n successMessage = \u0026quot;Your password has been reset.\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter a successful \u003Ccode\u003ECompleteResetAsync\u003C/code\u003E operation, the SDK publishes:\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\u003EUAuthStateEvent.CredentialsChanged\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe event is not the same as \u003Ccode\u003ECredentialsChangedSelf\u003C/code\u003E, which is published by \u003Ccode\u003EChangeMyAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-recovery-using-a-numeric-code\u0022\u003E9. Complete Recovery Using a Numeric Code\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports code-based recovery through \u003Ccode\u003EResetCodeType.Code\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EBegin the recovery:\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 begin = await UAuthClient.Credentials.BeginResetAsync(\n new BeginResetCredentialRequest\n {\n Identifier = email,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Code\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThen submit the code received by the 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 complete = await UAuthClient.Credentials.CompleteResetAsync(\n new CompleteResetCredentialRequest\n {\n Identifier = email,\n CredentialType = CredentialType.Password,\n ResetToken = enteredCode,\n NewSecret = newPassword\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same completion API handles token-based and code-based recovery.\u003C/p\u003E\n\u003Cp\u003EThe server is responsible for validating the submitted proof against the active recovery state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-forgot-password-form\u0022\u003E10. Build a Forgot Password Form\u003C/h2\u003E\n\u003Cp\u003EA typical forgot-password interface has two stages:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003ERequest a recovery token or code.\u003C/li\u003E\n\u003Cli\u003ESubmit the received proof with a new password.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-one-request-recovery\u0022\u003EStep One: Request Recovery\u003C/h3\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.Core.Domain\n@using CodeBeam.UltimateAuth.Credentials.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Forgot Password\u0026lt;/h3\u0026gt;\n\n\u0026lt;input @bind=\u0026quot;_email\u0026quot; type=\u0026quot;email\u0026quot; placeholder=\u0026quot;Email address\u0026quot; /\u0026gt;\n\n\u0026lt;button type=\u0026quot;button\u0026quot; disabled=\u0026quot;@_busy\u0026quot; @onclick=\u0026quot;BeginRecoveryAsync\u0026quot;\u0026gt;Send recovery instructions\u0026lt;/button\u0026gt;\n\n@if (_message is not null)\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private string _email = \u0026quot;\u0026quot;;\n private string? _message;\n private bool _busy;\n\n private async Task BeginRecoveryAsync()\n {\n if (_busy || string.IsNullOrWhiteSpace(_email))\n return;\n\n _busy = true;\n\n try\n {\n var result = await UAuthClient.Credentials.BeginResetAsync(\n new BeginResetCredentialRequest\n {\n Identifier = _email,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Token\n });\n\n _message = result.IsSuccess\n ? \u0026quot;If the account is eligible, recovery instructions will be sent.\u0026quot;\n : \u0026quot;Unable to process the recovery request.\u0026quot;;\n }\n catch (Exception)\n {\n _message = \u0026quot;Recovery is temporarily unavailable.\u0026quot;;\n }\n finally\n {\n _busy = false;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe confirmation message intentionally avoids revealing whether an account exists.\u003C/p\u003E\n\u003Cp\u003EApplications should avoid introducing account enumeration through UI messages, timing differences, or diagnostic details.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-two-complete-recovery\u0022\u003EStep Two: Complete Recovery\u003C/h3\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.Core.Domain\n@using CodeBeam.UltimateAuth.Credentials.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Reset Password\u0026lt;/h3\u0026gt;\n\n\u0026lt;input @bind=\u0026quot;_email\u0026quot; placeholder=\u0026quot;Email address\u0026quot; /\u0026gt;\n\u0026lt;input @bind=\u0026quot;_token\u0026quot; placeholder=\u0026quot;Recovery token or code\u0026quot; /\u0026gt;\n\u0026lt;input @bind=\u0026quot;_newPassword\u0026quot; type=\u0026quot;password\u0026quot; placeholder=\u0026quot;New password\u0026quot; /\u0026gt;\n\n\u0026lt;button type=\u0026quot;button\u0026quot; disabled=\u0026quot;@_busy\u0026quot; @onclick=\u0026quot;CompleteRecoveryAsync\u0026quot;\u0026gt;Reset password\u0026lt;/button\u0026gt;\n\n@if (_message is not null)\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private string _email = \u0026quot;\u0026quot;;\n private string _token = \u0026quot;\u0026quot;;\n private string _newPassword = \u0026quot;\u0026quot;;\n\n private string? _message;\n private bool _busy;\n\n private async Task CompleteRecoveryAsync()\n {\n if (_busy)\n return;\n\n _busy = true;\n _message = null;\n\n try\n {\n var result = await UAuthClient.Credentials.CompleteResetAsync(\n new CompleteResetCredentialRequest\n {\n Identifier = _email,\n CredentialType = CredentialType.Password,\n ResetToken = _token,\n NewSecret = _newPassword\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n {\n _token = \u0026quot;\u0026quot;;\n _newPassword = \u0026quot;\u0026quot;;\n _message = \u0026quot;Password reset successfully.\u0026quot;;\n }\n else\n {\n _message = \u0026quot;Unable to reset password.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _message = \u0026quot;Recovery is temporarily unavailable.\u0026quot;;\n }\n finally\n {\n _busy = false;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese examples show the SDK integration.\u003C/p\u003E\n\u003Cp\u003EA production implementation should also validate user input, protect recovery proofs from accidental disclosure, and avoid logging reset tokens or passwords.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-reset-notifications\u0022\u003E11. Configure Reset Notifications\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses \u003Ccode\u003EICredentialResetNotifier\u003C/code\u003E to deliver recovery tokens and codes.\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 interface ICredentialResetNotifier\n{\n Task NotifyAsync(CredentialResetNotification notification, CancellationToken ct = default);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe notification 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\u003Epublic sealed record CredentialResetNotification(\n TenantKey Tenant,\n UserKey UserKey,\n CredentialType CredentialType,\n ResetCodeType CodeType,\n string Token,\n DateTimeOffset ExpiresAt);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe application must provide a secure delivery implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022register-the-notifier\u0022\u003ERegister the Notifier\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\u003Ebuilder.Services.AddScoped\u0026lt;ICredentialResetNotifier, ApplicationCredentialResetNotifier\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationCredentialResetNotifier\u003C/code\u003E is an application-defined implementation.\u003C/p\u003E\n\u003Cp\u003EIt might deliver the proof through email, SMS, or another trusted channel associated with the user\u0027s account.\u003C/p\u003E\n\u003Cp\u003EThe notifier should not expose the proof to an arbitrary address supplied by an unauthenticated caller.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022missing-notifier-configuration\u0022\u003EMissing Notifier Configuration\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth provides a \u003Ccode\u003ENotConfiguredCredentialResetNotifier\u003C/code\u003E fallback.\u003C/p\u003E\n\u003Cp\u003EWhen recovery requires notification but no implementation has been configured, the fallback throws an explanatory exception.\u003C/p\u003E\n\u003Cp\u003EThis prevents a reset flow from silently succeeding without a configured proof delivery mechanism.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ENever return recovery tokens to anonymous clients as a substitute for configuring the notifier.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022recovery-security-behavior\u0022\u003E12. Recovery Security Behavior\u003C/h2\u003E\n\u003Cp\u003ECredential recovery is designed around several security properties.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022account-enumeration-resistance\u0022\u003EAccount Enumeration Resistance\u003C/h3\u003E\n\u003Cp\u003ERecovery initiation should not reveal whether a submitted identifier belongs to an account.\u003C/p\u003E\n\u003Cp\u003EApplications should preserve this behavior in their own UI and logging policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022proof-expiration\u0022\u003EProof Expiration\u003C/h3\u003E\n\u003Cp\u003ERecovery proofs have a server-controlled lifetime.\u003C/p\u003E\n\u003Cp\u003EThe client does not choose an arbitrary expiration time.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022single-use-proofs\u0022\u003ESingle-Use Proofs\u003C/h3\u003E\n\u003Cp\u003EA successfully consumed recovery proof must not be reusable.\u003C/p\u003E\n\u003Cp\u003EAfter successful completion, a second attempt using the same proof should be rejected.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-and-target-binding\u0022\u003EIdentifier and Target Binding\u003C/h3\u003E\n\u003Cp\u003ERecovery must apply to the correct user and credential.\u003C/p\u003E\n\u003Cp\u003EAdministrative recovery must not permit an identifier belonging to one user to reset another user\u0027s credential.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-revocation\u0022\u003ESession Revocation\u003C/h3\u003E\n\u003Cp\u003ESuccessful recovery may invalidate existing sessions according to the configured credential session policy.\u003C/p\u003E\n\u003Cp\u003EBy default, UltimateAuth revokes all session chains after recovery.\u003C/p\u003E\n\u003Cp\u003EFor further details, see \u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-your-credential\u0022\u003E13. Revoke Your Credential\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERevokeMyAsync\u003C/code\u003E to revoke a credential belonging to the authenticated 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.Credentials.RevokeMyAsync(\n new RevokeCredentialRequest\n {\n Id = credentialId\n });\n\nif (result.IsSuccess)\n{\n // Credential revoked.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request contract 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\u003Epublic sealed record RevokeCredentialRequest\n{\n public Guid Id { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse the credential record ID, not a user ID or session ID.\u003C/p\u003E\n\u003Cp\u003ECredential revocation is distinct from credential deletion.\u003C/p\u003E\n\u003Cp\u003ERevocation changes the credential\u0027s usability, while deletion uses a separate lifecycle operation available through the administrative API.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022state-event\u0022\u003EState Event\u003C/h3\u003E\n\u003Cp\u003EAfter successful \u003Ccode\u003ERevokeMyAsync\u003C/code\u003E, the SDK publishes:\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\u003EUAuthStateEvent.CredentialsChanged\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe application can use this event to refresh credential-related UI.\u003C/p\u003E\n\u003Cp\u003ECredential revocation may affect authentication eligibility and session continuation depending on server policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-credential-management\u0022\u003E14. Administrative Credential Management\u003C/h2\u003E\n\u003Cp\u003EAdministrative credential operations target a specific user.\u003C/p\u003E\n\u003Cp\u003EThey require appropriate server-side authorization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-credential-1\u0022\u003EAdd a Credential\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.Credentials.AddUserAsync(userKey,\n new AddCredentialRequest\n {\n Secret = temporaryPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } added)\n{\n Console.WriteLine(added.Id);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server validates whether the administrative actor may add a credential for the target user.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-a-users-password\u0022\u003EChange a User\u0027s Password\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.Credentials.ChangeUserAsync(userKey,\n new ChangeCredentialRequest\n {\n NewSecret = newPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n{\n // Administrative password change succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ECurrentSecret\u003C/code\u003E is not required for an authorized administrative credential change.\u003C/p\u003E\n\u003Cp\u003EThis makes administrative credential changes a privileged operation.\u003C/p\u003E\n\u003Cp\u003EDo not expose this capability through ordinary self-service UI.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-a-users-credential\u0022\u003ERevoke a User\u0027s Credential\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.Credentials.RevokeUserAsync(userKey,\n new RevokeCredentialRequest\n {\n Id = credentialId\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022begin-administrative-recovery\u0022\u003EBegin Administrative Recovery\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.Credentials.BeginResetUserAsync(userKey,\n new BeginResetCredentialRequest\n {\n Identifier = userEmail,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Token\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-administrative-recovery\u0022\u003EComplete Administrative Recovery\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.Credentials.CompleteResetUserAsync(userKey,\n new CompleteResetCredentialRequest\n {\n Identifier = userEmail,\n CredentialType = CredentialType.Password,\n ResetToken = recoveryToken,\n NewSecret = newPassword\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAdministrative recovery remains a proof-based workflow.\u003C/p\u003E\n\u003Cp\u003EThe server validates the relationship between the target user and the supplied identifier.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-a-users-credential\u0022\u003EDelete a User\u0027s Credential\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.Credentials.DeleteUserAsync(userKey,\n new DeleteCredentialRequest\n {\n Id = credentialId,\n Mode = DeleteMode.Soft\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hard-delete\u0022\u003EHard Delete\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.Credentials.DeleteUserAsync(userKey,\n new DeleteCredentialRequest\n {\n Id = credentialId,\n Mode = DeleteMode.Hard\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EHard deletion is a destructive administrative operation.\u003C/p\u003E\n\u003Cp\u003EIts availability and effects depend on server authorization and credential lifecycle policies.\u003C/p\u003E\n\u003Cp\u003EAdministrative operations do not automatically publish the same local credential state events as self-service operations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-changes-and-session-invalidation\u0022\u003E15. Credential Changes and Session Invalidation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth allows applications to configure how existing sessions are handled after credential changes and recovery.\u003C/p\u003E\n\u003Cp\u003EThe server exposes:\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\u003EUAuthServerOptions.CredentialSessionPolicy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe policy contains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOption\u003C/th\u003E\n\u003Cth\u003EDefault\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\u003EOnSelfChange\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreserveCurrent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESessions after self-service credential changes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnAdminChange\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESessions after administrative credential changes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnRecovery\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESessions after credential recovery\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=\u0022credentialsessionbehavior\u0022\u003ECredentialSessionBehavior\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\u003Epublic enum CredentialSessionBehavior\n{\n PreserveAll = 0,\n PreserveCurrent = 10,\n RevokeAll = 20\n}\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\u003EBehavior\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\u003EPreserveAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve existing session chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreserveCurrent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve the current chain and revoke other chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke all applicable session chains\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=\u0022configure-the-policy\u0022\u003EConfigure the Policy\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.CredentialSessionPolicy.OnSelfChange = CredentialSessionBehavior.PreserveCurrent;\n options.CredentialSessionPolicy.OnAdminChange = CredentialSessionBehavior.RevokeAll;\n options.CredentialSessionPolicy.OnRecovery = CredentialSessionBehavior.RevokeAll;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe defaults are intended to balance usability and security.\u003C/p\u003E\n\u003Cp\u003EFor example, an authenticated user changing their password can retain their current device while other devices are invalidated.\u003C/p\u003E\n\u003Cp\u003EAn administrator changing a user\u0027s password typically requires broader session revocation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recovery-and-preservecurrent\u0022\u003ERecovery and PreserveCurrent\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EPreserveCurrent\u003C/code\u003E is not meaningful for many recovery workflows because the user may not have an authenticated current session.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s recovery policy documentation specifies that \u003Ccode\u003EPreserveCurrent\u003C/code\u003E is treated as \u003Ccode\u003ERevokeAll\u003C/code\u003E in this context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-considerations\u0022\u003EAuthentication Mode Considerations\u003C/h3\u003E\n\u003Cp\u003EThe precise effect of session invalidation depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EIn particular, a locally validated JWT access token may remain usable until expiration if the resource server does not consult current revocation state.\u003C/p\u003E\n\u003Cp\u003ECredential session policies should be understood together with the application\u0027s token validation strategy.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-credential-results\u0022\u003E16. Handling Credential Results\u003C/h2\u003E\n\u003Cp\u003ECredential operations use \u003Ccode\u003EUAuthResult\u003C/code\u003E or \u003Ccode\u003EUAuthResult\u0026lt;T\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ESeveral operations also return a domain-level success flag.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-creation\u0022\u003ECredential Creation\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.Credentials.AddMyAsync(request);\n\nif (!result.IsSuccess)\n{\n // API-level failure.\n}\nelse if (result.Value is { IsSuccess: false } add)\n{\n // Credential creation was rejected.\n Console.WriteLine(add.Error);\n}\nelse if (result.Value is { IsSuccess: true } added)\n{\n Console.WriteLine(added.Id);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-recovery\u0022\u003ECredential Recovery\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.Credentials.CompleteResetAsync(request);\n\nif (!result.IsSuccess)\n{\n // API-level failure.\n}\nelse if (result.Value is { IsSuccess: false } action)\n{\n // Recovery did not complete successfully.\n}\nelse\n{\n // Recovery succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-status-helpers\u0022\u003EHTTP Status Helpers\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\u003Eif (result.Http.IsUnauthorized)\n{\n // Authentication required.\n}\nelse if (result.Http.IsForbidden)\n{\n // Operation not permitted.\n}\nelse if (result.Http.IsConflict)\n{\n // Conflicting or expired operation state.\n}\nelse if (result.Http.IsBadRequest)\n{\n // Invalid request.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actual status depends on the server\u0027s response.\u003C/p\u003E\n\u003Cp\u003EAvoid exposing sensitive internal credential errors directly to end users.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-state-events\u0022\u003E17. Credential State Events\u003C/h2\u003E\n\u003Cp\u003EThe current \u003Ccode\u003EUAuthCredentialClient\u003C/code\u003E publishes the following events after successful self-service operations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EEvent\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EChangeMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialsChangedSelf\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialsChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECompleteResetAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialsChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe following operations do not publish those events in the current client implementation:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EAddMyAsync\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EBeginResetAsync\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EAdministrative credential operations\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EApplications should refresh relevant data explicitly where needed.\u003C/p\u003E\n\u003Cp\u003EEvents help synchronize client state but do not themselves establish that a credential or session has been invalidated on the server.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003E18. Security Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022never-expose-stored-secrets\u0022\u003ENever Expose Stored Secrets\u003C/h3\u003E\n\u003Cp\u003ECredential secrets must not be returned through credential management responses.\u003C/p\u003E\n\u003Cp\u003EApplications should never log passwords, reset proofs, or other authentication secrets.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-secure-transport\u0022\u003EUse Secure Transport\u003C/h3\u003E\n\u003Cp\u003ECredential operations must be performed over appropriately secured transport.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-recovery-proofs\u0022\u003EProtect Recovery Proofs\u003C/h3\u003E\n\u003Cp\u003ERecovery tokens and codes should be short-lived, single-use, and delivered through trusted channels.\u003C/p\u003E\n\u003Cp\u003EDo not store recovery proofs in persistent client storage unless the application has a carefully justified security requirement.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022keep-self-service-and-administrative-operations-separate\u0022\u003EKeep Self-Service and Administrative Operations Separate\u003C/h3\u003E\n\u003Cp\u003ESelf-service changes require the current credential.\u003C/p\u003E\n\u003Cp\u003EAdministrative changes are privileged and require server-side authorization.\u003C/p\u003E\n\u003Cp\u003EDo not substitute one for the other.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022preserve-account-recovery-safety\u0022\u003EPreserve Account Recovery Safety\u003C/h3\u003E\n\u003Cp\u003EAvoid revealing account existence through public recovery responses.\u003C/p\u003E\n\u003Cp\u003EApply appropriate rate limiting, proof attempt limits, and abuse controls.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022consider-session-consequences\u0022\u003EConsider Session Consequences\u003C/h3\u003E\n\u003Cp\u003EChanging or resetting a password can affect existing sessions.\u003C/p\u003E\n\u003Cp\u003EApplications should explain the expected consequences to users, especially when other devices will be logged out.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-not-assume-every-credential-type-is-implemented\u0022\u003EDo Not Assume Every Credential Type Is Implemented\u003C/h3\u003E\n\u003Cp\u003EThe existence of a \u003Ccode\u003ECredentialType\u003C/code\u003E enum member does not establish that its complete lifecycle is supported.\u003C/p\u003E\n\u003Cp\u003EUse the currently implemented credential contracts and documented authentication flows.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022retrieving-password-requirements\u0022\u003ERetrieving Password Requirements\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth clients can retrieve the effective password policy configured by the authentication server.\u003C/p\u003E\n\u003Cp\u003EThis allows applications to display the current requirements during registration, user creation, and password changes without hardcoding password rules in the UI.\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.Credentials.GetPasswordPolicyAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } policy)\n{\n // Display the effective requirements.\n var minimumLength = policy.MinLength;\n var maximumLength = policy.MaxLength;\n var level = policy.Level;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe response includes the configured policy level, effective length and character requirements, and indicators for server-side common-password, user-similarity, and password-history checks.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-side-validation\u0022\u003EClient-Side Validation\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EUAuthPasswordClientValidator\u003C/code\u003E to provide immediate feedback before submitting a password:\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;\n\nvar errors = UAuthPasswordClientValidator.Validate(\n password,\n policy);\n\nif (errors.Count == 0)\n{\n // Local character and length requirements are satisfied.\n // The server must still validate the submitted password.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe validator returns \u003Ccode\u003EPasswordPolicyErrorCodes\u003C/code\u003E values for failed local requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient-side validation is advisory, not authoritative.\u003C/strong\u003E The server remains responsible for the complete password-policy decision, including common-password detection, user similarity, and password history.\u003C/p\u003E\n\u003Cp\u003EFor the built-in Basic, Standard, and Strict requirements, configuration examples, and password-history behavior, see \u003Ca href=\u0022/docs/security/password-policy\u0022\u003EPassword Policy \u0026amp; History\u003C/a\u003E.\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/authentication\u0022\u003EAuthentication\u003C/a\u003E \u2014 Login, logout, refresh, and authentication flows.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/identifiers\u0022\u003EIdentifiers\u003C/a\u003E \u2014 Manage user identifiers and ownership verification.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/user-management\u0022\u003EUser Management\u003C/a\u003E \u2014 Manage profiles and user lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Inspect and revoke sessions.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E \u2014 Understand token validation and revocation behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand session invalidation and account-level security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth provides a dedicated client API for managing authentication credentials, including password creation, password changes, credential revocation, account recovery, and administrative credential operations.\u003C/p\u003E\n\u003Cp\u003EThrough \u003Ccode\u003EUAuthClient.Credentials\u003C/code\u003E, applications can implement credential management without manually constructing requests to the underlying endpoints.\u003C/p\u003E\n\u003Cp\u003ECredential management is security-sensitive. UltimateAuth separates authenticated credential changes from recovery workflows and provides configurable session invalidation policies.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the current \u003Ccode\u003EICredentialClient\u003C/code\u003E API, its request and response models, and practical implementation patterns.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-credentials\u0022\u003E1. Understanding Credentials\u003C/h2\u003E\n\u003Cp\u003EA credential is authentication material associated with a user.\u003C/p\u003E\n\u003Cp\u003EPasswords are the primary credential type supported by the current credential management request contracts.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth also defines a broader \u003Ccode\u003ECredentialType\u003C/code\u003E enum for additional authentication mechanisms.\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 enum CredentialType\n{\n Password = 0,\n OneTimeCode = 10,\n EmailOtp = 11,\n SmsOtp = 12,\n Totp = 30,\n Passkey = 100,\n Certificate = 200,\n ApiKey = 210,\n External = 1000\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese enum values do not mean every credential type is currently supported by the credential management API.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EAddCredentialRequest\u003C/code\u003E currently accepts a secret but does not expose a credential type selector.\u003C/p\u003E\n\u003Cp\u003EDo not assume that passkeys, API keys, or external authentication credentials can be created through \u003Ccode\u003EAddMyAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-credential-client\u0022\u003E2. Accessing the Credential Client\u003C/h2\u003E\n\u003Cp\u003EInject \u003Ccode\u003EIUAuthClient\u003C/code\u003E into your Blazor 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\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECredential operations are available through:\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\u003EUAuthClient.Credentials\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003ECredentials\u003C/code\u003E property exposes \u003Ccode\u003EICredentialClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIts operations fall into three groups:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EGroup\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\u003ESelf-service\u003C/td\u003E\n\u003Ctd\u003EAdd, change, and revoke the current user\u0027s credentials\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERecovery\u003C/td\u003E\n\u003Ctd\u003EBegin and complete a credential reset\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAdministration\u003C/td\u003E\n\u003Ctd\u003EManage another user\u0027s credentials\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ERecovery operations are designed to support users who cannot authenticate normally.\u003C/p\u003E\n\u003Cp\u003EAdministrative operations require appropriate server-side authorization.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-your-password\u0022\u003E3. Change Your Password\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EChangeMyAsync\u003C/code\u003E when an authenticated user knows their current password and wants to replace it.\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.Credentials.ChangeMyAsync(\n new ChangeCredentialRequest\n {\n CurrentSecret = currentPassword,\n NewSecret = newPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n{\n // Password changed successfully.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;CredentialChangeResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022changecredentialrequest\u0022\u003EChangeCredentialRequest\u003C/h3\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\u003EType\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\u003ECurrentSecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECurrent password; required for self-service changes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENewSecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENew password\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ECurrentSecret\u003C/code\u003E is nullable in the shared request contract because administrative changes do not require the user\u0027s existing password.\u003C/p\u003E\n\u003Cp\u003EFor self-service password changes, provide the current secret.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialchangeresult\u0022\u003ECredentialChangeResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains:\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\u003EDescription\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 credential change succeeded\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EError\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional operation-level failure\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential type affected by the change\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.Credentials.ChangeMyAsync(\n new ChangeCredentialRequest\n {\n CurrentSecret = currentPassword,\n NewSecret = newPassword\n });\n\nif (!result.IsSuccess)\n{\n errorMessage = \u0026quot;Unable to change your password.\u0026quot;;\n}\nelse if (result.Value is { IsSuccess: false } change)\n{\n errorMessage = change.Error ?? \u0026quot;Password change failed.\u0026quot;;\n}\nelse\n{\n successMessage = \u0026quot;Your password has been changed.\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECheck both the API-level result and the operation-level result when processing credential changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-state-event\u0022\u003EClient State Event\u003C/h3\u003E\n\u003Cp\u003EAfter a successful \u003Ccode\u003EChangeMyAsync\u003C/code\u003E operation, the SDK publishes:\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\u003EUAuthStateEvent.CredentialsChangedSelf\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe event uses the configured client state event handling mode.\u003C/p\u003E\n\u003Cp\u003EIt can help the application refresh authentication-related UI.\u003C/p\u003E\n\u003Cp\u003EIt does not replace the server\u0027s credential or session security checks.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-password-change-form\u0022\u003E4. Build a Password Change Form\u003C/h2\u003E\n\u003Cp\u003EThe following example demonstrates an authenticated password change form in Blazor.\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.Credentials.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Change Password\u0026lt;/h3\u0026gt;\n\n\u0026lt;EditForm Model=\u0026quot;_model\u0026quot; OnValidSubmit=\u0026quot;ChangePasswordAsync\u0026quot;\u0026gt;\n \u0026lt;DataAnnotationsValidator /\u0026gt;\n\n \u0026lt;label\u0026gt;Current password\u0026lt;/label\u0026gt;\n \u0026lt;InputText type=\u0026quot;password\u0026quot; @bind-Value=\u0026quot;_model.CurrentPassword\u0026quot; /\u0026gt;\n\n \u0026lt;label\u0026gt;New password\u0026lt;/label\u0026gt;\n \u0026lt;InputText type=\u0026quot;password\u0026quot; @bind-Value=\u0026quot;_model.NewPassword\u0026quot; /\u0026gt;\n\n \u0026lt;button type=\u0026quot;submit\u0026quot; disabled=\u0026quot;@_busy\u0026quot;\u0026gt;Change password\u0026lt;/button\u0026gt;\n\u0026lt;/EditForm\u0026gt;\n\n@if (!string.IsNullOrWhiteSpace(_message))\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private readonly PasswordChangeModel _model = new();\n private bool _busy;\n private string? _message;\n\n private async Task ChangePasswordAsync()\n {\n if (_busy)\n return;\n\n _busy = true;\n _message = null;\n\n try\n {\n var result = await UAuthClient.Credentials.ChangeMyAsync(\n new ChangeCredentialRequest\n {\n CurrentSecret = _model.CurrentPassword,\n NewSecret = _model.NewPassword\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp;\n result.Value is { IsSuccess: true })\n {\n _model.CurrentPassword = \u0026quot;\u0026quot;;\n _model.NewPassword = \u0026quot;\u0026quot;;\n _message = \u0026quot;Password changed successfully.\u0026quot;;\n }\n else\n {\n _message = \u0026quot;Unable to change password.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _message = \u0026quot;The credential service is unavailable.\u0026quot;;\n }\n finally\n {\n _busy = false;\n }\n }\n\n private sealed class PasswordChangeModel\n {\n [System.ComponentModel.DataAnnotations.Required]\n public string CurrentPassword { get; set; } = \u0026quot;\u0026quot;;\n\n [System.ComponentModel.DataAnnotations.Required]\n public string NewPassword { get; set; } = \u0026quot;\u0026quot;;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example demonstrates the client integration.\u003C/p\u003E\n\u003Cp\u003EA production form should additionally provide password confirmation, appropriate password policy feedback, and safeguards against repeated submissions.\u003C/p\u003E\n\u003Cp\u003ENever log password values or include them in application telemetry.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-credential\u0022\u003E5. Add a Credential\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EAddMyAsync\u003C/code\u003E to add a credential to 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.Credentials.AddMyAsync(\n new AddCredentialRequest\n {\n Secret = password\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } added)\n{\n Console.WriteLine($\u0026quot;Credential ID: {added.Id}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;CredentialAddResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022addcredentialrequest\u0022\u003EAddCredentialRequest\u003C/h3\u003E\n\u003Cp\u003EThe current request contract 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\u003Epublic sealed record AddCredentialRequest\n{\n public required string Secret { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECredential type and source selection are not currently exposed by this request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialaddresult\u0022\u003ECredentialAddResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains:\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\u003EDescription\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 credential was added\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EError\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional failure information\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreated credential ID, when available\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreated credential type, when available\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EAdding a credential is subject to the server\u0027s credential policies.\u003C/p\u003E\n\u003Cp\u003EFor example, the server may reject attempts to create a duplicate or otherwise unsupported credential.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022password-change-versus-password-recovery\u0022\u003E6. Password Change Versus Password Recovery\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between two different workflows.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EWorkflow\u003C/th\u003E\n\u003Cth\u003ETypical situation\u003C/th\u003E\n\u003Cth\u003EAPI\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPassword change\u003C/td\u003E\n\u003Ctd\u003EAuthenticated user knows the current password\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EChangeMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPassword recovery\u003C/td\u003E\n\u003Ctd\u003EUser cannot authenticate using the existing password\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EBeginResetAsync\u003C/code\u003E \u2192 \u003Ccode\u003ECompleteResetAsync\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA password change verifies the existing secret.\u003C/p\u003E\n\u003Cp\u003EPassword recovery uses a separate proof mechanism, such as a reset token or code delivered through a trusted channel.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not use administrative password changes as a replacement for public password recovery.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022begin-password-recovery\u0022\u003E7. Begin Password Recovery\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EBeginResetAsync\u003C/code\u003E to initiate a credential recovery workflow.\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.Credentials.BeginResetAsync(\n new BeginResetCredentialRequest\n {\n Identifier = \u0026quot;john@example.com\u0026quot;,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Token\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } reset)\n{\n Console.WriteLine($\u0026quot;Expires at: {reset.ExpiresAt}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;CredentialBeginResetResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022beginresetcredentialrequest\u0022\u003EBeginResetCredentialRequest\u003C/h3\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\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\u003EUser identifier associated with the recovery request\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 to recover\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResetCodeType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReset proof format\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=\u0022reset-code-types\u0022\u003EReset Code Types\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\u003Epublic enum ResetCodeType\n{\n Token = 0,\n Code = 10\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003EToken\u003C/code\u003E for a token-based recovery workflow.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECode\u003C/code\u003E for a code-based recovery workflow.\u003C/p\u003E\n\u003Cp\u003EThe delivery mechanism is determined by the application\u0027s configured server-side notification implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialbeginresetresult\u0022\u003ECredentialBeginResetResult\u003C/h3\u003E\n\u003Cp\u003EThe result contains:\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 CredentialBeginResetResult\n{\n public DateTimeOffset ExpiresAt { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe reset token or code is not returned in the API response.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis prevents the recovery endpoint from directly exposing the secret needed to take over an account.\u003C/p\u003E\n\u003Cp\u003EThe proof must be delivered through the configured trusted notification channel.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-password-recovery\u0022\u003E8. Complete Password Recovery\u003C/h2\u003E\n\u003Cp\u003EAfter the user receives a reset token or code, call \u003Ccode\u003ECompleteResetAsync\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.Credentials.CompleteResetAsync(\n new CompleteResetCredentialRequest\n {\n Identifier = \u0026quot;john@example.com\u0026quot;,\n CredentialType = CredentialType.Password,\n ResetToken = resetToken,\n NewSecret = newPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n{\n // Password recovery completed.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;CredentialActionResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022completeresetcredentialrequest\u0022\u003ECompleteResetCredentialRequest\u003C/h3\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\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\u003EIdentifier used for the recovery workflow\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 being recovered\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResetToken\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDelivered recovery proof\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENewSecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReplacement secret\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe property is named \u003Ccode\u003EResetToken\u003C/code\u003E even when the selected reset format is a code.\u003C/p\u003E\n\u003Cp\u003EUse the exact contract property name.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialactionresult\u0022\u003ECredentialActionResult\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\u003Epublic sealed record CredentialActionResult\n{\n public bool IsSuccess { get; init; }\n public string? Error { get; init; }\n}\n\u003C/code\u003E\u003C/pre\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\u003Eif (!result.IsSuccess)\n{\n errorMessage = \u0026quot;Unable to complete password recovery.\u0026quot;;\n}\nelse if (result.Value is { IsSuccess: false } action)\n{\n errorMessage = action.Error ?? \u0026quot;Password recovery failed.\u0026quot;;\n}\nelse\n{\n successMessage = \u0026quot;Your password has been reset.\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter a successful \u003Ccode\u003ECompleteResetAsync\u003C/code\u003E operation, the SDK publishes:\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\u003EUAuthStateEvent.CredentialsChanged\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe event is not the same as \u003Ccode\u003ECredentialsChangedSelf\u003C/code\u003E, which is published by \u003Ccode\u003EChangeMyAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-recovery-using-a-numeric-code\u0022\u003E9. Complete Recovery Using a Numeric Code\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports code-based recovery through \u003Ccode\u003EResetCodeType.Code\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EBegin the recovery:\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 begin = await UAuthClient.Credentials.BeginResetAsync(\n new BeginResetCredentialRequest\n {\n Identifier = email,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Code\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThen submit the code received by the 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 complete = await UAuthClient.Credentials.CompleteResetAsync(\n new CompleteResetCredentialRequest\n {\n Identifier = email,\n CredentialType = CredentialType.Password,\n ResetToken = enteredCode,\n NewSecret = newPassword\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same completion API handles token-based and code-based recovery.\u003C/p\u003E\n\u003Cp\u003EThe server is responsible for validating the submitted proof against the active recovery state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-forgot-password-form\u0022\u003E10. Build a Forgot Password Form\u003C/h2\u003E\n\u003Cp\u003EA typical forgot-password interface has two stages:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003ERequest a recovery token or code.\u003C/li\u003E\n\u003Cli\u003ESubmit the received proof with a new password.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-one-request-recovery\u0022\u003EStep One: Request Recovery\u003C/h3\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.Core.Domain\n@using CodeBeam.UltimateAuth.Credentials.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Forgot Password\u0026lt;/h3\u0026gt;\n\n\u0026lt;input @bind=\u0026quot;_email\u0026quot; type=\u0026quot;email\u0026quot; placeholder=\u0026quot;Email address\u0026quot; /\u0026gt;\n\n\u0026lt;button type=\u0026quot;button\u0026quot; disabled=\u0026quot;@_busy\u0026quot; @onclick=\u0026quot;BeginRecoveryAsync\u0026quot;\u0026gt;Send recovery instructions\u0026lt;/button\u0026gt;\n\n@if (_message is not null)\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private string _email = \u0026quot;\u0026quot;;\n private string? _message;\n private bool _busy;\n\n private async Task BeginRecoveryAsync()\n {\n if (_busy || string.IsNullOrWhiteSpace(_email))\n return;\n\n _busy = true;\n\n try\n {\n var result = await UAuthClient.Credentials.BeginResetAsync(\n new BeginResetCredentialRequest\n {\n Identifier = _email,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Token\n });\n\n _message = result.IsSuccess\n ? \u0026quot;If the account is eligible, recovery instructions will be sent.\u0026quot;\n : \u0026quot;Unable to process the recovery request.\u0026quot;;\n }\n catch (Exception)\n {\n _message = \u0026quot;Recovery is temporarily unavailable.\u0026quot;;\n }\n finally\n {\n _busy = false;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe confirmation message intentionally avoids revealing whether an account exists.\u003C/p\u003E\n\u003Cp\u003EApplications should avoid introducing account enumeration through UI messages, timing differences, or diagnostic details.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-two-complete-recovery\u0022\u003EStep Two: Complete Recovery\u003C/h3\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.Core.Domain\n@using CodeBeam.UltimateAuth.Credentials.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Reset Password\u0026lt;/h3\u0026gt;\n\n\u0026lt;input @bind=\u0026quot;_email\u0026quot; placeholder=\u0026quot;Email address\u0026quot; /\u0026gt;\n\u0026lt;input @bind=\u0026quot;_token\u0026quot; placeholder=\u0026quot;Recovery token or code\u0026quot; /\u0026gt;\n\u0026lt;input @bind=\u0026quot;_newPassword\u0026quot; type=\u0026quot;password\u0026quot; placeholder=\u0026quot;New password\u0026quot; /\u0026gt;\n\n\u0026lt;button type=\u0026quot;button\u0026quot; disabled=\u0026quot;@_busy\u0026quot; @onclick=\u0026quot;CompleteRecoveryAsync\u0026quot;\u0026gt;Reset password\u0026lt;/button\u0026gt;\n\n@if (_message is not null)\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private string _email = \u0026quot;\u0026quot;;\n private string _token = \u0026quot;\u0026quot;;\n private string _newPassword = \u0026quot;\u0026quot;;\n\n private string? _message;\n private bool _busy;\n\n private async Task CompleteRecoveryAsync()\n {\n if (_busy)\n return;\n\n _busy = true;\n _message = null;\n\n try\n {\n var result = await UAuthClient.Credentials.CompleteResetAsync(\n new CompleteResetCredentialRequest\n {\n Identifier = _email,\n CredentialType = CredentialType.Password,\n ResetToken = _token,\n NewSecret = _newPassword\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n {\n _token = \u0026quot;\u0026quot;;\n _newPassword = \u0026quot;\u0026quot;;\n _message = \u0026quot;Password reset successfully.\u0026quot;;\n }\n else\n {\n _message = \u0026quot;Unable to reset password.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _message = \u0026quot;Recovery is temporarily unavailable.\u0026quot;;\n }\n finally\n {\n _busy = false;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese examples show the SDK integration.\u003C/p\u003E\n\u003Cp\u003EA production implementation should also validate user input, protect recovery proofs from accidental disclosure, and avoid logging reset tokens or passwords.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-reset-notifications\u0022\u003E11. Configure Reset Notifications\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses \u003Ccode\u003EICredentialResetNotifier\u003C/code\u003E to deliver recovery tokens and codes.\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 interface ICredentialResetNotifier\n{\n Task NotifyAsync(CredentialResetNotification notification, CancellationToken ct = default);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe notification 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\u003Epublic sealed record CredentialResetNotification(\n TenantKey Tenant,\n UserKey UserKey,\n CredentialType CredentialType,\n ResetCodeType CodeType,\n string Token,\n DateTimeOffset ExpiresAt);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe application must provide a secure delivery implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022register-the-notifier\u0022\u003ERegister the Notifier\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\u003Ebuilder.Services.AddScoped\u0026lt;ICredentialResetNotifier, ApplicationCredentialResetNotifier\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationCredentialResetNotifier\u003C/code\u003E is an application-defined implementation.\u003C/p\u003E\n\u003Cp\u003EIt might deliver the proof through email, SMS, or another trusted channel associated with the user\u0027s account.\u003C/p\u003E\n\u003Cp\u003EThe notifier should not expose the proof to an arbitrary address supplied by an unauthenticated caller.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022missing-notifier-configuration\u0022\u003EMissing Notifier Configuration\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth provides a \u003Ccode\u003ENotConfiguredCredentialResetNotifier\u003C/code\u003E fallback.\u003C/p\u003E\n\u003Cp\u003EWhen recovery requires notification but no implementation has been configured, the fallback throws an explanatory exception.\u003C/p\u003E\n\u003Cp\u003EThis prevents a reset flow from silently succeeding without a configured proof delivery mechanism.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ENever return recovery tokens to anonymous clients as a substitute for configuring the notifier.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022recovery-security-behavior\u0022\u003E12. Recovery Security Behavior\u003C/h2\u003E\n\u003Cp\u003ECredential recovery is designed around several security properties.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022account-enumeration-resistance\u0022\u003EAccount Enumeration Resistance\u003C/h3\u003E\n\u003Cp\u003ERecovery initiation should not reveal whether a submitted identifier belongs to an account.\u003C/p\u003E\n\u003Cp\u003EApplications should preserve this behavior in their own UI and logging policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022proof-expiration\u0022\u003EProof Expiration\u003C/h3\u003E\n\u003Cp\u003ERecovery proofs have a server-controlled lifetime.\u003C/p\u003E\n\u003Cp\u003EThe client does not choose an arbitrary expiration time.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022single-use-proofs\u0022\u003ESingle-Use Proofs\u003C/h3\u003E\n\u003Cp\u003EA successfully consumed recovery proof must not be reusable.\u003C/p\u003E\n\u003Cp\u003EAfter successful completion, a second attempt using the same proof should be rejected.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-and-target-binding\u0022\u003EIdentifier and Target Binding\u003C/h3\u003E\n\u003Cp\u003ERecovery must apply to the correct user and credential.\u003C/p\u003E\n\u003Cp\u003EAdministrative recovery must not permit an identifier belonging to one user to reset another user\u0027s credential.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-revocation\u0022\u003ESession Revocation\u003C/h3\u003E\n\u003Cp\u003ESuccessful recovery may invalidate existing sessions according to the configured credential session policy.\u003C/p\u003E\n\u003Cp\u003EBy default, UltimateAuth revokes all session chains after recovery.\u003C/p\u003E\n\u003Cp\u003EFor further details, see \u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-your-credential\u0022\u003E13. Revoke Your Credential\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERevokeMyAsync\u003C/code\u003E to revoke a credential belonging to the authenticated 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.Credentials.RevokeMyAsync(\n new RevokeCredentialRequest\n {\n Id = credentialId\n });\n\nif (result.IsSuccess)\n{\n // Credential revoked.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request contract 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\u003Epublic sealed record RevokeCredentialRequest\n{\n public Guid Id { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse the credential record ID, not a user ID or session ID.\u003C/p\u003E\n\u003Cp\u003ECredential revocation is distinct from credential deletion.\u003C/p\u003E\n\u003Cp\u003ERevocation changes the credential\u0027s usability, while deletion uses a separate lifecycle operation available through the administrative API.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022state-event\u0022\u003EState Event\u003C/h3\u003E\n\u003Cp\u003EAfter successful \u003Ccode\u003ERevokeMyAsync\u003C/code\u003E, the SDK publishes:\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\u003EUAuthStateEvent.CredentialsChanged\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe application can use this event to refresh credential-related UI.\u003C/p\u003E\n\u003Cp\u003ECredential revocation may affect authentication eligibility and session continuation depending on server policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-credential-management\u0022\u003E14. Administrative Credential Management\u003C/h2\u003E\n\u003Cp\u003EAdministrative credential operations target a specific user.\u003C/p\u003E\n\u003Cp\u003EThey require appropriate server-side authorization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-credential-1\u0022\u003EAdd a Credential\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.Credentials.AddUserAsync(userKey,\n new AddCredentialRequest\n {\n Secret = temporaryPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } added)\n{\n Console.WriteLine(added.Id);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server validates whether the administrative actor may add a credential for the target user.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-a-users-password\u0022\u003EChange a User\u0027s Password\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.Credentials.ChangeUserAsync(userKey,\n new ChangeCredentialRequest\n {\n NewSecret = newPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n{\n // Administrative password change succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ECurrentSecret\u003C/code\u003E is not required for an authorized administrative credential change.\u003C/p\u003E\n\u003Cp\u003EThis makes administrative credential changes a privileged operation.\u003C/p\u003E\n\u003Cp\u003EDo not expose this capability through ordinary self-service UI.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-a-users-credential\u0022\u003ERevoke a User\u0027s Credential\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.Credentials.RevokeUserAsync(userKey,\n new RevokeCredentialRequest\n {\n Id = credentialId\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022begin-administrative-recovery\u0022\u003EBegin Administrative Recovery\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.Credentials.BeginResetUserAsync(userKey,\n new BeginResetCredentialRequest\n {\n Identifier = userEmail,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Token\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-administrative-recovery\u0022\u003EComplete Administrative Recovery\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.Credentials.CompleteResetUserAsync(userKey,\n new CompleteResetCredentialRequest\n {\n Identifier = userEmail,\n CredentialType = CredentialType.Password,\n ResetToken = recoveryToken,\n NewSecret = newPassword\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAdministrative recovery remains a proof-based workflow.\u003C/p\u003E\n\u003Cp\u003EThe server validates the relationship between the target user and the supplied identifier.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-a-users-credential\u0022\u003EDelete a User\u0027s Credential\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.Credentials.DeleteUserAsync(userKey,\n new DeleteCredentialRequest\n {\n Id = credentialId,\n Mode = DeleteMode.Soft\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hard-delete\u0022\u003EHard Delete\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.Credentials.DeleteUserAsync(userKey,\n new DeleteCredentialRequest\n {\n Id = credentialId,\n Mode = DeleteMode.Hard\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EHard deletion is a destructive administrative operation.\u003C/p\u003E\n\u003Cp\u003EIts availability and effects depend on server authorization and credential lifecycle policies.\u003C/p\u003E\n\u003Cp\u003EAdministrative operations do not automatically publish the same local credential state events as self-service operations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-changes-and-session-invalidation\u0022\u003E15. Credential Changes and Session Invalidation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth allows applications to configure how existing sessions are handled after credential changes and recovery.\u003C/p\u003E\n\u003Cp\u003EThe server exposes:\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\u003EUAuthServerOptions.CredentialSessionPolicy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe policy contains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOption\u003C/th\u003E\n\u003Cth\u003EDefault\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\u003EOnSelfChange\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreserveCurrent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESessions after self-service credential changes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnAdminChange\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESessions after administrative credential changes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnRecovery\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESessions after credential recovery\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=\u0022credentialsessionbehavior\u0022\u003ECredentialSessionBehavior\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\u003Epublic enum CredentialSessionBehavior\n{\n PreserveAll = 0,\n PreserveCurrent = 10,\n RevokeAll = 20\n}\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\u003EBehavior\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\u003EPreserveAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve existing session chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreserveCurrent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve the current chain and revoke other chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke all applicable session chains\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=\u0022configure-the-policy\u0022\u003EConfigure the Policy\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.CredentialSessionPolicy.OnSelfChange = CredentialSessionBehavior.PreserveCurrent;\n options.CredentialSessionPolicy.OnAdminChange = CredentialSessionBehavior.RevokeAll;\n options.CredentialSessionPolicy.OnRecovery = CredentialSessionBehavior.RevokeAll;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe defaults are intended to balance usability and security.\u003C/p\u003E\n\u003Cp\u003EFor example, an authenticated user changing their password can retain their current device while other devices are invalidated.\u003C/p\u003E\n\u003Cp\u003EAn administrator changing a user\u0027s password typically requires broader session revocation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recovery-and-preservecurrent\u0022\u003ERecovery and PreserveCurrent\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EPreserveCurrent\u003C/code\u003E is not meaningful for many recovery workflows because the user may not have an authenticated current session.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s recovery policy documentation specifies that \u003Ccode\u003EPreserveCurrent\u003C/code\u003E is treated as \u003Ccode\u003ERevokeAll\u003C/code\u003E in this context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-considerations\u0022\u003EAuthentication Mode Considerations\u003C/h3\u003E\n\u003Cp\u003EThe precise effect of session invalidation depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EIn particular, a locally validated JWT access token may remain usable until expiration if the resource server does not consult current revocation state.\u003C/p\u003E\n\u003Cp\u003ECredential session policies should be understood together with the application\u0027s token validation strategy.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-credential-results\u0022\u003E16. Handling Credential Results\u003C/h2\u003E\n\u003Cp\u003ECredential operations use \u003Ccode\u003EUAuthResult\u003C/code\u003E or \u003Ccode\u003EUAuthResult\u0026lt;T\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ESeveral operations also return a domain-level success flag.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-creation\u0022\u003ECredential Creation\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.Credentials.AddMyAsync(request);\n\nif (!result.IsSuccess)\n{\n // API-level failure.\n}\nelse if (result.Value is { IsSuccess: false } add)\n{\n // Credential creation was rejected.\n Console.WriteLine(add.Error);\n}\nelse if (result.Value is { IsSuccess: true } added)\n{\n Console.WriteLine(added.Id);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-recovery\u0022\u003ECredential Recovery\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.Credentials.CompleteResetAsync(request);\n\nif (!result.IsSuccess)\n{\n // API-level failure.\n}\nelse if (result.Value is { IsSuccess: false } action)\n{\n // Recovery did not complete successfully.\n}\nelse\n{\n // Recovery succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-status-helpers\u0022\u003EHTTP Status Helpers\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\u003Eif (result.Http.IsUnauthorized)\n{\n // Authentication required.\n}\nelse if (result.Http.IsForbidden)\n{\n // Operation not permitted.\n}\nelse if (result.Http.IsConflict)\n{\n // Conflicting or expired operation state.\n}\nelse if (result.Http.IsBadRequest)\n{\n // Invalid request.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actual status depends on the server\u0027s response.\u003C/p\u003E\n\u003Cp\u003EAvoid exposing sensitive internal credential errors directly to end users.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-state-events\u0022\u003E17. Credential State Events\u003C/h2\u003E\n\u003Cp\u003EThe current \u003Ccode\u003EUAuthCredentialClient\u003C/code\u003E publishes the following events after successful self-service operations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EEvent\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EChangeMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialsChangedSelf\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialsChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECompleteResetAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialsChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe following operations do not publish those events in the current client implementation:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EAddMyAsync\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EBeginResetAsync\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EAdministrative credential operations\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EApplications should refresh relevant data explicitly where needed.\u003C/p\u003E\n\u003Cp\u003EEvents help synchronize client state but do not themselves establish that a credential or session has been invalidated on the server.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003E18. Security Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022never-expose-stored-secrets\u0022\u003ENever Expose Stored Secrets\u003C/h3\u003E\n\u003Cp\u003ECredential secrets must not be returned through credential management responses.\u003C/p\u003E\n\u003Cp\u003EApplications should never log passwords, reset proofs, or other authentication secrets.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-secure-transport\u0022\u003EUse Secure Transport\u003C/h3\u003E\n\u003Cp\u003ECredential operations must be performed over appropriately secured transport.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-recovery-proofs\u0022\u003EProtect Recovery Proofs\u003C/h3\u003E\n\u003Cp\u003ERecovery tokens and codes should be short-lived, single-use, and delivered through trusted channels.\u003C/p\u003E\n\u003Cp\u003EDo not store recovery proofs in persistent client storage unless the application has a carefully justified security requirement.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022keep-self-service-and-administrative-operations-separate\u0022\u003EKeep Self-Service and Administrative Operations Separate\u003C/h3\u003E\n\u003Cp\u003ESelf-service changes require the current credential.\u003C/p\u003E\n\u003Cp\u003EAdministrative changes are privileged and require server-side authorization.\u003C/p\u003E\n\u003Cp\u003EDo not substitute one for the other.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022preserve-account-recovery-safety\u0022\u003EPreserve Account Recovery Safety\u003C/h3\u003E\n\u003Cp\u003EAvoid revealing account existence through public recovery responses.\u003C/p\u003E\n\u003Cp\u003EApply appropriate rate limiting, proof attempt limits, and abuse controls.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022consider-session-consequences\u0022\u003EConsider Session Consequences\u003C/h3\u003E\n\u003Cp\u003EChanging or resetting a password can affect existing sessions.\u003C/p\u003E\n\u003Cp\u003EApplications should explain the expected consequences to users, especially when other devices will be logged out.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-not-assume-every-credential-type-is-implemented\u0022\u003EDo Not Assume Every Credential Type Is Implemented\u003C/h3\u003E\n\u003Cp\u003EThe existence of a \u003Ccode\u003ECredentialType\u003C/code\u003E enum member does not establish that its complete lifecycle is supported.\u003C/p\u003E\n\u003Cp\u003EUse the currently implemented credential contracts and documented authentication flows.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022retrieving-password-requirements\u0022\u003ERetrieving Password Requirements\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth clients can retrieve the effective password policy configured by the authentication server.\u003C/p\u003E\n\u003Cp\u003EThis allows applications to display the current requirements during registration, user creation, and password changes without hardcoding password rules in the UI.\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.Credentials.GetPasswordPolicyAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } policy)\n{\n // Display the effective requirements.\n var minimumLength = policy.MinLength;\n var maximumLength = policy.MaxLength;\n var level = policy.Level;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe response includes the configured policy level, effective length and character requirements, and indicators for server-side common-password, user-similarity, and password-history checks.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-side-validation\u0022\u003EClient-Side Validation\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EUAuthPasswordClientValidator\u003C/code\u003E to provide immediate feedback before submitting a password:\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;\n\nvar errors = UAuthPasswordClientValidator.Validate(password, policy);\n\nif (errors.Count == 0)\n{\n // Local character and length requirements are satisfied.\n // The server must still validate the submitted password.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe validator returns \u003Ccode\u003EPasswordPolicyErrorCodes\u003C/code\u003E values for failed local requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient-side validation is advisory, not authoritative.\u003C/strong\u003E The server remains responsible for the complete password-policy decision, including common-password detection, user similarity, and password history.\u003C/p\u003E\n\u003Cp\u003EFor the built-in Basic, Standard, and Strict requirements, configuration examples, and password-history behavior, see \u003Ca href=\u0022/docs/security/password-policy\u0022\u003EPassword Policy \u0026amp; History\u003C/a\u003E.\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/authentication\u0022\u003EAuthentication\u003C/a\u003E \u2014 Login, logout, refresh, and authentication flows.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/identifiers\u0022\u003EIdentifiers\u003C/a\u003E \u2014 Manage user identifiers and ownership verification.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/user-management\u0022\u003EUser Management\u003C/a\u003E \u2014 Manage profiles and user lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Inspect and revoke sessions.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E \u2014 Understand token validation and revocation behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand session invalidation and account-level security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "understanding-credentials", @@ -314,6 +314,6 @@ "Level": 0 } ], - "SourceHash": "8A73382F304D6EC208718BEDC7D300B180A397144C25FB5FCC4BCC3F330FAF8F", + "SourceHash": "1B43969AC7029BFBA504044032854991F7A864A82EEE12176E992D9B9B4CB0FA", "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/identifiers.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/identifiers.json index d360d1c1..2c842970 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/identifiers.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/identifiers.json @@ -1,7 +1,7 @@ { "Slug": "client-sdk/identifiers", "Title": "Identifiers", - "Html": "\n\u003Cp\u003EUltimateAuth treats user identifiers as independent, managed entities rather than simple fields stored directly on a user profile.\u003C/p\u003E\n\u003Cp\u003EThrough \u003Ccode\u003EUAuthClient.Identifiers\u003C/code\u003E, applications can manage usernames, email addresses, phone numbers, and custom identifiers, including primary designation, ownership verification, availability checks, and administrative operations.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the public \u003Ccode\u003EIUserIdentifierClient\u003C/code\u003E API with practical examples for account settings, registration forms, and administration interfaces.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-user-identifiers\u0022\u003E1. Understanding User Identifiers\u003C/h2\u003E\n\u003Cp\u003EAn identifier represents a value associated with a user that may be used for identification, authentication, or communication, depending on the application\u0027s configuration.\u003C/p\u003E\n\u003Cp\u003ESupported identifier types are:\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 enum UserIdentifierType\n{\n Username = 0,\n Email = 10,\n Phone = 20,\n Custom = 100\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA single user can have multiple identifiers.\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\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\u003EUser\n |\n \u002B-- Username\n | johndoe [Primary]\n |\n \u002B-- Email\n | john@example.com [Primary, Verified]\n | john@business.com [Verified]\n |\n \u002B-- Phone\n \u002B1 555 0100 [Primary, Verified]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach identifier has its own value, type, verification state, and primary designation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EIdentifiers are not profiles.\u003C/strong\u003E A user profile contains information such as display name and biography, while identifiers represent managed identity-related values.\u003C/p\u003E\n\u003Cp\u003EFor general profile management, see \u003Ca href=\u0022/docs/client/user-management\u0022\u003EUser Management\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-identifier-client\u0022\u003E2. Accessing the Identifier Client\u003C/h2\u003E\n\u003Cp\u003EInject \u003Ccode\u003EIUAuthClient\u003C/code\u003E into a Blazor 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\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIdentifier operations are available through:\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\u003EUAuthClient.Identifiers\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis property exposes \u003Ccode\u003EIUserIdentifierClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe client supports three main categories of operations:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECategory\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\u003ESelf-service\u003C/td\u003E\n\u003Ctd\u003EManage identifiers belonging to the current user\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAdministration\u003C/td\u003E\n\u003Ctd\u003EManage identifiers belonging to a specified user\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAvailability\u003C/td\u003E\n\u003Ctd\u003ECheck whether an identifier value is valid and available\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EAll operations are subject to server-side authorization and identifier policies.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-my-identifiers\u0022\u003E3. Get My Identifiers\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMyAsync\u003C/code\u003E to retrieve identifiers belonging to the authenticated 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.Identifiers.GetMyAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var identifier in page.Items)\n {\n Console.WriteLine($\u0026quot;{identifier.Type}: {identifier.Value}\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;PagedResult\u0026lt;UserIdentifierInfo\u0026gt;\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022useridentifierinfo\u0022\u003EUserIdentifierInfo\u003C/h3\u003E\n\u003Cp\u003EEach identifier is represented by \u003Ccode\u003EUserIdentifierInfo\u003C/code\u003E.\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\u003EType\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\u003EId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EGuid\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier record ID\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserIdentifierType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier type\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EValue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier value\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENormalizedValue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EServer-normalized value\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsPrimary\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ebool\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether it is primary for its type\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsVerified\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ebool\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether ownership has been verified\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreation timestamp\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EVerifiedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EVerification timestamp\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EVersion\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Elong\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEntity version\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003EId\u003C/code\u003E identifies the managed identifier record and is used by update, verification, primary designation, and deletion operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022filter-identifiers-in-the-ui\u0022\u003EFilter Identifiers in the UI\u003C/h3\u003E\n\u003Cp\u003EFor example, display verified email addresses from a returned page:\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.Identifiers.GetMyAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n var verifiedEmails = page.Items\n .Where(x =\u0026gt;\n x.Type == UserIdentifierType.Email \u0026amp;\u0026amp;\n x.IsVerified)\n .ToList();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis filters the current page only. Retrieve additional pages when the application needs the complete collection.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pagination\u0022\u003E4. Pagination\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EGetMyAsync\u003C/code\u003E accepts an optional \u003Ccode\u003EPageRequest\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.Identifiers.GetMyAsync(\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server applies its configured pagination policy.\u003C/p\u003E\n\u003Cp\u003EThe returned \u003Ccode\u003EPagedResult\u0026lt;UserIdentifierInfo\u0026gt;\u003C/code\u003E includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EItems\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ETotalCount\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EPageNumber\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EPageSize\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EHasNext\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\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.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n Console.WriteLine($\u0026quot;Total: {page.TotalCount}\u0026quot;);\n\n if (page.HasNext)\n {\n // Enable the next-page control.\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDo not assume the first page contains every identifier.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-an-identifier\u0022\u003E5. Add an Identifier\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EAddMyAsync\u003C/code\u003E to associate a new identifier with the current user.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-an-email-address\u0022\u003EAdd an Email Address\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.Identifiers.AddMyAsync(\n new AddUserIdentifierRequest\n {\n Type = UserIdentifierType.Email,\n Value = \u0026quot;john@business.com\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Identifier added.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-phone-number\u0022\u003EAdd a Phone Number\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.Identifiers.AddMyAsync(\n new AddUserIdentifierRequest\n {\n Type = UserIdentifierType.Phone,\n Value = \u0026quot;\u002B15550100\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-username\u0022\u003EAdd a Username\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.Identifiers.AddMyAsync(\n new AddUserIdentifierRequest\n {\n Type = UserIdentifierType.Username,\n Value = \u0026quot;johndoe\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-custom-identifier\u0022\u003EAdd a Custom Identifier\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.Identifiers.AddMyAsync(\n new AddUserIdentifierRequest\n {\n Type = UserIdentifierType.Custom,\n Value = \u0026quot;external-customer-123\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECustom identifier support does not automatically mean that arbitrary custom values can be used for authentication.\u003C/p\u003E\n\u003Cp\u003ELogin eligibility depends on server configuration and the relevant identifier policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-as-primary\u0022\u003EAdd as Primary\u003C/h3\u003E\n\u003Cp\u003ESet \u003Ccode\u003EIsPrimary\u003C/code\u003E when creating the identifier:\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.Identifiers.AddMyAsync(\n new AddUserIdentifierRequest\n {\n Type = UserIdentifierType.Email,\n Value = \u0026quot;john@example.com\u0026quot;,\n IsPrimary = true\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server determines whether the new value can be assigned as primary.\u003C/p\u003E\n\u003Cp\u003EAdding an identifier does not automatically establish verified ownership.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-limits\u0022\u003EIdentifier Limits\u003C/h3\u003E\n\u003Cp\u003EThe server may restrict how many identifiers a user can maintain.\u003C/p\u003E\n\u003Cp\u003EBy default, UltimateAuth allows up to \u003Cstrong\u003E10 active identifiers per user\u003C/strong\u003E. Applications may configure a different global limit or optional limits for individual identifier types.\u003C/p\u003E\n\u003Cp\u003EThese limits apply to both self-service and administrative identifier operations.\u003C/p\u003E\n\u003Cp\u003EWhen adding an identifier, the server validates the configured limits before accepting the operation.\u003C/p\u003E\n\u003Cp\u003EPossible validation errors include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EError Code\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_total_limit_exceeded\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EThe maximum number of active identifiers has been reached\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eidentifier_type_limit_exceeded\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EThe maximum number of active identifiers of this type has been reached\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EApplications should display an appropriate message when an addition is rejected.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ETotal limit reached:\u003C/strong\u003E \u0026quot;You have reached the maximum number of identifiers allowed for your account.\u0026quot;\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EType limit reached:\u003C/strong\u003E \u0026quot;You have reached the maximum number of identifiers allowed for this type.\u0026quot;\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ESoft-deleted identifiers do not count toward active limits.\u003C/p\u003E\n\u003Cp\u003EHowever, deleting an identifier does not necessarily make its value available for reuse. Identifier uniqueness and identifier capacity are separate rules.\u003C/p\u003E\n\u003Cp\u003EThe server remains authoritative when enforcing these limits. Client applications should not rely solely on local identifier counts to determine whether an addition will succeed.\u003C/p\u003E\n\u003Cp\u003EFor configuration details, see \u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-uniqueness-and-normalization\u0022\u003E6. Identifier Uniqueness and Normalization\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth normalizes identifier values and enforces uniqueness within the relevant tenant boundary.\u003C/p\u003E\n\u003Cp\u003EThis allows applications to work with consistent identifier representations.\u003C/p\u003E\n\u003Cp\u003EFor example, email or username normalization may affect how equivalent input values are compared.\u003C/p\u003E\n\u003Cp\u003EThe exact normalization rules depend on the identifier type and server implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-server-results-as-authoritative\u0022\u003EUse Server Results as Authoritative\u003C/h3\u003E\n\u003Cp\u003EDo not implement a separate client-side normalization algorithm and assume it exactly matches the server.\u003C/p\u003E\n\u003Cp\u003EClient-side formatting can improve the user experience, but the server must remain responsible for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EValidating identifier format.\u003C/li\u003E\n\u003Cli\u003ENormalizing identifier values.\u003C/li\u003E\n\u003Cli\u003EEnforcing uniqueness.\u003C/li\u003E\n\u003Cli\u003EChecking tenant ownership.\u003C/li\u003E\n\u003Cli\u003EApplying identifier-specific policies.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAn identifier value that appears available in the UI may become unavailable before the user submits it.\u003C/p\u003E\n\u003Cp\u003EAlways handle the result of the actual add or update operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-identifier-availability\u0022\u003E7. Check Identifier Availability\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECheckAvailabilityAsync\u003C/code\u003E to check whether an identifier value is valid and available.\u003C/p\u003E\n\u003Cp\u003EThis is useful for registration forms and account settings.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-a-username\u0022\u003ECheck a Username\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.Identifiers.CheckAvailabilityAsync(\n new CheckUserIdentifierAvailabilityRequest\n {\n Type = UserIdentifierType.Username,\n Value = \u0026quot;johndoe\u0026quot;\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } availability)\n{\n if (!availability.IsValid)\n {\n // The identifier value is invalid.\n }\n else if (!availability.IsAvailable)\n {\n // The identifier is already unavailable.\n }\n else\n {\n // The identifier is currently available.\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-an-email-address\u0022\u003ECheck an Email Address\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.Identifiers.CheckAvailabilityAsync(\n new CheckUserIdentifierAvailabilityRequest\n {\n Type = UserIdentifierType.Email,\n Value = \u0026quot;john@example.com\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022availability-result\u0022\u003EAvailability Result\u003C/h3\u003E\n\u003Cp\u003EThe operation 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\u003EUAuthResult\u0026lt;UserIdentifierAvailabilityResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe response value contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsValid\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the value passes validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAvailable\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the value is currently available\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENormalizedValue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENormalized identifier value, when available\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EErrors\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EValidation errors\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=\u0022display-validation-feedback\u0022\u003EDisplay Validation Feedback\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.Identifiers.CheckAvailabilityAsync(\n new CheckUserIdentifierAvailabilityRequest\n {\n Type = UserIdentifierType.Username,\n Value = username\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } availability)\n{\n if (!availability.IsValid)\n {\n foreach (var error in availability.Errors)\n {\n // Display the appropriate validation feedback.\n }\n }\n else if (!availability.IsAvailable)\n {\n message = \u0026quot;This username is unavailable.\u0026quot;;\n }\n else\n {\n message = \u0026quot;This username is available.\u0026quot;;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EAn availability check is advisory, not a reservation.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThe value may become unavailable between the availability check and the subsequent creation operation.\u003C/p\u003E\n\u003Cp\u003EFor public registration forms, also consider whether exposing detailed availability information could enable account enumeration. Apply appropriate server-side controls.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-an-identifier\u0022\u003E8. Update an Identifier\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EUpdateMyAsync\u003C/code\u003E to change the value of an existing identifier.\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.Identifiers.UpdateMyAsync(\n new UpdateUserIdentifierRequest\n {\n Id = identifierId,\n NewValue = \u0026quot;newemail@example.com\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Identifier updated.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUpdateUserIdentifierRequest\u003C/code\u003E contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExisting identifier record ID\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENewValue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReplacement identifier value\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EUnlike profile updates, this operation targets one identifier record.\u003C/p\u003E\n\u003Cp\u003EIt does not replace the user\u0027s entire identifier collection.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022verification-after-an-update\u0022\u003EVerification After an Update\u003C/h3\u003E\n\u003Cp\u003EChanging an identifier\u0027s value may require ownership verification again.\u003C/p\u003E\n\u003Cp\u003EFor example, verification of an old email address does not prove ownership of a new email address.\u003C/p\u003E\n\u003Cp\u003EApplications should reload the identifier after updating it and inspect its current \u003Ccode\u003EIsVerified\u003C/code\u003E state.\u003C/p\u003E\n\u003Cp\u003EDo not assume the previous verification state remains valid.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-primary-identifiers\u0022\u003E9. Understanding Primary Identifiers\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports \u003Cstrong\u003Eone primary identifier per type\u003C/strong\u003E, rather than one global primary identifier for the entire user.\u003C/p\u003E\n\u003Cp\u003EFor example, a user may have:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EOne primary username.\u003C/li\u003E\n\u003Cli\u003EOne primary email.\u003C/li\u003E\n\u003Cli\u003EOne primary phone number.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese primary designations are independent.\u003C/p\u003E\n\u003Cp\u003EThe primary flag identifies the preferred identifier within its type.\u003C/p\u003E\n\u003Cp\u003EHow the application uses primary identifiers for display, communication, or authentication depends on its configuration and business rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022set-a-primary-identifier\u0022\u003ESet a Primary Identifier\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003ESetMyPrimaryAsync\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.Identifiers.SetMyPrimaryAsync(\n new SetPrimaryUserIdentifierRequest\n {\n Id = identifierId\n });\n\nif (result.IsSuccess)\n{\n // Primary designation updated.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen a different identifier becomes primary within the same type, the server maintains the per-type primary constraint.\u003C/p\u003E\n\u003Cp\u003ESetting an email address as primary does not unset the primary phone number or username.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022unset-a-primary-identifier\u0022\u003EUnset a Primary Identifier\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EUnsetMyPrimaryAsync\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.Identifiers.UnsetMyPrimaryAsync(\n new UnsetPrimaryUserIdentifierRequest\n {\n Id = identifierId\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhether a primary identifier can be unset without selecting a replacement depends on server policy.\u003C/p\u003E\n\u003Cp\u003EApplications should handle rejection when the server requires a primary identifier of that type.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022display-primary-identifiers\u0022\u003EDisplay Primary Identifiers\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.Identifiers.GetMyAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n var primaryIdentifiers = page.Items.Where(x =\u0026gt; x.IsPrimary).ToList();\n\n foreach (var identifier in primaryIdentifiers)\n {\n Console.WriteLine($\u0026quot;{identifier.Type}: {identifier.Value}\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERemember that this example searches only the returned page.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022verify-identifier-ownership\u0022\u003E10. Verify Identifier Ownership\u003C/h2\u003E\n\u003Cp\u003EVerification is a security-sensitive operation.\u003C/p\u003E\n\u003Cp\u003EA user must not be able to mark an email address or phone number as verified merely by knowing or submitting its identifier ID.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth requires ownership proof for self-service verification.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022verify-an-identifier\u0022\u003EVerify an Identifier\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EVerifyMyAsync\u003C/code\u003E with a proof obtained through your application\u0027s verification workflow.\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.Identifiers.VerifyMyAsync(\n new VerifyUserIdentifierRequest\n {\n Id = identifierId,\n Proof = verificationProof\n });\n\nif (result.IsSuccess)\n{\n // Identifier verification succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003EProof\u003C/code\u003E property is required for self-service verification.\u003C/p\u003E\n\u003Cp\u003EFor example, a verification workflow may deliver a code to the email address or phone number being verified.\u003C/p\u003E\n\u003Cp\u003EThe application then submits the received proof through \u003Ccode\u003EVerifyMyAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe SDK does not itself guarantee delivery of verification messages.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022verification-flow\u0022\u003EVerification Flow\u003C/h3\u003E\n\u003Cp\u003EA typical verification workflow looks like this:\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\u003EUser adds identifier\n |\n v\nApplication initiates verification\n |\n v\nVerification proof is delivered\n |\n v\nUser submits proof\n |\n v\nIdentifiers.VerifyMyAsync(...)\n |\n v\nServer validates ownership proof\n |\n \u002B-- Valid --\u0026gt; Identifier verified\n |\n \u002B-- Invalid --\u0026gt; Verification rejected\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe mechanism used to issue and deliver the proof is application-specific.\u003C/p\u003E\n\u003Cp\u003EThe server-side verification implementation must validate the proof before updating the identifier\u0027s verified state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-side-verification-with-iuseridentifierverifier\u0022\u003E11. Server-Side Verification with IUserIdentifierVerifier\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth defines \u003Ccode\u003EIUserIdentifierVerifier\u003C/code\u003E for validating identifier ownership proof.\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 interface IUserIdentifierVerifier\n{\n Task\u0026lt;bool\u0026gt; VerifyAsync(UserIdentifierVerificationContext context, CancellationToken ct = default);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe verification context 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\u003Epublic sealed record UserIdentifierVerificationContext(\n TenantKey Tenant,\n UserKey UserKey,\n Guid IdentifierId,\n UserIdentifierType Type,\n string Value,\n string Proof);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA secure verifier must bind the proof to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe tenant.\u003C/li\u003E\n\u003Cli\u003EThe user.\u003C/li\u003E\n\u003Cli\u003EThe identifier record.\u003C/li\u003E\n\u003Cli\u003EThe current identifier value.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIt must also enforce appropriate expiration, attempt limits, and single-use consumption.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022register-your-verifier\u0022\u003ERegister Your Verifier\u003C/h3\u003E\n\u003Cp\u003EIn the server application, register your implementation:\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.AddScoped\u0026lt;IUserIdentifierVerifier, ApplicationIdentifierVerifier\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationIdentifierVerifier\u003C/code\u003E represents your own implementation of the interface.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth includes a \u003Ccode\u003ENotConfiguredUserIdentifierVerifier\u003C/code\u003E fallback that throws an explanatory exception when verification is attempted without a configured implementation.\u003C/p\u003E\n\u003Cp\u003EThis prevents an unconfigured verifier from silently approving identifier ownership.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not implement a verifier that returns \u003Ccode\u003Etrue\u003C/code\u003E unconditionally in production.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-an-identifier\u0022\u003E12. Delete an Identifier\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EDeleteMyAsync\u003C/code\u003E to remove an identifier.\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.Identifiers.DeleteMyAsync(\n new DeleteUserIdentifierRequest\n {\n Id = identifierId,\n Mode = DeleteMode.Soft\n });\n\nif (result.IsSuccess)\n{\n // Identifier deleted.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request includes a deletion mode:\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 enum DeleteMode\n{\n Soft = 0,\n Hard = 10\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hard-delete\u0022\u003EHard Delete\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.Identifiers.DeleteMyAsync(\n new DeleteUserIdentifierRequest\n {\n Id = identifierId,\n Mode = DeleteMode.Hard\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDeletion behavior depends on the server\u0027s identifier lifecycle and authorization rules.\u003C/p\u003E\n\u003Cp\u003EAn application should not assume that every caller can select either deletion mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deleting-primary-identifiers\u0022\u003EDeleting Primary Identifiers\u003C/h3\u003E\n\u003Cp\u003EDeleting a primary identifier may be restricted.\u003C/p\u003E\n\u003Cp\u003EFor example, the server may require another identifier of the same type to become primary first.\u003C/p\u003E\n\u003Cp\u003EApplications should inspect the result and provide appropriate feedback.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deleting-login-identifiers\u0022\u003EDeleting Login Identifiers\u003C/h3\u003E\n\u003Cp\u003ERemoving an identifier that is used for authentication may affect future login attempts.\u003C/p\u003E\n\u003Cp\u003EBefore exposing deletion, consider whether the user will retain a supported method of authentication.\u003C/p\u003E\n\u003Cp\u003EIdentifier deletion should not be treated as equivalent to revoking all existing sessions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-an-identifier-management-page\u0022\u003E13. Build an Identifier Management Page\u003C/h2\u003E\n\u003Cp\u003EThe following example demonstrates a basic Blazor page that lists identifiers, adds an email address, and allows users to change primary designation.\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.Core.Contracts\n@using CodeBeam.UltimateAuth.Users.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;My Identifiers\u0026lt;/h3\u0026gt;\n\n@if (_loading)\n{\n \u0026lt;p\u0026gt;Loading identifiers...\u0026lt;/p\u0026gt;\n}\nelse\n{\n @foreach (var item in _identifiers)\n {\n \u0026lt;div @key=\u0026quot;item.Id\u0026quot;\u0026gt;\n \u0026lt;strong\u0026gt;@item.Value\u0026lt;/strong\u0026gt;\n \u0026lt;span\u0026gt;@item.Type\u0026lt;/span\u0026gt;\n\n @if (item.IsVerified)\n {\n \u0026lt;span\u0026gt;Verified\u0026lt;/span\u0026gt;\n }\n else\n {\n \u0026lt;span\u0026gt;Not verified\u0026lt;/span\u0026gt;\n }\n\n @if (item.IsPrimary)\n {\n \u0026lt;span\u0026gt;Primary\u0026lt;/span\u0026gt;\n }\n else\n {\n \u0026lt;button type=\u0026quot;button\u0026quot; @onclick=\u0026quot;() =\u0026gt; SetPrimaryAsync(item.Id)\u0026quot;\u0026gt;Make primary\u0026lt;/button\u0026gt;\n }\n \u0026lt;/div\u0026gt;\n }\n}\n\n\u0026lt;h4\u0026gt;Add Email\u0026lt;/h4\u0026gt;\n\u0026lt;input @bind=\u0026quot;_newEmail\u0026quot; type=\u0026quot;email\u0026quot; placeholder=\u0026quot;Email address\u0026quot; /\u0026gt;\n\u0026lt;button type=\u0026quot;button\u0026quot; @onclick=\u0026quot;AddEmailAsync\u0026quot; disabled=\u0026quot;@_busy\u0026quot;\u0026gt;Add email\u0026lt;/button\u0026gt;\n\n@if (!string.IsNullOrWhiteSpace(_message))\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private IReadOnlyList\u0026lt;UserIdentifierInfo\u0026gt; _identifiers = [];\n\n private string _newEmail = \u0026quot;\u0026quot;;\n private string? _message;\n\n private bool _loading = true;\n private bool _busy;\n\n protected override async Task OnInitializedAsync()\n {\n await LoadAsync();\n }\n\n private async Task LoadAsync()\n {\n _loading = true;\n\n try\n {\n var result = await UAuthClient.Identifiers.GetMyAsync(\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 50\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n {\n _identifiers = page.Items;\n }\n else\n {\n _message = \u0026quot;Unable to load identifiers.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _message = \u0026quot;The identifier service is unavailable.\u0026quot;;\n }\n finally\n {\n _loading = false;\n }\n }\n\n private async Task AddEmailAsync()\n {\n if (_busy || string.IsNullOrWhiteSpace(_newEmail))\n return;\n\n _busy = true;\n _message = null;\n\n try\n {\n var result = await UAuthClient.Identifiers.AddMyAsync(\n new AddUserIdentifierRequest\n {\n Type = UserIdentifierType.Email,\n Value = _newEmail\n });\n\n if (result.IsSuccess)\n {\n _newEmail = \u0026quot;\u0026quot;;\n await LoadAsync();\n }\n else\n {\n _message = \u0026quot;Unable to add email.\u0026quot;;\n }\n }\n finally\n {\n _busy = false;\n }\n }\n\n private async Task SetPrimaryAsync(Guid id)\n {\n var result = await UAuthClient.Identifiers.SetMyPrimaryAsync(\n new SetPrimaryUserIdentifierRequest\n {\n Id = id\n });\n\n if (result.IsSuccess)\n {\n await LoadAsync();\n }\n else\n {\n _message = \u0026quot;Unable to update primary identifier.\u0026quot;;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example demonstrates the core SDK integration.\u003C/p\u003E\n\u003Cp\u003EIt displays the first page of identifiers. A production implementation should add pagination, verification actions, deletion confirmation, appropriate exception handling, and loading states for all mutations.\u003C/p\u003E\n\u003Cp\u003EFor sensitive changes, consider requiring recent authentication or additional verification according to application policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-identifier-management\u0022\u003E14. Administrative Identifier Management\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides administrative methods for managing identifiers belonging to another user.\u003C/p\u003E\n\u003Cp\u003EThese operations require appropriate server-side authorization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-a-users-identifiers\u0022\u003EGet a User\u0027s Identifiers\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.Identifiers.GetUserAsync(userKey,\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var identifier in page.Items)\n {\n Console.WriteLine(identifier.Value);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-an-identifier-1\u0022\u003EAdd an Identifier\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.Identifiers.AddUserAsync(userKey,\n new AddUserIdentifierRequest\n {\n Type = UserIdentifierType.Email,\n Value = \u0026quot;employee@example.com\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-an-identifier-1\u0022\u003EUpdate an Identifier\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.Identifiers.UpdateUserAsync(userKey,\n new UpdateUserIdentifierRequest\n {\n Id = identifierId,\n NewValue = \u0026quot;updated@example.com\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022set-primary\u0022\u003ESet Primary\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.Identifiers.SetUserPrimaryAsync(userKey,\n new SetPrimaryUserIdentifierRequest\n {\n Id = identifierId\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022unset-primary\u0022\u003EUnset Primary\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.Identifiers.UnsetUserPrimaryAsync(userKey,\n new UnsetPrimaryUserIdentifierRequest\n {\n Id = identifierId\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022verify-an-identifier-as-administrator\u0022\u003EVerify an Identifier as Administrator\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.Identifiers.VerifyUserAsync(userKey,\n new VerifyUserIdentifierRequest\n {\n Id = identifierId\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request contract does not require \u003Ccode\u003EProof\u003C/code\u003E for authorized administrative verification.\u003C/p\u003E\n\u003Cp\u003EHowever, this does not mean any authenticated caller may bypass ownership verification.\u003C/p\u003E\n\u003Cp\u003EThe server must authorize the administrative verification operation and enforce its security requirements.\u003C/p\u003E\n\u003Cp\u003EAdministrative verification should be reserved for trusted workflows with appropriate auditability.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-an-identifier-1\u0022\u003EDelete an Identifier\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.Identifiers.DeleteUserAsync(userKey,\n new DeleteUserIdentifierRequest\n {\n Id = identifierId,\n Mode = DeleteMode.Soft\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAdministrative operations act on the specified target user.\u003C/p\u003E\n\u003Cp\u003EThey do not automatically publish the same local identifier state events as self-service operations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-identifier-operation-results\u0022\u003E15. Handling Identifier Operation Results\u003C/h2\u003E\n\u003Cp\u003EMost identifier mutations return \u003Ccode\u003EUAuthResult\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ERetrieval and availability methods return \u003Ccode\u003EUAuthResult\u0026lt;T\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-success-and-failure\u0022\u003EHandle Success and Failure\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.Identifiers.AddMyAsync(request);\n\nif (result.IsSuccess)\n{\n // Operation succeeded.\n}\nelse if (result.Http.IsConflict)\n{\n // Identifier conflict.\n}\nelse if (result.Http.IsForbidden)\n{\n // Operation not permitted.\n}\nelse if (result.Http.IsBadRequest)\n{\n // Invalid request.\n}\nelse\n{\n // Handle another failure.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actual status code depends on the server\u0027s response.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthResult\u003C/code\u003E also exposes \u003Ccode\u003EProblem\u003C/code\u003E, \u003Ccode\u003EErrorText\u003C/code\u003E, \u003Ccode\u003ECorrelationId\u003C/code\u003E, and \u003Ccode\u003ETraceId\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EUse structured errors when appropriate, but avoid displaying sensitive internal information directly to users.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-state-events\u0022\u003E16. Identifier State Events\u003C/h2\u003E\n\u003Cp\u003EThe client publishes \u003Ccode\u003EIdentifiersChanged\u003C/code\u003E after successful self-service identifier mutations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EEvent\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAddMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiersChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUpdateMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiersChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESetMyPrimaryAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiersChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUnsetMyPrimaryAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiersChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EVerifyMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiersChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeleteMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiersChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EFor add and update operations, the event carries the corresponding request payload.\u003C/p\u003E\n\u003Cp\u003EFor primary designation, verification, and deletion, the SDK publishes the event without a request payload.\u003C/p\u003E\n\u003Cp\u003EThe event handling mode follows the configured client state event options.\u003C/p\u003E\n\u003Cp\u003ERead operations and availability checks do not publish \u003Ccode\u003EIdentifiersChanged\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EAdministrative mutations also do not automatically publish this local self-service event.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-identifier-data-after-changes\u0022\u003ERefresh Identifier Data After Changes\u003C/h3\u003E\n\u003Cp\u003EA component may respond to an identifier state event by reloading its identifier list.\u003C/p\u003E\n\u003Cp\u003EAlternatively, after a successful mutation, the calling component can explicitly refresh the relevant data.\u003C/p\u003E\n\u003Cp\u003EClient events are synchronization mechanisms, not authoritative security decisions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003E17. Security Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022verification-requires-proof\u0022\u003EVerification Requires Proof\u003C/h3\u003E\n\u003Cp\u003ESelf-service verification must establish ownership through a valid proof.\u003C/p\u003E\n\u003Cp\u003EKnowing an identifier ID is not sufficient.\u003C/p\u003E\n\u003Cp\u003EThe server must validate proof binding, expiration, attempt limits, and single-use consumption.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022primary-does-not-mean-verified\u0022\u003EPrimary Does Not Mean Verified\u003C/h3\u003E\n\u003Cp\u003EAn identifier\u0027s primary designation and verification state are separate properties.\u003C/p\u003E\n\u003Cp\u003EApplications should not assume that a primary email or phone number has been verified.\u003C/p\u003E\n\u003Cp\u003ECheck \u003Ccode\u003EIsVerified\u003C/code\u003E where verified ownership is required.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022login-eligibility-is-separate\u0022\u003ELogin Eligibility Is Separate\u003C/h3\u003E\n\u003Cp\u003EAn identifier may exist on an account without being permitted for login.\u003C/p\u003E\n\u003Cp\u003ELogin eligibility depends on the configured authentication and identifier policies.\u003C/p\u003E\n\u003Cp\u003EDo not assume that every custom identifier is automatically an authentication identifier.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022tenant-boundaries-matter\u0022\u003ETenant Boundaries Matter\u003C/h3\u003E\n\u003Cp\u003EIdentifier uniqueness and ownership are enforced within the relevant tenant context.\u003C/p\u003E\n\u003Cp\u003EClient code must not attempt to bypass tenant isolation by modifying request paths or target user identifiers.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022availability-checks-are-not-reservations\u0022\u003EAvailability Checks Are Not Reservations\u003C/h3\u003E\n\u003Cp\u003EAvailability can change between requests.\u003C/p\u003E\n\u003Cp\u003EAlways enforce uniqueness during the actual identifier mutation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-verification-is-privileged\u0022\u003EAdministrative Verification Is Privileged\u003C/h3\u003E\n\u003Cp\u003EThe administrative verification API has different proof requirements from self-service verification.\u003C/p\u003E\n\u003Cp\u003EOnly appropriately authorized actors should be able to use it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-changes-may-affect-authentication\u0022\u003EIdentifier Changes May Affect Authentication\u003C/h3\u003E\n\u003Cp\u003EChanging or deleting a login identifier may affect future authentication.\u003C/p\u003E\n\u003Cp\u003EApplications should distinguish identifier management from session revocation and credential management.\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/user-management\u0022\u003EUser Management\u003C/a\u003E \u2014 Manage user profiles, account status, and lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Manage passwords, credentials, and recovery workflows.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/authentication\u0022\u003EAuthentication\u003C/a\u003E \u2014 Understand login and authentication flows.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/users\u0022\u003EUsers\u003C/a\u003E \u2014 Explore the user domain architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand authentication security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth treats user identifiers as independent, managed entities rather than simple fields stored directly on a user profile.\u003C/p\u003E\n\u003Cp\u003EThrough \u003Ccode\u003EUAuthClient.Identifiers\u003C/code\u003E, applications can manage usernames, email addresses, phone numbers, and custom identifiers, including primary designation, ownership verification, availability checks, and administrative operations.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the public \u003Ccode\u003EIUserIdentifierClient\u003C/code\u003E API with practical examples for account settings, registration forms, and administration interfaces.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-user-identifiers\u0022\u003E1. Understanding User Identifiers\u003C/h2\u003E\n\u003Cp\u003EAn identifier represents a value associated with a user that may be used for identification, authentication, or communication, depending on the application\u0027s configuration.\u003C/p\u003E\n\u003Cp\u003ESupported identifier types are:\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 enum UserIdentifierType\n{\n Username = 0,\n Email = 10,\n Phone = 20,\n Custom = 100\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA single user can have multiple identifiers.\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\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\u003EUser\n |\n \u002B-- Username\n | johndoe [Primary]\n |\n \u002B-- Email\n | john@example.com [Primary, Verified]\n | john@business.com [Verified]\n |\n \u002B-- Phone\n \u002B1 555 0100 [Primary, Verified]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach identifier has its own value, type, verification state, and primary designation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EIdentifiers are not profiles.\u003C/strong\u003E A user profile contains information such as display name and biography, while identifiers represent managed identity-related values.\u003C/p\u003E\n\u003Cp\u003EFor general profile management, see \u003Ca href=\u0022/docs/client-sdk/user-management\u0022\u003EUser Management\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-identifier-client\u0022\u003E2. Accessing the Identifier Client\u003C/h2\u003E\n\u003Cp\u003EInject \u003Ccode\u003EIUAuthClient\u003C/code\u003E into a Blazor 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\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIdentifier operations are available through:\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\u003EUAuthClient.Identifiers\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis property exposes \u003Ccode\u003EIUserIdentifierClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe client supports three main categories of operations:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECategory\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\u003ESelf-service\u003C/td\u003E\n\u003Ctd\u003EManage identifiers belonging to the current user\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAdministration\u003C/td\u003E\n\u003Ctd\u003EManage identifiers belonging to a specified user\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAvailability\u003C/td\u003E\n\u003Ctd\u003ECheck whether an identifier value is valid and available\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EAll operations are subject to server-side authorization and identifier policies.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-my-identifiers\u0022\u003E3. Get My Identifiers\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMyAsync\u003C/code\u003E to retrieve identifiers belonging to the authenticated 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.Identifiers.GetMyAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var identifier in page.Items)\n {\n Console.WriteLine($\u0026quot;{identifier.Type}: {identifier.Value}\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;PagedResult\u0026lt;UserIdentifierInfo\u0026gt;\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022useridentifierinfo\u0022\u003EUserIdentifierInfo\u003C/h3\u003E\n\u003Cp\u003EEach identifier is represented by \u003Ccode\u003EUserIdentifierInfo\u003C/code\u003E.\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\u003EType\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\u003EId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EGuid\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier record ID\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserIdentifierType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier type\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EValue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier value\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENormalizedValue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EServer-normalized value\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsPrimary\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ebool\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether it is primary for its type\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsVerified\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ebool\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether ownership has been verified\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreation timestamp\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EVerifiedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EVerification timestamp\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EVersion\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Elong\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEntity version\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003EId\u003C/code\u003E identifies the managed identifier record and is used by update, verification, primary designation, and deletion operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022filter-identifiers-in-the-ui\u0022\u003EFilter Identifiers in the UI\u003C/h3\u003E\n\u003Cp\u003EFor example, display verified email addresses from a returned page:\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.Identifiers.GetMyAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n var verifiedEmails = page.Items\n .Where(x =\u0026gt;\n x.Type == UserIdentifierType.Email \u0026amp;\u0026amp;\n x.IsVerified)\n .ToList();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis filters the current page only. Retrieve additional pages when the application needs the complete collection.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pagination\u0022\u003E4. Pagination\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EGetMyAsync\u003C/code\u003E accepts an optional \u003Ccode\u003EPageRequest\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.Identifiers.GetMyAsync(\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server applies its configured pagination policy.\u003C/p\u003E\n\u003Cp\u003EThe returned \u003Ccode\u003EPagedResult\u0026lt;UserIdentifierInfo\u0026gt;\u003C/code\u003E includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EItems\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ETotalCount\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EPageNumber\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EPageSize\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EHasNext\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\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.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n Console.WriteLine($\u0026quot;Total: {page.TotalCount}\u0026quot;);\n\n if (page.HasNext)\n {\n // Enable the next-page control.\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDo not assume the first page contains every identifier.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-an-identifier\u0022\u003E5. Add an Identifier\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EAddMyAsync\u003C/code\u003E to associate a new identifier with the current user.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-an-email-address\u0022\u003EAdd an Email Address\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.Identifiers.AddMyAsync(\n new AddUserIdentifierRequest\n {\n Type = UserIdentifierType.Email,\n Value = \u0026quot;john@business.com\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Identifier added.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-phone-number\u0022\u003EAdd a Phone Number\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.Identifiers.AddMyAsync(\n new AddUserIdentifierRequest\n {\n Type = UserIdentifierType.Phone,\n Value = \u0026quot;\u002B15550100\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-username\u0022\u003EAdd a Username\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.Identifiers.AddMyAsync(\n new AddUserIdentifierRequest\n {\n Type = UserIdentifierType.Username,\n Value = \u0026quot;johndoe\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-custom-identifier\u0022\u003EAdd a Custom Identifier\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.Identifiers.AddMyAsync(\n new AddUserIdentifierRequest\n {\n Type = UserIdentifierType.Custom,\n Value = \u0026quot;external-customer-123\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECustom identifier support does not automatically mean that arbitrary custom values can be used for authentication.\u003C/p\u003E\n\u003Cp\u003ELogin eligibility depends on server configuration and the relevant identifier policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-as-primary\u0022\u003EAdd as Primary\u003C/h3\u003E\n\u003Cp\u003ESet \u003Ccode\u003EIsPrimary\u003C/code\u003E when creating the identifier:\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.Identifiers.AddMyAsync(\n new AddUserIdentifierRequest\n {\n Type = UserIdentifierType.Email,\n Value = \u0026quot;john@example.com\u0026quot;,\n IsPrimary = true\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server determines whether the new value can be assigned as primary.\u003C/p\u003E\n\u003Cp\u003EAdding an identifier does not automatically establish verified ownership.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-limits\u0022\u003EIdentifier Limits\u003C/h3\u003E\n\u003Cp\u003EThe server may restrict how many identifiers a user can maintain.\u003C/p\u003E\n\u003Cp\u003EBy default, UltimateAuth allows up to \u003Cstrong\u003E10 active identifiers per user\u003C/strong\u003E. Applications may configure a different global limit or optional limits for individual identifier types.\u003C/p\u003E\n\u003Cp\u003EThese limits apply to both self-service and administrative identifier operations.\u003C/p\u003E\n\u003Cp\u003EWhen adding an identifier, the server validates the configured limits before accepting the operation.\u003C/p\u003E\n\u003Cp\u003EPossible validation errors include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EError Code\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_total_limit_exceeded\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EThe maximum number of active identifiers has been reached\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eidentifier_type_limit_exceeded\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EThe maximum number of active identifiers of this type has been reached\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EApplications should display an appropriate message when an addition is rejected.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ETotal limit reached:\u003C/strong\u003E \u0026quot;You have reached the maximum number of identifiers allowed for your account.\u0026quot;\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EType limit reached:\u003C/strong\u003E \u0026quot;You have reached the maximum number of identifiers allowed for this type.\u0026quot;\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ESoft-deleted identifiers do not count toward active limits.\u003C/p\u003E\n\u003Cp\u003EHowever, deleting an identifier does not necessarily make its value available for reuse. Identifier uniqueness and identifier capacity are separate rules.\u003C/p\u003E\n\u003Cp\u003EThe server remains authoritative when enforcing these limits. Client applications should not rely solely on local identifier counts to determine whether an addition will succeed.\u003C/p\u003E\n\u003Cp\u003EFor configuration details, see \u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-uniqueness-and-normalization\u0022\u003E6. Identifier Uniqueness and Normalization\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth normalizes identifier values and enforces uniqueness within the relevant tenant boundary.\u003C/p\u003E\n\u003Cp\u003EThis allows applications to work with consistent identifier representations.\u003C/p\u003E\n\u003Cp\u003EFor example, email or username normalization may affect how equivalent input values are compared.\u003C/p\u003E\n\u003Cp\u003EThe exact normalization rules depend on the identifier type and server implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-server-results-as-authoritative\u0022\u003EUse Server Results as Authoritative\u003C/h3\u003E\n\u003Cp\u003EDo not implement a separate client-side normalization algorithm and assume it exactly matches the server.\u003C/p\u003E\n\u003Cp\u003EClient-side formatting can improve the user experience, but the server must remain responsible for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EValidating identifier format.\u003C/li\u003E\n\u003Cli\u003ENormalizing identifier values.\u003C/li\u003E\n\u003Cli\u003EEnforcing uniqueness.\u003C/li\u003E\n\u003Cli\u003EChecking tenant ownership.\u003C/li\u003E\n\u003Cli\u003EApplying identifier-specific policies.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAn identifier value that appears available in the UI may become unavailable before the user submits it.\u003C/p\u003E\n\u003Cp\u003EAlways handle the result of the actual add or update operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-identifier-availability\u0022\u003E7. Check Identifier Availability\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECheckAvailabilityAsync\u003C/code\u003E to check whether an identifier value is valid and available.\u003C/p\u003E\n\u003Cp\u003EThis is useful for registration forms and account settings.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-a-username\u0022\u003ECheck a Username\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.Identifiers.CheckAvailabilityAsync(\n new CheckUserIdentifierAvailabilityRequest\n {\n Type = UserIdentifierType.Username,\n Value = \u0026quot;johndoe\u0026quot;\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } availability)\n{\n if (!availability.IsValid)\n {\n // The identifier value is invalid.\n }\n else if (!availability.IsAvailable)\n {\n // The identifier is already unavailable.\n }\n else\n {\n // The identifier is currently available.\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-an-email-address\u0022\u003ECheck an Email Address\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.Identifiers.CheckAvailabilityAsync(\n new CheckUserIdentifierAvailabilityRequest\n {\n Type = UserIdentifierType.Email,\n Value = \u0026quot;john@example.com\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022availability-result\u0022\u003EAvailability Result\u003C/h3\u003E\n\u003Cp\u003EThe operation 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\u003EUAuthResult\u0026lt;UserIdentifierAvailabilityResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe response value contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsValid\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the value passes validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAvailable\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the value is currently available\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENormalizedValue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENormalized identifier value, when available\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EErrors\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EValidation errors\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=\u0022display-validation-feedback\u0022\u003EDisplay Validation Feedback\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.Identifiers.CheckAvailabilityAsync(\n new CheckUserIdentifierAvailabilityRequest\n {\n Type = UserIdentifierType.Username,\n Value = username\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } availability)\n{\n if (!availability.IsValid)\n {\n foreach (var error in availability.Errors)\n {\n // Display the appropriate validation feedback.\n }\n }\n else if (!availability.IsAvailable)\n {\n message = \u0026quot;This username is unavailable.\u0026quot;;\n }\n else\n {\n message = \u0026quot;This username is available.\u0026quot;;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EAn availability check is advisory, not a reservation.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThe value may become unavailable between the availability check and the subsequent creation operation.\u003C/p\u003E\n\u003Cp\u003EFor public registration forms, also consider whether exposing detailed availability information could enable account enumeration. Apply appropriate server-side controls.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-an-identifier\u0022\u003E8. Update an Identifier\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EUpdateMyAsync\u003C/code\u003E to change the value of an existing identifier.\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.Identifiers.UpdateMyAsync(\n new UpdateUserIdentifierRequest\n {\n Id = identifierId,\n NewValue = \u0026quot;newemail@example.com\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Identifier updated.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUpdateUserIdentifierRequest\u003C/code\u003E contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExisting identifier record ID\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENewValue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReplacement identifier value\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EUnlike profile updates, this operation targets one identifier record.\u003C/p\u003E\n\u003Cp\u003EIt does not replace the user\u0027s entire identifier collection.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022verification-after-an-update\u0022\u003EVerification After an Update\u003C/h3\u003E\n\u003Cp\u003EChanging an identifier\u0027s value may require ownership verification again.\u003C/p\u003E\n\u003Cp\u003EFor example, verification of an old email address does not prove ownership of a new email address.\u003C/p\u003E\n\u003Cp\u003EApplications should reload the identifier after updating it and inspect its current \u003Ccode\u003EIsVerified\u003C/code\u003E state.\u003C/p\u003E\n\u003Cp\u003EDo not assume the previous verification state remains valid.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-primary-identifiers\u0022\u003E9. Understanding Primary Identifiers\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports \u003Cstrong\u003Eone primary identifier per type\u003C/strong\u003E, rather than one global primary identifier for the entire user.\u003C/p\u003E\n\u003Cp\u003EFor example, a user may have:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EOne primary username.\u003C/li\u003E\n\u003Cli\u003EOne primary email.\u003C/li\u003E\n\u003Cli\u003EOne primary phone number.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese primary designations are independent.\u003C/p\u003E\n\u003Cp\u003EThe primary flag identifies the preferred identifier within its type.\u003C/p\u003E\n\u003Cp\u003EHow the application uses primary identifiers for display, communication, or authentication depends on its configuration and business rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022set-a-primary-identifier\u0022\u003ESet a Primary Identifier\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003ESetMyPrimaryAsync\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.Identifiers.SetMyPrimaryAsync(\n new SetPrimaryUserIdentifierRequest\n {\n Id = identifierId\n });\n\nif (result.IsSuccess)\n{\n // Primary designation updated.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen a different identifier becomes primary within the same type, the server maintains the per-type primary constraint.\u003C/p\u003E\n\u003Cp\u003ESetting an email address as primary does not unset the primary phone number or username.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022unset-a-primary-identifier\u0022\u003EUnset a Primary Identifier\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EUnsetMyPrimaryAsync\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.Identifiers.UnsetMyPrimaryAsync(\n new UnsetPrimaryUserIdentifierRequest\n {\n Id = identifierId\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhether a primary identifier can be unset without selecting a replacement depends on server policy.\u003C/p\u003E\n\u003Cp\u003EApplications should handle rejection when the server requires a primary identifier of that type.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022display-primary-identifiers\u0022\u003EDisplay Primary Identifiers\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.Identifiers.GetMyAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n var primaryIdentifiers = page.Items.Where(x =\u0026gt; x.IsPrimary).ToList();\n\n foreach (var identifier in primaryIdentifiers)\n {\n Console.WriteLine($\u0026quot;{identifier.Type}: {identifier.Value}\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERemember that this example searches only the returned page.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022verify-identifier-ownership\u0022\u003E10. Verify Identifier Ownership\u003C/h2\u003E\n\u003Cp\u003EVerification is a security-sensitive operation.\u003C/p\u003E\n\u003Cp\u003EA user must not be able to mark an email address or phone number as verified merely by knowing or submitting its identifier ID.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth requires ownership proof for self-service verification.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022verify-an-identifier\u0022\u003EVerify an Identifier\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EVerifyMyAsync\u003C/code\u003E with a proof obtained through your application\u0027s verification workflow.\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.Identifiers.VerifyMyAsync(\n new VerifyUserIdentifierRequest\n {\n Id = identifierId,\n Proof = verificationProof\n });\n\nif (result.IsSuccess)\n{\n // Identifier verification succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003EProof\u003C/code\u003E property is required for self-service verification.\u003C/p\u003E\n\u003Cp\u003EFor example, a verification workflow may deliver a code to the email address or phone number being verified.\u003C/p\u003E\n\u003Cp\u003EThe application then submits the received proof through \u003Ccode\u003EVerifyMyAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe SDK does not itself guarantee delivery of verification messages.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022verification-flow\u0022\u003EVerification Flow\u003C/h3\u003E\n\u003Cp\u003EA typical verification workflow looks like this:\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\u003EUser adds identifier\n |\n v\nApplication initiates verification\n |\n v\nVerification proof is delivered\n |\n v\nUser submits proof\n |\n v\nIdentifiers.VerifyMyAsync(...)\n |\n v\nServer validates ownership proof\n |\n \u002B-- Valid --\u0026gt; Identifier verified\n |\n \u002B-- Invalid --\u0026gt; Verification rejected\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe mechanism used to issue and deliver the proof is application-specific.\u003C/p\u003E\n\u003Cp\u003EThe server-side verification implementation must validate the proof before updating the identifier\u0027s verified state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-side-verification-with-iuseridentifierverifier\u0022\u003E11. Server-Side Verification with IUserIdentifierVerifier\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth defines \u003Ccode\u003EIUserIdentifierVerifier\u003C/code\u003E for validating identifier ownership proof.\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 interface IUserIdentifierVerifier\n{\n Task\u0026lt;bool\u0026gt; VerifyAsync(UserIdentifierVerificationContext context, CancellationToken ct = default);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe verification context 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\u003Epublic sealed record UserIdentifierVerificationContext(\n TenantKey Tenant,\n UserKey UserKey,\n Guid IdentifierId,\n UserIdentifierType Type,\n string Value,\n string Proof);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA secure verifier must bind the proof to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe tenant.\u003C/li\u003E\n\u003Cli\u003EThe user.\u003C/li\u003E\n\u003Cli\u003EThe identifier record.\u003C/li\u003E\n\u003Cli\u003EThe current identifier value.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIt must also enforce appropriate expiration, attempt limits, and single-use consumption.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022register-your-verifier\u0022\u003ERegister Your Verifier\u003C/h3\u003E\n\u003Cp\u003EIn the server application, register your implementation:\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.AddScoped\u0026lt;IUserIdentifierVerifier, ApplicationIdentifierVerifier\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationIdentifierVerifier\u003C/code\u003E represents your own implementation of the interface.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth includes a \u003Ccode\u003ENotConfiguredUserIdentifierVerifier\u003C/code\u003E fallback that throws an explanatory exception when verification is attempted without a configured implementation.\u003C/p\u003E\n\u003Cp\u003EThis prevents an unconfigured verifier from silently approving identifier ownership.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not implement a verifier that returns \u003Ccode\u003Etrue\u003C/code\u003E unconditionally in production.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-an-identifier\u0022\u003E12. Delete an Identifier\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EDeleteMyAsync\u003C/code\u003E to remove an identifier.\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.Identifiers.DeleteMyAsync(\n new DeleteUserIdentifierRequest\n {\n Id = identifierId,\n Mode = DeleteMode.Soft\n });\n\nif (result.IsSuccess)\n{\n // Identifier deleted.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request includes a deletion mode:\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 enum DeleteMode\n{\n Soft = 0,\n Hard = 10\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hard-delete\u0022\u003EHard Delete\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.Identifiers.DeleteMyAsync(\n new DeleteUserIdentifierRequest\n {\n Id = identifierId,\n Mode = DeleteMode.Hard\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDeletion behavior depends on the server\u0027s identifier lifecycle and authorization rules.\u003C/p\u003E\n\u003Cp\u003EAn application should not assume that every caller can select either deletion mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deleting-primary-identifiers\u0022\u003EDeleting Primary Identifiers\u003C/h3\u003E\n\u003Cp\u003EDeleting a primary identifier may be restricted.\u003C/p\u003E\n\u003Cp\u003EFor example, the server may require another identifier of the same type to become primary first.\u003C/p\u003E\n\u003Cp\u003EApplications should inspect the result and provide appropriate feedback.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deleting-login-identifiers\u0022\u003EDeleting Login Identifiers\u003C/h3\u003E\n\u003Cp\u003ERemoving an identifier that is used for authentication may affect future login attempts.\u003C/p\u003E\n\u003Cp\u003EBefore exposing deletion, consider whether the user will retain a supported method of authentication.\u003C/p\u003E\n\u003Cp\u003EIdentifier deletion should not be treated as equivalent to revoking all existing sessions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-an-identifier-management-page\u0022\u003E13. Build an Identifier Management Page\u003C/h2\u003E\n\u003Cp\u003EThe following example demonstrates a basic Blazor page that lists identifiers, adds an email address, and allows users to change primary designation.\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.Core.Contracts\n@using CodeBeam.UltimateAuth.Users.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;My Identifiers\u0026lt;/h3\u0026gt;\n\n@if (_loading)\n{\n \u0026lt;p\u0026gt;Loading identifiers...\u0026lt;/p\u0026gt;\n}\nelse\n{\n @foreach (var item in _identifiers)\n {\n \u0026lt;div @key=\u0026quot;item.Id\u0026quot;\u0026gt;\n \u0026lt;strong\u0026gt;@item.Value\u0026lt;/strong\u0026gt;\n \u0026lt;span\u0026gt;@item.Type\u0026lt;/span\u0026gt;\n\n @if (item.IsVerified)\n {\n \u0026lt;span\u0026gt;Verified\u0026lt;/span\u0026gt;\n }\n else\n {\n \u0026lt;span\u0026gt;Not verified\u0026lt;/span\u0026gt;\n }\n\n @if (item.IsPrimary)\n {\n \u0026lt;span\u0026gt;Primary\u0026lt;/span\u0026gt;\n }\n else\n {\n \u0026lt;button type=\u0026quot;button\u0026quot; @onclick=\u0026quot;() =\u0026gt; SetPrimaryAsync(item.Id)\u0026quot;\u0026gt;Make primary\u0026lt;/button\u0026gt;\n }\n \u0026lt;/div\u0026gt;\n }\n}\n\n\u0026lt;h4\u0026gt;Add Email\u0026lt;/h4\u0026gt;\n\u0026lt;input @bind=\u0026quot;_newEmail\u0026quot; type=\u0026quot;email\u0026quot; placeholder=\u0026quot;Email address\u0026quot; /\u0026gt;\n\u0026lt;button type=\u0026quot;button\u0026quot; @onclick=\u0026quot;AddEmailAsync\u0026quot; disabled=\u0026quot;@_busy\u0026quot;\u0026gt;Add email\u0026lt;/button\u0026gt;\n\n@if (!string.IsNullOrWhiteSpace(_message))\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private IReadOnlyList\u0026lt;UserIdentifierInfo\u0026gt; _identifiers = [];\n\n private string _newEmail = \u0026quot;\u0026quot;;\n private string? _message;\n\n private bool _loading = true;\n private bool _busy;\n\n protected override async Task OnInitializedAsync()\n {\n await LoadAsync();\n }\n\n private async Task LoadAsync()\n {\n _loading = true;\n\n try\n {\n var result = await UAuthClient.Identifiers.GetMyAsync(\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 50\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n {\n _identifiers = page.Items;\n }\n else\n {\n _message = \u0026quot;Unable to load identifiers.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _message = \u0026quot;The identifier service is unavailable.\u0026quot;;\n }\n finally\n {\n _loading = false;\n }\n }\n\n private async Task AddEmailAsync()\n {\n if (_busy || string.IsNullOrWhiteSpace(_newEmail))\n return;\n\n _busy = true;\n _message = null;\n\n try\n {\n var result = await UAuthClient.Identifiers.AddMyAsync(\n new AddUserIdentifierRequest\n {\n Type = UserIdentifierType.Email,\n Value = _newEmail\n });\n\n if (result.IsSuccess)\n {\n _newEmail = \u0026quot;\u0026quot;;\n await LoadAsync();\n }\n else\n {\n _message = \u0026quot;Unable to add email.\u0026quot;;\n }\n }\n finally\n {\n _busy = false;\n }\n }\n\n private async Task SetPrimaryAsync(Guid id)\n {\n var result = await UAuthClient.Identifiers.SetMyPrimaryAsync(\n new SetPrimaryUserIdentifierRequest\n {\n Id = id\n });\n\n if (result.IsSuccess)\n {\n await LoadAsync();\n }\n else\n {\n _message = \u0026quot;Unable to update primary identifier.\u0026quot;;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example demonstrates the core SDK integration.\u003C/p\u003E\n\u003Cp\u003EIt displays the first page of identifiers. A production implementation should add pagination, verification actions, deletion confirmation, appropriate exception handling, and loading states for all mutations.\u003C/p\u003E\n\u003Cp\u003EFor sensitive changes, consider requiring recent authentication or additional verification according to application policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-identifier-management\u0022\u003E14. Administrative Identifier Management\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides administrative methods for managing identifiers belonging to another user.\u003C/p\u003E\n\u003Cp\u003EThese operations require appropriate server-side authorization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-a-users-identifiers\u0022\u003EGet a User\u0027s Identifiers\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.Identifiers.GetUserAsync(userKey,\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var identifier in page.Items)\n {\n Console.WriteLine(identifier.Value);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-an-identifier-1\u0022\u003EAdd an Identifier\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.Identifiers.AddUserAsync(userKey,\n new AddUserIdentifierRequest\n {\n Type = UserIdentifierType.Email,\n Value = \u0026quot;employee@example.com\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-an-identifier-1\u0022\u003EUpdate an Identifier\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.Identifiers.UpdateUserAsync(userKey,\n new UpdateUserIdentifierRequest\n {\n Id = identifierId,\n NewValue = \u0026quot;updated@example.com\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022set-primary\u0022\u003ESet Primary\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.Identifiers.SetUserPrimaryAsync(userKey,\n new SetPrimaryUserIdentifierRequest\n {\n Id = identifierId\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022unset-primary\u0022\u003EUnset Primary\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.Identifiers.UnsetUserPrimaryAsync(userKey,\n new UnsetPrimaryUserIdentifierRequest\n {\n Id = identifierId\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022verify-an-identifier-as-administrator\u0022\u003EVerify an Identifier as Administrator\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.Identifiers.VerifyUserAsync(userKey,\n new VerifyUserIdentifierRequest\n {\n Id = identifierId\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request contract does not require \u003Ccode\u003EProof\u003C/code\u003E for authorized administrative verification.\u003C/p\u003E\n\u003Cp\u003EHowever, this does not mean any authenticated caller may bypass ownership verification.\u003C/p\u003E\n\u003Cp\u003EThe server must authorize the administrative verification operation and enforce its security requirements.\u003C/p\u003E\n\u003Cp\u003EAdministrative verification should be reserved for trusted workflows with appropriate auditability.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-an-identifier-1\u0022\u003EDelete an Identifier\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.Identifiers.DeleteUserAsync(userKey,\n new DeleteUserIdentifierRequest\n {\n Id = identifierId,\n Mode = DeleteMode.Soft\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAdministrative operations act on the specified target user.\u003C/p\u003E\n\u003Cp\u003EThey do not automatically publish the same local identifier state events as self-service operations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-identifier-operation-results\u0022\u003E15. Handling Identifier Operation Results\u003C/h2\u003E\n\u003Cp\u003EMost identifier mutations return \u003Ccode\u003EUAuthResult\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ERetrieval and availability methods return \u003Ccode\u003EUAuthResult\u0026lt;T\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-success-and-failure\u0022\u003EHandle Success and Failure\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.Identifiers.AddMyAsync(request);\n\nif (result.IsSuccess)\n{\n // Operation succeeded.\n}\nelse if (result.Http.IsConflict)\n{\n // Identifier conflict.\n}\nelse if (result.Http.IsForbidden)\n{\n // Operation not permitted.\n}\nelse if (result.Http.IsBadRequest)\n{\n // Invalid request.\n}\nelse\n{\n // Handle another failure.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actual status code depends on the server\u0027s response.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthResult\u003C/code\u003E also exposes \u003Ccode\u003EProblem\u003C/code\u003E, \u003Ccode\u003EErrorText\u003C/code\u003E, \u003Ccode\u003ECorrelationId\u003C/code\u003E, and \u003Ccode\u003ETraceId\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EUse structured errors when appropriate, but avoid displaying sensitive internal information directly to users.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-state-events\u0022\u003E16. Identifier State Events\u003C/h2\u003E\n\u003Cp\u003EThe client publishes \u003Ccode\u003EIdentifiersChanged\u003C/code\u003E after successful self-service identifier mutations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EEvent\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAddMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiersChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUpdateMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiersChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESetMyPrimaryAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiersChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUnsetMyPrimaryAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiersChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EVerifyMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiersChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeleteMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiersChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EFor add and update operations, the event carries the corresponding request payload.\u003C/p\u003E\n\u003Cp\u003EFor primary designation, verification, and deletion, the SDK publishes the event without a request payload.\u003C/p\u003E\n\u003Cp\u003EThe event handling mode follows the configured client state event options.\u003C/p\u003E\n\u003Cp\u003ERead operations and availability checks do not publish \u003Ccode\u003EIdentifiersChanged\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EAdministrative mutations also do not automatically publish this local self-service event.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-identifier-data-after-changes\u0022\u003ERefresh Identifier Data After Changes\u003C/h3\u003E\n\u003Cp\u003EA component may respond to an identifier state event by reloading its identifier list.\u003C/p\u003E\n\u003Cp\u003EAlternatively, after a successful mutation, the calling component can explicitly refresh the relevant data.\u003C/p\u003E\n\u003Cp\u003EClient events are synchronization mechanisms, not authoritative security decisions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003E17. Security Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022verification-requires-proof\u0022\u003EVerification Requires Proof\u003C/h3\u003E\n\u003Cp\u003ESelf-service verification must establish ownership through a valid proof.\u003C/p\u003E\n\u003Cp\u003EKnowing an identifier ID is not sufficient.\u003C/p\u003E\n\u003Cp\u003EThe server must validate proof binding, expiration, attempt limits, and single-use consumption.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022primary-does-not-mean-verified\u0022\u003EPrimary Does Not Mean Verified\u003C/h3\u003E\n\u003Cp\u003EAn identifier\u0027s primary designation and verification state are separate properties.\u003C/p\u003E\n\u003Cp\u003EApplications should not assume that a primary email or phone number has been verified.\u003C/p\u003E\n\u003Cp\u003ECheck \u003Ccode\u003EIsVerified\u003C/code\u003E where verified ownership is required.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022login-eligibility-is-separate\u0022\u003ELogin Eligibility Is Separate\u003C/h3\u003E\n\u003Cp\u003EAn identifier may exist on an account without being permitted for login.\u003C/p\u003E\n\u003Cp\u003ELogin eligibility depends on the configured authentication and identifier policies.\u003C/p\u003E\n\u003Cp\u003EDo not assume that every custom identifier is automatically an authentication identifier.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022tenant-boundaries-matter\u0022\u003ETenant Boundaries Matter\u003C/h3\u003E\n\u003Cp\u003EIdentifier uniqueness and ownership are enforced within the relevant tenant context.\u003C/p\u003E\n\u003Cp\u003EClient code must not attempt to bypass tenant isolation by modifying request paths or target user identifiers.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022availability-checks-are-not-reservations\u0022\u003EAvailability Checks Are Not Reservations\u003C/h3\u003E\n\u003Cp\u003EAvailability can change between requests.\u003C/p\u003E\n\u003Cp\u003EAlways enforce uniqueness during the actual identifier mutation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-verification-is-privileged\u0022\u003EAdministrative Verification Is Privileged\u003C/h3\u003E\n\u003Cp\u003EThe administrative verification API has different proof requirements from self-service verification.\u003C/p\u003E\n\u003Cp\u003EOnly appropriately authorized actors should be able to use it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-changes-may-affect-authentication\u0022\u003EIdentifier Changes May Affect Authentication\u003C/h3\u003E\n\u003Cp\u003EChanging or deleting a login identifier may affect future authentication.\u003C/p\u003E\n\u003Cp\u003EApplications should distinguish identifier management from session revocation and credential management.\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/user-management\u0022\u003EUser Management\u003C/a\u003E \u2014 Manage user profiles, account status, and lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client-sdk/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Manage passwords, credentials, and recovery workflows.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client-sdk/authentication\u0022\u003EAuthentication\u003C/a\u003E \u2014 Understand login and authentication flows.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/users\u0022\u003EUsers\u003C/a\u003E \u2014 Explore the user domain architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand authentication security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "understanding-user-identifiers", @@ -289,6 +289,6 @@ "Level": 0 } ], - "SourceHash": "4F102EDD321F0410AEDF49D69BE34B330E136108A23527A994CE2EA152EF8356", + "SourceHash": "ADAC74CA7B87824BCE22E4C2EABE20A76E240F2A57AEC96E517FD9498274F0EF", "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/session-management.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/session-management.json index bbc427b2..1e95af80 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/session-management.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/session-management.json @@ -1,7 +1,7 @@ { "Slug": "client-sdk/session-management", "Title": "Session Management", - "Html": "\n\u003Cp\u003EUltimateAuth provides a dedicated client API for inspecting and managing authentication sessions across devices and application instances.\u003C/p\u003E\n\u003Cp\u003EThrough \u003Ccode\u003EUAuthClient.Sessions\u003C/code\u003E, applications can list session chains, inspect authentication history, revoke individual chains, terminate sessions across devices, and perform administrative session management.\u003C/p\u003E\n\u003Cp\u003EThis guide demonstrates the available operations using the current \u003Ccode\u003EISessionClient\u003C/code\u003E contract.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-the-session-hierarchy\u0022\u003E1. Understanding the Session Hierarchy\u003C/h2\u003E\n\u003Cp\u003EIn session-managed authentication modes, UltimateAuth organizes authentication state into three levels:\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\u003ESession Root\n |\n \u002B-- Session Chain A (Browser)\n | |\n | \u002B-- Session A1\n | \u002B-- Session A2\n |\n \u002B-- Session Chain B (Mobile)\n |\n \u002B-- Session B1\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\u003ELevel\u003C/th\u003E\n\u003Cth\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERoot\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAccount-level session authority\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EChain\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EDevice-associated authentication continuity\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESession\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EIndividual authentication instance\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese levels provide different boundaries for session management and revocation.\u003C/p\u003E\n\u003Cp\u003EA chain is associated with a device or client context, but it is not cryptographic proof of physical device identity.\u003C/p\u003E\n\u003Cp\u003EThe available hierarchy and enforcement behavior depend on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EFor architectural details, see \u003Ca href=\u0022/docs/sessions-devices/session-lifecycle\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-session-client\u0022\u003E2. Accessing the Session Client\u003C/h2\u003E\n\u003Cp\u003EInject \u003Ccode\u003EIUAuthClient\u003C/code\u003E into your Blazor 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\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ESession operations are available through:\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\u003EUAuthClient.Sessions\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003ESessions\u003C/code\u003E property exposes \u003Ccode\u003EISessionClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe interface separates operations for the current user from administrative operations targeting another user.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022list-my-devices-and-session-chains\u0022\u003E3. List My Devices and Session Chains\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMyChainsAsync\u003C/code\u003E to retrieve the current user\u0027s session chains.\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.Sessions.GetMyChainsAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var chain in page.Items)\n {\n Console.WriteLine($\u0026quot;{chain.Browser} - {chain.OperatingSystem}\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;PagedResult\u0026lt;SessionChainSummary\u0026gt;\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe response contains a paginated collection of session chain summaries.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessionchainsummary\u0022\u003ESessionChainSummary\u003C/h3\u003E\n\u003Cp\u003EEach summary provides information suitable for building a device management interface.\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\u003EType\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\u003EChainId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessionChainId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUnique chain identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeviceType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDevice category\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOperatingSystem\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReported operating system\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPlatform\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EClient platform\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBrowser\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReported browser\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EChain creation time\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELastSeenAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELast recorded activity\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERotationCount\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eint\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERecorded rotation count\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETouchCount\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eint\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERecorded touch count\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessionCount\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eint\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETotal number of session records in the chain, including historical and revoked sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsRevoked\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ebool\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the chain is revoked\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevocation timestamp\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EActiveSessionId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EAuthSessionId?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssociated active session identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsCurrentDevice\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ebool\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether this is the current device context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EState\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessionChainState\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECurrent chain state\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EDevice metadata is descriptive information, not a trusted physical device identifier.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identify-the-current-device\u0022\u003EIdentify the Current Device\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EIsCurrentDevice\u003C/code\u003E to distinguish the current chain in your UI.\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.Sessions.GetMyChainsAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n var current = page.Items.FirstOrDefault(x =\u0026gt; x.IsCurrentDevice);\n\n if (current is not null)\n {\n Console.WriteLine($\u0026quot;Current chain: {current.ChainId}\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is useful when displaying a device list with a \u003Cstrong\u003ECurrent device\u003C/strong\u003E indicator.\u003C/p\u003E\n\u003Cp\u003EThe property is determined by the server\u0027s current authentication context.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pagination\u0022\u003E4. Pagination\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EGetMyChainsAsync\u003C/code\u003E accepts an optional \u003Ccode\u003EPageRequest\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.Sessions.GetMyChainsAsync(\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 10,\n SortBy = \u0026quot;CreatedAt\u0026quot;,\n Descending = true\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EPageRequest\u003C/code\u003E supports:\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\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\u003EPageNumber\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E1\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested page\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPageSize\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E0\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested page size; zero uses the server pagination default\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESortBy\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional sorting field\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDescending\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDescending sort order\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe server applies its pagination policy, including default and maximum page sizes.\u003C/p\u003E\n\u003Cp\u003ESupported sorting fields depend on the server endpoint.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022navigating-pages\u0022\u003ENavigating Pages\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 pageNumber = 1;\n\nvar result = await UAuthClient.Sessions.GetMyChainsAsync(\n new PageRequest\n {\n PageNumber = pageNumber,\n PageSize = 10\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var chain in page.Items)\n {\n // Render the chain.\n }\n\n if (page.HasNext)\n {\n // Enable the next-page action.\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EPagedResult\u0026lt;T\u0026gt;\u003C/code\u003E includes \u003Ccode\u003ETotalCount\u003C/code\u003E, \u003Ccode\u003EPageNumber\u003C/code\u003E, \u003Ccode\u003EPageSize\u003C/code\u003E, and \u003Ccode\u003EHasNext\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EYou can use these properties directly when building pagination controls.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-a-session-chain\u0022\u003E5. Inspect a Session Chain\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMyChainDetailAsync\u003C/code\u003E to retrieve detailed information about a specific chain.\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\u003ESessionChainId chainId = selectedChain.ChainId;\n\nvar result = await UAuthClient.Sessions.GetMyChainDetailAsync(chainId);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } detail)\n{\n Console.WriteLine($\u0026quot;State: {detail.State}\u0026quot;);\n Console.WriteLine($\u0026quot;Created: {detail.CreatedAt}\u0026quot;);\n Console.WriteLine($\u0026quot;Last seen: {detail.LastSeenAt}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;SessionChainDetail\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessionchaindetail\u0022\u003ESessionChainDetail\u003C/h3\u003E\n\u003Cp\u003EThe detail model includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EChain identity and device metadata.\u003C/li\u003E\n\u003Cli\u003ECreation and last-seen timestamps.\u003C/li\u003E\n\u003Cli\u003EChain state and revocation information.\u003C/li\u003E\n\u003Cli\u003ERotation and touch counters.\u003C/li\u003E\n\u003Cli\u003EActive session identifier.\u003C/li\u003E\n\u003Cli\u003ESession history.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-session-history\u0022\u003EInspect Session History\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.Sessions.GetMyChainDetailAsync(chainId);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } detail)\n{\n foreach (var session in detail.Sessions)\n {\n Console.WriteLine($\u0026quot;Session: {session.SessionId}\u0026quot;);\n Console.WriteLine($\u0026quot;Created: {session.CreatedAt}\u0026quot;);\n Console.WriteLine($\u0026quot;Expires: {session.ExpiresAt}\u0026quot;);\n Console.WriteLine($\u0026quot;Revoked: {session.IsRevoked}\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach session is represented by \u003Ccode\u003ESessionInfo\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 SessionInfo(\n AuthSessionId SessionId,\n DateTimeOffset CreatedAt,\n DateTimeOffset ExpiresAt,\n bool IsRevoked);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows applications to display individual authentication instances within a device-associated chain.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-chain-states\u0022\u003E6. Understanding Chain States\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ESessionChainState\u003C/code\u003E defines three values:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EState\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\u003EActive\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EActive chain state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPassive\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPassive chain state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevoked\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoked chain state\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\u003Eswitch (chain.State)\n{\n case SessionChainState.Active:\n // Display as active.\n break;\n\n case SessionChainState.Passive:\n // Display as passive.\n break;\n\n case SessionChainState.Revoked:\n // Display as revoked.\n break;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA passive chain should not automatically be presented as a revoked chain.\u003C/p\u003E\n\u003Cp\u003EUse the returned state rather than inferring revocation from the absence of an active session.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-one-of-my-devices\u0022\u003E7. Revoke One of My Devices\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERevokeMyChainAsync\u003C/code\u003E to revoke a selected session chain.\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.Sessions.RevokeMyChainAsync(chainId);\n\nif (result.IsSuccess)\n{\n // Refresh the device list.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;RevokeResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERevoking a chain invalidates its associated session continuity according to the server\u0027s revocation model.\u003C/p\u003E\n\u003Cp\u003EIt does not physically disable the device or prevent the user from authenticating again.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022detect-revocation-of-the-current-chain\u0022\u003EDetect Revocation of the Current Chain\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ERevokeResult\u003C/code\u003E contains:\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 RevokeResult\n{\n public bool CurrentChain { get; init; }\n public bool RootRevoked { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EYou can inspect these properties:\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.Sessions.RevokeMyChainAsync(chainId);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } revoke)\n{\n if (revoke.CurrentChain)\n {\n // The current authentication chain was affected.\n }\n\n if (revoke.RootRevoked)\n {\n // The result reports root-level revocation.\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen \u003Ccode\u003ERevokeMyChainAsync\u003C/code\u003E succeeds and \u003Ccode\u003ECurrentChain\u003C/code\u003E is true, the SDK publishes a \u003Ccode\u003ESessionRevoked\u003C/code\u003E state event.\u003C/p\u003E\n\u003Cp\u003EThis helps the application react when its own authentication context has been invalidated.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-all-other-devices\u0022\u003E8. Revoke All Other Devices\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERevokeMyOtherChainsAsync\u003C/code\u003E to revoke other session chains while retaining the current chain.\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.Sessions.RevokeMyOtherChainsAsync();\n\nif (result.IsSuccess)\n{\n // Other device chains were revoked.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is suitable for a \u003Cstrong\u003ELog out other devices\u003C/strong\u003E action in account settings.\u003C/p\u003E\n\u003Cp\u003EThe server determines the current chain and applies the corresponding revocation scope.\u003C/p\u003E\n\u003Cp\u003EApplications should not attempt to identify the current device by comparing browser names, operating systems, or other descriptive metadata.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-all-my-devices\u0022\u003E9. Revoke All My Devices\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERevokeAllMyChainsAsync\u003C/code\u003E to revoke every session chain associated with 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.Sessions.RevokeAllMyChainsAsync();\n\nif (result.IsSuccess)\n{\n // All applicable chains were revoked.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis includes the current device-associated chain.\u003C/p\u003E\n\u003Cp\u003EWhen the operation succeeds, the SDK publishes a \u003Ccode\u003ESessionRevoked\u003C/code\u003E event.\u003C/p\u003E\n\u003Cp\u003EA typical use case is an account security page offering \u003Cstrong\u003ELog out everywhere\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003ESecurity Considerations\u003C/h3\u003E\n\u003Cp\u003ERevoking server-managed sessions prevents their continued use where the authentication mode enforces that state.\u003C/p\u003E\n\u003Cp\u003EHowever, previously issued JWT access tokens may remain valid until expiration if the receiving resource server validates them locally without consulting current revocation state.\u003C/p\u003E\n\u003Cp\u003EDo not assume that chain revocation automatically invalidates every access token in every authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-versus-session-revocation\u0022\u003E10. Logout Versus Session Revocation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth exposes authentication logout operations through \u003Ccode\u003EFlows\u003C/code\u003E and session management operations through \u003Ccode\u003ESessions\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EBoth may affect authentication state, but they serve different application responsibilities.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003ERecommended API\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ENormal logout\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Flows.LogoutAsync()\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ELogout a selected device\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Flows.LogoutMyDeviceAsync(request)\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ELogout other devices\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Flows.LogoutMyOtherDevicesAsync()\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ELogout all devices\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Flows.LogoutAllMyDevicesAsync()\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInspect session chains\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Sessions.GetMyChainsAsync()\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERevoke a selected chain\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Sessions.RevokeMyChainAsync(chainId)\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERevoke other chains\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Sessions.RevokeMyOtherChainsAsync()\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERevoke all chains\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Sessions.RevokeAllMyChainsAsync()\u003C/code\u003E\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=\u0022which-api-should-you-use\u0022\u003EWhich API Should You Use?\u003C/h3\u003E\n\u003Cp\u003EFor ordinary authentication actions, prefer \u003Ccode\u003EFlows\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\u003Eawait UAuthClient.Flows.LogoutAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor a session management interface that lists and controls chains, use \u003Ccode\u003ESessions\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.Sessions.RevokeMyChainAsync(chainId);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe APIs expose related capabilities through different client surfaces.\u003C/p\u003E\n\u003Cp\u003EThe \u003Ccode\u003EFlows\u003C/code\u003E API emphasizes authentication workflows, while \u003Ccode\u003ESessions\u003C/code\u003E exposes the session management model more directly.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-a-device-through-flows\u0022\u003ELogout a Device Through Flows\u003C/h3\u003E\n\u003Cp\u003EThe device logout method requires \u003Ccode\u003ELogoutDeviceRequest\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.LogoutMyDeviceAsync(\n new LogoutDeviceRequest\n {\n ChainId = chainId\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EPass a \u003Ccode\u003ESessionChainId\u003C/code\u003E, not an individual \u003Ccode\u003EAuthSessionId\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor complete logout examples, see \u003Ca href=\u0022/docs/client/authentication\u0022\u003EAuthentication\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-device-management-page\u0022\u003E11. Build a Device Management Page\u003C/h2\u003E\n\u003Cp\u003EA common application requirement is to show all devices associated with the current account and allow the user to revoke them.\u003C/p\u003E\n\u003Cp\u003EThe following example uses the session client to build a simple Blazor device list.\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.Core.Contracts\n@using CodeBeam.UltimateAuth.Core.Domain\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;My Devices\u0026lt;/h3\u0026gt;\n\n@if (_loading)\n{\n \u0026lt;p\u0026gt;Loading devices...\u0026lt;/p\u0026gt;\n}\nelse if (_error is not null)\n{\n \u0026lt;p role=\u0026quot;alert\u0026quot;\u0026gt;@_error\u0026lt;/p\u0026gt;\n}\nelse\n{\n @foreach (var chain in _chains)\n {\n \u0026lt;div @key=\u0026quot;chain.ChainId\u0026quot;\u0026gt;\n \u0026lt;strong\u0026gt;@(chain.Browser ?? \u0026quot;Unknown browser\u0026quot;)\u0026lt;/strong\u0026gt;\n \u0026lt;p\u0026gt;@(chain.OperatingSystem ?? \u0026quot;Unknown OS\u0026quot;)\u0026lt;/p\u0026gt;\n \u0026lt;p\u0026gt;State: @chain.State\u0026lt;/p\u0026gt;\n\n @if (chain.IsCurrentDevice)\n {\n \u0026lt;span\u0026gt;Current device\u0026lt;/span\u0026gt;\n }\n\n \u0026lt;button type=\u0026quot;button\u0026quot; @onclick=\u0026quot;() =\u0026gt; RevokeAsync(chain.ChainId)\u0026quot;\u0026gt;Revoke\u0026lt;/button\u0026gt;\n \u0026lt;/div\u0026gt;\n }\n}\n\n@code {\n private IReadOnlyList\u0026lt;SessionChainSummary\u0026gt; _chains = [];\n private bool _loading = true;\n private string? _error;\n\n protected override async Task OnInitializedAsync()\n {\n await LoadAsync();\n }\n\n private async Task LoadAsync()\n {\n _loading = true;\n _error = null;\n\n try\n {\n var result = await UAuthClient.Sessions.GetMyChainsAsync(new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n {\n _chains = page.Items;\n }\n else\n {\n _error = \u0026quot;Unable to load devices.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _error = \u0026quot;The session service is unavailable.\u0026quot;;\n }\n finally\n {\n _loading = false;\n }\n }\n\n private async Task RevokeAsync(SessionChainId chainId)\n {\n var result = await UAuthClient.Sessions.RevokeMyChainAsync(chainId);\n\n if (result.IsSuccess)\n {\n await LoadAsync();\n }\n else\n {\n _error = \u0026quot;Unable to revoke the selected device.\u0026quot;;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example demonstrates the core integration.\u003C/p\u003E\n\u003Cp\u003EA production UI should additionally provide confirmation before revocation, disable repeated submissions while an operation is in progress, and handle the possibility that the current device is being revoked.\u003C/p\u003E\n\u003Cp\u003EThe example displays the first page. Use the pagination API to expose additional chains.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-session-management\u0022\u003E12. Administrative Session Management\u003C/h2\u003E\n\u003Cp\u003EAdministrative operations allow authorized actors to inspect and manage another user\u0027s session state.\u003C/p\u003E\n\u003Cp\u003EThese methods require appropriate server-side permissions.\u003C/p\u003E\n\u003Cp\u003EThe presence of an administrative SDK method does not grant administrative access.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022list-a-users-devices\u0022\u003EList a User\u0027s Devices\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.Sessions.GetUserChainsAsync(userKey);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var chain in page.Items)\n {\n Console.WriteLine(chain.ChainId);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method accepts an optional \u003Ccode\u003EPageRequest\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.Sessions.GetUserChainsAsync(userKey,\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-a-users-chain\u0022\u003EInspect a User\u0027s Chain\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetUserChainDetailAsync\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.Sessions.GetUserChainDetailAsync(userKey, chainId);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } detail)\n{\n foreach (var session in detail.Sessions)\n {\n Console.WriteLine(session.SessionId);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis provides the same \u003Ccode\u003ESessionChainDetail\u003C/code\u003E model used for self-service chain inspection.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-an-individual-session\u0022\u003ERevoke an Individual Session\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERevokeUserSessionAsync\u003C/code\u003E when you need to revoke one authentication instance rather than its entire chain.\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.Sessions.RevokeUserSessionAsync(userKey, sessionId);\n\nif (result.IsSuccess)\n{\n // The session revocation operation succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method requires an \u003Ccode\u003EAuthSessionId\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis is different from chain revocation.\u003C/p\u003E\n\u003Cp\u003EA session-level operation targets a single authentication instance; a chain-level operation targets the associated session continuity boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-a-users-device-chain\u0022\u003ERevoke a User\u0027s Device Chain\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.Sessions.RevokeUserChainAsync(userKey, chainId);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } revoke)\n{\n // Inspect revoke.CurrentChain or revoke.RootRevoked if needed.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003EUAuthResult\u0026lt;RevokeResult\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-all-user-chains\u0022\u003ERevoke All User Chains\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.Sessions.RevokeAllUserChainsAsync(userKey);\n\nif (result.IsSuccess)\n{\n // All applicable user chains were revoked.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is useful for administrative account security operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-the-users-session-root\u0022\u003ERevoke the User\u0027s Session Root\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth also exposes root-level revocation:\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.Sessions.RevokeUserRootAsync(userKey);\n\nif (result.IsSuccess)\n{\n // Root-level revocation succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERoot revocation is a stronger account-level security operation than revoking one session or one chain.\u003C/p\u003E\n\u003Cp\u003EIt is intended for situations where the user\u0027s broader session authority must be invalidated.\u003C/p\u003E\n\u003Cp\u003EExamples include account compromise response or other security-critical administrative actions.\u003C/p\u003E\n\u003Cp\u003EDo not use root revocation as a routine substitute for ordinary logout.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-the-correct-revocation-scope\u0022\u003E13. Choosing the Correct Revocation Scope\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides multiple revocation levels because different security events require different responses.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EScope\u003C/th\u003E\n\u003Cth\u003EAPI\u003C/th\u003E\n\u003Cth\u003EIntended effect\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECurrent logout\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EFlows.LogoutAsync()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEnd current authentication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIndividual session\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessions.RevokeUserSessionAsync()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke one authentication instance\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOne chain\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessions.RevokeMyChainAsync()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke a device-associated chain\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOther chains\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessions.RevokeMyOtherChainsAsync()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERetain current chain, revoke others\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAll chains\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessions.RevokeAllMyChainsAsync()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke all current user\u0027s chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUser root\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessions.RevokeUserRootAsync()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EInvalidate account-level session authority\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EUse the narrowest scope that satisfies the security requirement.\u003C/p\u003E\n\u003Cp\u003EFor example, removing one unrecognized device from an account should not automatically require revoking the entire user\u0027s session root.\u003C/p\u003E\n\u003Cp\u003EConversely, a confirmed account-wide compromise may justify broader revocation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-session-operation-results\u0022\u003E14. Handling Session Operation Results\u003C/h2\u003E\n\u003Cp\u003ESession client methods return \u003Ccode\u003EUAuthResult\u003C/code\u003E or \u003Ccode\u003EUAuthResult\u0026lt;T\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA typical result-handling pattern 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\u003Evar result = await UAuthClient.Sessions.GetMyChainsAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n // Use the returned data.\n}\nelse if (result.Http.IsUnauthorized)\n{\n // Authentication is required.\n}\nelse if (result.Http.IsForbidden)\n{\n // The operation is not authorized.\n}\nelse\n{\n // Handle another failure.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAvailable response information includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EIsSuccess\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EStatus\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EProblem\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EErrorText\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ECorrelationId\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ETraceId\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe \u003Ccode\u003EHttp\u003C/code\u003E helper exposes status checks such as \u003Ccode\u003EIsBadRequest\u003C/code\u003E, \u003Ccode\u003EIsUnauthorized\u003C/code\u003E, \u003Ccode\u003EIsForbidden\u003C/code\u003E, and \u003Ccode\u003EIsConflict\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-authorization-failures\u0022\u003EAdministrative Authorization Failures\u003C/h3\u003E\n\u003Cp\u003EAn administrative session operation may return \u003Ccode\u003E403 Forbidden\u003C/code\u003E when the actor lacks the required permission.\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.Sessions.GetUserChainsAsync(userKey);\n\nif (result.Http.IsForbidden)\n{\n // The current actor cannot inspect this user\u0027s sessions.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDo not attempt to work around authorization failures by switching to a different endpoint.\u003C/p\u003E\n\u003Cp\u003EThe server is responsible for enforcing the operation\u0027s access policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-state-events\u0022\u003E15. Session State Events\u003C/h2\u003E\n\u003Cp\u003EThe session client publishes state events for certain successful revocation operations.\u003C/p\u003E\n\u003Cp\u003EThe current implementation publishes \u003Ccode\u003ESessionRevoked\u003C/code\u003E when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ERevokeMyChainAsync\u003C/code\u003E succeeds and the result identifies the current chain.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ERevokeAllMyChainsAsync\u003C/code\u003E succeeds.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EOther session management methods do not necessarily publish the same event.\u003C/p\u003E\n\u003Cp\u003EApplications should not assume that every successful administrative revocation produces a local state event.\u003C/p\u003E\n\u003Cp\u003EState events help the client update its authentication-related UI.\u003C/p\u003E\n\u003Cp\u003EThey do not replace authoritative server-side session validation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-and-operational-considerations\u0022\u003E16. Security and Operational Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-metadata-is-descriptive\u0022\u003EDevice Metadata Is Descriptive\u003C/h3\u003E\n\u003Cp\u003EBrowser, operating system, platform, and device type are useful for displaying session information.\u003C/p\u003E\n\u003Cp\u003EThey are not reliable proof of physical device ownership.\u003C/p\u003E\n\u003Cp\u003EA malicious or modified client may be able to influence some reported metadata.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-chains-are-not-permanent-device-identities\u0022\u003ESession Chains Are Not Permanent Device Identities\u003C/h3\u003E\n\u003Cp\u003ERevoking a chain invalidates its authentication continuity.\u003C/p\u003E\n\u003Cp\u003EIt does not permanently ban the physical device.\u003C/p\u003E\n\u003Cp\u003EA later successful login may establish new authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-is-server-controlled\u0022\u003ERevocation Is Server-Controlled\u003C/h3\u003E\n\u003Cp\u003EClient-side removal of a device card does not revoke the underlying session.\u003C/p\u003E\n\u003Cp\u003EAlways use the appropriate session or logout operation and inspect its result.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-modes-matter\u0022\u003EAuthentication Modes Matter\u003C/h3\u003E\n\u003Cp\u003EThe Root \u2192 Chain \u2192 Session hierarchy is relevant to server-managed session modes.\u003C/p\u003E\n\u003Cp\u003EDo not assume identical session management semantics in PureJwt or other modes that do not enforce the same hierarchy during ordinary request validation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-administrative-operations\u0022\u003EProtect Administrative Operations\u003C/h3\u003E\n\u003Cp\u003EAdministrative session revocation can interrupt another user\u0027s authentication.\u003C/p\u003E\n\u003Cp\u003EThese operations should be protected by appropriate permissions and, where required, stronger authentication or administrative confirmation.\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/authentication\u0022\u003EAuthentication\u003C/a\u003E \u2014 Login, logout, refresh, and authentication state validation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/session-lifecycle\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E \u2014 Session roots, chains, lifecycle, and expiration.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E \u2014 Device-associated authentication contexts.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Revocation and session security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth provides a dedicated client API for inspecting and managing authentication sessions across devices and application instances.\u003C/p\u003E\n\u003Cp\u003EThrough \u003Ccode\u003EUAuthClient.Sessions\u003C/code\u003E, applications can list session chains, inspect authentication history, revoke individual chains, terminate sessions across devices, and perform administrative session management.\u003C/p\u003E\n\u003Cp\u003EThis guide demonstrates the available operations using the current \u003Ccode\u003EISessionClient\u003C/code\u003E contract.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-the-session-hierarchy\u0022\u003E1. Understanding the Session Hierarchy\u003C/h2\u003E\n\u003Cp\u003EIn session-managed authentication modes, UltimateAuth organizes authentication state into three levels:\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\u003ESession Root\n |\n \u002B-- Session Chain A (Browser)\n | |\n | \u002B-- Session A1\n | \u002B-- Session A2\n |\n \u002B-- Session Chain B (Mobile)\n |\n \u002B-- Session B1\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\u003ELevel\u003C/th\u003E\n\u003Cth\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERoot\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAccount-level session authority\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EChain\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EDevice-associated authentication continuity\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESession\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EIndividual authentication instance\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese levels provide different boundaries for session management and revocation.\u003C/p\u003E\n\u003Cp\u003EA chain is associated with a device or client context, but it is not cryptographic proof of physical device identity.\u003C/p\u003E\n\u003Cp\u003EThe available hierarchy and enforcement behavior depend on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EFor architectural details, see \u003Ca href=\u0022/docs/sessions-devices/session-lifecycle\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-session-client\u0022\u003E2. Accessing the Session Client\u003C/h2\u003E\n\u003Cp\u003EInject \u003Ccode\u003EIUAuthClient\u003C/code\u003E into your Blazor 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\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ESession operations are available through:\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\u003EUAuthClient.Sessions\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003ESessions\u003C/code\u003E property exposes \u003Ccode\u003EISessionClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe interface separates operations for the current user from administrative operations targeting another user.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022list-my-devices-and-session-chains\u0022\u003E3. List My Devices and Session Chains\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMyChainsAsync\u003C/code\u003E to retrieve the current user\u0027s session chains.\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.Sessions.GetMyChainsAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var chain in page.Items)\n {\n Console.WriteLine($\u0026quot;{chain.Browser} - {chain.OperatingSystem}\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;PagedResult\u0026lt;SessionChainSummary\u0026gt;\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe response contains a paginated collection of session chain summaries.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessionchainsummary\u0022\u003ESessionChainSummary\u003C/h3\u003E\n\u003Cp\u003EEach summary provides information suitable for building a device management interface.\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\u003EType\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\u003EChainId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessionChainId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUnique chain identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeviceType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDevice category\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOperatingSystem\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReported operating system\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPlatform\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EClient platform\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBrowser\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReported browser\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EChain creation time\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELastSeenAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELast recorded activity\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERotationCount\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eint\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERecorded rotation count\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETouchCount\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eint\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERecorded touch count\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessionCount\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eint\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETotal number of session records in the chain, including historical and revoked sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsRevoked\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ebool\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the chain is revoked\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevocation timestamp\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EActiveSessionId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EAuthSessionId?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssociated active session identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsCurrentDevice\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ebool\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether this is the current device context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EState\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessionChainState\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECurrent chain state\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EDevice metadata is descriptive information, not a trusted physical device identifier.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identify-the-current-device\u0022\u003EIdentify the Current Device\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EIsCurrentDevice\u003C/code\u003E to distinguish the current chain in your UI.\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.Sessions.GetMyChainsAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n var current = page.Items.FirstOrDefault(x =\u0026gt; x.IsCurrentDevice);\n\n if (current is not null)\n {\n Console.WriteLine($\u0026quot;Current chain: {current.ChainId}\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is useful when displaying a device list with a \u003Cstrong\u003ECurrent device\u003C/strong\u003E indicator.\u003C/p\u003E\n\u003Cp\u003EThe property is determined by the server\u0027s current authentication context.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pagination\u0022\u003E4. Pagination\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EGetMyChainsAsync\u003C/code\u003E accepts an optional \u003Ccode\u003EPageRequest\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.Sessions.GetMyChainsAsync(\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 10,\n SortBy = \u0026quot;CreatedAt\u0026quot;,\n Descending = true\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EPageRequest\u003C/code\u003E supports:\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\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\u003EPageNumber\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E1\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested page\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPageSize\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E0\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested page size; zero uses the server pagination default\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESortBy\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional sorting field\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDescending\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDescending sort order\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe server applies its pagination policy, including default and maximum page sizes.\u003C/p\u003E\n\u003Cp\u003ESupported sorting fields depend on the server endpoint.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022navigating-pages\u0022\u003ENavigating Pages\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 pageNumber = 1;\n\nvar result = await UAuthClient.Sessions.GetMyChainsAsync(\n new PageRequest\n {\n PageNumber = pageNumber,\n PageSize = 10\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var chain in page.Items)\n {\n // Render the chain.\n }\n\n if (page.HasNext)\n {\n // Enable the next-page action.\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EPagedResult\u0026lt;T\u0026gt;\u003C/code\u003E includes \u003Ccode\u003ETotalCount\u003C/code\u003E, \u003Ccode\u003EPageNumber\u003C/code\u003E, \u003Ccode\u003EPageSize\u003C/code\u003E, and \u003Ccode\u003EHasNext\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EYou can use these properties directly when building pagination controls.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-a-session-chain\u0022\u003E5. Inspect a Session Chain\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMyChainDetailAsync\u003C/code\u003E to retrieve detailed information about a specific chain.\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\u003ESessionChainId chainId = selectedChain.ChainId;\n\nvar result = await UAuthClient.Sessions.GetMyChainDetailAsync(chainId);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } detail)\n{\n Console.WriteLine($\u0026quot;State: {detail.State}\u0026quot;);\n Console.WriteLine($\u0026quot;Created: {detail.CreatedAt}\u0026quot;);\n Console.WriteLine($\u0026quot;Last seen: {detail.LastSeenAt}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;SessionChainDetail\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessionchaindetail\u0022\u003ESessionChainDetail\u003C/h3\u003E\n\u003Cp\u003EThe detail model includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EChain identity and device metadata.\u003C/li\u003E\n\u003Cli\u003ECreation and last-seen timestamps.\u003C/li\u003E\n\u003Cli\u003EChain state and revocation information.\u003C/li\u003E\n\u003Cli\u003ERotation and touch counters.\u003C/li\u003E\n\u003Cli\u003EActive session identifier.\u003C/li\u003E\n\u003Cli\u003ESession history.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-session-history\u0022\u003EInspect Session History\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.Sessions.GetMyChainDetailAsync(chainId);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } detail)\n{\n foreach (var session in detail.Sessions)\n {\n Console.WriteLine($\u0026quot;Session: {session.SessionId}\u0026quot;);\n Console.WriteLine($\u0026quot;Created: {session.CreatedAt}\u0026quot;);\n Console.WriteLine($\u0026quot;Expires: {session.ExpiresAt}\u0026quot;);\n Console.WriteLine($\u0026quot;Revoked: {session.IsRevoked}\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach session is represented by \u003Ccode\u003ESessionInfo\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 SessionInfo(\n AuthSessionId SessionId,\n DateTimeOffset CreatedAt,\n DateTimeOffset ExpiresAt,\n bool IsRevoked);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows applications to display individual authentication instances within a device-associated chain.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-chain-states\u0022\u003E6. Understanding Chain States\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ESessionChainState\u003C/code\u003E defines three values:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EState\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\u003EActive\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EActive chain state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPassive\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPassive chain state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevoked\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoked chain state\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\u003Eswitch (chain.State)\n{\n case SessionChainState.Active:\n // Display as active.\n break;\n\n case SessionChainState.Passive:\n // Display as passive.\n break;\n\n case SessionChainState.Revoked:\n // Display as revoked.\n break;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA passive chain should not automatically be presented as a revoked chain.\u003C/p\u003E\n\u003Cp\u003EUse the returned state rather than inferring revocation from the absence of an active session.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-one-of-my-devices\u0022\u003E7. Revoke One of My Devices\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERevokeMyChainAsync\u003C/code\u003E to revoke a selected session chain.\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.Sessions.RevokeMyChainAsync(chainId);\n\nif (result.IsSuccess)\n{\n // Refresh the device list.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;RevokeResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERevoking a chain invalidates its associated session continuity according to the server\u0027s revocation model.\u003C/p\u003E\n\u003Cp\u003EIt does not physically disable the device or prevent the user from authenticating again.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022detect-revocation-of-the-current-chain\u0022\u003EDetect Revocation of the Current Chain\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ERevokeResult\u003C/code\u003E contains:\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 RevokeResult\n{\n public bool CurrentChain { get; init; }\n public bool RootRevoked { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EYou can inspect these properties:\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.Sessions.RevokeMyChainAsync(chainId);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } revoke)\n{\n if (revoke.CurrentChain)\n {\n // The current authentication chain was affected.\n }\n\n if (revoke.RootRevoked)\n {\n // The result reports root-level revocation.\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen \u003Ccode\u003ERevokeMyChainAsync\u003C/code\u003E succeeds and \u003Ccode\u003ECurrentChain\u003C/code\u003E is true, the SDK publishes a \u003Ccode\u003ESessionRevoked\u003C/code\u003E state event.\u003C/p\u003E\n\u003Cp\u003EThis helps the application react when its own authentication context has been invalidated.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-all-other-devices\u0022\u003E8. Revoke All Other Devices\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERevokeMyOtherChainsAsync\u003C/code\u003E to revoke other session chains while retaining the current chain.\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.Sessions.RevokeMyOtherChainsAsync();\n\nif (result.IsSuccess)\n{\n // Other device chains were revoked.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is suitable for a \u003Cstrong\u003ELog out other devices\u003C/strong\u003E action in account settings.\u003C/p\u003E\n\u003Cp\u003EThe server determines the current chain and applies the corresponding revocation scope.\u003C/p\u003E\n\u003Cp\u003EApplications should not attempt to identify the current device by comparing browser names, operating systems, or other descriptive metadata.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-all-my-devices\u0022\u003E9. Revoke All My Devices\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERevokeAllMyChainsAsync\u003C/code\u003E to revoke every session chain associated with 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.Sessions.RevokeAllMyChainsAsync();\n\nif (result.IsSuccess)\n{\n // All applicable chains were revoked.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis includes the current device-associated chain.\u003C/p\u003E\n\u003Cp\u003EWhen the operation succeeds, the SDK publishes a \u003Ccode\u003ESessionRevoked\u003C/code\u003E event.\u003C/p\u003E\n\u003Cp\u003EA typical use case is an account security page offering \u003Cstrong\u003ELog out everywhere\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003ESecurity Considerations\u003C/h3\u003E\n\u003Cp\u003ERevoking server-managed sessions prevents their continued use where the authentication mode enforces that state.\u003C/p\u003E\n\u003Cp\u003EHowever, previously issued JWT access tokens may remain valid until expiration if the receiving resource server validates them locally without consulting current revocation state.\u003C/p\u003E\n\u003Cp\u003EDo not assume that chain revocation automatically invalidates every access token in every authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-versus-session-revocation\u0022\u003E10. Logout Versus Session Revocation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth exposes authentication logout operations through \u003Ccode\u003EFlows\u003C/code\u003E and session management operations through \u003Ccode\u003ESessions\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EBoth may affect authentication state, but they serve different application responsibilities.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003ERecommended API\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ENormal logout\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Flows.LogoutAsync()\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ELogout a selected device\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Flows.LogoutMyDeviceAsync(request)\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ELogout other devices\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Flows.LogoutMyOtherDevicesAsync()\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ELogout all devices\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Flows.LogoutAllMyDevicesAsync()\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInspect session chains\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Sessions.GetMyChainsAsync()\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERevoke a selected chain\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Sessions.RevokeMyChainAsync(chainId)\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERevoke other chains\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Sessions.RevokeMyOtherChainsAsync()\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERevoke all chains\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient.Sessions.RevokeAllMyChainsAsync()\u003C/code\u003E\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=\u0022which-api-should-you-use\u0022\u003EWhich API Should You Use?\u003C/h3\u003E\n\u003Cp\u003EFor ordinary authentication actions, prefer \u003Ccode\u003EFlows\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\u003Eawait UAuthClient.Flows.LogoutAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor a session management interface that lists and controls chains, use \u003Ccode\u003ESessions\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.Sessions.RevokeMyChainAsync(chainId);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe APIs expose related capabilities through different client surfaces.\u003C/p\u003E\n\u003Cp\u003EThe \u003Ccode\u003EFlows\u003C/code\u003E API emphasizes authentication workflows, while \u003Ccode\u003ESessions\u003C/code\u003E exposes the session management model more directly.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-a-device-through-flows\u0022\u003ELogout a Device Through Flows\u003C/h3\u003E\n\u003Cp\u003EThe device logout method requires \u003Ccode\u003ELogoutDeviceRequest\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.LogoutMyDeviceAsync(\n new LogoutDeviceRequest\n {\n ChainId = chainId\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EPass a \u003Ccode\u003ESessionChainId\u003C/code\u003E, not an individual \u003Ccode\u003EAuthSessionId\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor complete logout examples, see \u003Ca href=\u0022/docs/client-sdk/authentication\u0022\u003EAuthentication\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-device-management-page\u0022\u003E11. Build a Device Management Page\u003C/h2\u003E\n\u003Cp\u003EA common application requirement is to show all devices associated with the current account and allow the user to revoke them.\u003C/p\u003E\n\u003Cp\u003EThe following example uses the session client to build a simple Blazor device list.\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.Core.Contracts\n@using CodeBeam.UltimateAuth.Core.Domain\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;My Devices\u0026lt;/h3\u0026gt;\n\n@if (_loading)\n{\n \u0026lt;p\u0026gt;Loading devices...\u0026lt;/p\u0026gt;\n}\nelse if (_error is not null)\n{\n \u0026lt;p role=\u0026quot;alert\u0026quot;\u0026gt;@_error\u0026lt;/p\u0026gt;\n}\nelse\n{\n @foreach (var chain in _chains)\n {\n \u0026lt;div @key=\u0026quot;chain.ChainId\u0026quot;\u0026gt;\n \u0026lt;strong\u0026gt;@(chain.Browser ?? \u0026quot;Unknown browser\u0026quot;)\u0026lt;/strong\u0026gt;\n \u0026lt;p\u0026gt;@(chain.OperatingSystem ?? \u0026quot;Unknown OS\u0026quot;)\u0026lt;/p\u0026gt;\n \u0026lt;p\u0026gt;State: @chain.State\u0026lt;/p\u0026gt;\n\n @if (chain.IsCurrentDevice)\n {\n \u0026lt;span\u0026gt;Current device\u0026lt;/span\u0026gt;\n }\n\n \u0026lt;button type=\u0026quot;button\u0026quot; @onclick=\u0026quot;() =\u0026gt; RevokeAsync(chain.ChainId)\u0026quot;\u0026gt;Revoke\u0026lt;/button\u0026gt;\n \u0026lt;/div\u0026gt;\n }\n}\n\n@code {\n private IReadOnlyList\u0026lt;SessionChainSummary\u0026gt; _chains = [];\n private bool _loading = true;\n private string? _error;\n\n protected override async Task OnInitializedAsync()\n {\n await LoadAsync();\n }\n\n private async Task LoadAsync()\n {\n _loading = true;\n _error = null;\n\n try\n {\n var result = await UAuthClient.Sessions.GetMyChainsAsync(new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n {\n _chains = page.Items;\n }\n else\n {\n _error = \u0026quot;Unable to load devices.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _error = \u0026quot;The session service is unavailable.\u0026quot;;\n }\n finally\n {\n _loading = false;\n }\n }\n\n private async Task RevokeAsync(SessionChainId chainId)\n {\n var result = await UAuthClient.Sessions.RevokeMyChainAsync(chainId);\n\n if (result.IsSuccess)\n {\n await LoadAsync();\n }\n else\n {\n _error = \u0026quot;Unable to revoke the selected device.\u0026quot;;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example demonstrates the core integration.\u003C/p\u003E\n\u003Cp\u003EA production UI should additionally provide confirmation before revocation, disable repeated submissions while an operation is in progress, and handle the possibility that the current device is being revoked.\u003C/p\u003E\n\u003Cp\u003EThe example displays the first page. Use the pagination API to expose additional chains.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-session-management\u0022\u003E12. Administrative Session Management\u003C/h2\u003E\n\u003Cp\u003EAdministrative operations allow authorized actors to inspect and manage another user\u0027s session state.\u003C/p\u003E\n\u003Cp\u003EThese methods require appropriate server-side permissions.\u003C/p\u003E\n\u003Cp\u003EThe presence of an administrative SDK method does not grant administrative access.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022list-a-users-devices\u0022\u003EList a User\u0027s Devices\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.Sessions.GetUserChainsAsync(userKey);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var chain in page.Items)\n {\n Console.WriteLine(chain.ChainId);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method accepts an optional \u003Ccode\u003EPageRequest\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.Sessions.GetUserChainsAsync(userKey,\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-a-users-chain\u0022\u003EInspect a User\u0027s Chain\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetUserChainDetailAsync\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.Sessions.GetUserChainDetailAsync(userKey, chainId);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } detail)\n{\n foreach (var session in detail.Sessions)\n {\n Console.WriteLine(session.SessionId);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis provides the same \u003Ccode\u003ESessionChainDetail\u003C/code\u003E model used for self-service chain inspection.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-an-individual-session\u0022\u003ERevoke an Individual Session\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERevokeUserSessionAsync\u003C/code\u003E when you need to revoke one authentication instance rather than its entire chain.\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.Sessions.RevokeUserSessionAsync(userKey, sessionId);\n\nif (result.IsSuccess)\n{\n // The session revocation operation succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method requires an \u003Ccode\u003EAuthSessionId\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis is different from chain revocation.\u003C/p\u003E\n\u003Cp\u003EA session-level operation targets a single authentication instance; a chain-level operation targets the associated session continuity boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-a-users-device-chain\u0022\u003ERevoke a User\u0027s Device Chain\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.Sessions.RevokeUserChainAsync(userKey, chainId);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } revoke)\n{\n // Inspect revoke.CurrentChain or revoke.RootRevoked if needed.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003EUAuthResult\u0026lt;RevokeResult\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-all-user-chains\u0022\u003ERevoke All User Chains\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.Sessions.RevokeAllUserChainsAsync(userKey);\n\nif (result.IsSuccess)\n{\n // All applicable user chains were revoked.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is useful for administrative account security operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-the-users-session-root\u0022\u003ERevoke the User\u0027s Session Root\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth also exposes root-level revocation:\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.Sessions.RevokeUserRootAsync(userKey);\n\nif (result.IsSuccess)\n{\n // Root-level revocation succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERoot revocation is a stronger account-level security operation than revoking one session or one chain.\u003C/p\u003E\n\u003Cp\u003EIt is intended for situations where the user\u0027s broader session authority must be invalidated.\u003C/p\u003E\n\u003Cp\u003EExamples include account compromise response or other security-critical administrative actions.\u003C/p\u003E\n\u003Cp\u003EDo not use root revocation as a routine substitute for ordinary logout.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-the-correct-revocation-scope\u0022\u003E13. Choosing the Correct Revocation Scope\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides multiple revocation levels because different security events require different responses.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EScope\u003C/th\u003E\n\u003Cth\u003EAPI\u003C/th\u003E\n\u003Cth\u003EIntended effect\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECurrent logout\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EFlows.LogoutAsync()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEnd current authentication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIndividual session\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessions.RevokeUserSessionAsync()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke one authentication instance\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOne chain\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessions.RevokeMyChainAsync()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke a device-associated chain\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOther chains\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessions.RevokeMyOtherChainsAsync()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERetain current chain, revoke others\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAll chains\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessions.RevokeAllMyChainsAsync()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke all current user\u0027s chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUser root\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessions.RevokeUserRootAsync()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EInvalidate account-level session authority\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EUse the narrowest scope that satisfies the security requirement.\u003C/p\u003E\n\u003Cp\u003EFor example, removing one unrecognized device from an account should not automatically require revoking the entire user\u0027s session root.\u003C/p\u003E\n\u003Cp\u003EConversely, a confirmed account-wide compromise may justify broader revocation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-session-operation-results\u0022\u003E14. Handling Session Operation Results\u003C/h2\u003E\n\u003Cp\u003ESession client methods return \u003Ccode\u003EUAuthResult\u003C/code\u003E or \u003Ccode\u003EUAuthResult\u0026lt;T\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA typical result-handling pattern 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\u003Evar result = await UAuthClient.Sessions.GetMyChainsAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n // Use the returned data.\n}\nelse if (result.Http.IsUnauthorized)\n{\n // Authentication is required.\n}\nelse if (result.Http.IsForbidden)\n{\n // The operation is not authorized.\n}\nelse\n{\n // Handle another failure.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAvailable response information includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EIsSuccess\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EStatus\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EProblem\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EErrorText\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ECorrelationId\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ETraceId\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe \u003Ccode\u003EHttp\u003C/code\u003E helper exposes status checks such as \u003Ccode\u003EIsBadRequest\u003C/code\u003E, \u003Ccode\u003EIsUnauthorized\u003C/code\u003E, \u003Ccode\u003EIsForbidden\u003C/code\u003E, and \u003Ccode\u003EIsConflict\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-authorization-failures\u0022\u003EAdministrative Authorization Failures\u003C/h3\u003E\n\u003Cp\u003EAn administrative session operation may return \u003Ccode\u003E403 Forbidden\u003C/code\u003E when the actor lacks the required permission.\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.Sessions.GetUserChainsAsync(userKey);\n\nif (result.Http.IsForbidden)\n{\n // The current actor cannot inspect this user\u0027s sessions.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDo not attempt to work around authorization failures by switching to a different endpoint.\u003C/p\u003E\n\u003Cp\u003EThe server is responsible for enforcing the operation\u0027s access policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-state-events\u0022\u003E15. Session State Events\u003C/h2\u003E\n\u003Cp\u003EThe session client publishes state events for certain successful revocation operations.\u003C/p\u003E\n\u003Cp\u003EThe current implementation publishes \u003Ccode\u003ESessionRevoked\u003C/code\u003E when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ERevokeMyChainAsync\u003C/code\u003E succeeds and the result identifies the current chain.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ERevokeAllMyChainsAsync\u003C/code\u003E succeeds.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EOther session management methods do not necessarily publish the same event.\u003C/p\u003E\n\u003Cp\u003EApplications should not assume that every successful administrative revocation produces a local state event.\u003C/p\u003E\n\u003Cp\u003EState events help the client update its authentication-related UI.\u003C/p\u003E\n\u003Cp\u003EThey do not replace authoritative server-side session validation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-and-operational-considerations\u0022\u003E16. Security and Operational Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-metadata-is-descriptive\u0022\u003EDevice Metadata Is Descriptive\u003C/h3\u003E\n\u003Cp\u003EBrowser, operating system, platform, and device type are useful for displaying session information.\u003C/p\u003E\n\u003Cp\u003EThey are not reliable proof of physical device ownership.\u003C/p\u003E\n\u003Cp\u003EA malicious or modified client may be able to influence some reported metadata.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-chains-are-not-permanent-device-identities\u0022\u003ESession Chains Are Not Permanent Device Identities\u003C/h3\u003E\n\u003Cp\u003ERevoking a chain invalidates its authentication continuity.\u003C/p\u003E\n\u003Cp\u003EIt does not permanently ban the physical device.\u003C/p\u003E\n\u003Cp\u003EA later successful login may establish new authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-is-server-controlled\u0022\u003ERevocation Is Server-Controlled\u003C/h3\u003E\n\u003Cp\u003EClient-side removal of a device card does not revoke the underlying session.\u003C/p\u003E\n\u003Cp\u003EAlways use the appropriate session or logout operation and inspect its result.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-modes-matter\u0022\u003EAuthentication Modes Matter\u003C/h3\u003E\n\u003Cp\u003EThe Root \u2192 Chain \u2192 Session hierarchy is relevant to server-managed session modes.\u003C/p\u003E\n\u003Cp\u003EDo not assume identical session management semantics in PureJwt or other modes that do not enforce the same hierarchy during ordinary request validation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-administrative-operations\u0022\u003EProtect Administrative Operations\u003C/h3\u003E\n\u003Cp\u003EAdministrative session revocation can interrupt another user\u0027s authentication.\u003C/p\u003E\n\u003Cp\u003EThese operations should be protected by appropriate permissions and, where required, stronger authentication or administrative confirmation.\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/authentication\u0022\u003EAuthentication\u003C/a\u003E \u2014 Login, logout, refresh, and authentication state validation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/session-lifecycle\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E \u2014 Session roots, chains, lifecycle, and expiration.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E \u2014 Device-associated authentication contexts.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Revocation and session security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "understanding-the-session-hierarchy", @@ -194,6 +194,6 @@ "Level": 0 } ], - "SourceHash": "0F38795BE63F3BCDFA1D1E61B4E7A67D6F735C58869EBDFD5CF6118A57A46E2C", + "SourceHash": "BB7D06D3A3986BDE3FA6843804B51649FE7B3DE752C889A120F8AE68F3634308", "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/user-management.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/user-management.json index 89a7e8ca..ffce1857 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/user-management.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/user-management.json @@ -1,7 +1,7 @@ { "Slug": "client-sdk/user-management", "Title": "User Management", - "Html": "\n\u003Cp\u003EThe UltimateAuth Client SDK provides a comprehensive API for managing users, profiles, account status, and user lifecycle operations.\u003C/p\u003E\n\u003Cp\u003EThrough \u003Ccode\u003EUAuthClient.Users\u003C/code\u003E, applications can implement account settings, profile management, user registration, and administrative user management without manually constructing API requests.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth distinguishes between \u003Cstrong\u003Euser identity\u003C/strong\u003E, \u003Cstrong\u003Euser profiles\u003C/strong\u003E, and \u003Cstrong\u003Eaccount lifecycle state\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EA user has a stable identity and may have multiple profiles. Account status controls lifecycle behavior independently of profile data.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the public \u003Ccode\u003EIUserClient\u003C/code\u003E API, including self-service and administrative operations, structured results, pagination, multi-profile support, and important update semantics.\u003C/p\u003E\n\u003Cp\u003EFor email addresses, usernames, phone numbers, and identifier verification, see \u003Ca href=\u0022/docs/client/identifiers\u0022\u003EIdentifiers\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-user-client\u0022\u003E1. Accessing the User 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\u003EUser operations are available through:\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\u003EUAuthClient.Users\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003EUsers\u003C/code\u003E property exposes \u003Ccode\u003EIUserClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIts operations are divided into three functional areas:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EArea\u003C/th\u003E\n\u003Cth\u003ECapabilities\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUser lifecycle\u003C/td\u003E\n\u003Ctd\u003ECreate users, change status, delete accounts\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESelf-service profiles\u003C/td\u003E\n\u003Ctd\u003ERetrieve, update, create, and delete your own profiles\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAdministration\u003C/td\u003E\n\u003Ctd\u003EQuery users and manage another user\u0027s account and profiles\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ESelf-service methods operate on the authenticated user.\u003C/p\u003E\n\u003Cp\u003EAdministrative methods operate on an explicitly identified target and require the appropriate server-side authorization.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-the-current-user\u0022\u003E2. Get the Current User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMeAsync\u003C/code\u003E to retrieve the current user\u0027s profile.\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.Users.GetMeAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } user)\n{\n Console.WriteLine(user.DisplayName);\n Console.WriteLine(user.Status);\n Console.WriteLine(user.UserKey);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;UserView\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen no profile is specified, UltimateAuth returns the default profile.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userview\u0022\u003EUserView\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUserView\u003C/code\u003E contains account information and the selected profile\u0027s data.\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EStable user identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EStatus\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECurrent account status\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUsername\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryEmail\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPrimary email address\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryPhone\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPrimary phone number\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EProfileKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESelected profile\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EFirstName\u003C/code\u003E, \u003Ccode\u003ELastName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EProfile name fields\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDisplayName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDisplay name\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBio\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBiography\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBirthDate\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBirth date\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EGender\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EProfile gender field\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELanguage\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreferred language\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETimeZone\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreferred time zone\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECulture\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECulture preference\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEmailVerified\u003C/code\u003E, \u003Ccode\u003EPhoneVerified\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier verification information\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAccount creation timestamp\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMetadata\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECustom profile metadata\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe returned profile is a view of server-managed user information.\u003C/p\u003E\n\u003Cp\u003EIt should not be treated as a substitute for authoritative authentication or authorization state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-the-current-users-profile\u0022\u003E3. Update the Current User\u0027s Profile\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EUpdateMyProfileAsync\u003C/code\u003E to update the authenticated user\u0027s profile.\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.Users.UpdateMyProfileAsync(\n new UpdateProfileRequest\n {\n DisplayName = \u0026quot;John Doe\u0026quot;,\n FirstName = \u0026quot;John\u0026quot;,\n LastName = \u0026quot;Doe\u0026quot;,\n Language = \u0026quot;en\u0026quot;,\n TimeZone = \u0026quot;Europe/London\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Profile update succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003EUAuthResult\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-profile-updates-replace-editable-fields\u0022\u003EImportant: Profile Updates Replace Editable Fields\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUpdateProfileRequest\u003C/code\u003E uses replacement semantics rather than partial-update semantics.\u003C/p\u003E\n\u003Cp\u003EThis means:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEditable fields supplied in the request replace their previous values.\u003C/li\u003E\n\u003Cli\u003EExplicit \u003Ccode\u003Enull\u003C/code\u003E values clear existing values.\u003C/li\u003E\n\u003Cli\u003EOmitted editable properties are also treated as \u003Ccode\u003Enull\u003C/code\u003E.\u003C/li\u003E\n\u003C/ul\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\u003Eawait UAuthClient.Users.UpdateMyProfileAsync(\n new UpdateProfileRequest\n {\n DisplayName = \u0026quot;John Doe\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis does \u003Cstrong\u003Enot\u003C/strong\u003E mean \u201Cchange only the display name.\u201D\u003C/p\u003E\n\u003Cp\u003EOther editable profile fields may be cleared.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-read-modify-and-submit\u0022\u003ERecommended: Read, Modify, and Submit\u003C/h3\u003E\n\u003Cp\u003EWhen building a profile editor, retrieve the current profile and preserve values that should remain unchanged.\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 currentResult = await UAuthClient.Users.GetMeAsync();\n\nif (!currentResult.IsSuccess || currentResult.Value is not { } current)\n{\n return;\n}\n\nvar update = new UpdateProfileRequest\n{\n FirstName = current.FirstName,\n LastName = current.LastName,\n DisplayName = \u0026quot;John Doe\u0026quot;,\n BirthDate = current.BirthDate,\n Gender = current.Gender,\n Bio = current.Bio,\n Language = current.Language,\n TimeZone = current.TimeZone,\n Culture = current.Culture,\n Metadata = current.Metadata\n};\n\nvar result = await UAuthClient.Users.UpdateMyProfileAsync(update);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis preserves the values returned by the initial read, but it is not an atomic patch operation.\u003C/p\u003E\n\u003Cp\u003EIf another request modifies the profile between the read and update, the later replacement may overwrite those changes.\u003C/p\u003E\n\u003Cp\u003EApplications with concurrent profile editing requirements should account for this behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-state-events\u0022\u003EProfile State Events\u003C/h3\u003E\n\u003Cp\u003EAfter a successful \u003Ccode\u003EUpdateMyProfileAsync\u003C/code\u003E operation, the client publishes a \u003Ccode\u003EProfileChanged\u003C/code\u003E event.\u003C/p\u003E\n\u003Cp\u003EThe event carries the \u003Ccode\u003EUpdateProfileRequest\u003C/code\u003E used for the operation.\u003C/p\u003E\n\u003Cp\u003EThe configured client state event handling mode determines how the application processes the notification.\u003C/p\u003E\n\u003Cp\u003EThe event is published only when the SDK operation reports success.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-multiple-profiles\u0022\u003E4. Understanding Multiple Profiles\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports multiple profiles for a single user when enabled through server configuration.\u003C/p\u003E\n\u003Cp\u003EFor example, one account may have:\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\u003EUser\n |\n \u002B-- default\n |\n \u002B-- business\n |\n \u002B-- public\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAll profiles belong to the same user identity.\u003C/p\u003E\n\u003Cp\u003EThey do not represent separate authentication accounts or independently authenticated principals.\u003C/p\u003E\n\u003Cp\u003EProfiles allow applications to organize different sets of user-facing information under one account.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022default-profile\u0022\u003EDefault Profile\u003C/h3\u003E\n\u003Cp\u003EEvery newly created user receives a default profile.\u003C/p\u003E\n\u003Cp\u003EIts key 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\u003EProfileKey.Default\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe default profile cannot be deleted.\u003C/p\u003E\n\u003Cp\u003EWhen a profile request omits \u003Ccode\u003EProfileKey\u003C/code\u003E, the default profile is selected.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-keys\u0022\u003EProfile Keys\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EProfileKey\u003C/code\u003E is a strongly typed value.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003ETryCreate\u003C/code\u003E to construct a custom key:\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 (!ProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var profileKey))\n{\n throw new InvalidOperationException(\u0026quot;Invalid profile key.\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EProfile keys are normalized by trimming whitespace and converting the value to lowercase.\u003C/p\u003E\n\u003Cp\u003EA valid key must not be empty or exceed 64 characters.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022retrieve-a-specific-profile\u0022\u003E5. Retrieve a Specific Profile\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetProfileRequest\u003C/code\u003E to select a profile.\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 (!ProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var profileKey))\n{\n return;\n}\n\nvar result = await UAuthClient.Users.GetMeAsync(\n new GetProfileRequest\n {\n ProfileKey = profileKey\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } profile)\n{\n Console.WriteLine(profile.DisplayName);\n Console.WriteLine(profile.ProfileKey);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe response is still \u003Ccode\u003EUserView\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe account identity and status remain associated with the same user; the selected profile determines which profile information is returned.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-an-additional-profile\u0022\u003E6. Create an Additional Profile\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECreateMyProfileAsync\u003C/code\u003E to create a new profile for the authenticated 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\u003Eif (!ProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var profileKey))\n{\n return;\n}\n\nvar result = await UAuthClient.Users.CreateMyProfileAsync(\n new CreateProfileRequest\n {\n ProfileKey = profileKey,\n DisplayName = \u0026quot;John Doe Consulting\u0026quot;,\n Bio = \u0026quot;Business profile\u0026quot;,\n Language = \u0026quot;en\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Profile created.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ECreateProfileRequest\u003C/code\u003E requires \u003Ccode\u003EProfileKey\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIt also supports the same general profile information used for user-facing profiles.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022clone-an-existing-profile\u0022\u003EClone an Existing Profile\u003C/h3\u003E\n\u003Cp\u003EA new profile can be created using an existing profile as its source.\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\u003EProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var businessKey);\n\nvar result = await UAuthClient.Users.CreateMyProfileAsync(\n new CreateProfileRequest\n {\n ProfileKey = businessKey,\n CloneFrom = ProfileKey.Default\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ECloneFrom\u003C/code\u003E identifies the source profile.\u003C/p\u003E\n\u003Cp\u003EThis is useful when creating a new profile that should initially reuse information from an existing one.\u003C/p\u003E\n\u003Cp\u003EThe exact merge and override behavior is determined by the server\u0027s profile creation implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-creation-events\u0022\u003EProfile Creation Events\u003C/h3\u003E\n\u003Cp\u003EAfter successful self-service profile creation, the SDK publishes \u003Ccode\u003EProfileChanged\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis allows application components to refresh profile-related views.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-a-specific-profile\u0022\u003E7. Update a Specific Profile\u003C/h2\u003E\n\u003Cp\u003ESet \u003Ccode\u003EProfileKey\u003C/code\u003E in \u003Ccode\u003EUpdateProfileRequest\u003C/code\u003E to select the profile being updated.\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\u003EProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var businessKey);\n\nvar result = await UAuthClient.Users.UpdateMyProfileAsync(\n new UpdateProfileRequest\n {\n ProfileKey = businessKey,\n DisplayName = \u0026quot;John Doe Consulting\u0026quot;,\n Bio = \u0026quot;Updated business profile\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same replacement semantics apply to custom profiles.\u003C/p\u003E\n\u003Cp\u003EIf the application intends to preserve existing fields, retrieve the selected profile first and include the values that should remain unchanged.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-a-profile\u0022\u003E8. Delete a Profile\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EDeleteMyProfileAsync\u003C/code\u003E to remove a custom profile.\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\u003EProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var businessKey);\n\nvar result = await UAuthClient.Users.DeleteMyProfileAsync(\n new DeleteProfileRequest\n {\n ProfileKey = businessKey\n });\n\nif (result.IsSuccess)\n{\n // Profile deleted.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe default profile cannot be deleted.\u003C/p\u003E\n\u003Cp\u003EDeleting a profile is different from deleting the user account.\u003C/p\u003E\n\u003Cp\u003EThe user identity and other profiles remain subject to their own lifecycle and server-side state.\u003C/p\u003E\n\u003Cp\u003EOn successful self-service profile deletion, the SDK publishes \u003Ccode\u003EProfileChanged\u003C/code\u003E with the deleted profile key.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-user\u0022\u003E9. Create a User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECreateAsync\u003C/code\u003E to request user creation through the standard user creation endpoint.\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.Users.CreateAsync(\n new CreateUserRequest\n {\n UserName = \u0026quot;johndoe\u0026quot;,\n Email = \u0026quot;john@example.com\u0026quot;,\n Password = password,\n FirstName = \u0026quot;John\u0026quot;,\n LastName = \u0026quot;Doe\u0026quot;,\n DisplayName = \u0026quot;John Doe\u0026quot;\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } created)\n{\n Console.WriteLine($\u0026quot;Created user: {created.UserKey}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;UserCreateResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUser creation availability depends on the server\u0027s configuration and access policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022createuserrequest\u0022\u003ECreateUserRequest\u003C/h3\u003E\n\u003Cp\u003EThe request supports several groups of fields:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECategory\u003C/th\u003E\n\u003Cth\u003EProperties\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIdentifiers\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserName\u003C/code\u003E, \u003Ccode\u003EEmail\u003C/code\u003E, \u003Ccode\u003EPhone\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EVerification flags\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserNameVerified\u003C/code\u003E, \u003Ccode\u003EEmailVerified\u003C/code\u003E, \u003Ccode\u003EPhoneVerified\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredentials\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPassword\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EProfile\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EFirstName\u003C/code\u003E, \u003Ccode\u003ELastName\u003C/code\u003E, \u003Ccode\u003EDisplayName\u003C/code\u003E, \u003Ccode\u003EBirthDate\u003C/code\u003E, \u003Ccode\u003EGender\u003C/code\u003E, \u003Ccode\u003EBio\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPreferences\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ELanguage\u003C/code\u003E, \u003Ccode\u003ETimeZone\u003C/code\u003E, \u003Ccode\u003ECulture\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EExtensions\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EMetadata\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EVerification flags must not be treated as client-controlled proof of identifier ownership.\u003C/p\u003E\n\u003Cp\u003EThe server is responsible for enforcing verification requirements and determining whether a caller is authorized to establish verified identifier state.\u003C/p\u003E\n\u003Cp\u003EFor identifier-specific operations and verification workflows, see \u003Ca href=\u0022/docs/client/identifiers\u0022\u003EIdentifiers\u003C/a\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022usercreateresult\u0022\u003EUserCreateResult\u003C/h3\u003E\n\u003Cp\u003EThe returned value 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\u003Epublic sealed record UserCreateResult\n{\n public required bool IsSuccess { get; init; }\n public string? UserKey { get; init; }\n public string? FailureReason { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003EUserKey\u003C/code\u003E is returned as a string when creation succeeds.\u003C/p\u003E\n\u003Cp\u003ECheck both the transport-level \u003Ccode\u003EUAuthResult.IsSuccess\u003C/code\u003E and the operation-level \u003Ccode\u003EUserCreateResult.IsSuccess\u003C/code\u003E where applicable.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-user-creation\u0022\u003E10. Administrative User Creation\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECreateAsAdminAsync\u003C/code\u003E when creating a user through an administrative context.\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.Users.CreateAsAdminAsync(\n new CreateUserRequest\n {\n UserName = \u0026quot;newemployee\u0026quot;,\n Email = \u0026quot;employee@example.com\u0026quot;,\n DisplayName = \u0026quot;New Employee\u0026quot;\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } created)\n{\n Console.WriteLine(created.UserKey);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAdministrative creation is distinct from ordinary user creation.\u003C/p\u003E\n\u003Cp\u003EThe server enforces the required permissions and applies the relevant user creation policies.\u003C/p\u003E\n\u003Cp\u003EApplications should not assume that calling the administrative endpoint bypasses validation or identifier ownership rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022query-users\u0022\u003E11. Query Users\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EQueryAsync\u003C/code\u003E to search and paginate users.\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.Users.QueryAsync(\n new UserQuery\n {\n Search = \u0026quot;john\u0026quot;,\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var user in page.Items)\n {\n Console.WriteLine(user.DisplayName);\n Console.WriteLine(user.Status);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;PagedResult\u0026lt;UserSummary\u0026gt;\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userquery-filters\u0022\u003EUserQuery Filters\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUserQuery\u003C/code\u003E extends \u003Ccode\u003EPageRequest\u003C/code\u003E and supports:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESearch\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESearch term\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EStatus\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EFilter by user status\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIncludeDeleted\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EInclude deleted users when authorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EProfileKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESelect the profile context for the query\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPageNumber\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPage number\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPageSize\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPage size\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESortBy\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional sort field\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDescending\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDescending sort order\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=\u0022search-behavior\u0022\u003ESearch Behavior\u003C/h3\u003E\n\u003Cp\u003EThe query model documents case-insensitive partial matching for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDisplay name.\u003C/li\u003E\n\u003Cli\u003EUsername.\u003C/li\u003E\n\u003Cli\u003EPrimary email.\u003C/li\u003E\n\u003Cli\u003EPrimary phone.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA complete valid \u003Ccode\u003EUserKey\u003C/code\u003E can also be used as a search term.\u003C/p\u003E\n\u003Cp\u003EPartial \u003Ccode\u003EUserKey\u003C/code\u003E matching is not supported.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022filter-by-status\u0022\u003EFilter by Status\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.Users.QueryAsync(\n new UserQuery\n {\n Status = UserStatus.Active,\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022include-deleted-users\u0022\u003EInclude Deleted Users\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.Users.QueryAsync(\n new UserQuery\n {\n IncludeDeleted = true,\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe ability to query deleted accounts depends on server-side authorization and data retention behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022usersummary-and-pagination\u0022\u003E12. UserSummary and Pagination\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EQueryAsync\u003C/code\u003E returns \u003Ccode\u003EUserSummary\u003C/code\u003E items.\u003C/p\u003E\n\u003Cp\u003EEach summary contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUsername\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDisplayName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDisplay name\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryEmail\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPrimary email\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryPhone\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPrimary phone\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EStatus\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAccount status\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreation timestamp\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis model is suitable for administrative user lists.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022navigating-pages\u0022\u003ENavigating Pages\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.Users.QueryAsync(\n new UserQuery\n {\n PageNumber = 2,\n PageSize = 25\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n Console.WriteLine($\u0026quot;Total users: {page.TotalCount}\u0026quot;);\n Console.WriteLine($\u0026quot;Current page: {page.PageNumber}\u0026quot;);\n Console.WriteLine($\u0026quot;Has next page: {page.HasNext}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003EPagedResult\u0026lt;T\u0026gt;\u003C/code\u003E rather than assuming every query returns the complete user collection.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022retrieve-users-by-keys\u0022\u003E13. Retrieve Users by Keys\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetByKeysAsync\u003C/code\u003E to retrieve multiple user summaries when their \u003Ccode\u003EUserKey\u003C/code\u003E values are already known.\u003C/p\u003E\n\u003Cp\u003EUnlike \u003Ccode\u003EQueryAsync\u003C/code\u003E, which searches and paginates users, \u003Ccode\u003EGetByKeysAsync\u003C/code\u003E retrieves a specific collection of users in a single request.\u003C/p\u003E\n\u003Cp\u003EThis is particularly useful for applications that maintain relationships between users, such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ETeam members.\u003C/li\u003E\n\u003Cli\u003EOrganization members.\u003C/li\u003E\n\u003Cli\u003EProject participants.\u003C/li\u003E\n\u003Cli\u003EGroup memberships.\u003C/li\u003E\n\u003Cli\u003EUsers assigned to a particular resource.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EInstead of requesting each user\u0027s information separately, the application can retrieve the required summaries in one operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic-usage\u0022\u003EBasic Usage\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 userKeys = new[]\n{\n firstUserKey,\n secondUserKey,\n thirdUserKey\n};\n\nvar result = await UAuthClient.Users.GetByKeysAsync(userKeys);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } users)\n{\n foreach (var user in users)\n {\n Console.WriteLine($\u0026quot;{user.DisplayName} ({user.UserName})\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;IReadOnlyList\u0026lt;UserSummary\u0026gt;\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach returned item uses the same \u003Ccode\u003EUserSummary\u003C/code\u003E model described above.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022batch-size-and-behavior\u0022\u003EBatch Size and Behavior\u003C/h3\u003E\n\u003Cp\u003EA single request supports up to \u003Cstrong\u003E250 distinct user keys\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe operation:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAccepts a collection of strongly typed \u003Ccode\u003EUserKey\u003C/code\u003E values.\u003C/li\u003E\n\u003Cli\u003ERemoves duplicate keys.\u003C/li\u003E\n\u003Cli\u003EReturns existing, non-deleted users within the applicable tenant.\u003C/li\u003E\n\u003Cli\u003EOmits user keys that do not match an available user.\u003C/li\u003E\n\u003Cli\u003EReturns an empty collection when no users match.\u003C/li\u003E\n\u003Cli\u003EDoes not use pagination.\u003C/li\u003E\n\u003Cli\u003EDoes not guarantee that the response follows the order of the requested keys.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ERequests exceeding 250 distinct keys are rejected with the \u003Ccode\u003Euser_batch_size_exceeded\u003C/code\u003E validation error.\u003C/p\u003E\n\u003Cp\u003EFor larger collections, split the keys into batches:\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 allUsers = new List\u0026lt;UserSummary\u0026gt;();\n\nforeach (var batch in userKeys.Distinct().Chunk(250))\n{\n var result = await UAuthClient.Users.GetByKeysAsync(batch);\n\n if (!result.IsSuccess || result.Value is null)\n {\n // Handle the failed batch.\n return;\n }\n\n allUsers.AddRange(result.Value);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor large datasets that require searching, filtering, sorting, or pagination, use \u003Ccode\u003EQueryAsync\u003C/code\u003E instead.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022queryasync-vs.getbykeysasync\u0022\u003EQueryAsync vs. GetByKeysAsync\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECapability\u003C/th\u003E\n\u003Cth\u003EQueryAsync\u003C/th\u003E\n\u003Cth\u003EGetByKeysAsync\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESearch users\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERetrieve known user keys\u003C/td\u003E\n\u003Ctd\u003EIndirectly\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPagination\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EFiltering and sorting\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMultiple specific users\u003C/td\u003E\n\u003Ctd\u003ENot optimized for this purpose\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMaximum batch size\u003C/td\u003E\n\u003Ctd\u003EPagination limits\u003C/td\u003E\n\u003Ctd\u003E250 distinct keys\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EResponse\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPagedResult\u0026lt;UserSummary\u0026gt;\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIReadOnlyList\u0026lt;UserSummary\u0026gt;\u003C/code\u003E\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=\u0022authorization\u0022\u003EAuthorization\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EGetByKeysAsync\u003C/code\u003E is a protected operation.\u003C/p\u003E\n\u003Cp\u003EIt uses the action:\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\u003EUAuthActions.Users.GetByKeysAdmin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe corresponding permission value 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\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\u003Eusers.getbykeys.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAlthough the action uses the \u003Ccode\u003E.admin\u003C/code\u003E suffix, access is determined by the server\u0027s authorization policies and the actor\u0027s effective permissions.\u003C/p\u003E\n\u003Cp\u003EApplications can authorize non-administrator users to retrieve specific groups of users when their policies explicitly allow it.\u003C/p\u003E\n\u003Cp\u003EFor example, an application may allow team members to retrieve summaries of other members belonging to the same team while denying access to users outside that team.\u003C/p\u003E\n\u003Cp\u003EThe server exposes the requested users through \u003Ccode\u003EAccessContext.TargetUserKeys\u003C/code\u003E, allowing application policies to validate the complete target collection.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E Possessing the \u003Ccode\u003Eusers.getbykeys.admin\u003C/code\u003E permission does not automatically establish that every requested user belongs to an allowed group. Applications requiring group, organization, or resource-level isolation must enforce those restrictions through server-side authorization policies.\u003C/p\u003E\n\u003Cp\u003EFor details, see \u003Ca href=\u0022/docs/plugin-domains/policies\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022performance-considerations\u0022\u003EPerformance Considerations\u003C/h3\u003E\n\u003Cp\u003EBatch retrieval avoids issuing a separate client request for every user.\u003C/p\u003E\n\u003Cp\u003EThe reference persistence implementations support retrieving multiple user summaries through a single batch operation.\u003C/p\u003E\n\u003Cp\u003EActual database query counts and execution costs depend on the configured persistence provider.\u003C/p\u003E\n\u003Cp\u003EFor example, an application displaying 50 team members can request their summaries together rather than making 50 individual \u003Ccode\u003EGetUserAsync\u003C/code\u003E calls.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetByKeysAsync\u003C/code\u003E when user keys are already available. Use \u003Ccode\u003EGetUserAsync\u003C/code\u003E when the application needs the detailed profile of one specific user.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-a-specific-user\u0022\u003E14. Get a Specific User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetUserAsync\u003C/code\u003E to retrieve a user\u0027s profile through the administrative API.\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.Users.GetUserAsync(userKey);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } user)\n{\n Console.WriteLine(user.DisplayName);\n Console.WriteLine(user.Status);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003EUAuthResult\u0026lt;UserView\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EWhen no profile is specified, the default profile is selected.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022retrieve-a-specific-user-profile\u0022\u003ERetrieve a Specific User Profile\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\u003EProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var businessKey);\n\nvar result = await UAuthClient.Users.GetUserAsync(\n userKey,\n new GetProfileRequest\n {\n ProfileKey = businessKey\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actor must have permission to access the target user\u0027s requested information.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-another-users-profile\u0022\u003E15. Update Another User\u0027s Profile\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EUpdateUserProfileAsync\u003C/code\u003E to update a selected user\u0027s profile.\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.Users.UpdateUserProfileAsync(\n userKey,\n new UpdateProfileRequest\n {\n DisplayName = \u0026quot;Updated Display Name\u0026quot;,\n FirstName = \u0026quot;John\u0026quot;,\n LastName = \u0026quot;Doe\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Profile updated.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis operation uses the same replacement semantics as \u003Ccode\u003EUpdateMyProfileAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EOmitted editable fields may be cleared.\u003C/p\u003E\n\u003Cp\u003EFor partial UI editing, retrieve the target profile and preserve values that should not change.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-a-named-profile\u0022\u003EUpdate a Named Profile\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\u003EProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var businessKey);\n\nvar result = await UAuthClient.Users.UpdateUserProfileAsync(userKey,\n new UpdateProfileRequest\n {\n ProfileKey = businessKey,\n DisplayName = \u0026quot;Updated Business Profile\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAdministrative profile updates require the appropriate authorization for the target user.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022manage-another-users-profiles\u0022\u003E16. Manage Another User\u0027s Profiles\u003C/h2\u003E\n\u003Cp\u003EAdministrators can create and delete profiles for a selected user.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-profile\u0022\u003ECreate a Profile\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\u003EProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var businessKey);\n\nvar result = await UAuthClient.Users.CreateUserProfileAsync(userKey,\n new CreateProfileRequest\n {\n ProfileKey = businessKey,\n DisplayName = \u0026quot;Business Profile\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-a-profile-1\u0022\u003EDelete a Profile\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\u003EProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var businessKey);\n\nvar result = await UAuthClient.Users.DeleteUserProfileAsync(userKey,\n new DeleteProfileRequest\n {\n ProfileKey = businessKey\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe default profile cannot be deleted.\u003C/p\u003E\n\u003Cp\u003EAdministrative profile operations do not automatically publish the same local \u003Ccode\u003EProfileChanged\u003C/code\u003E event as self-service profile operations.\u003C/p\u003E\n\u003Cp\u003ERefresh affected administrative UI data explicitly when needed.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-user-status\u0022\u003E17. Understanding User Status\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates account lifecycle status from profile information.\u003C/p\u003E\n\u003Cp\u003EA user may be active, suspended, disabled, locked, awaiting activation, or in another supported lifecycle state.\u003C/p\u003E\n\u003Cp\u003EStatus can affect authentication eligibility, session continuation, and authorization.\u003C/p\u003E\n\u003Cp\u003EHowever, the exact enforcement behavior depends on the configured server policies and authentication mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self-service-and-administrative-statuses\u0022\u003ESelf-Service and Administrative Statuses\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth exposes separate status enums for self-service and administrative operations.\u003C/p\u003E\n\u003Cp\u003EThis prevents the client contract from treating every administrative lifecycle transition as a self-service capability.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESelfAssignableUserStatus\u003C/strong\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 enum SelfAssignableUserStatus\n{\n Active = 0,\n SelfSuspended = 10\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EAdminAssignableUserStatus\u003C/strong\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 enum AdminAssignableUserStatus\n{\n Active = 0,\n Disabled = 20,\n Suspended = 30,\n Locked = 40,\n RiskHold = 50,\n PendingActivation = 60,\n PendingVerification = 70,\n Unknown = 100\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese enums describe requestable statuses.\u003C/p\u003E\n\u003Cp\u003EThey do not mean every transition is permitted in every application context.\u003C/p\u003E\n\u003Cp\u003EThe server validates the requested transition.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-your-own-status\u0022\u003E18. Change Your Own Status\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EChangeMyStatusAsync\u003C/code\u003E for supported self-service status transitions.\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.Users.ChangeMyStatusAsync(\n new ChangeUserStatusSelfRequest\n {\n NewStatus = SelfAssignableUserStatus.SelfSuspended\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } change)\n{\n Console.WriteLine($\u0026quot;New status: {change.CurrentStatus}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis operation can be used to implement account self-suspension.\u003C/p\u003E\n\u003Cp\u003EThe server determines whether the transition is permitted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userstatuschangeresult\u0022\u003EUserStatusChangeResult\u003C/h3\u003E\n\u003Cp\u003EThe result 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\u003EDescription\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 status operation succeeded\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreviousStatus\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EStatus before the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECurrentStatus\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EStatus after the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EFailureReason\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional failure description\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\u003Eif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } change)\n{\n if (change.IsSuccess)\n {\n Console.WriteLine($\u0026quot;{change.PreviousStatus} -\u0026gt; {change.CurrentStatus}\u0026quot;);\n }\n else\n {\n Console.WriteLine(change.FailureReason ?? \u0026quot;Status change failed.\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe result may also report a successful no-change outcome when the requested state already matches the current state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022state-event\u0022\u003EState Event\u003C/h3\u003E\n\u003Cp\u003EAfter a successful SDK operation, \u003Ccode\u003EChangeMyStatusAsync\u003C/code\u003E publishes \u003Ccode\u003EProfileChanged\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis is the current client event behavior, even though the operation changes account status rather than editable profile fields.\u003C/p\u003E\n\u003Cp\u003EApplications should not assume that a status change always produces a dedicated \u003Ccode\u003EAuthorizationChanged\u003C/code\u003E event.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-another-users-status\u0022\u003E19. Change Another User\u0027s Status\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EChangeUserStatusAsync\u003C/code\u003E for administrative lifecycle management.\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.Users.ChangeUserStatusAsync(userKey,\n new ChangeUserStatusAdminRequest\n {\n NewStatus = AdminAssignableUserStatus.Suspended\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } change)\n{\n Console.WriteLine($\u0026quot;Current status: {change.CurrentStatus}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis operation requires administrative authorization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-disable-an-account\u0022\u003EExample: Disable an Account\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.Users.ChangeUserStatusAsync(userKey,\n new ChangeUserStatusAdminRequest\n {\n NewStatus = AdminAssignableUserStatus.Disabled\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-restore-an-active-status\u0022\u003EExample: Restore an Active Status\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.Users.ChangeUserStatusAsync(userKey,\n new ChangeUserStatusAdminRequest\n {\n NewStatus = AdminAssignableUserStatus.Active\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhether a particular transition is permitted depends on the server\u0027s lifecycle rules.\u003C/p\u003E\n\u003Cp\u003EDo not assume that setting \u003Ccode\u003EActive\u003C/code\u003E automatically restores every form of previously revoked authentication state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-the-current-user\u0022\u003E20. Delete the Current User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EDeleteMeAsync\u003C/code\u003E to request deletion of the current account.\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.Users.DeleteMeAsync();\n\nif (result.IsSuccess)\n{\n // Account deletion succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ESelf-service deletion is a soft-delete operation.\u003C/p\u003E\n\u003Cp\u003EAccording to the \u003Ccode\u003EIUserClient\u003C/code\u003E contract, this operation removes associated profiles and sessions.\u003C/p\u003E\n\u003Cp\u003EAfter successful deletion, the client publishes a \u003Ccode\u003EUserDeleted\u003C/code\u003E state event using patch handling mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-considerations\u0022\u003EImportant Considerations\u003C/h3\u003E\n\u003Cp\u003EAccount deletion is not equivalent to ordinary logout.\u003C/p\u003E\n\u003Cp\u003EIt changes the user\u0027s lifecycle state and may affect associated data across multiple domains.\u003C/p\u003E\n\u003Cp\u003EBefore exposing a delete-account action, applications should provide an appropriate confirmation flow and explain the consequences.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for authorization, data cleanup, and deletion policy enforcement.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-user-deletion\u0022\u003E21. Administrative User Deletion\u003C/h2\u003E\n\u003Cp\u003EAdministrators can delete users through \u003Ccode\u003EDeleteUserAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe method accepts a \u003Ccode\u003EDeleteUserRequest\u003C/code\u003E specifying the deletion mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022soft-delete\u0022\u003ESoft Delete\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.Users.DeleteUserAsync(userKey,\n new DeleteUserRequest\n {\n Mode = DeleteMode.Soft\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } deleted)\n{\n Console.WriteLine($\u0026quot;Deletion mode: {deleted.Mode}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hard-delete\u0022\u003EHard Delete\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.Users.DeleteUserAsync(userKey,\n new DeleteUserRequest\n {\n Mode = DeleteMode.Hard\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EHard deletion is a more destructive operation and should be restricted to appropriately authorized administrative workflows.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deletemode\u0022\u003EDeleteMode\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\u003Epublic enum DeleteMode\n{\n Soft = 0,\n Hard = 10\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userdeleteresult\u0022\u003EUserDeleteResult\u003C/h3\u003E\n\u003Cp\u003EThe operation returns \u003Ccode\u003EUAuthResult\u0026lt;UserDeleteResult\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EUserDeleteResult\u003C/code\u003E includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EIsSuccess\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EMode\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EFailureReason\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\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.IsSuccess)\n{\n // Handle transport or API-level failure.\n}\nelse if (result.Value is { IsSuccess: false } deletion)\n{\n // Handle the operation-level failure.\n Console.WriteLine(deletion.FailureReason);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA previously deleted account may produce an operation-level success result indicating that no additional deletion was necessary.\u003C/p\u003E\n\u003Cp\u003EDo not infer the complete persistence or retention behavior from the \u003Ccode\u003EDeleteMode\u003C/code\u003E value alone.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-user-administration-table\u0022\u003E22. Build a User Administration Table\u003C/h2\u003E\n\u003Cp\u003EThe following example demonstrates a basic Blazor user list using \u003Ccode\u003EQueryAsync\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.Core.Contracts\n@using CodeBeam.UltimateAuth.Users.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Users\u0026lt;/h3\u0026gt;\n\n\u0026lt;input @bind=\u0026quot;_search\u0026quot; placeholder=\u0026quot;Search users\u0026quot; /\u0026gt;\n\u0026lt;button @onclick=\u0026quot;SearchAsync\u0026quot;\u0026gt;Search\u0026lt;/button\u0026gt;\n\n@if (_loading)\n{\n \u0026lt;p\u0026gt;Loading users...\u0026lt;/p\u0026gt;\n}\nelse if (_error is not null)\n{\n \u0026lt;p role=\u0026quot;alert\u0026quot;\u0026gt;@_error\u0026lt;/p\u0026gt;\n}\nelse\n{\n \u0026lt;table\u0026gt;\n \u0026lt;thead\u0026gt;\n \u0026lt;tr\u0026gt;\n \u0026lt;th\u0026gt;Name\u0026lt;/th\u0026gt;\n \u0026lt;th\u0026gt;Username\u0026lt;/th\u0026gt;\n \u0026lt;th\u0026gt;Status\u0026lt;/th\u0026gt;\n \u0026lt;/tr\u0026gt;\n \u0026lt;/thead\u0026gt;\n \u0026lt;tbody\u0026gt;\n @foreach (var user in _users)\n {\n \u0026lt;tr @key=\u0026quot;user.UserKey\u0026quot;\u0026gt;\n \u0026lt;td\u0026gt;@user.DisplayName\u0026lt;/td\u0026gt;\n \u0026lt;td\u0026gt;@user.UserName\u0026lt;/td\u0026gt;\n \u0026lt;td\u0026gt;@user.Status\u0026lt;/td\u0026gt;\n \u0026lt;/tr\u0026gt;\n }\n \u0026lt;/tbody\u0026gt;\n \u0026lt;/table\u0026gt;\n\n \u0026lt;button @onclick=\u0026quot;PreviousPageAsync\u0026quot; disabled=\u0026quot;@(_pageNumber \u0026lt;= 1)\u0026quot;\u0026gt;Previous\u0026lt;/button\u0026gt;\n \u0026lt;button @onclick=\u0026quot;NextPageAsync\u0026quot; disabled=\u0026quot;@(!_hasNext)\u0026quot;\u0026gt;Next\u0026lt;/button\u0026gt;\n}\n\n@code {\n private string _search = \u0026quot;\u0026quot;;\n private int _pageNumber = 1;\n private bool _hasNext;\n private bool _loading;\n private string? _error;\n private IReadOnlyList\u0026lt;UserSummary\u0026gt; _users = [];\n\n protected override async Task OnInitializedAsync()\n {\n await LoadAsync();\n }\n\n private async Task SearchAsync()\n {\n _pageNumber = 1;\n await LoadAsync();\n }\n\n private async Task PreviousPageAsync()\n {\n if (_pageNumber \u0026lt;= 1)\n return;\n\n _pageNumber--;\n await LoadAsync();\n }\n\n private async Task NextPageAsync()\n {\n if (!_hasNext)\n return;\n\n _pageNumber\u002B\u002B;\n await LoadAsync();\n }\n\n private async Task LoadAsync()\n {\n _loading = true;\n _error = null;\n\n try\n {\n var result = await UAuthClient.Users.QueryAsync(\n new UserQuery\n {\n Search = _search,\n PageNumber = _pageNumber,\n PageSize = 20\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n {\n _users = page.Items;\n _hasNext = page.HasNext;\n }\n else\n {\n _error = \u0026quot;Unable to load users.\u0026quot;;\n _users = [];\n _hasNext = false;\n }\n }\n catch (Exception)\n {\n _error = \u0026quot;The user service is unavailable.\u0026quot;;\n _users = [];\n _hasNext = false;\n }\n finally\n {\n _loading = false;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example demonstrates searching and pagination.\u003C/p\u003E\n\u003Cp\u003EA production administration interface should additionally include loading indicators, action confirmation, permission-aware UI controls, and appropriate handling for concurrent changes.\u003C/p\u003E\n\u003Cp\u003EAll administrative operations must remain protected by server-side authorization.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-user-operation-results\u0022\u003E23. Handling User Operation Results\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses structured result contracts for user operations.\u003C/p\u003E\n\u003Cp\u003ESome methods return:\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\u003EUAuthResult\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOthers return:\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\u003EUAuthResult\u0026lt;T\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor operations such as user creation, status changes, and deletion, the returned value also contains an operation-level \u003Ccode\u003EIsSuccess\u003C/code\u003E property.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022transport-level-versus-operation-level-success\u0022\u003ETransport-Level Versus Operation-Level Success\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.Users.CreateAsync(request);\n\nif (!result.IsSuccess)\n{\n // API-level failure.\n}\nelse if (result.Value is { IsSuccess: false } operation)\n{\n // User creation was processed but did not succeed.\n Console.WriteLine(operation.FailureReason);\n}\nelse if (result.Value is { IsSuccess: true } created)\n{\n Console.WriteLine(created.UserKey);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECheck the appropriate success level for the operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-status-helpers\u0022\u003EHTTP Status Helpers\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthResult\u003C/code\u003E exposes convenient status helpers.\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.Http.IsUnauthorized)\n{\n // Authentication required.\n}\nelse if (result.Http.IsForbidden)\n{\n // Insufficient permission.\n}\nelse if (result.Http.IsConflict)\n{\n // Conflicting operation or state.\n}\nelse if (result.Http.IsBadRequest)\n{\n // Invalid request.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAdditional diagnostic fields include \u003Ccode\u003ECorrelationId\u003C/code\u003E, \u003Ccode\u003ETraceId\u003C/code\u003E, \u003Ccode\u003EProblem\u003C/code\u003E, and \u003Ccode\u003EErrorText\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EAvoid exposing sensitive internal failure details directly to end users.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-state-events\u0022\u003E24. Client State Events\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthUserClient\u003C/code\u003E publishes state events for successful self-service operations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EEvent\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUpdateMyProfileAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EProfileChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreateMyProfileAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EProfileChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeleteMyProfileAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EProfileChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EChangeMyStatusAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EProfileChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeleteMeAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserDeleted\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe event payload depends on the operation.\u003C/p\u003E\n\u003Cp\u003EAdministrative user operations do not automatically publish these same local self-service events.\u003C/p\u003E\n\u003Cp\u003EFor example, after updating another user\u0027s profile, an administration panel should refresh the affected user data as needed.\u003C/p\u003E\n\u003Cp\u003EClient events help synchronize application state, but they do not replace authoritative server-side account status or permission checks.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-and-lifecycle-considerations\u0022\u003E25. Security and Lifecycle Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self-service-and-administrative-boundaries\u0022\u003ESelf-Service and Administrative Boundaries\u003C/h3\u003E\n\u003Cp\u003ESelf-service methods operate on the current authenticated user.\u003C/p\u003E\n\u003Cp\u003EAdministrative methods identify an explicit target user.\u003C/p\u003E\n\u003Cp\u003EDo not assume that access to the SDK automatically authorizes an operation.\u003C/p\u003E\n\u003Cp\u003EThe server must enforce actor, target, tenant, and action requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022status-changes-and-existing-sessions\u0022\u003EStatus Changes and Existing Sessions\u003C/h3\u003E\n\u003Cp\u003EChanging account status may affect authentication eligibility and session continuation.\u003C/p\u003E\n\u003Cp\u003EHowever, immediate invalidation of existing credentials depends on the server\u0027s security policy and authentication mode.\u003C/p\u003E\n\u003Cp\u003EA client-side status update is not itself proof that every previously issued token has been invalidated.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-data-is-not-authorization-data\u0022\u003EProfile Data Is Not Authorization Data\u003C/h3\u003E\n\u003Cp\u003EA user\u0027s display name, biography, or profile metadata should not be used as proof of permissions.\u003C/p\u003E\n\u003Cp\u003EAuthorization decisions must use the relevant trusted security context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-replacement-can-cause-data-loss\u0022\u003EProfile Replacement Can Cause Data Loss\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUpdateProfileRequest\u003C/code\u003E is not a patch contract.\u003C/p\u003E\n\u003Cp\u003ESubmitting an incomplete request may clear existing profile fields.\u003C/p\u003E\n\u003Cp\u003EApplications should preserve intended values and consider concurrency when multiple editors can modify the same profile.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deletion-is-a-lifecycle-operation\u0022\u003EDeletion Is a Lifecycle Operation\u003C/h3\u003E\n\u003Cp\u003ESoft deletion and hard deletion have different implications.\u003C/p\u003E\n\u003Cp\u003EApplications should consider retention, recovery, audit requirements, and dependent data before exposing destructive actions.\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/identifiers\u0022\u003EIdentifiers\u003C/a\u003E \u2014 Manage usernames, email addresses, phone numbers, and verification.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/authentication\u0022\u003EAuthentication\u003C/a\u003E \u2014 Login, refresh, logout, and authentication state.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Inspect and revoke sessions across devices.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/users\u0022\u003EUsers\u003C/a\u003E \u2014 Understand the user domain and lifecycle architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/policies\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E \u2014 Understand authorization enforcement for user operations.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EThe UltimateAuth Client SDK provides a comprehensive API for managing users, profiles, account status, and user lifecycle operations.\u003C/p\u003E\n\u003Cp\u003EThrough \u003Ccode\u003EUAuthClient.Users\u003C/code\u003E, applications can implement account settings, profile management, user registration, and administrative user management without manually constructing API requests.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth distinguishes between \u003Cstrong\u003Euser identity\u003C/strong\u003E, \u003Cstrong\u003Euser profiles\u003C/strong\u003E, and \u003Cstrong\u003Eaccount lifecycle state\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EA user has a stable identity and may have multiple profiles. Account status controls lifecycle behavior independently of profile data.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the public \u003Ccode\u003EIUserClient\u003C/code\u003E API, including self-service and administrative operations, structured results, pagination, multi-profile support, and important update semantics.\u003C/p\u003E\n\u003Cp\u003EFor email addresses, usernames, phone numbers, and identifier verification, see \u003Ca href=\u0022/docs/client-sdk/identifiers\u0022\u003EIdentifiers\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-user-client\u0022\u003E1. Accessing the User 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\u003EUser operations are available through:\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\u003EUAuthClient.Users\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003EUsers\u003C/code\u003E property exposes \u003Ccode\u003EIUserClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIts operations are divided into three functional areas:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EArea\u003C/th\u003E\n\u003Cth\u003ECapabilities\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUser lifecycle\u003C/td\u003E\n\u003Ctd\u003ECreate users, change status, delete accounts\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESelf-service profiles\u003C/td\u003E\n\u003Ctd\u003ERetrieve, update, create, and delete your own profiles\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAdministration\u003C/td\u003E\n\u003Ctd\u003EQuery users and manage another user\u0027s account and profiles\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ESelf-service methods operate on the authenticated user.\u003C/p\u003E\n\u003Cp\u003EAdministrative methods operate on an explicitly identified target and require the appropriate server-side authorization.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-the-current-user\u0022\u003E2. Get the Current User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMeAsync\u003C/code\u003E to retrieve the current user\u0027s profile.\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.Users.GetMeAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } user)\n{\n Console.WriteLine(user.DisplayName);\n Console.WriteLine(user.Status);\n Console.WriteLine(user.UserKey);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;UserView\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen no profile is specified, UltimateAuth returns the default profile.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userview\u0022\u003EUserView\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUserView\u003C/code\u003E contains account information and the selected profile\u0027s data.\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EStable user identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EStatus\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECurrent account status\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUsername\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryEmail\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPrimary email address\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryPhone\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPrimary phone number\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EProfileKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESelected profile\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EFirstName\u003C/code\u003E, \u003Ccode\u003ELastName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EProfile name fields\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDisplayName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDisplay name\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBio\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBiography\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBirthDate\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBirth date\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EGender\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EProfile gender field\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELanguage\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreferred language\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETimeZone\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreferred time zone\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECulture\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECulture preference\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEmailVerified\u003C/code\u003E, \u003Ccode\u003EPhoneVerified\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier verification information\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAccount creation timestamp\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMetadata\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECustom profile metadata\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe returned profile is a view of server-managed user information.\u003C/p\u003E\n\u003Cp\u003EIt should not be treated as a substitute for authoritative authentication or authorization state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-the-current-users-profile\u0022\u003E3. Update the Current User\u0027s Profile\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EUpdateMyProfileAsync\u003C/code\u003E to update the authenticated user\u0027s profile.\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.Users.UpdateMyProfileAsync(\n new UpdateProfileRequest\n {\n DisplayName = \u0026quot;John Doe\u0026quot;,\n FirstName = \u0026quot;John\u0026quot;,\n LastName = \u0026quot;Doe\u0026quot;,\n Language = \u0026quot;en\u0026quot;,\n TimeZone = \u0026quot;Europe/London\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Profile update succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003EUAuthResult\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-profile-updates-replace-editable-fields\u0022\u003EImportant: Profile Updates Replace Editable Fields\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUpdateProfileRequest\u003C/code\u003E uses replacement semantics rather than partial-update semantics.\u003C/p\u003E\n\u003Cp\u003EThis means:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEditable fields supplied in the request replace their previous values.\u003C/li\u003E\n\u003Cli\u003EExplicit \u003Ccode\u003Enull\u003C/code\u003E values clear existing values.\u003C/li\u003E\n\u003Cli\u003EOmitted editable properties are also treated as \u003Ccode\u003Enull\u003C/code\u003E.\u003C/li\u003E\n\u003C/ul\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\u003Eawait UAuthClient.Users.UpdateMyProfileAsync(\n new UpdateProfileRequest\n {\n DisplayName = \u0026quot;John Doe\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis does \u003Cstrong\u003Enot\u003C/strong\u003E mean \u201Cchange only the display name.\u201D\u003C/p\u003E\n\u003Cp\u003EOther editable profile fields may be cleared.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-read-modify-and-submit\u0022\u003ERecommended: Read, Modify, and Submit\u003C/h3\u003E\n\u003Cp\u003EWhen building a profile editor, retrieve the current profile and preserve values that should remain unchanged.\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 currentResult = await UAuthClient.Users.GetMeAsync();\n\nif (!currentResult.IsSuccess || currentResult.Value is not { } current)\n{\n return;\n}\n\nvar update = new UpdateProfileRequest\n{\n FirstName = current.FirstName,\n LastName = current.LastName,\n DisplayName = \u0026quot;John Doe\u0026quot;,\n BirthDate = current.BirthDate,\n Gender = current.Gender,\n Bio = current.Bio,\n Language = current.Language,\n TimeZone = current.TimeZone,\n Culture = current.Culture,\n Metadata = current.Metadata\n};\n\nvar result = await UAuthClient.Users.UpdateMyProfileAsync(update);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis preserves the values returned by the initial read, but it is not an atomic patch operation.\u003C/p\u003E\n\u003Cp\u003EIf another request modifies the profile between the read and update, the later replacement may overwrite those changes.\u003C/p\u003E\n\u003Cp\u003EApplications with concurrent profile editing requirements should account for this behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-state-events\u0022\u003EProfile State Events\u003C/h3\u003E\n\u003Cp\u003EAfter a successful \u003Ccode\u003EUpdateMyProfileAsync\u003C/code\u003E operation, the client publishes a \u003Ccode\u003EProfileChanged\u003C/code\u003E event.\u003C/p\u003E\n\u003Cp\u003EThe event carries the \u003Ccode\u003EUpdateProfileRequest\u003C/code\u003E used for the operation.\u003C/p\u003E\n\u003Cp\u003EThe configured client state event handling mode determines how the application processes the notification.\u003C/p\u003E\n\u003Cp\u003EThe event is published only when the SDK operation reports success.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-multiple-profiles\u0022\u003E4. Understanding Multiple Profiles\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports multiple profiles for a single user when enabled through server configuration.\u003C/p\u003E\n\u003Cp\u003EFor example, one account may have:\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\u003EUser\n |\n \u002B-- default\n |\n \u002B-- business\n |\n \u002B-- public\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAll profiles belong to the same user identity.\u003C/p\u003E\n\u003Cp\u003EThey do not represent separate authentication accounts or independently authenticated principals.\u003C/p\u003E\n\u003Cp\u003EProfiles allow applications to organize different sets of user-facing information under one account.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022default-profile\u0022\u003EDefault Profile\u003C/h3\u003E\n\u003Cp\u003EEvery newly created user receives a default profile.\u003C/p\u003E\n\u003Cp\u003EIts key 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\u003EProfileKey.Default\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe default profile cannot be deleted.\u003C/p\u003E\n\u003Cp\u003EWhen a profile request omits \u003Ccode\u003EProfileKey\u003C/code\u003E, the default profile is selected.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-keys\u0022\u003EProfile Keys\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EProfileKey\u003C/code\u003E is a strongly typed value.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003ETryCreate\u003C/code\u003E to construct a custom key:\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 (!ProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var profileKey))\n{\n throw new InvalidOperationException(\u0026quot;Invalid profile key.\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EProfile keys are normalized by trimming whitespace and converting the value to lowercase.\u003C/p\u003E\n\u003Cp\u003EA valid key must not be empty or exceed 64 characters.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022retrieve-a-specific-profile\u0022\u003E5. Retrieve a Specific Profile\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetProfileRequest\u003C/code\u003E to select a profile.\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 (!ProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var profileKey))\n{\n return;\n}\n\nvar result = await UAuthClient.Users.GetMeAsync(\n new GetProfileRequest\n {\n ProfileKey = profileKey\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } profile)\n{\n Console.WriteLine(profile.DisplayName);\n Console.WriteLine(profile.ProfileKey);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe response is still \u003Ccode\u003EUserView\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe account identity and status remain associated with the same user; the selected profile determines which profile information is returned.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-an-additional-profile\u0022\u003E6. Create an Additional Profile\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECreateMyProfileAsync\u003C/code\u003E to create a new profile for the authenticated 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\u003Eif (!ProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var profileKey))\n{\n return;\n}\n\nvar result = await UAuthClient.Users.CreateMyProfileAsync(\n new CreateProfileRequest\n {\n ProfileKey = profileKey,\n DisplayName = \u0026quot;John Doe Consulting\u0026quot;,\n Bio = \u0026quot;Business profile\u0026quot;,\n Language = \u0026quot;en\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Profile created.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ECreateProfileRequest\u003C/code\u003E requires \u003Ccode\u003EProfileKey\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIt also supports the same general profile information used for user-facing profiles.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022clone-an-existing-profile\u0022\u003EClone an Existing Profile\u003C/h3\u003E\n\u003Cp\u003EA new profile can be created using an existing profile as its source.\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\u003EProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var businessKey);\n\nvar result = await UAuthClient.Users.CreateMyProfileAsync(\n new CreateProfileRequest\n {\n ProfileKey = businessKey,\n CloneFrom = ProfileKey.Default\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ECloneFrom\u003C/code\u003E identifies the source profile.\u003C/p\u003E\n\u003Cp\u003EThis is useful when creating a new profile that should initially reuse information from an existing one.\u003C/p\u003E\n\u003Cp\u003EThe exact merge and override behavior is determined by the server\u0027s profile creation implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-creation-events\u0022\u003EProfile Creation Events\u003C/h3\u003E\n\u003Cp\u003EAfter successful self-service profile creation, the SDK publishes \u003Ccode\u003EProfileChanged\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis allows application components to refresh profile-related views.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-a-specific-profile\u0022\u003E7. Update a Specific Profile\u003C/h2\u003E\n\u003Cp\u003ESet \u003Ccode\u003EProfileKey\u003C/code\u003E in \u003Ccode\u003EUpdateProfileRequest\u003C/code\u003E to select the profile being updated.\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\u003EProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var businessKey);\n\nvar result = await UAuthClient.Users.UpdateMyProfileAsync(\n new UpdateProfileRequest\n {\n ProfileKey = businessKey,\n DisplayName = \u0026quot;John Doe Consulting\u0026quot;,\n Bio = \u0026quot;Updated business profile\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same replacement semantics apply to custom profiles.\u003C/p\u003E\n\u003Cp\u003EIf the application intends to preserve existing fields, retrieve the selected profile first and include the values that should remain unchanged.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-a-profile\u0022\u003E8. Delete a Profile\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EDeleteMyProfileAsync\u003C/code\u003E to remove a custom profile.\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\u003EProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var businessKey);\n\nvar result = await UAuthClient.Users.DeleteMyProfileAsync(\n new DeleteProfileRequest\n {\n ProfileKey = businessKey\n });\n\nif (result.IsSuccess)\n{\n // Profile deleted.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe default profile cannot be deleted.\u003C/p\u003E\n\u003Cp\u003EDeleting a profile is different from deleting the user account.\u003C/p\u003E\n\u003Cp\u003EThe user identity and other profiles remain subject to their own lifecycle and server-side state.\u003C/p\u003E\n\u003Cp\u003EOn successful self-service profile deletion, the SDK publishes \u003Ccode\u003EProfileChanged\u003C/code\u003E with the deleted profile key.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-user\u0022\u003E9. Create a User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECreateAsync\u003C/code\u003E to request user creation through the standard user creation endpoint.\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.Users.CreateAsync(\n new CreateUserRequest\n {\n UserName = \u0026quot;johndoe\u0026quot;,\n Email = \u0026quot;john@example.com\u0026quot;,\n Password = password,\n FirstName = \u0026quot;John\u0026quot;,\n LastName = \u0026quot;Doe\u0026quot;,\n DisplayName = \u0026quot;John Doe\u0026quot;\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } created)\n{\n Console.WriteLine($\u0026quot;Created user: {created.UserKey}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;UserCreateResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUser creation availability depends on the server\u0027s configuration and access policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022createuserrequest\u0022\u003ECreateUserRequest\u003C/h3\u003E\n\u003Cp\u003EThe request supports several groups of fields:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECategory\u003C/th\u003E\n\u003Cth\u003EProperties\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIdentifiers\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserName\u003C/code\u003E, \u003Ccode\u003EEmail\u003C/code\u003E, \u003Ccode\u003EPhone\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EVerification flags\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserNameVerified\u003C/code\u003E, \u003Ccode\u003EEmailVerified\u003C/code\u003E, \u003Ccode\u003EPhoneVerified\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredentials\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPassword\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EProfile\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EFirstName\u003C/code\u003E, \u003Ccode\u003ELastName\u003C/code\u003E, \u003Ccode\u003EDisplayName\u003C/code\u003E, \u003Ccode\u003EBirthDate\u003C/code\u003E, \u003Ccode\u003EGender\u003C/code\u003E, \u003Ccode\u003EBio\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPreferences\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ELanguage\u003C/code\u003E, \u003Ccode\u003ETimeZone\u003C/code\u003E, \u003Ccode\u003ECulture\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EExtensions\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EMetadata\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EVerification flags must not be treated as client-controlled proof of identifier ownership.\u003C/p\u003E\n\u003Cp\u003EThe server is responsible for enforcing verification requirements and determining whether a caller is authorized to establish verified identifier state.\u003C/p\u003E\n\u003Cp\u003EFor identifier-specific operations and verification workflows, see \u003Ca href=\u0022/docs/client-sdk/identifiers\u0022\u003EIdentifiers\u003C/a\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022usercreateresult\u0022\u003EUserCreateResult\u003C/h3\u003E\n\u003Cp\u003EThe returned value 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\u003Epublic sealed record UserCreateResult\n{\n public required bool IsSuccess { get; init; }\n public string? UserKey { get; init; }\n public string? FailureReason { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003EUserKey\u003C/code\u003E is returned as a string when creation succeeds.\u003C/p\u003E\n\u003Cp\u003ECheck both the transport-level \u003Ccode\u003EUAuthResult.IsSuccess\u003C/code\u003E and the operation-level \u003Ccode\u003EUserCreateResult.IsSuccess\u003C/code\u003E where applicable.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-user-creation\u0022\u003E10. Administrative User Creation\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECreateAsAdminAsync\u003C/code\u003E when creating a user through an administrative context.\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.Users.CreateAsAdminAsync(\n new CreateUserRequest\n {\n UserName = \u0026quot;newemployee\u0026quot;,\n Email = \u0026quot;employee@example.com\u0026quot;,\n DisplayName = \u0026quot;New Employee\u0026quot;\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } created)\n{\n Console.WriteLine(created.UserKey);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAdministrative creation is distinct from ordinary user creation.\u003C/p\u003E\n\u003Cp\u003EThe server enforces the required permissions and applies the relevant user creation policies.\u003C/p\u003E\n\u003Cp\u003EApplications should not assume that calling the administrative endpoint bypasses validation or identifier ownership rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022query-users\u0022\u003E11. Query Users\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EQueryAsync\u003C/code\u003E to search and paginate users.\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.Users.QueryAsync(\n new UserQuery\n {\n Search = \u0026quot;john\u0026quot;,\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var user in page.Items)\n {\n Console.WriteLine(user.DisplayName);\n Console.WriteLine(user.Status);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;PagedResult\u0026lt;UserSummary\u0026gt;\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userquery-filters\u0022\u003EUserQuery Filters\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUserQuery\u003C/code\u003E extends \u003Ccode\u003EPageRequest\u003C/code\u003E and supports:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESearch\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESearch term\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EStatus\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EFilter by user status\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIncludeDeleted\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EInclude deleted users when authorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EProfileKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESelect the profile context for the query\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPageNumber\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPage number\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPageSize\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPage size\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESortBy\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional sort field\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDescending\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDescending sort order\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=\u0022search-behavior\u0022\u003ESearch Behavior\u003C/h3\u003E\n\u003Cp\u003EThe query model documents case-insensitive partial matching for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDisplay name.\u003C/li\u003E\n\u003Cli\u003EUsername.\u003C/li\u003E\n\u003Cli\u003EPrimary email.\u003C/li\u003E\n\u003Cli\u003EPrimary phone.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA complete valid \u003Ccode\u003EUserKey\u003C/code\u003E can also be used as a search term.\u003C/p\u003E\n\u003Cp\u003EPartial \u003Ccode\u003EUserKey\u003C/code\u003E matching is not supported.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022filter-by-status\u0022\u003EFilter by Status\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.Users.QueryAsync(\n new UserQuery\n {\n Status = UserStatus.Active,\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022include-deleted-users\u0022\u003EInclude Deleted Users\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.Users.QueryAsync(\n new UserQuery\n {\n IncludeDeleted = true,\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe ability to query deleted accounts depends on server-side authorization and data retention behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022usersummary-and-pagination\u0022\u003E12. UserSummary and Pagination\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EQueryAsync\u003C/code\u003E returns \u003Ccode\u003EUserSummary\u003C/code\u003E items.\u003C/p\u003E\n\u003Cp\u003EEach summary contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUsername\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDisplayName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDisplay name\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryEmail\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPrimary email\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryPhone\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPrimary phone\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EStatus\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAccount status\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreation timestamp\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis model is suitable for administrative user lists.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022navigating-pages\u0022\u003ENavigating Pages\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.Users.QueryAsync(\n new UserQuery\n {\n PageNumber = 2,\n PageSize = 25\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n Console.WriteLine($\u0026quot;Total users: {page.TotalCount}\u0026quot;);\n Console.WriteLine($\u0026quot;Current page: {page.PageNumber}\u0026quot;);\n Console.WriteLine($\u0026quot;Has next page: {page.HasNext}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003EPagedResult\u0026lt;T\u0026gt;\u003C/code\u003E rather than assuming every query returns the complete user collection.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022retrieve-users-by-keys\u0022\u003E13. Retrieve Users by Keys\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetByKeysAsync\u003C/code\u003E to retrieve multiple user summaries when their \u003Ccode\u003EUserKey\u003C/code\u003E values are already known.\u003C/p\u003E\n\u003Cp\u003EUnlike \u003Ccode\u003EQueryAsync\u003C/code\u003E, which searches and paginates users, \u003Ccode\u003EGetByKeysAsync\u003C/code\u003E retrieves a specific collection of users in a single request.\u003C/p\u003E\n\u003Cp\u003EThis is particularly useful for applications that maintain relationships between users, such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ETeam members.\u003C/li\u003E\n\u003Cli\u003EOrganization members.\u003C/li\u003E\n\u003Cli\u003EProject participants.\u003C/li\u003E\n\u003Cli\u003EGroup memberships.\u003C/li\u003E\n\u003Cli\u003EUsers assigned to a particular resource.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EInstead of requesting each user\u0027s information separately, the application can retrieve the required summaries in one operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic-usage\u0022\u003EBasic Usage\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 userKeys = new[]\n{\n firstUserKey,\n secondUserKey,\n thirdUserKey\n};\n\nvar result = await UAuthClient.Users.GetByKeysAsync(userKeys);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } users)\n{\n foreach (var user in users)\n {\n Console.WriteLine($\u0026quot;{user.DisplayName} ({user.UserName})\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;IReadOnlyList\u0026lt;UserSummary\u0026gt;\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach returned item uses the same \u003Ccode\u003EUserSummary\u003C/code\u003E model described above.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022batch-size-and-behavior\u0022\u003EBatch Size and Behavior\u003C/h3\u003E\n\u003Cp\u003EA single request supports up to \u003Cstrong\u003E250 distinct user keys\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe operation:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAccepts a collection of strongly typed \u003Ccode\u003EUserKey\u003C/code\u003E values.\u003C/li\u003E\n\u003Cli\u003ERemoves duplicate keys.\u003C/li\u003E\n\u003Cli\u003EReturns existing, non-deleted users within the applicable tenant.\u003C/li\u003E\n\u003Cli\u003EOmits user keys that do not match an available user.\u003C/li\u003E\n\u003Cli\u003EReturns an empty collection when no users match.\u003C/li\u003E\n\u003Cli\u003EDoes not use pagination.\u003C/li\u003E\n\u003Cli\u003EDoes not guarantee that the response follows the order of the requested keys.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ERequests exceeding 250 distinct keys are rejected with the \u003Ccode\u003Euser_batch_size_exceeded\u003C/code\u003E validation error.\u003C/p\u003E\n\u003Cp\u003EFor larger collections, split the keys into batches:\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 allUsers = new List\u0026lt;UserSummary\u0026gt;();\n\nforeach (var batch in userKeys.Distinct().Chunk(250))\n{\n var result = await UAuthClient.Users.GetByKeysAsync(batch);\n\n if (!result.IsSuccess || result.Value is null)\n {\n // Handle the failed batch.\n return;\n }\n\n allUsers.AddRange(result.Value);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor large datasets that require searching, filtering, sorting, or pagination, use \u003Ccode\u003EQueryAsync\u003C/code\u003E instead.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022queryasync-vs.getbykeysasync\u0022\u003EQueryAsync vs. GetByKeysAsync\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECapability\u003C/th\u003E\n\u003Cth\u003EQueryAsync\u003C/th\u003E\n\u003Cth\u003EGetByKeysAsync\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESearch users\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERetrieve known user keys\u003C/td\u003E\n\u003Ctd\u003EIndirectly\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPagination\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EFiltering and sorting\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMultiple specific users\u003C/td\u003E\n\u003Ctd\u003ENot optimized for this purpose\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMaximum batch size\u003C/td\u003E\n\u003Ctd\u003EPagination limits\u003C/td\u003E\n\u003Ctd\u003E250 distinct keys\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EResponse\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPagedResult\u0026lt;UserSummary\u0026gt;\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIReadOnlyList\u0026lt;UserSummary\u0026gt;\u003C/code\u003E\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=\u0022authorization\u0022\u003EAuthorization\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EGetByKeysAsync\u003C/code\u003E is a protected operation.\u003C/p\u003E\n\u003Cp\u003EIt uses the action:\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\u003EUAuthActions.Users.GetByKeysAdmin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe corresponding permission value 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\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\u003Eusers.getbykeys.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAlthough the action uses the \u003Ccode\u003E.admin\u003C/code\u003E suffix, access is determined by the server\u0027s authorization policies and the actor\u0027s effective permissions.\u003C/p\u003E\n\u003Cp\u003EApplications can authorize non-administrator users to retrieve specific groups of users when their policies explicitly allow it.\u003C/p\u003E\n\u003Cp\u003EFor example, an application may allow team members to retrieve summaries of other members belonging to the same team while denying access to users outside that team.\u003C/p\u003E\n\u003Cp\u003EThe server exposes the requested users through \u003Ccode\u003EAccessContext.TargetUserKeys\u003C/code\u003E, allowing application policies to validate the complete target collection.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E Possessing the \u003Ccode\u003Eusers.getbykeys.admin\u003C/code\u003E permission does not automatically establish that every requested user belongs to an allowed group. Applications requiring group, organization, or resource-level isolation must enforce those restrictions through server-side authorization policies.\u003C/p\u003E\n\u003Cp\u003EFor details, see \u003Ca href=\u0022/docs/plugin-domains/policies\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022performance-considerations\u0022\u003EPerformance Considerations\u003C/h3\u003E\n\u003Cp\u003EBatch retrieval avoids issuing a separate client request for every user.\u003C/p\u003E\n\u003Cp\u003EThe reference persistence implementations support retrieving multiple user summaries through a single batch operation.\u003C/p\u003E\n\u003Cp\u003EActual database query counts and execution costs depend on the configured persistence provider.\u003C/p\u003E\n\u003Cp\u003EFor example, an application displaying 50 team members can request their summaries together rather than making 50 individual \u003Ccode\u003EGetUserAsync\u003C/code\u003E calls.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetByKeysAsync\u003C/code\u003E when user keys are already available. Use \u003Ccode\u003EGetUserAsync\u003C/code\u003E when the application needs the detailed profile of one specific user.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-a-specific-user\u0022\u003E14. Get a Specific User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetUserAsync\u003C/code\u003E to retrieve a user\u0027s profile through the administrative API.\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.Users.GetUserAsync(userKey);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } user)\n{\n Console.WriteLine(user.DisplayName);\n Console.WriteLine(user.Status);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003EUAuthResult\u0026lt;UserView\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EWhen no profile is specified, the default profile is selected.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022retrieve-a-specific-user-profile\u0022\u003ERetrieve a Specific User Profile\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\u003EProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var businessKey);\n\nvar result = await UAuthClient.Users.GetUserAsync(\n userKey,\n new GetProfileRequest\n {\n ProfileKey = businessKey\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actor must have permission to access the target user\u0027s requested information.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-another-users-profile\u0022\u003E15. Update Another User\u0027s Profile\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EUpdateUserProfileAsync\u003C/code\u003E to update a selected user\u0027s profile.\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.Users.UpdateUserProfileAsync(\n userKey,\n new UpdateProfileRequest\n {\n DisplayName = \u0026quot;Updated Display Name\u0026quot;,\n FirstName = \u0026quot;John\u0026quot;,\n LastName = \u0026quot;Doe\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Profile updated.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis operation uses the same replacement semantics as \u003Ccode\u003EUpdateMyProfileAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EOmitted editable fields may be cleared.\u003C/p\u003E\n\u003Cp\u003EFor partial UI editing, retrieve the target profile and preserve values that should not change.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-a-named-profile\u0022\u003EUpdate a Named Profile\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\u003EProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var businessKey);\n\nvar result = await UAuthClient.Users.UpdateUserProfileAsync(userKey,\n new UpdateProfileRequest\n {\n ProfileKey = businessKey,\n DisplayName = \u0026quot;Updated Business Profile\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAdministrative profile updates require the appropriate authorization for the target user.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022manage-another-users-profiles\u0022\u003E16. Manage Another User\u0027s Profiles\u003C/h2\u003E\n\u003Cp\u003EAdministrators can create and delete profiles for a selected user.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-profile\u0022\u003ECreate a Profile\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\u003EProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var businessKey);\n\nvar result = await UAuthClient.Users.CreateUserProfileAsync(userKey,\n new CreateProfileRequest\n {\n ProfileKey = businessKey,\n DisplayName = \u0026quot;Business Profile\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-a-profile-1\u0022\u003EDelete a Profile\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\u003EProfileKey.TryCreate(\u0026quot;business\u0026quot;, out var businessKey);\n\nvar result = await UAuthClient.Users.DeleteUserProfileAsync(userKey,\n new DeleteProfileRequest\n {\n ProfileKey = businessKey\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe default profile cannot be deleted.\u003C/p\u003E\n\u003Cp\u003EAdministrative profile operations do not automatically publish the same local \u003Ccode\u003EProfileChanged\u003C/code\u003E event as self-service profile operations.\u003C/p\u003E\n\u003Cp\u003ERefresh affected administrative UI data explicitly when needed.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-user-status\u0022\u003E17. Understanding User Status\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates account lifecycle status from profile information.\u003C/p\u003E\n\u003Cp\u003EA user may be active, suspended, disabled, locked, awaiting activation, or in another supported lifecycle state.\u003C/p\u003E\n\u003Cp\u003EStatus can affect authentication eligibility, session continuation, and authorization.\u003C/p\u003E\n\u003Cp\u003EHowever, the exact enforcement behavior depends on the configured server policies and authentication mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self-service-and-administrative-statuses\u0022\u003ESelf-Service and Administrative Statuses\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth exposes separate status enums for self-service and administrative operations.\u003C/p\u003E\n\u003Cp\u003EThis prevents the client contract from treating every administrative lifecycle transition as a self-service capability.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESelfAssignableUserStatus\u003C/strong\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 enum SelfAssignableUserStatus\n{\n Active = 0,\n SelfSuspended = 10\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EAdminAssignableUserStatus\u003C/strong\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 enum AdminAssignableUserStatus\n{\n Active = 0,\n Disabled = 20,\n Suspended = 30,\n Locked = 40,\n RiskHold = 50,\n PendingActivation = 60,\n PendingVerification = 70,\n Unknown = 100\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese enums describe requestable statuses.\u003C/p\u003E\n\u003Cp\u003EThey do not mean every transition is permitted in every application context.\u003C/p\u003E\n\u003Cp\u003EThe server validates the requested transition.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-your-own-status\u0022\u003E18. Change Your Own Status\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EChangeMyStatusAsync\u003C/code\u003E for supported self-service status transitions.\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.Users.ChangeMyStatusAsync(\n new ChangeUserStatusSelfRequest\n {\n NewStatus = SelfAssignableUserStatus.SelfSuspended\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } change)\n{\n Console.WriteLine($\u0026quot;New status: {change.CurrentStatus}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis operation can be used to implement account self-suspension.\u003C/p\u003E\n\u003Cp\u003EThe server determines whether the transition is permitted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userstatuschangeresult\u0022\u003EUserStatusChangeResult\u003C/h3\u003E\n\u003Cp\u003EThe result 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\u003EDescription\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 status operation succeeded\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreviousStatus\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EStatus before the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECurrentStatus\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EStatus after the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EFailureReason\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional failure description\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\u003Eif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } change)\n{\n if (change.IsSuccess)\n {\n Console.WriteLine($\u0026quot;{change.PreviousStatus} -\u0026gt; {change.CurrentStatus}\u0026quot;);\n }\n else\n {\n Console.WriteLine(change.FailureReason ?? \u0026quot;Status change failed.\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe result may also report a successful no-change outcome when the requested state already matches the current state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022state-event\u0022\u003EState Event\u003C/h3\u003E\n\u003Cp\u003EAfter a successful SDK operation, \u003Ccode\u003EChangeMyStatusAsync\u003C/code\u003E publishes \u003Ccode\u003EProfileChanged\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis is the current client event behavior, even though the operation changes account status rather than editable profile fields.\u003C/p\u003E\n\u003Cp\u003EApplications should not assume that a status change always produces a dedicated \u003Ccode\u003EAuthorizationChanged\u003C/code\u003E event.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-another-users-status\u0022\u003E19. Change Another User\u0027s Status\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EChangeUserStatusAsync\u003C/code\u003E for administrative lifecycle management.\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.Users.ChangeUserStatusAsync(userKey,\n new ChangeUserStatusAdminRequest\n {\n NewStatus = AdminAssignableUserStatus.Suspended\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } change)\n{\n Console.WriteLine($\u0026quot;Current status: {change.CurrentStatus}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis operation requires administrative authorization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-disable-an-account\u0022\u003EExample: Disable an Account\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.Users.ChangeUserStatusAsync(userKey,\n new ChangeUserStatusAdminRequest\n {\n NewStatus = AdminAssignableUserStatus.Disabled\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-restore-an-active-status\u0022\u003EExample: Restore an Active Status\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.Users.ChangeUserStatusAsync(userKey,\n new ChangeUserStatusAdminRequest\n {\n NewStatus = AdminAssignableUserStatus.Active\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhether a particular transition is permitted depends on the server\u0027s lifecycle rules.\u003C/p\u003E\n\u003Cp\u003EDo not assume that setting \u003Ccode\u003EActive\u003C/code\u003E automatically restores every form of previously revoked authentication state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-the-current-user\u0022\u003E20. Delete the Current User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EDeleteMeAsync\u003C/code\u003E to request deletion of the current account.\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.Users.DeleteMeAsync();\n\nif (result.IsSuccess)\n{\n // Account deletion succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ESelf-service deletion is a soft-delete operation.\u003C/p\u003E\n\u003Cp\u003EAccording to the \u003Ccode\u003EIUserClient\u003C/code\u003E contract, this operation removes associated profiles and sessions.\u003C/p\u003E\n\u003Cp\u003EAfter successful deletion, the client publishes a \u003Ccode\u003EUserDeleted\u003C/code\u003E state event using patch handling mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-considerations\u0022\u003EImportant Considerations\u003C/h3\u003E\n\u003Cp\u003EAccount deletion is not equivalent to ordinary logout.\u003C/p\u003E\n\u003Cp\u003EIt changes the user\u0027s lifecycle state and may affect associated data across multiple domains.\u003C/p\u003E\n\u003Cp\u003EBefore exposing a delete-account action, applications should provide an appropriate confirmation flow and explain the consequences.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for authorization, data cleanup, and deletion policy enforcement.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-user-deletion\u0022\u003E21. Administrative User Deletion\u003C/h2\u003E\n\u003Cp\u003EAdministrators can delete users through \u003Ccode\u003EDeleteUserAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe method accepts a \u003Ccode\u003EDeleteUserRequest\u003C/code\u003E specifying the deletion mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022soft-delete\u0022\u003ESoft Delete\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.Users.DeleteUserAsync(userKey,\n new DeleteUserRequest\n {\n Mode = DeleteMode.Soft\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } deleted)\n{\n Console.WriteLine($\u0026quot;Deletion mode: {deleted.Mode}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hard-delete\u0022\u003EHard Delete\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.Users.DeleteUserAsync(userKey,\n new DeleteUserRequest\n {\n Mode = DeleteMode.Hard\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EHard deletion is a more destructive operation and should be restricted to appropriately authorized administrative workflows.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deletemode\u0022\u003EDeleteMode\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\u003Epublic enum DeleteMode\n{\n Soft = 0,\n Hard = 10\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userdeleteresult\u0022\u003EUserDeleteResult\u003C/h3\u003E\n\u003Cp\u003EThe operation returns \u003Ccode\u003EUAuthResult\u0026lt;UserDeleteResult\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EUserDeleteResult\u003C/code\u003E includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EIsSuccess\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EMode\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EFailureReason\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\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.IsSuccess)\n{\n // Handle transport or API-level failure.\n}\nelse if (result.Value is { IsSuccess: false } deletion)\n{\n // Handle the operation-level failure.\n Console.WriteLine(deletion.FailureReason);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA previously deleted account may produce an operation-level success result indicating that no additional deletion was necessary.\u003C/p\u003E\n\u003Cp\u003EDo not infer the complete persistence or retention behavior from the \u003Ccode\u003EDeleteMode\u003C/code\u003E value alone.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-user-administration-table\u0022\u003E22. Build a User Administration Table\u003C/h2\u003E\n\u003Cp\u003EThe following example demonstrates a basic Blazor user list using \u003Ccode\u003EQueryAsync\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.Core.Contracts\n@using CodeBeam.UltimateAuth.Users.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Users\u0026lt;/h3\u0026gt;\n\n\u0026lt;input @bind=\u0026quot;_search\u0026quot; placeholder=\u0026quot;Search users\u0026quot; /\u0026gt;\n\u0026lt;button @onclick=\u0026quot;SearchAsync\u0026quot;\u0026gt;Search\u0026lt;/button\u0026gt;\n\n@if (_loading)\n{\n \u0026lt;p\u0026gt;Loading users...\u0026lt;/p\u0026gt;\n}\nelse if (_error is not null)\n{\n \u0026lt;p role=\u0026quot;alert\u0026quot;\u0026gt;@_error\u0026lt;/p\u0026gt;\n}\nelse\n{\n \u0026lt;table\u0026gt;\n \u0026lt;thead\u0026gt;\n \u0026lt;tr\u0026gt;\n \u0026lt;th\u0026gt;Name\u0026lt;/th\u0026gt;\n \u0026lt;th\u0026gt;Username\u0026lt;/th\u0026gt;\n \u0026lt;th\u0026gt;Status\u0026lt;/th\u0026gt;\n \u0026lt;/tr\u0026gt;\n \u0026lt;/thead\u0026gt;\n \u0026lt;tbody\u0026gt;\n @foreach (var user in _users)\n {\n \u0026lt;tr @key=\u0026quot;user.UserKey\u0026quot;\u0026gt;\n \u0026lt;td\u0026gt;@user.DisplayName\u0026lt;/td\u0026gt;\n \u0026lt;td\u0026gt;@user.UserName\u0026lt;/td\u0026gt;\n \u0026lt;td\u0026gt;@user.Status\u0026lt;/td\u0026gt;\n \u0026lt;/tr\u0026gt;\n }\n \u0026lt;/tbody\u0026gt;\n \u0026lt;/table\u0026gt;\n\n \u0026lt;button @onclick=\u0026quot;PreviousPageAsync\u0026quot; disabled=\u0026quot;@(_pageNumber \u0026lt;= 1)\u0026quot;\u0026gt;Previous\u0026lt;/button\u0026gt;\n \u0026lt;button @onclick=\u0026quot;NextPageAsync\u0026quot; disabled=\u0026quot;@(!_hasNext)\u0026quot;\u0026gt;Next\u0026lt;/button\u0026gt;\n}\n\n@code {\n private string _search = \u0026quot;\u0026quot;;\n private int _pageNumber = 1;\n private bool _hasNext;\n private bool _loading;\n private string? _error;\n private IReadOnlyList\u0026lt;UserSummary\u0026gt; _users = [];\n\n protected override async Task OnInitializedAsync()\n {\n await LoadAsync();\n }\n\n private async Task SearchAsync()\n {\n _pageNumber = 1;\n await LoadAsync();\n }\n\n private async Task PreviousPageAsync()\n {\n if (_pageNumber \u0026lt;= 1)\n return;\n\n _pageNumber--;\n await LoadAsync();\n }\n\n private async Task NextPageAsync()\n {\n if (!_hasNext)\n return;\n\n _pageNumber\u002B\u002B;\n await LoadAsync();\n }\n\n private async Task LoadAsync()\n {\n _loading = true;\n _error = null;\n\n try\n {\n var result = await UAuthClient.Users.QueryAsync(\n new UserQuery\n {\n Search = _search,\n PageNumber = _pageNumber,\n PageSize = 20\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n {\n _users = page.Items;\n _hasNext = page.HasNext;\n }\n else\n {\n _error = \u0026quot;Unable to load users.\u0026quot;;\n _users = [];\n _hasNext = false;\n }\n }\n catch (Exception)\n {\n _error = \u0026quot;The user service is unavailable.\u0026quot;;\n _users = [];\n _hasNext = false;\n }\n finally\n {\n _loading = false;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example demonstrates searching and pagination.\u003C/p\u003E\n\u003Cp\u003EA production administration interface should additionally include loading indicators, action confirmation, permission-aware UI controls, and appropriate handling for concurrent changes.\u003C/p\u003E\n\u003Cp\u003EAll administrative operations must remain protected by server-side authorization.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-user-operation-results\u0022\u003E23. Handling User Operation Results\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses structured result contracts for user operations.\u003C/p\u003E\n\u003Cp\u003ESome methods return:\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\u003EUAuthResult\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOthers return:\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\u003EUAuthResult\u0026lt;T\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor operations such as user creation, status changes, and deletion, the returned value also contains an operation-level \u003Ccode\u003EIsSuccess\u003C/code\u003E property.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022transport-level-versus-operation-level-success\u0022\u003ETransport-Level Versus Operation-Level Success\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.Users.CreateAsync(request);\n\nif (!result.IsSuccess)\n{\n // API-level failure.\n}\nelse if (result.Value is { IsSuccess: false } operation)\n{\n // User creation was processed but did not succeed.\n Console.WriteLine(operation.FailureReason);\n}\nelse if (result.Value is { IsSuccess: true } created)\n{\n Console.WriteLine(created.UserKey);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECheck the appropriate success level for the operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-status-helpers\u0022\u003EHTTP Status Helpers\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthResult\u003C/code\u003E exposes convenient status helpers.\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.Http.IsUnauthorized)\n{\n // Authentication required.\n}\nelse if (result.Http.IsForbidden)\n{\n // Insufficient permission.\n}\nelse if (result.Http.IsConflict)\n{\n // Conflicting operation or state.\n}\nelse if (result.Http.IsBadRequest)\n{\n // Invalid request.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAdditional diagnostic fields include \u003Ccode\u003ECorrelationId\u003C/code\u003E, \u003Ccode\u003ETraceId\u003C/code\u003E, \u003Ccode\u003EProblem\u003C/code\u003E, and \u003Ccode\u003EErrorText\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EAvoid exposing sensitive internal failure details directly to end users.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-state-events\u0022\u003E24. Client State Events\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthUserClient\u003C/code\u003E publishes state events for successful self-service operations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EEvent\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUpdateMyProfileAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EProfileChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreateMyProfileAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EProfileChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeleteMyProfileAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EProfileChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EChangeMyStatusAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EProfileChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeleteMeAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserDeleted\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe event payload depends on the operation.\u003C/p\u003E\n\u003Cp\u003EAdministrative user operations do not automatically publish these same local self-service events.\u003C/p\u003E\n\u003Cp\u003EFor example, after updating another user\u0027s profile, an administration panel should refresh the affected user data as needed.\u003C/p\u003E\n\u003Cp\u003EClient events help synchronize application state, but they do not replace authoritative server-side account status or permission checks.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-and-lifecycle-considerations\u0022\u003E25. Security and Lifecycle Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self-service-and-administrative-boundaries\u0022\u003ESelf-Service and Administrative Boundaries\u003C/h3\u003E\n\u003Cp\u003ESelf-service methods operate on the current authenticated user.\u003C/p\u003E\n\u003Cp\u003EAdministrative methods identify an explicit target user.\u003C/p\u003E\n\u003Cp\u003EDo not assume that access to the SDK automatically authorizes an operation.\u003C/p\u003E\n\u003Cp\u003EThe server must enforce actor, target, tenant, and action requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022status-changes-and-existing-sessions\u0022\u003EStatus Changes and Existing Sessions\u003C/h3\u003E\n\u003Cp\u003EChanging account status may affect authentication eligibility and session continuation.\u003C/p\u003E\n\u003Cp\u003EHowever, immediate invalidation of existing credentials depends on the server\u0027s security policy and authentication mode.\u003C/p\u003E\n\u003Cp\u003EA client-side status update is not itself proof that every previously issued token has been invalidated.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-data-is-not-authorization-data\u0022\u003EProfile Data Is Not Authorization Data\u003C/h3\u003E\n\u003Cp\u003EA user\u0027s display name, biography, or profile metadata should not be used as proof of permissions.\u003C/p\u003E\n\u003Cp\u003EAuthorization decisions must use the relevant trusted security context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-replacement-can-cause-data-loss\u0022\u003EProfile Replacement Can Cause Data Loss\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUpdateProfileRequest\u003C/code\u003E is not a patch contract.\u003C/p\u003E\n\u003Cp\u003ESubmitting an incomplete request may clear existing profile fields.\u003C/p\u003E\n\u003Cp\u003EApplications should preserve intended values and consider concurrency when multiple editors can modify the same profile.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deletion-is-a-lifecycle-operation\u0022\u003EDeletion Is a Lifecycle Operation\u003C/h3\u003E\n\u003Cp\u003ESoft deletion and hard deletion have different implications.\u003C/p\u003E\n\u003Cp\u003EApplications should consider retention, recovery, audit requirements, and dependent data before exposing destructive actions.\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/identifiers\u0022\u003EIdentifiers\u003C/a\u003E \u2014 Manage usernames, email addresses, phone numbers, and verification.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client-sdk/authentication\u0022\u003EAuthentication\u003C/a\u003E \u2014 Login, refresh, logout, and authentication state.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client-sdk/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Inspect and revoke sessions across devices.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/users\u0022\u003EUsers\u003C/a\u003E \u2014 Understand the user domain and lifecycle architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/policies\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E \u2014 Understand authorization enforcement for user operations.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "accessing-the-user-client", @@ -339,6 +339,6 @@ "Level": 0 } ], - "SourceHash": "6E53F83F18629B64B09F13E568D5A6D71C6954D07C5842E288430FFB7B526FB6", + "SourceHash": "81CF0482498EB09AACD57DF9BC1FC7D586A11FF62FD14BF9AA97AB3C43C10B0E", "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/overview.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/overview.json index eccab71e..8555cd1c 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/overview.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/overview.json @@ -1,7 +1,7 @@ { "Slug": "configuration/overview", "Title": "Configuration Overview", - "Html": "\n\u003Cp\u003EUltimateAuth provides a structured configuration system for controlling authentication behavior, session lifecycles, token policies, client integration, and server security.\u003C/p\u003E\n\u003Cp\u003EConfiguration is organized around the application components that actually consume it.\u003C/p\u003E\n\u003Cp\u003EFor most applications, the two primary configuration types are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E\u003C/strong\u003E \u2014 Controls authentication and security behavior on the UltimateAuth server.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ccode\u003EUAuthClientOptions\u003C/code\u003E\u003C/strong\u003E \u2014 Controls how an application client communicates with UltimateAuth and executes authentication flows.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth also defines reusable options in its Core package. These types form the foundation of server configuration, but they do not need to be configured independently when using \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis guide explains where configuration belongs, how options are registered, how configuration sources interact, and how to customize behavior safely.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-architecture\u0022\u003E1. Configuration Architecture\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates reusable option definitions from the runtime components that consume them.\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\u003EUltimateAuth\n\u2502\n\u251C\u2500\u2500 Core\n\u2502 \u2514\u2500\u2500 Shared option definitions\n\u2502 \u251C\u2500\u2500 Login\n\u2502 \u251C\u2500\u2500 Session\n\u2502 \u251C\u2500\u2500 Token\n\u2502 \u251C\u2500\u2500 PKCE\n\u2502 \u2514\u2500\u2500 Multi-tenancy\n\u2502\n\u251C\u2500\u2500 Server\n\u2502 \u2514\u2500\u2500 UAuthServerOptions\n\u2502 \u251C\u2500\u2500 Core-defined options\n\u2502 \u251C\u2500\u2500 Authentication mode restrictions\n\u2502 \u251C\u2500\u2500 Cookie and session resolution\n\u2502 \u251C\u2500\u2500 Endpoints\n\u2502 \u251C\u2500\u2500 Hub configuration\n\u2502 \u251C\u2500\u2500 Credential policies\n\u2502 \u2514\u2500\u2500 Server-specific behavior\n\u2502\n\u2514\u2500\u2500 Client\n \u2514\u2500\u2500 UAuthClientOptions\n \u251C\u2500\u2500 Client profile\n \u251C\u2500\u2500 Login and PKCE flows\n \u251C\u2500\u2500 Endpoint configuration\n \u251C\u2500\u2500 Auto-refresh\n \u251C\u2500\u2500 Reauthentication\n \u2514\u2500\u2500 Client state events\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe important distinction is between \u003Cstrong\u003Ewhere an option type is defined\u003C/strong\u003E and \u003Cstrong\u003Ewhere its value is consumed\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EUAuthSessionOptions\u003C/code\u003E is defined in Core, but a normal UltimateAuth Server application configures it through:\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.Session.Lifetime = TimeSpan.FromHours(8);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server consumes \u003Ccode\u003Eoptions.Session\u003C/code\u003E from \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThere is no need to configure a separate Core options instance for this setting.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-server\u0022\u003E2. Configure the Server\u003C/h2\u003E\n\u003Cp\u003EThe main server registration 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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n // Server configuration.\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis registers the UltimateAuth server infrastructure and its configuration.\u003C/p\u003E\n\u003Cp\u003EThe method accepts an optional configuration delegate:\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\u003EAction\u0026lt;UAuthServerOptions\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIt also supports a separate optional delegate for registering access policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-authentication-and-session-settings\u0022\u003EExample: Authentication and Session Settings\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Login.MaxFailedAttempts = 5;\n\n options.Session.Lifetime = TimeSpan.FromHours(8);\n\n options.Token.AccessTokenLifetime =\n TimeSpan.FromMinutes(15);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese settings control different parts of the authentication lifecycle:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOption\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\u003ELogin.MaxFailedAttempts\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EMaximum failed login attempts under the configured login policy\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESession.Lifetime\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EToken.AccessTokenLifetime\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAccess token lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe server applies its validation rules to the resulting configuration.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EConfiguration values should be chosen together.\u003C/strong\u003E For example, token lifetime settings must remain compatible with the configured session lifetime limits.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-options-are-composed-into-server-options\u0022\u003E3. Core Options Are Composed into Server Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E includes the following Core-defined option groups:\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 UAuthLoginOptions Login { get; set; } = new();\n\npublic UAuthSessionOptions Session { get; set; } = new();\n\npublic UAuthTokenOptions Token { get; set; } = new();\n\npublic UAuthPkceOptions Pkce { get; set; } = new();\n\npublic UAuthMultiTenantOptions MultiTenant { get; set; } = new();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is composition, not duplication.\u003C/p\u003E\n\u003Cp\u003EThe Core package owns the shared option definitions, while the Server package uses those definitions as part of its effective server configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended\u0022\u003ERecommended\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Session.Lifetime = TimeSpan.FromHours(8);\n options.Pkce.Enabled = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022not-recommended-for-server-configuration\u0022\u003ENot Recommended for Server Configuration\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\u003Ebuilder.Services.AddUltimateAuth(options =\u0026gt;\n{\n options.Session.Lifetime = TimeSpan.FromHours(8);\n});\n\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe second example configures \u003Ccode\u003EUAuthOptions\u003C/code\u003E, not the \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E instance consumed by the server\u0027s authentication runtime.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EWhen using UltimateAuth Server, configure Core-defined behavior through \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E or the \u003Ccode\u003EUltimateAuth:Server\u003C/code\u003E configuration section.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EDirect Core configuration is intended for scenarios that explicitly consume \u003Ccode\u003EUAuthOptions\u003C/code\u003E rather than the server configuration model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-configuration-groups\u0022\u003E4. Server Configuration Groups\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E organizes server behavior into focused option groups.\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\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin attempts and authentication behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESession\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession lifetime and lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EToken\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EToken issuance, lifetimes, and refresh behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkce\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPKCE flow configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMultiTenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant resolution and multi-tenant behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAllowedModes\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERestricts permitted authentication modes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHubDeploymentMode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EHub deployment arrangement\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHub\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EHub-specific configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECookie\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication cookie behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessionResolution\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolving session identifiers from incoming requests\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEndpoints\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EServer endpoint configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialSessionPolicy\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession handling after credential changes and recovery\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResetCredential\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential reset configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryCredential\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPrimary credential policy\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiers\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier management behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifierValidation\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier validation rules\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELoginIdentifiers\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier types permitted for login\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserProfile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser profile behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPagination\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPagination limits and defaults\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAuthResponse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication response behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENavigation\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENavigation and redirect behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDiagnostics\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDiagnostic configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEvents\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EServer event configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese groups allow applications to customize specific behavior without replacing the overall authentication architecture.\u003C/p\u003E\n\u003Cp\u003EThe individual option types and their defaults are documented in \u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-client\u0022\u003E5. Configure the Client\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth clients have their own configuration model.\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.AddUltimateAuthClient(options =\u0026gt;\n{\n options.AutoDetectClientProfile = true;\n options.DefaultReturnUrl = \u0026quot;/\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe registration accepts:\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\u003EAction\u0026lt;UAuthClientOptions\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client options control how the client behaves, not the server\u0027s security policy.\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\u003Ebuilder.Services.AddUltimateAuthClient(options =\u0026gt;\n{\n options.AutoRefresh.Enabled = true;\n options.DefaultReturnUrl = \u0026quot;/dashboard\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEnabling client auto-refresh does not change the server\u0027s refresh token lifetime, session expiration rules, or rotation policy.\u003C/p\u003E\n\u003Cp\u003EThose are server-side responsibilities.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-configuration-groups\u0022\u003EClient Configuration Groups\u003C/h3\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\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EClientProfile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExplicit client profile\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAutoDetectClientProfile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAutomatic client profile detection\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDefaultReturnUrl\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDefault post-authentication navigation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EClient login flow behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkce\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EClient PKCE flow behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAutoRefresh\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAutomatic refresh behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReauth\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReauthentication behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEndpoints\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EServer endpoint paths used by the client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMultiTenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EClient tenant propagation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EStateEvents\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EClient state event handling\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E for the full reference.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-and-client-configuration-are-independent\u0022\u003E6. Server and Client Configuration Are Independent\u003C/h2\u003E\n\u003Cp\u003EA project may register both UltimateAuth Server and UltimateAuth Client.\u003C/p\u003E\n\u003Cp\u003EThis is common in applications that host authentication services and also use the client SDK.\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Session.Lifetime = TimeSpan.FromHours(8);\n});\n\nbuilder.Services.AddUltimateAuthClient(options =\u0026gt;\n{\n options.AutoDetectClientProfile = true;\n options.DefaultReturnUrl = \u0026quot;/\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese registrations configure different components.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ESetting\u003C/th\u003E\n\u003Cth\u003EServer\u003C/th\u003E\n\u003Cth\u003EClient\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession lifetime\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003Ctd\u003EDoes not control\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EToken lifetime\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003Ctd\u003EDoes not control\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential reset policy\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003Ctd\u003EDoes not control\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAllowed authentication modes\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003Ctd\u003EDoes not control\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient profile\u003C/td\u003E\n\u003Ctd\u003EDoes not control\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient auto-refresh\u003C/td\u003E\n\u003Ctd\u003EDoes not control\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient navigation\u003C/td\u003E\n\u003Ctd\u003EDoes not control\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EServer endpoint exposure\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003Ctd\u003EUses configured routes\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe client must be compatible with the server\u0027s configuration, but it cannot override server-side security requirements.\u003C/p\u003E\n\u003Cp\u003EFor example, enabling PKCE in client options does not make a disabled or unsupported PKCE server flow available.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-sources\u0022\u003E7. Configuration Sources\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports configuration through registration delegates and the standard ASP.NET Core configuration system.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022code-based-configuration\u0022\u003ECode-Based Configuration\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EProgram.cs\u003C/code\u003E for explicit configuration.\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.Login.MaxFailedAttempts = 5;\n options.Session.Lifetime = TimeSpan.FromHours(8);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is useful when configuration is closely tied to application behavior or requires strongly typed values.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-based-setup\u0022\u003EConfiguration-Based Setup\u003C/h3\u003E\n\u003Cp\u003EThe server binds settings from:\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\u003EUltimateAuth:Server\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor example, in \u003Ccode\u003Eappsettings.json\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\u003EJSON\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-json\u0022\u003E{\n \u0026quot;UltimateAuth\u0026quot;: {\n \u0026quot;Server\u0026quot;: {\n \u0026quot;Login\u0026quot;: {\n \u0026quot;MaxFailedAttempts\u0026quot;: 5\n },\n \u0026quot;Session\u0026quot;: {\n \u0026quot;Lifetime\u0026quot;: \u0026quot;08:00:00\u0026quot;\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client binds settings from:\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\u003EUltimateAuth:Client\n\u003C/code\u003E\u003C/pre\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\u003EJSON\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-json\u0022\u003E{\n \u0026quot;UltimateAuth\u0026quot;: {\n \u0026quot;Client\u0026quot;: {\n \u0026quot;AutoDetectClientProfile\u0026quot;: true,\n \u0026quot;DefaultReturnUrl\u0026quot;: \u0026quot;/dashboard\u0026quot;,\n \u0026quot;AutoRefresh\u0026quot;: {\n \u0026quot;Enabled\u0026quot;: true\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese sections are independent.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-both-sources\u0022\u003EUsing Both Sources\u003C/h3\u003E\n\u003Cp\u003EYou can combine code-based and configuration-based settings:\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.Login.MaxFailedAttempts = 5;\n options.Session.Lifetime = TimeSpan.FromHours(8);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWith:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EJSON\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-json\u0022\u003E{\n \u0026quot;UltimateAuth\u0026quot;: {\n \u0026quot;Server\u0026quot;: {\n \u0026quot;Login\u0026quot;: {\n \u0026quot;MaxFailedAttempts\u0026quot;: 3\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe effective \u003Ccode\u003ELogin.MaxFailedAttempts\u003C/code\u003E is \u003Ccode\u003E3\u003C/code\u003E, because the registration binds the configuration section after applying the configuration delegate.\u003C/p\u003E\n\u003Cp\u003EThe session lifetime remains eight hours because the configuration section does not override it.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-precedence\u0022\u003E8. Configuration Precedence\u003C/h2\u003E\n\u003Cp\u003EThe current registration pipeline applies options in this order:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EOption type defaults.\u003C/li\u003E\n\u003Cli\u003EValues supplied by the registration delegate.\u003C/li\u003E\n\u003Cli\u003EValues bound from the corresponding configuration section.\u003C/li\u003E\n\u003Cli\u003EApplicable post-configuration processing.\u003C/li\u003E\n\u003Cli\u003EOptions validation.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EFor the server:\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\u003Eservices.AddOptions\u0026lt;UAuthServerOptions\u0026gt;()\n .Configure(options =\u0026gt;\n {\n configure?.Invoke(options);\n })\n .BindConfiguration(\u0026quot;UltimateAuth:Server\u0026quot;)\n .ValidateOnStart();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor the client, the corresponding configuration section is \u003Ccode\u003EUltimateAuth:Client\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-distinction\u0022\u003EImportant Distinction\u003C/h3\u003E\n\u003Cp\u003EWhen we say configuration values override \u003Ccode\u003EProgram.cs\u003C/code\u003E, we mean that the \u003Cstrong\u003Elater configuration binding takes precedence for the same property\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe exact value also depends on the normal ASP.NET Core configuration provider order.\u003C/p\u003E\n\u003Cp\u003EFor example, environment-specific configuration files and environment variables may override values from the base \u003Ccode\u003Eappsettings.json\u003C/code\u003E file.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022environment-variable-example\u0022\u003EEnvironment Variable Example\u003C/h3\u003E\n\u003Cp\u003EASP.NET Core configuration supports double underscores as section separators.\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\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\u003EUltimateAuth__Server__Login__MaxFailedAttempts=3\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis maps 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\u003EUltimateAuth:Server:Login:MaxFailedAttempts\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor a client setting:\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\u003EUltimateAuth__Client__DefaultReturnUrl=/dashboard\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis makes it possible to provide environment-specific settings without modifying application code.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-configuration\u0022\u003E9. Authentication Mode Configuration\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports multiple authentication mode concepts.\u003C/p\u003E\n\u003Cp\u003EThe server exposes:\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.AllowedModes\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis setting defines which modes the server is permitted to use.\u003C/p\u003E\n\u003Cp\u003EIt is a \u003Cstrong\u003Erestriction\u003C/strong\u003E, not a direct mode selector.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022restrict-allowed-modes\u0022\u003ERestrict Allowed Modes\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.AllowedModes =\n [\n UAuthMode.PureOpaque,\n UAuthMode.Hybrid\n ];\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe effective authentication mode is resolved by the server\u0027s authentication mode resolution mechanism.\u003C/p\u003E\n\u003Cp\u003ESetting \u003Ccode\u003EAllowedModes\u003C/code\u003E does not directly choose which mode every client will use.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022unsupported-modes\u0022\u003EUnsupported Modes\u003C/h3\u003E\n\u003Cp\u003EIn the current implementation, \u003Ccode\u003ESemiHybrid\u003C/code\u003E and \u003Ccode\u003EPureJwt\u003C/code\u003E are not yet accepted as enabled server modes.\u003C/p\u003E\n\u003Cp\u003EThe server options validator rejects them when explicitly included in \u003Ccode\u003EAllowedModes\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example, this configuration is invalid:\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.AllowedModes =\n [\n UAuthMode.PureJwt\n ];\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDo not enable an unsupported mode merely because it exists in the enum.\u003C/p\u003E\n\u003Cp\u003EFor supported modes and their behavior, see \u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-credential-session-policies\u0022\u003E10. Configure Credential Session Policies\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports configurable session invalidation after credential changes.\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.CredentialSessionPolicy.OnSelfChange =\n CredentialSessionBehavior.PreserveCurrent;\n\n options.CredentialSessionPolicy.OnAdminChange =\n CredentialSessionBehavior.RevokeAll;\n\n options.CredentialSessionPolicy.OnRecovery =\n CredentialSessionBehavior.RevokeAll;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe available behaviors are:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EBehavior\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\u003EPreserveAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve existing session chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreserveCurrent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve the current chain and revoke others\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke all applicable session chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis is an example of configuration controlling \u003Cstrong\u003Esecurity behavior\u003C/strong\u003E, rather than merely adjusting a numeric value.\u003C/p\u003E\n\u003Cp\u003EThe client does not decide which sessions remain valid after a credential change.\u003C/p\u003E\n\u003Cp\u003EThat decision belongs to the server.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-endpoint-exposure\u0022\u003E11. Configure Endpoint Exposure\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth allows server applications to configure endpoint groups.\u003C/p\u003E\n\u003Cp\u003EThe relevant options are available through:\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.Endpoints\n\u003C/code\u003E\u003C/pre\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;/uauth\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client must use compatible endpoint paths.\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.AddUltimateAuthClient(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;/uauth\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server controls which endpoints are registered and exposed.\u003C/p\u003E\n\u003Cp\u003EThe client controls where it sends requests.\u003C/p\u003E\n\u003Cp\u003EChanging a client endpoint path does not create or enable a server endpoint.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E and \u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022multi-tenant-configuration\u0022\u003E12. Multi-Tenant Configuration\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports tenant-aware authentication and authorization.\u003C/p\u003E\n\u003Cp\u003EThe server\u0027s multi-tenant configuration is available through:\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.MultiTenant\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server uses this configuration to determine how tenant information is resolved.\u003C/p\u003E\n\u003Cp\u003ESupported resolution mechanisms include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoute-based resolution.\u003C/li\u003E\n\u003Cli\u003EHeader-based resolution.\u003C/li\u003E\n\u003Cli\u003EHost-based resolution.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe corresponding server 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.MultiTenant.EnableRoute;\noptions.MultiTenant.EnableHeader;\noptions.MultiTenant.EnableDomain;\n\u003C/code\u003E\u003C/pre\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.MultiTenant.EnableHeader = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client has its own multi-tenant options for propagating tenant information to the server.\u003C/p\u003E\n\u003Cp\u003EClient-side tenant selection does not authorize cross-tenant access.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for tenant resolution, validation, and isolation.\u003C/p\u003E\n\u003Cp\u003EFor more details, see \u003Ca href=\u0022/docs/configuration/advanced-configuration\u0022\u003EAdvanced Configuration\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-api-configuration\u0022\u003E13. Resource API Configuration\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth also supports a separate Resource API integration.\u003C/p\u003E\n\u003Cp\u003EResource APIs use their own configuration model:\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\u003EUAuthResourceApiOptions\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERegister a Resource API 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\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.AddUltimateAuthResourceApi(options =\u0026gt;\n{\n options.UAuthHubBaseUrl = \u0026quot;https://auth.example.com\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe configuration section 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\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\u003EUltimateAuth:ResourceApi\n\u003C/code\u003E\u003C/pre\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\u003EJSON\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-json\u0022\u003E{\n \u0026quot;UltimateAuth\u0026quot;: {\n \u0026quot;ResourceApi\u0026quot;: {\n \u0026quot;UAuthHubBaseUrl\u0026quot;: \u0026quot;https://auth.example.com\u0026quot;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA Resource API does not become a full UltimateAuth authentication server merely by registering this integration.\u003C/p\u003E\n\u003Cp\u003EIts role is to protect application resources using the configured UltimateAuth authentication infrastructure.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-resource-api-cors-configuration\u0022\u003EImportant: Resource API CORS Configuration\u003C/h3\u003E\n\u003Cp\u003EThe current Resource API registration constructs its CORS policy using the values supplied to the registration delegate.\u003C/p\u003E\n\u003Cp\u003ETherefore, when configuring \u003Ccode\u003EAllowedClientOrigins\u003C/code\u003E, prefer supplying them directly in the registration delegate rather than assuming values bound only from \u003Ccode\u003Eappsettings.json\u003C/code\u003E will automatically populate that CORS policy.\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\u003Ebuilder.Services.AddUltimateAuthResourceApi(options =\u0026gt;\n{\n options.UAuthHubBaseUrl = \u0026quot;https://auth.example.com\u0026quot;;\n options.AllowedClientOrigins.Add(\u0026quot;https://app.example.com\u0026quot;);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EKeep CORS configuration explicit and restrict allowed origins to trusted applications.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022validation-and-safe-configuration\u0022\u003E14. Validation and Safe Configuration\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses ASP.NET Core options validation to reject invalid configurations.\u003C/p\u003E\n\u003Cp\u003EThe server registers validators for areas including:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EGeneral server configuration.\u003C/li\u003E\n\u003Cli\u003ELogin settings.\u003C/li\u003E\n\u003Cli\u003ESession settings.\u003C/li\u003E\n\u003Cli\u003EToken settings.\u003C/li\u003E\n\u003Cli\u003EMulti-tenancy.\u003C/li\u003E\n\u003Cli\u003EUser identifiers.\u003C/li\u003E\n\u003Cli\u003ESession resolution.\u003C/li\u003E\n\u003Cli\u003EPagination.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, the server validates that:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe endpoint base path is specified.\u003C/li\u003E\n\u003Cli\u003EThe base path does not contain invalid double slashes.\u003C/li\u003E\n\u003Cli\u003EAllowed authentication modes are valid.\u003C/li\u003E\n\u003Cli\u003EExplicitly unsupported modes are not enabled.\u003C/li\u003E\n\u003Cli\u003ESession lifetimes satisfy applicable constraints.\u003C/li\u003E\n\u003Cli\u003EAccess token lifetime does not exceed the configured session maximum lifetime.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-invalid-session-lifetime\u0022\u003EExample: Invalid Session Lifetime\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Session.Lifetime = TimeSpan.Zero;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis configuration is rejected when the applicable session lifetime validation runs.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-invalid-endpoint-path\u0022\u003EExample: Invalid Endpoint Path\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;//invalid\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis configuration fails server options validation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validation-timing\u0022\u003EValidation Timing\u003C/h3\u003E\n\u003Cp\u003EServer options use \u003Ccode\u003EValidateOnStart()\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis allows configuration errors to surface during application startup instead of waiting until the first authentication request.\u003C/p\u003E\n\u003Cp\u003EClient options also have registered validators and post-configuration processing, but their validation timing follows the client options registration and resolution lifecycle.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EValidation is an important safeguard, not a guarantee that every logically undesirable configuration is automatically detected.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EApplications must still choose appropriate production settings and securely implement custom extensions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extensibility-through-dependency-injection\u0022\u003E15. Extensibility Through Dependency Injection\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses dependency injection to support customization of selected runtime behaviors.\u003C/p\u003E\n\u003Cp\u003EConfiguration controls the behavior of existing components.\u003C/p\u003E\n\u003Cp\u003EDependency injection allows applications to supply implementations for supported extension points.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EExtension point\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\u003EICredentialResetNotifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDeliver credential recovery proofs\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIUserIdentifierVerifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EValidate identifier ownership proof\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EITenantIdResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECustomize tenant identification\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIClock\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EProvide a clock implementation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIJwtSigningKeyProvider\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESupply JWT signing keys\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EFor example, a credential reset notifier can be registered as:\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.AddScoped\u0026lt;ICredentialResetNotifier, ApplicationCredentialResetNotifier\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationCredentialResetNotifier\u003C/code\u003E represents an implementation supplied by your application.\u003C/p\u003E\n\u003Cp\u003EThe server\u0027s supported registration and resolution behavior must be respected when replacing services.\u003C/p\u003E\n\u003Cp\u003ENot every internal service is intended to be replaced, and custom implementations must preserve UltimateAuth\u0027s security invariants.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-versus-extension\u0022\u003EConfiguration Versus Extension\u003C/h3\u003E\n\u003Cp\u003EUse configuration when you want to adjust a supported behavior:\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.Session.Lifetime = TimeSpan.FromHours(8);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse an extension point when your application needs to provide its own implementation:\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.AddScoped\u0026lt;ICredentialResetNotifier, ApplicationCredentialResetNotifier\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis separation helps keep ordinary configuration simple while allowing advanced integration where necessary.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoint-customization\u0022\u003E16. Endpoint Customization\u003C/h2\u003E\n\u003Cp\u003EThe server exposes an advanced endpoint configuration hook:\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.OnConfigureEndpoints\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe hook receives an \u003Ccode\u003EIEndpointRouteBuilder\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIt is intended for advanced endpoint configuration after UltimateAuth registers its default endpoints.\u003C/p\u003E\n\u003Cp\u003EFor example, an application may use the hook to apply additional endpoint metadata or filters where appropriate.\u003C/p\u003E\n\u003Cp\u003EHowever, it must not remove or remap UltimateAuth endpoints in ways that invalidate expected security behavior.\u003C/p\u003E\n\u003Cp\u003EEndpoint customization should be treated as an advanced integration mechanism, not a replacement for the supported endpoint configuration options.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/configuration/advanced-configuration\u0022\u003EAdvanced Configuration\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-configuration-strategy\u0022\u003E17. Recommended Configuration Strategy\u003C/h2\u003E\n\u003Cp\u003EFor most applications, use the following approach.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022keep-server-security-settings-on-the-server\u0022\u003EKeep Server Security Settings on the Server\u003C/h3\u003E\n\u003Cp\u003EConfigure authentication modes, token lifetimes, session lifetimes, credential policies, and tenant resolution through \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022keep-client-behavior-in-client-options\u0022\u003EKeep Client Behavior in Client Options\u003C/h3\u003E\n\u003Cp\u003EConfigure client profiles, navigation, auto-refresh, reauthentication, and endpoint usage through \u003Ccode\u003EUAuthClientOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-configuration-files-for-environment-differences\u0022\u003EUse Configuration Files for Environment Differences\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003Eappsettings.json\u003C/code\u003E, environment-specific configuration, and environment variables for values that differ between development, staging, and production.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022prefer-strongly-typed-configuration\u0022\u003EPrefer Strongly Typed Configuration\u003C/h3\u003E\n\u003Cp\u003EUse the provided option types rather than inventing custom configuration keys for features already supported by UltimateAuth.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022preserve-secure-defaults\u0022\u003EPreserve Secure Defaults\u003C/h3\u003E\n\u003Cp\u003EOnly change security-sensitive defaults when the application\u0027s requirements justify the change.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022avoid-duplicate-core-configuration\u0022\u003EAvoid Duplicate Core Configuration\u003C/h3\u003E\n\u003Cp\u003EWhen using \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E, do not separately configure Core options and expect them to override the server\u0027s composed options.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validate-production-configuration\u0022\u003EValidate Production Configuration\u003C/h3\u003E\n\u003Cp\u003ETest startup validation and authentication flows using the effective configuration that will be deployed.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-reference\u0022\u003E18. Configuration Reference\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EApplication role\u003C/th\u003E\n\u003Cth\u003ERegistration method\u003C/th\u003E\n\u003Cth\u003EOptions type\u003C/th\u003E\n\u003Cth\u003EConfiguration section\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthentication server\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUltimateAuth:Server\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUltimateAuth client\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EAddUltimateAuthClient()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClientOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUltimateAuth:Client\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EResource API\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EAddUltimateAuthResourceApi()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthResourceApiOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUltimateAuth:ResourceApi\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECore-only infrastructure\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EAddUltimateAuth()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUltimateAuth:Core\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EFor normal server applications, \u003Cstrong\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E is the effective configuration entry point for both Core-defined authentication settings and server-specific behavior\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe Core-only registration exists for infrastructure scenarios that explicitly consume \u003Ccode\u003EUAuthOptions\u003C/code\u003E; it should not be treated as an additional server configuration layer.\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/configuration/configuration-overview\u0022\u003EConfiguration Overview\u003C/a\u003E \u2014 Understand the overall configuration model.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/core-options\u0022\u003ECore Options\u003C/a\u003E \u2014 Explore the reusable option definitions composed into server configuration.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E \u2014 Configure authentication and security behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E \u2014 Configure client profiles, flows, and SDK behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/advanced-configuration\u0022\u003EAdvanced Configuration\u003C/a\u003E \u2014 Customize integration, multi-tenancy, and runtime extension points.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth provides a structured configuration system for controlling authentication behavior, session lifecycles, token policies, client integration, and server security.\u003C/p\u003E\n\u003Cp\u003EConfiguration is organized around the application components that actually consume it.\u003C/p\u003E\n\u003Cp\u003EFor most applications, the two primary configuration types are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E\u003C/strong\u003E \u2014 Controls authentication and security behavior on the UltimateAuth server.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ccode\u003EUAuthClientOptions\u003C/code\u003E\u003C/strong\u003E \u2014 Controls how an application client communicates with UltimateAuth and executes authentication flows.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth also defines reusable options in its Core package. These types form the foundation of server configuration, but they do not need to be configured independently when using \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis guide explains where configuration belongs, how options are registered, how configuration sources interact, and how to customize behavior safely.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-architecture\u0022\u003E1. Configuration Architecture\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates reusable option definitions from the runtime components that consume them.\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\u003EUltimateAuth\n\u2502\n\u251C\u2500\u2500 Core\n\u2502 \u2514\u2500\u2500 Shared option definitions\n\u2502 \u251C\u2500\u2500 Login\n\u2502 \u251C\u2500\u2500 Session\n\u2502 \u251C\u2500\u2500 Token\n\u2502 \u251C\u2500\u2500 PKCE\n\u2502 \u2514\u2500\u2500 Multi-tenancy\n\u2502\n\u251C\u2500\u2500 Server\n\u2502 \u2514\u2500\u2500 UAuthServerOptions\n\u2502 \u251C\u2500\u2500 Core-defined options\n\u2502 \u251C\u2500\u2500 Authentication mode restrictions\n\u2502 \u251C\u2500\u2500 Cookie and session resolution\n\u2502 \u251C\u2500\u2500 Endpoints\n\u2502 \u251C\u2500\u2500 Hub configuration\n\u2502 \u251C\u2500\u2500 Credential policies\n\u2502 \u2514\u2500\u2500 Server-specific behavior\n\u2502\n\u2514\u2500\u2500 Client\n \u2514\u2500\u2500 UAuthClientOptions\n \u251C\u2500\u2500 Client profile\n \u251C\u2500\u2500 Login and PKCE flows\n \u251C\u2500\u2500 Endpoint configuration\n \u251C\u2500\u2500 Auto-refresh\n \u251C\u2500\u2500 Reauthentication\n \u2514\u2500\u2500 Client state events\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe important distinction is between \u003Cstrong\u003Ewhere an option type is defined\u003C/strong\u003E and \u003Cstrong\u003Ewhere its value is consumed\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EUAuthSessionOptions\u003C/code\u003E is defined in Core, but a normal UltimateAuth Server application configures it through:\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.Session.Lifetime = TimeSpan.FromHours(8);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server consumes \u003Ccode\u003Eoptions.Session\u003C/code\u003E from \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThere is no need to configure a separate Core options instance for this setting.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-server\u0022\u003E2. Configure the Server\u003C/h2\u003E\n\u003Cp\u003EThe main server registration 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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n // Server configuration.\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis registers the UltimateAuth server infrastructure and its configuration.\u003C/p\u003E\n\u003Cp\u003EThe method accepts an optional configuration delegate:\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\u003EAction\u0026lt;UAuthServerOptions\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIt also supports a separate optional delegate for registering access policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-authentication-and-session-settings\u0022\u003EExample: Authentication and Session Settings\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Login.MaxFailedAttempts = 5;\n\n options.Session.Lifetime = TimeSpan.FromHours(8);\n\n options.Token.AccessTokenLifetime =\n TimeSpan.FromMinutes(15);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese settings control different parts of the authentication lifecycle:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOption\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\u003ELogin.MaxFailedAttempts\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EMaximum failed login attempts under the configured login policy\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESession.Lifetime\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EToken.AccessTokenLifetime\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAccess token lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe server applies its validation rules to the resulting configuration.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EConfiguration values should be chosen together.\u003C/strong\u003E For example, token lifetime settings must remain compatible with the configured session lifetime limits.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-options-are-composed-into-server-options\u0022\u003E3. Core Options Are Composed into Server Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E includes the following Core-defined option groups:\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 UAuthLoginOptions Login { get; set; } = new();\n\npublic UAuthSessionOptions Session { get; set; } = new();\n\npublic UAuthTokenOptions Token { get; set; } = new();\n\npublic UAuthPkceOptions Pkce { get; set; } = new();\n\npublic UAuthMultiTenantOptions MultiTenant { get; set; } = new();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is composition, not duplication.\u003C/p\u003E\n\u003Cp\u003EThe Core package owns the shared option definitions, while the Server package uses those definitions as part of its effective server configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended\u0022\u003ERecommended\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Session.Lifetime = TimeSpan.FromHours(8);\n options.Pkce.Enabled = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022not-recommended-for-server-configuration\u0022\u003ENot Recommended for Server Configuration\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\u003Ebuilder.Services.AddUltimateAuth(options =\u0026gt;\n{\n options.Session.Lifetime = TimeSpan.FromHours(8);\n});\n\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe second example configures \u003Ccode\u003EUAuthOptions\u003C/code\u003E, not the \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E instance consumed by the server\u0027s authentication runtime.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EWhen using UltimateAuth Server, configure Core-defined behavior through \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E or the \u003Ccode\u003EUltimateAuth:Server\u003C/code\u003E configuration section.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EDirect Core configuration is intended for scenarios that explicitly consume \u003Ccode\u003EUAuthOptions\u003C/code\u003E rather than the server configuration model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-configuration-groups\u0022\u003E4. Server Configuration Groups\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E organizes server behavior into focused option groups.\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\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin attempts and authentication behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESession\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession lifetime and lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EToken\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EToken issuance, lifetimes, and refresh behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkce\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPKCE flow configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMultiTenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant resolution and multi-tenant behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAllowedModes\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERestricts permitted authentication modes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHubDeploymentMode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EHub deployment arrangement\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHub\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EHub-specific configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECookie\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication cookie behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessionResolution\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolving session identifiers from incoming requests\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEndpoints\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EServer endpoint configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialSessionPolicy\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession handling after credential changes and recovery\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResetCredential\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential reset configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryCredential\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPrimary credential policy\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiers\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier management behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifierValidation\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier validation rules\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELoginIdentifiers\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier types permitted for login\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserProfile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser profile behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPagination\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPagination limits and defaults\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAuthResponse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication response behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENavigation\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENavigation and redirect behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDiagnostics\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDiagnostic configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEvents\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EServer event configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese groups allow applications to customize specific behavior without replacing the overall authentication architecture.\u003C/p\u003E\n\u003Cp\u003EThe individual option types and their defaults are documented in \u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-client\u0022\u003E5. Configure the Client\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth clients have their own configuration model.\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.AddUltimateAuthClient(options =\u0026gt;\n{\n options.AutoDetectClientProfile = true;\n options.DefaultReturnUrl = \u0026quot;/\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe registration accepts:\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\u003EAction\u0026lt;UAuthClientOptions\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client options control how the client behaves, not the server\u0027s security policy.\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\u003Ebuilder.Services.AddUltimateAuthClient(options =\u0026gt;\n{\n options.AutoRefresh.Enabled = true;\n options.DefaultReturnUrl = \u0026quot;/dashboard\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEnabling client auto-refresh does not change the server\u0027s refresh token lifetime, session expiration rules, or rotation policy.\u003C/p\u003E\n\u003Cp\u003EThose are server-side responsibilities.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-configuration-groups\u0022\u003EClient Configuration Groups\u003C/h3\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\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EClientProfile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExplicit client profile\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAutoDetectClientProfile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAutomatic client profile detection\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDefaultReturnUrl\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDefault post-authentication navigation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EClient login flow behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkce\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EClient PKCE flow behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAutoRefresh\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAutomatic refresh behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReauth\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReauthentication behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEndpoints\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EServer endpoint paths used by the client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMultiTenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EClient tenant propagation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EStateEvents\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EClient state event handling\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E for the full reference.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-and-client-configuration-are-independent\u0022\u003E6. Server and Client Configuration Are Independent\u003C/h2\u003E\n\u003Cp\u003EA project may register both UltimateAuth Server and UltimateAuth Client.\u003C/p\u003E\n\u003Cp\u003EThis is common in applications that host authentication services and also use the client SDK.\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Session.Lifetime = TimeSpan.FromHours(8);\n});\n\nbuilder.Services.AddUltimateAuthClient(options =\u0026gt;\n{\n options.AutoDetectClientProfile = true;\n options.DefaultReturnUrl = \u0026quot;/\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese registrations configure different components.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ESetting\u003C/th\u003E\n\u003Cth\u003EServer\u003C/th\u003E\n\u003Cth\u003EClient\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession lifetime\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003Ctd\u003EDoes not control\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EToken lifetime\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003Ctd\u003EDoes not control\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential reset policy\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003Ctd\u003EDoes not control\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAllowed authentication modes\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003Ctd\u003EDoes not control\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient profile\u003C/td\u003E\n\u003Ctd\u003EDoes not control\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient auto-refresh\u003C/td\u003E\n\u003Ctd\u003EDoes not control\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient navigation\u003C/td\u003E\n\u003Ctd\u003EDoes not control\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EServer endpoint exposure\u003C/td\u003E\n\u003Ctd\u003EControls\u003C/td\u003E\n\u003Ctd\u003EUses configured routes\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe client must be compatible with the server\u0027s configuration, but it cannot override server-side security requirements.\u003C/p\u003E\n\u003Cp\u003EFor example, enabling PKCE in client options does not make a disabled or unsupported PKCE server flow available.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-sources\u0022\u003E7. Configuration Sources\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports configuration through registration delegates and the standard ASP.NET Core configuration system.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022code-based-configuration\u0022\u003ECode-Based Configuration\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EProgram.cs\u003C/code\u003E for explicit configuration.\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.Login.MaxFailedAttempts = 5;\n options.Session.Lifetime = TimeSpan.FromHours(8);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is useful when configuration is closely tied to application behavior or requires strongly typed values.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-based-setup\u0022\u003EConfiguration-Based Setup\u003C/h3\u003E\n\u003Cp\u003EThe server binds settings from:\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\u003EUltimateAuth:Server\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor example, in \u003Ccode\u003Eappsettings.json\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\u003EJSON\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-json\u0022\u003E{\n \u0026quot;UltimateAuth\u0026quot;: {\n \u0026quot;Server\u0026quot;: {\n \u0026quot;Login\u0026quot;: {\n \u0026quot;MaxFailedAttempts\u0026quot;: 5\n },\n \u0026quot;Session\u0026quot;: {\n \u0026quot;Lifetime\u0026quot;: \u0026quot;08:00:00\u0026quot;\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client binds settings from:\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\u003EUltimateAuth:Client\n\u003C/code\u003E\u003C/pre\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\u003EJSON\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-json\u0022\u003E{\n \u0026quot;UltimateAuth\u0026quot;: {\n \u0026quot;Client\u0026quot;: {\n \u0026quot;AutoDetectClientProfile\u0026quot;: true,\n \u0026quot;DefaultReturnUrl\u0026quot;: \u0026quot;/dashboard\u0026quot;,\n \u0026quot;AutoRefresh\u0026quot;: {\n \u0026quot;Enabled\u0026quot;: true\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese sections are independent.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-both-sources\u0022\u003EUsing Both Sources\u003C/h3\u003E\n\u003Cp\u003EYou can combine code-based and configuration-based settings:\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.Login.MaxFailedAttempts = 5;\n options.Session.Lifetime = TimeSpan.FromHours(8);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWith:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EJSON\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-json\u0022\u003E{\n \u0026quot;UltimateAuth\u0026quot;: {\n \u0026quot;Server\u0026quot;: {\n \u0026quot;Login\u0026quot;: {\n \u0026quot;MaxFailedAttempts\u0026quot;: 3\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe effective \u003Ccode\u003ELogin.MaxFailedAttempts\u003C/code\u003E is \u003Ccode\u003E3\u003C/code\u003E, because the registration binds the configuration section after applying the configuration delegate.\u003C/p\u003E\n\u003Cp\u003EThe session lifetime remains eight hours because the configuration section does not override it.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-precedence\u0022\u003E8. Configuration Precedence\u003C/h2\u003E\n\u003Cp\u003EThe current registration pipeline applies options in this order:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EOption type defaults.\u003C/li\u003E\n\u003Cli\u003EValues supplied by the registration delegate.\u003C/li\u003E\n\u003Cli\u003EValues bound from the corresponding configuration section.\u003C/li\u003E\n\u003Cli\u003EApplicable post-configuration processing.\u003C/li\u003E\n\u003Cli\u003EOptions validation.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EFor the server:\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\u003Eservices.AddOptions\u0026lt;UAuthServerOptions\u0026gt;()\n .Configure(options =\u0026gt;\n {\n configure?.Invoke(options);\n })\n .BindConfiguration(\u0026quot;UltimateAuth:Server\u0026quot;)\n .ValidateOnStart();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor the client, the corresponding configuration section is \u003Ccode\u003EUltimateAuth:Client\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-distinction\u0022\u003EImportant Distinction\u003C/h3\u003E\n\u003Cp\u003EWhen we say configuration values override \u003Ccode\u003EProgram.cs\u003C/code\u003E, we mean that the \u003Cstrong\u003Elater configuration binding takes precedence for the same property\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe exact value also depends on the normal ASP.NET Core configuration provider order.\u003C/p\u003E\n\u003Cp\u003EFor example, environment-specific configuration files and environment variables may override values from the base \u003Ccode\u003Eappsettings.json\u003C/code\u003E file.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022environment-variable-example\u0022\u003EEnvironment Variable Example\u003C/h3\u003E\n\u003Cp\u003EASP.NET Core configuration supports double underscores as section separators.\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\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\u003EUltimateAuth__Server__Login__MaxFailedAttempts=3\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis maps 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\u003EUltimateAuth:Server:Login:MaxFailedAttempts\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor a client setting:\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\u003EUltimateAuth__Client__DefaultReturnUrl=/dashboard\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis makes it possible to provide environment-specific settings without modifying application code.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-configuration\u0022\u003E9. Authentication Mode Configuration\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports multiple authentication mode concepts.\u003C/p\u003E\n\u003Cp\u003EThe server exposes:\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.AllowedModes\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis setting defines which modes the server is permitted to use.\u003C/p\u003E\n\u003Cp\u003EIt is a \u003Cstrong\u003Erestriction\u003C/strong\u003E, not a direct mode selector.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022restrict-allowed-modes\u0022\u003ERestrict Allowed Modes\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.AllowedModes =\n [\n UAuthMode.PureOpaque,\n UAuthMode.Hybrid\n ];\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe effective authentication mode is resolved by the server\u0027s authentication mode resolution mechanism.\u003C/p\u003E\n\u003Cp\u003ESetting \u003Ccode\u003EAllowedModes\u003C/code\u003E does not directly choose which mode every client will use.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022unsupported-modes\u0022\u003EUnsupported Modes\u003C/h3\u003E\n\u003Cp\u003EIn the current implementation, \u003Ccode\u003ESemiHybrid\u003C/code\u003E and \u003Ccode\u003EPureJwt\u003C/code\u003E are not yet accepted as enabled server modes.\u003C/p\u003E\n\u003Cp\u003EThe server options validator rejects them when explicitly included in \u003Ccode\u003EAllowedModes\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example, this configuration is invalid:\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.AllowedModes =\n [\n UAuthMode.PureJwt\n ];\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDo not enable an unsupported mode merely because it exists in the enum.\u003C/p\u003E\n\u003Cp\u003EFor supported modes and their behavior, see \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-credential-session-policies\u0022\u003E10. Configure Credential Session Policies\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports configurable session invalidation after credential changes.\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.CredentialSessionPolicy.OnSelfChange =\n CredentialSessionBehavior.PreserveCurrent;\n\n options.CredentialSessionPolicy.OnAdminChange =\n CredentialSessionBehavior.RevokeAll;\n\n options.CredentialSessionPolicy.OnRecovery =\n CredentialSessionBehavior.RevokeAll;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe available behaviors are:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EBehavior\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\u003EPreserveAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve existing session chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreserveCurrent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve the current chain and revoke others\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke all applicable session chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis is an example of configuration controlling \u003Cstrong\u003Esecurity behavior\u003C/strong\u003E, rather than merely adjusting a numeric value.\u003C/p\u003E\n\u003Cp\u003EThe client does not decide which sessions remain valid after a credential change.\u003C/p\u003E\n\u003Cp\u003EThat decision belongs to the server.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-endpoint-exposure\u0022\u003E11. Configure Endpoint Exposure\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth allows server applications to configure endpoint groups.\u003C/p\u003E\n\u003Cp\u003EThe relevant options are available through:\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.Endpoints\n\u003C/code\u003E\u003C/pre\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;/uauth\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client must use compatible endpoint paths.\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.AddUltimateAuthClient(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;/uauth\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server controls which endpoints are registered and exposed.\u003C/p\u003E\n\u003Cp\u003EThe client controls where it sends requests.\u003C/p\u003E\n\u003Cp\u003EChanging a client endpoint path does not create or enable a server endpoint.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E and \u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022multi-tenant-configuration\u0022\u003E12. Multi-Tenant Configuration\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports tenant-aware authentication and authorization.\u003C/p\u003E\n\u003Cp\u003EThe server\u0027s multi-tenant configuration is available through:\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.MultiTenant\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server uses this configuration to determine how tenant information is resolved.\u003C/p\u003E\n\u003Cp\u003ESupported resolution mechanisms include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoute-based resolution.\u003C/li\u003E\n\u003Cli\u003EHeader-based resolution.\u003C/li\u003E\n\u003Cli\u003EHost-based resolution.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe corresponding server 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.MultiTenant.EnableRoute;\noptions.MultiTenant.EnableHeader;\noptions.MultiTenant.EnableDomain;\n\u003C/code\u003E\u003C/pre\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.MultiTenant.EnableHeader = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client has its own multi-tenant options for propagating tenant information to the server.\u003C/p\u003E\n\u003Cp\u003EClient-side tenant selection does not authorize cross-tenant access.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for tenant resolution, validation, and isolation.\u003C/p\u003E\n\u003Cp\u003EFor more details, see \u003Ca href=\u0022/docs/configuration/advanced-configuration\u0022\u003EAdvanced Configuration\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-api-configuration\u0022\u003E13. Resource API Configuration\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth also supports a separate Resource API integration.\u003C/p\u003E\n\u003Cp\u003EResource APIs use their own configuration model:\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\u003EUAuthResourceApiOptions\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERegister a Resource API 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\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.AddUltimateAuthResourceApi(options =\u0026gt;\n{\n options.UAuthHubBaseUrl = \u0026quot;https://auth.example.com\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe configuration section 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\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\u003EUltimateAuth:ResourceApi\n\u003C/code\u003E\u003C/pre\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\u003EJSON\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-json\u0022\u003E{\n \u0026quot;UltimateAuth\u0026quot;: {\n \u0026quot;ResourceApi\u0026quot;: {\n \u0026quot;UAuthHubBaseUrl\u0026quot;: \u0026quot;https://auth.example.com\u0026quot;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA Resource API does not become a full UltimateAuth authentication server merely by registering this integration.\u003C/p\u003E\n\u003Cp\u003EIts role is to protect application resources using the configured UltimateAuth authentication infrastructure.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-resource-api-cors-configuration\u0022\u003EImportant: Resource API CORS Configuration\u003C/h3\u003E\n\u003Cp\u003EThe current Resource API registration constructs its CORS policy using the values supplied to the registration delegate.\u003C/p\u003E\n\u003Cp\u003ETherefore, when configuring \u003Ccode\u003EAllowedClientOrigins\u003C/code\u003E, prefer supplying them directly in the registration delegate rather than assuming values bound only from \u003Ccode\u003Eappsettings.json\u003C/code\u003E will automatically populate that CORS policy.\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\u003Ebuilder.Services.AddUltimateAuthResourceApi(options =\u0026gt;\n{\n options.UAuthHubBaseUrl = \u0026quot;https://auth.example.com\u0026quot;;\n options.AllowedClientOrigins.Add(\u0026quot;https://app.example.com\u0026quot;);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EKeep CORS configuration explicit and restrict allowed origins to trusted applications.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022validation-and-safe-configuration\u0022\u003E14. Validation and Safe Configuration\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses ASP.NET Core options validation to reject invalid configurations.\u003C/p\u003E\n\u003Cp\u003EThe server registers validators for areas including:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EGeneral server configuration.\u003C/li\u003E\n\u003Cli\u003ELogin settings.\u003C/li\u003E\n\u003Cli\u003ESession settings.\u003C/li\u003E\n\u003Cli\u003EToken settings.\u003C/li\u003E\n\u003Cli\u003EMulti-tenancy.\u003C/li\u003E\n\u003Cli\u003EUser identifiers.\u003C/li\u003E\n\u003Cli\u003ESession resolution.\u003C/li\u003E\n\u003Cli\u003EPagination.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, the server validates that:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe endpoint base path is specified.\u003C/li\u003E\n\u003Cli\u003EThe base path does not contain invalid double slashes.\u003C/li\u003E\n\u003Cli\u003EAllowed authentication modes are valid.\u003C/li\u003E\n\u003Cli\u003EExplicitly unsupported modes are not enabled.\u003C/li\u003E\n\u003Cli\u003ESession lifetimes satisfy applicable constraints.\u003C/li\u003E\n\u003Cli\u003EAccess token lifetime does not exceed the configured session maximum lifetime.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-invalid-session-lifetime\u0022\u003EExample: Invalid Session Lifetime\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Session.Lifetime = TimeSpan.Zero;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis configuration is rejected when the applicable session lifetime validation runs.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-invalid-endpoint-path\u0022\u003EExample: Invalid Endpoint Path\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;//invalid\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis configuration fails server options validation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validation-timing\u0022\u003EValidation Timing\u003C/h3\u003E\n\u003Cp\u003EServer options use \u003Ccode\u003EValidateOnStart()\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis allows configuration errors to surface during application startup instead of waiting until the first authentication request.\u003C/p\u003E\n\u003Cp\u003EClient options also have registered validators and post-configuration processing, but their validation timing follows the client options registration and resolution lifecycle.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EValidation is an important safeguard, not a guarantee that every logically undesirable configuration is automatically detected.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EApplications must still choose appropriate production settings and securely implement custom extensions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extensibility-through-dependency-injection\u0022\u003E15. Extensibility Through Dependency Injection\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses dependency injection to support customization of selected runtime behaviors.\u003C/p\u003E\n\u003Cp\u003EConfiguration controls the behavior of existing components.\u003C/p\u003E\n\u003Cp\u003EDependency injection allows applications to supply implementations for supported extension points.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EExtension point\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\u003EICredentialResetNotifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDeliver credential recovery proofs\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIUserIdentifierVerifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EValidate identifier ownership proof\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EITenantIdResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECustomize tenant identification\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIClock\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EProvide a clock implementation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIJwtSigningKeyProvider\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESupply JWT signing keys\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EFor example, a credential reset notifier can be registered as:\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.AddScoped\u0026lt;ICredentialResetNotifier, ApplicationCredentialResetNotifier\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationCredentialResetNotifier\u003C/code\u003E represents an implementation supplied by your application.\u003C/p\u003E\n\u003Cp\u003EThe server\u0027s supported registration and resolution behavior must be respected when replacing services.\u003C/p\u003E\n\u003Cp\u003ENot every internal service is intended to be replaced, and custom implementations must preserve UltimateAuth\u0027s security invariants.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-versus-extension\u0022\u003EConfiguration Versus Extension\u003C/h3\u003E\n\u003Cp\u003EUse configuration when you want to adjust a supported behavior:\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.Session.Lifetime = TimeSpan.FromHours(8);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse an extension point when your application needs to provide its own implementation:\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.AddScoped\u0026lt;ICredentialResetNotifier, ApplicationCredentialResetNotifier\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis separation helps keep ordinary configuration simple while allowing advanced integration where necessary.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoint-customization\u0022\u003E16. Endpoint Customization\u003C/h2\u003E\n\u003Cp\u003EThe server exposes an advanced endpoint configuration hook:\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.OnConfigureEndpoints\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe hook receives an \u003Ccode\u003EIEndpointRouteBuilder\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIt is intended for advanced endpoint configuration after UltimateAuth registers its default endpoints.\u003C/p\u003E\n\u003Cp\u003EFor example, an application may use the hook to apply additional endpoint metadata or filters where appropriate.\u003C/p\u003E\n\u003Cp\u003EHowever, it must not remove or remap UltimateAuth endpoints in ways that invalidate expected security behavior.\u003C/p\u003E\n\u003Cp\u003EEndpoint customization should be treated as an advanced integration mechanism, not a replacement for the supported endpoint configuration options.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/configuration/advanced-configuration\u0022\u003EAdvanced Configuration\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-configuration-strategy\u0022\u003E17. Recommended Configuration Strategy\u003C/h2\u003E\n\u003Cp\u003EFor most applications, use the following approach.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022keep-server-security-settings-on-the-server\u0022\u003EKeep Server Security Settings on the Server\u003C/h3\u003E\n\u003Cp\u003EConfigure authentication modes, token lifetimes, session lifetimes, credential policies, and tenant resolution through \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022keep-client-behavior-in-client-options\u0022\u003EKeep Client Behavior in Client Options\u003C/h3\u003E\n\u003Cp\u003EConfigure client profiles, navigation, auto-refresh, reauthentication, and endpoint usage through \u003Ccode\u003EUAuthClientOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-configuration-files-for-environment-differences\u0022\u003EUse Configuration Files for Environment Differences\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003Eappsettings.json\u003C/code\u003E, environment-specific configuration, and environment variables for values that differ between development, staging, and production.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022prefer-strongly-typed-configuration\u0022\u003EPrefer Strongly Typed Configuration\u003C/h3\u003E\n\u003Cp\u003EUse the provided option types rather than inventing custom configuration keys for features already supported by UltimateAuth.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022preserve-secure-defaults\u0022\u003EPreserve Secure Defaults\u003C/h3\u003E\n\u003Cp\u003EOnly change security-sensitive defaults when the application\u0027s requirements justify the change.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022avoid-duplicate-core-configuration\u0022\u003EAvoid Duplicate Core Configuration\u003C/h3\u003E\n\u003Cp\u003EWhen using \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E, do not separately configure Core options and expect them to override the server\u0027s composed options.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validate-production-configuration\u0022\u003EValidate Production Configuration\u003C/h3\u003E\n\u003Cp\u003ETest startup validation and authentication flows using the effective configuration that will be deployed.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-reference\u0022\u003E18. Configuration Reference\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EApplication role\u003C/th\u003E\n\u003Cth\u003ERegistration method\u003C/th\u003E\n\u003Cth\u003EOptions type\u003C/th\u003E\n\u003Cth\u003EConfiguration section\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthentication server\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUltimateAuth:Server\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUltimateAuth client\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EAddUltimateAuthClient()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClientOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUltimateAuth:Client\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EResource API\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EAddUltimateAuthResourceApi()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthResourceApiOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUltimateAuth:ResourceApi\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECore-only infrastructure\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EAddUltimateAuth()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUltimateAuth:Core\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EFor normal server applications, \u003Cstrong\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E is the effective configuration entry point for both Core-defined authentication settings and server-specific behavior\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe Core-only registration exists for infrastructure scenarios that explicitly consume \u003Ccode\u003EUAuthOptions\u003C/code\u003E; it should not be treated as an additional server configuration layer.\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/configuration/configuration-overview\u0022\u003EConfiguration Overview\u003C/a\u003E \u2014 Understand the overall configuration model.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E \u2014 Configure authentication and security behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E \u2014 Configure client profiles, flows, and SDK behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/advanced-configuration\u0022\u003EAdvanced Configuration\u003C/a\u003E \u2014 Customize integration, multi-tenancy, and runtime extension points.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "configuration-architecture", @@ -214,6 +214,6 @@ "Level": 0 } ], - "SourceHash": "42602ECDDEC509FEAAEF5F7835FCE10B05B3FCAC730541040EF2E96C21180A86", + "SourceHash": "A7FB3BC604C292181E8C5201DE6235C0020C825876F238F5265F38E4A23A383B", "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/flow-based-auth.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/flow-based-auth.json index 6586b2fa..9170ffd7 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/flow-based-auth.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/flow-based-auth.json @@ -1,7 +1,7 @@ { "Slug": "fundamentals/flow-based-auth", "Title": "Flow-Based Authentication", - "Html": "\n\u003Cp\u003EUltimateAuth organizes authentication around explicit, controlled operations called \u003Cstrong\u003Eauthentication flows\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EA flow represents an authentication intention, such as establishing access, continuing an existing authentication context, or terminating it.\u003C/p\u003E\n\u003Cp\u003ERather than allowing individual endpoints or client applications to independently decide how authentication should work, UltimateAuth evaluates each operation within a consistent, server-controlled security context.\u003C/p\u003E\n\u003Cp\u003EThis approach is independent of the credential format.\u003C/p\u003E\n\u003Cp\u003EA flow may work with a server-side session, an opaque access token, a JWT, or a refresh token. The effective authentication mode determines which credentials and validation rules apply.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EFlow-based authentication is not an alternative to session-based or token-based authentication. It is the architecture that coordinates those strategies.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-authentication-flows-matter\u0022\u003EWhy authentication flows matter\u003C/h2\u003E\n\u003Cp\u003EAuthentication involves more than issuing and validating credentials.\u003C/p\u003E\n\u003Cp\u003EA complete authentication operation may need to determine:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhich tenant the operation belongs to.\u003C/li\u003E\n\u003Cli\u003EWhich authentication mode is permitted.\u003C/li\u003E\n\u003Cli\u003EWhich client profile and runtime characteristics apply.\u003C/li\u003E\n\u003Cli\u003EWhat authentication proof is required.\u003C/li\u003E\n\u003Cli\u003EWhether an existing authentication context can continue.\u003C/li\u003E\n\u003Cli\u003EWhich security policies must be enforced.\u003C/li\u003E\n\u003Cli\u003EWhether server-side state must be created, updated, or revoked.\u003C/li\u003E\n\u003Cli\u003EWhich credentials or other results should be returned to the client.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese decisions should not be independently reconstructed by every endpoint, client SDK, or authentication service.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth establishes a shared context for the operation and delegates the relevant decisions to the components responsible for them.\u003C/p\u003E\n\u003Cp\u003EThe result is a predictable authentication process whose behavior can vary by mode without changing its fundamental security boundaries.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-an-authentication-flow\u0022\u003EWhat is an authentication flow?\u003C/h2\u003E\n\u003Cp\u003EAn authentication flow is a controlled operation with a defined purpose, applicable security requirements, and an explicit outcome.\u003C/p\u003E\n\u003Cp\u003EThree primary flows illustrate the model.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022login\u0022\u003ELogin\u003C/h3\u003E\n\u003Cp\u003ELogin establishes a new authenticated context after the required authentication proof has been accepted.\u003C/p\u003E\n\u003Cp\u003EDepending on the effective mode, the operation may create server-side authentication state, issue access credentials, or perform both actions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh\u0022\u003ERefresh\u003C/h3\u003E\n\u003Cp\u003ERefresh attempts to continue an existing authenticated context without requiring the original authentication proof again.\u003C/p\u003E\n\u003Cp\u003EIts behavior depends on the mode.\u003C/p\u003E\n\u003Cp\u003EFor example, PureOpaque can continue a session through touch semantics, while token-based modes may issue replacement credentials.\u003C/p\u003E\n\u003Cp\u003ERefresh must not bypass expiration, revocation, or other continuation requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003ELogout\u003C/h3\u003E\n\u003Cp\u003ELogout terminates or discards the applicable authentication context.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, this can involve revoking server-side sessions.\u003C/p\u003E\n\u003Cp\u003EIn stateless JWT authentication, client-side credential removal does not automatically invalidate an already issued access token.\u003C/p\u003E\n\u003Cp\u003EThese operations share architectural boundaries but do not perform identical work.\u003C/p\u003E\n\u003Cp\u003EFor their implementation details, see \u003Ca href=\u0022/docs/auth-flows/overview\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-flow-execution-model\u0022\u003EThe flow execution model\u003C/h2\u003E\n\u003Cp\u003EAt a conceptual level, an authentication flow follows a common progression:\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\u003EAuthentication Intent\n |\n v\nResolve Effective Security Context\n |\n v\nValidate Required Evidence\n |\n v\nEvaluate Applicable Security Rules\n |\n v\nExecute Mode-Specific Operation\n |\n v\nProduce Authentication Outcome\n |\n v\nDeliver Result to Client\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis diagram represents responsibilities, not a fixed implementation sequence.\u003C/p\u003E\n\u003Cp\u003EFor example, credential validation, authority decisions, and state transitions may involve different components or ordering depending on the operation.\u003C/p\u003E\n\u003Cp\u003ESome flows create sessions. Others update existing state, rotate tokens, revoke credentials, or perform no persistent state change.\u003C/p\u003E\n\u003Cp\u003EThe important guarantee is that each operation executes within a controlled context and follows the security rules applicable to its purpose and authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authflowcontext\u0022\u003EAuthFlowContext\u003C/h2\u003E\n\u003Cp\u003EA central concept in this architecture is \u003Ccode\u003EAuthFlowContext\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlowContext\u003C/code\u003E represents the effective authentication environment in which an operation executes.\u003C/p\u003E\n\u003Cp\u003EIt brings together the information required to apply consistent authentication behavior, including:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe intended authentication flow.\u003C/li\u003E\n\u003Cli\u003EThe effective authentication mode.\u003C/li\u003E\n\u003Cli\u003EThe resolved tenant context.\u003C/li\u003E\n\u003Cli\u003EThe applicable client profile.\u003C/li\u003E\n\u003Cli\u003EEffective server options and security policies.\u003C/li\u003E\n\u003Cli\u003ERelevant device and session context, when applicable.\u003C/li\u003E\n\u003Cli\u003ECredential delivery and response behavior.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe context is established through framework-controlled boundaries.\u003C/p\u003E\n\u003Cp\u003EOnce established, it must remain stable throughout the operation. Individual services and endpoints must not independently replace it or reconstruct it using different assumptions.\u003C/p\u003E\n\u003Cp\u003EThis matters particularly in applications serving multiple tenants, client runtimes, and authentication modes.\u003C/p\u003E\n\u003Cp\u003EA flow must not begin under one security policy and complete under another merely because different components interpreted the request differently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022context-is-not-authorization\u0022\u003EContext is not authorization\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlowContext\u003C/code\u003E describes the environment and rules under which an operation executes.\u003C/p\u003E\n\u003Cp\u003EIts existence does not mean that the operation has been authorized or that the presented credentials are valid.\u003C/p\u003E\n\u003Cp\u003EThe relevant authentication evidence and security requirements must still be evaluated.\u003C/p\u003E\n\u003Cp\u003EAfter authentication has established trusted identity, authorization can operate through the appropriate \u003Ccode\u003EAccessContext\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe distinction between these contexts is explained in \u003Ca href=\u0022/docs/fundamentals/auth-model\u0022\u003EAuthentication Model\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flows-and-authentication-modes\u0022\u003EFlows and authentication modes\u003C/h2\u003E\n\u003Cp\u003EAuthentication flows describe \u003Cstrong\u003Ewhat operation is being performed\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EAuthentication modes determine \u003Cstrong\u003Ehow the operation establishes or continues trust\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EConsider login.\u003C/p\u003E\n\u003Cp\u003EIn PureOpaque, a successful login establishes a server-side session and provides a session credential.\u003C/p\u003E\n\u003Cp\u003EIn Hybrid, login establishes server-side session authority and issues separate opaque access and refresh tokens.\u003C/p\u003E\n\u003Cp\u003EIn SemiHybrid, login establishes the session lifecycle state required by the mode and issues JWT-based access credentials.\u003C/p\u003E\n\u003Cp\u003EIn PureJwt, login issues JWT credentials without requiring a server-side session.\u003C/p\u003E\n\u003Cp\u003EAll four are login operations, but their persistence and credential-issuance requirements differ.\u003C/p\u003E\n\u003Cp\u003EThe same principle applies to refresh and logout.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EFlow\u003C/th\u003E\n\u003Cth\u003EWhat remains consistent\u003C/th\u003E\n\u003Cth\u003EWhat varies by mode\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ELogin\u003C/td\u003E\n\u003Ctd\u003EEstablish authenticated access from validated evidence\u003C/td\u003E\n\u003Ctd\u003ESession creation, token format, credential issuance\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERefresh\u003C/td\u003E\n\u003Ctd\u003EDetermine whether authentication may continue\u003C/td\u003E\n\u003Ctd\u003ESession touch, session validation, token rotation, renewal state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ELogout\u003C/td\u003E\n\u003Ctd\u003EEnd or discard the applicable authentication context\u003C/td\u003E\n\u003Ctd\u003ESession revocation, refresh-token invalidation, client credential cleanup\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERequest authentication\u003C/td\u003E\n\u003Ctd\u003EEstablish trusted identity from presented credentials\u003C/td\u003E\n\u003Ctd\u003ESession validation, opaque token resolution, JWT validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA flow must never introduce a session dependency merely because another authentication mode requires one.\u003C/p\u003E\n\u003Cp\u003EIn particular, PureJwt must remain independent of mandatory session persistence and validation.\u003C/p\u003E\n\u003Cp\u003EFor the complete mode definitions and their guarantees, see \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-controlled-decisions\u0022\u003EServer-controlled decisions\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth treats authentication as a server-controlled process.\u003C/p\u003E\n\u003Cp\u003EThe client can request an operation and provide the evidence needed to perform it.\u003C/p\u003E\n\u003Cp\u003EHowever, the client does not independently determine:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhether the authentication evidence is acceptable.\u003C/li\u003E\n\u003Cli\u003EWhich security policies can be bypassed.\u003C/li\u003E\n\u003Cli\u003EWhether an expired or revoked authentication context may continue.\u003C/li\u003E\n\u003Cli\u003EWhether a new session or token may be issued.\u003C/li\u003E\n\u003Cli\u003EWhether a particular authentication mode is permitted.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese decisions belong to trusted framework and application components operating under the effective server configuration.\u003C/p\u003E\n\u003Cp\u003EThis does not mean every authentication decision requires a database lookup.\u003C/p\u003E\n\u003Cp\u003EFor stateless JWT authentication, the server can establish identity through cryptographic verification and the applicable token-validation rules.\u003C/p\u003E\n\u003Cp\u003EFor session-authoritative authentication, the server must also validate the required session state.\u003C/p\u003E\n\u003Cp\u003EServer authority describes \u003Cstrong\u003Ewho controls the rules\u003C/strong\u003E, not whether every request must consult persistent storage.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022separation-of-responsibilities\u0022\u003ESeparation of responsibilities\u003C/h2\u003E\n\u003Cp\u003EFlow-based authentication is also a separation-of-concerns model.\u003C/p\u003E\n\u003Cp\u003EThe components participating in an authentication operation have distinct responsibilities.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERequest and context resolution\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EEstablish the trusted execution environment, including tenant, mode, client profile, and effective policies.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication validation\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EEvaluate the evidence required by the operation and authentication mode.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESecurity decisions\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EDetermine whether the requested operation is permitted under the applicable rules and authoritative state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOrchestration\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ECoordinate the services and state transitions needed to complete the operation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EPersistence and credential issuance\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ECreate, update, or revoke the relevant authentication state and credentials when required.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EResponse delivery\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ECommunicate the result through the appropriate cookie, header, redirect, or other supported delivery mechanism.\u003C/p\u003E\n\u003Cp\u003ESeparating these responsibilities prevents transport behavior from becoming authentication policy.\u003C/p\u003E\n\u003Cp\u003EFor example, whether an access token is delivered through a cookie or header should not change the security requirements used to issue it.\u003C/p\u003E\n\u003Cp\u003EThe technical components and request pipeline implementing these responsibilities are documented in \u003Ca href=\u0022../auth-flows/\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flows-across-client-runtimes\u0022\u003EFlows across client runtimes\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports different application environments through a common authentication architecture.\u003C/p\u003E\n\u003Cp\u003EA browser application, Blazor Server application, Blazor WebAssembly application, mobile client, and resource API may interact with authentication differently.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA browser application may rely on secure cookies.\u003C/li\u003E\n\u003Cli\u003EA mobile application may manage credentials through secure device storage.\u003C/li\u003E\n\u003Cli\u003EA resource API may validate bearer access tokens.\u003C/li\u003E\n\u003Cli\u003EA Blazor Server application may use a server-controlled session without managing refresh-token rotation.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese differences affect client integration and credential transport.\u003C/p\u003E\n\u003Cp\u003EThey do not require independent implementations of the fundamental authentication model.\u003C/p\u003E\n\u003Cp\u003EClient Profiles provide runtime-appropriate behavior and defaults, while authentication modes define the validation and lifecycle strategy.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/client-profiles\u0022\u003EClient Profiles\u003C/a\u003E for how runtime characteristics influence authentication configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022predictable-outcomes\u0022\u003EPredictable outcomes\u003C/h2\u003E\n\u003Cp\u003EAn authentication flow should produce an explicit result that reflects what the operation actually accomplished.\u003C/p\u003E\n\u003Cp\u003EFor example, a refresh operation may:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EContinue an existing session without changing persistent state.\u003C/li\u003E\n\u003Cli\u003EUpdate session activity or expiration when permitted.\u003C/li\u003E\n\u003Cli\u003ERotate refresh credentials and issue replacement tokens.\u003C/li\u003E\n\u003Cli\u003EReject continuation and require reauthentication.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese outcomes are not interchangeable.\u003C/p\u003E\n\u003Cp\u003EA successful operation should not imply that a token was issued when no token was needed.\u003C/p\u003E\n\u003Cp\u003ELikewise, an unsuccessful refresh must not silently create a new authenticated context or bypass the original authentication requirements.\u003C/p\u003E\n\u003Cp\u003EExplicit outcomes allow the client SDK and server components to respond consistently without assuming that every mode behaves the same way.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extensibility-and-security-boundaries\u0022\u003EExtensibility and security boundaries\u003C/h2\u003E\n\u003Cp\u003EA flow-based architecture provides controlled extension points for authentication behavior.\u003C/p\u003E\n\u003Cp\u003EDepending on the supported features and configured services, applications may integrate additional requirements such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMulti-factor authentication.\u003C/li\u003E\n\u003Cli\u003EAdditional credential verification.\u003C/li\u003E\n\u003Cli\u003ETenant-specific authentication policies.\u003C/li\u003E\n\u003Cli\u003EDevice-related security checks.\u003C/li\u003E\n\u003Cli\u003ECustom authentication proof providers.\u003C/li\u003E\n\u003Cli\u003EAdditional security decisions before issuing or continuing access.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EExtensions must preserve the security guarantees of the effective authentication mode.\u003C/p\u003E\n\u003Cp\u003EFor example, a custom flow must not bypass mandatory session validation in Hybrid.\u003C/p\u003E\n\u003Cp\u003ESimilarly, an extension must not make session persistence an undocumented prerequisite for PureJwt.\u003C/p\u003E\n\u003Cp\u003EFlexibility must not silently change the meaning of an authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-based-does-not-mean-session-based\u0022\u003EFlow-based does not mean session-based\u003C/h2\u003E\n\u003Cp\u003EAn important distinction is that authentication flows and authentication lineage are related but separate concepts.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication flows\u003C/strong\u003E describe controlled operations.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication lineage\u003C/strong\u003E describes the Root, Chain, and Session relationships used by session-backed authentication.\u003C/p\u003E\n\u003Cp\u003EA flow may operate on that lineage when required.\u003C/p\u003E\n\u003Cp\u003EHowever, not every flow or mode requires persisted Root, Chain, or Session state.\u003C/p\u003E\n\u003Cp\u003EThis separation allows UltimateAuth to support both stateful and stateless authentication without maintaining two unrelated authentication architectures.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/auth-model\u0022\u003EAuthentication Model\u003C/a\u003E for the lineage model and its role in authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses authentication flows to coordinate security-sensitive operations through a consistent, trusted execution context.\u003C/p\u003E\n\u003Cp\u003EA flow:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERepresents an explicit authentication intention.\u003C/li\u003E\n\u003Cli\u003EOperates under a resolved and stable \u003Ccode\u003EAuthFlowContext\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EApplies the validation and security rules required by the effective mode.\u003C/li\u003E\n\u003Cli\u003ECoordinates only the state transitions and credential operations necessary for its purpose.\u003C/li\u003E\n\u003Cli\u003EProduces an explicit outcome.\u003C/li\u003E\n\u003Cli\u003EDelivers that outcome according to the applicable client and response configuration.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe architecture does not require every mode to use the same credentials, persistence model, or validation strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EFlows define the operation. Modes define its authentication behavior. Client Profiles adapt its integration with the runtime.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003ENext step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E to understand how PureOpaque, Hybrid, SemiHybrid, and PureJwt implement different authentication and session lifecycle strategies.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth organizes authentication around explicit, controlled operations called \u003Cstrong\u003Eauthentication flows\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EA flow represents an authentication intention, such as establishing access, continuing an existing authentication context, or terminating it.\u003C/p\u003E\n\u003Cp\u003ERather than allowing individual endpoints or client applications to independently decide how authentication should work, UltimateAuth evaluates each operation within a consistent, server-controlled security context.\u003C/p\u003E\n\u003Cp\u003EThis approach is independent of the credential format.\u003C/p\u003E\n\u003Cp\u003EA flow may work with a server-side session, an opaque access token, a JWT, or a refresh token. The effective authentication mode determines which credentials and validation rules apply.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EFlow-based authentication is not an alternative to session-based or token-based authentication. It is the architecture that coordinates those strategies.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-authentication-flows-matter\u0022\u003EWhy authentication flows matter\u003C/h2\u003E\n\u003Cp\u003EAuthentication involves more than issuing and validating credentials.\u003C/p\u003E\n\u003Cp\u003EA complete authentication operation may need to determine:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhich tenant the operation belongs to.\u003C/li\u003E\n\u003Cli\u003EWhich authentication mode is permitted.\u003C/li\u003E\n\u003Cli\u003EWhich client profile and runtime characteristics apply.\u003C/li\u003E\n\u003Cli\u003EWhat authentication proof is required.\u003C/li\u003E\n\u003Cli\u003EWhether an existing authentication context can continue.\u003C/li\u003E\n\u003Cli\u003EWhich security policies must be enforced.\u003C/li\u003E\n\u003Cli\u003EWhether server-side state must be created, updated, or revoked.\u003C/li\u003E\n\u003Cli\u003EWhich credentials or other results should be returned to the client.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese decisions should not be independently reconstructed by every endpoint, client SDK, or authentication service.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth establishes a shared context for the operation and delegates the relevant decisions to the components responsible for them.\u003C/p\u003E\n\u003Cp\u003EThe result is a predictable authentication process whose behavior can vary by mode without changing its fundamental security boundaries.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-an-authentication-flow\u0022\u003EWhat is an authentication flow?\u003C/h2\u003E\n\u003Cp\u003EAn authentication flow is a controlled operation with a defined purpose, applicable security requirements, and an explicit outcome.\u003C/p\u003E\n\u003Cp\u003EThree primary flows illustrate the model.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022login\u0022\u003ELogin\u003C/h3\u003E\n\u003Cp\u003ELogin establishes a new authenticated context after the required authentication proof has been accepted.\u003C/p\u003E\n\u003Cp\u003EDepending on the effective mode, the operation may create server-side authentication state, issue access credentials, or perform both actions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh\u0022\u003ERefresh\u003C/h3\u003E\n\u003Cp\u003ERefresh attempts to continue an existing authenticated context without requiring the original authentication proof again.\u003C/p\u003E\n\u003Cp\u003EIts behavior depends on the mode.\u003C/p\u003E\n\u003Cp\u003EFor example, PureOpaque can continue a session through touch semantics, while token-based modes may issue replacement credentials.\u003C/p\u003E\n\u003Cp\u003ERefresh must not bypass expiration, revocation, or other continuation requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003ELogout\u003C/h3\u003E\n\u003Cp\u003ELogout terminates or discards the applicable authentication context.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, this can involve revoking server-side sessions.\u003C/p\u003E\n\u003Cp\u003EIn stateless JWT authentication, client-side credential removal does not automatically invalidate an already issued access token.\u003C/p\u003E\n\u003Cp\u003EThese operations share architectural boundaries but do not perform identical work.\u003C/p\u003E\n\u003Cp\u003EFor their implementation details, see \u003Ca href=\u0022/docs/auth-flows/overview\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-flow-execution-model\u0022\u003EThe flow execution model\u003C/h2\u003E\n\u003Cp\u003EAt a conceptual level, an authentication flow follows a common progression:\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\u003EAuthentication Intent\n |\n v\nResolve Effective Security Context\n |\n v\nValidate Required Evidence\n |\n v\nEvaluate Applicable Security Rules\n |\n v\nExecute Mode-Specific Operation\n |\n v\nProduce Authentication Outcome\n |\n v\nDeliver Result to Client\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis diagram represents responsibilities, not a fixed implementation sequence.\u003C/p\u003E\n\u003Cp\u003EFor example, credential validation, authority decisions, and state transitions may involve different components or ordering depending on the operation.\u003C/p\u003E\n\u003Cp\u003ESome flows create sessions. Others update existing state, rotate tokens, revoke credentials, or perform no persistent state change.\u003C/p\u003E\n\u003Cp\u003EThe important guarantee is that each operation executes within a controlled context and follows the security rules applicable to its purpose and authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authflowcontext\u0022\u003EAuthFlowContext\u003C/h2\u003E\n\u003Cp\u003EA central concept in this architecture is \u003Ccode\u003EAuthFlowContext\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlowContext\u003C/code\u003E represents the effective authentication environment in which an operation executes.\u003C/p\u003E\n\u003Cp\u003EIt brings together the information required to apply consistent authentication behavior, including:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe intended authentication flow.\u003C/li\u003E\n\u003Cli\u003EThe effective authentication mode.\u003C/li\u003E\n\u003Cli\u003EThe resolved tenant context.\u003C/li\u003E\n\u003Cli\u003EThe applicable client profile.\u003C/li\u003E\n\u003Cli\u003EEffective server options and security policies.\u003C/li\u003E\n\u003Cli\u003ERelevant device and session context, when applicable.\u003C/li\u003E\n\u003Cli\u003ECredential delivery and response behavior.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe context is established through framework-controlled boundaries.\u003C/p\u003E\n\u003Cp\u003EOnce established, it must remain stable throughout the operation. Individual services and endpoints must not independently replace it or reconstruct it using different assumptions.\u003C/p\u003E\n\u003Cp\u003EThis matters particularly in applications serving multiple tenants, client runtimes, and authentication modes.\u003C/p\u003E\n\u003Cp\u003EA flow must not begin under one security policy and complete under another merely because different components interpreted the request differently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022context-is-not-authorization\u0022\u003EContext is not authorization\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlowContext\u003C/code\u003E describes the environment and rules under which an operation executes.\u003C/p\u003E\n\u003Cp\u003EIts existence does not mean that the operation has been authorized or that the presented credentials are valid.\u003C/p\u003E\n\u003Cp\u003EThe relevant authentication evidence and security requirements must still be evaluated.\u003C/p\u003E\n\u003Cp\u003EAfter authentication has established trusted identity, authorization can operate through the appropriate \u003Ccode\u003EAccessContext\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe distinction between these contexts is explained in \u003Ca href=\u0022/docs/fundamentals/auth-model\u0022\u003EAuthentication Model\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flows-and-authentication-modes\u0022\u003EFlows and authentication modes\u003C/h2\u003E\n\u003Cp\u003EAuthentication flows describe \u003Cstrong\u003Ewhat operation is being performed\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EAuthentication modes determine \u003Cstrong\u003Ehow the operation establishes or continues trust\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EConsider login.\u003C/p\u003E\n\u003Cp\u003EIn PureOpaque, a successful login establishes a server-side session and provides a session credential.\u003C/p\u003E\n\u003Cp\u003EIn Hybrid, login establishes server-side session authority and issues separate opaque access and refresh tokens.\u003C/p\u003E\n\u003Cp\u003EIn SemiHybrid, login establishes the session lifecycle state required by the mode and issues JWT-based access credentials.\u003C/p\u003E\n\u003Cp\u003EIn PureJwt, login issues JWT credentials without requiring a server-side session.\u003C/p\u003E\n\u003Cp\u003EAll four are login operations, but their persistence and credential-issuance requirements differ.\u003C/p\u003E\n\u003Cp\u003EThe same principle applies to refresh and logout.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EFlow\u003C/th\u003E\n\u003Cth\u003EWhat remains consistent\u003C/th\u003E\n\u003Cth\u003EWhat varies by mode\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ELogin\u003C/td\u003E\n\u003Ctd\u003EEstablish authenticated access from validated evidence\u003C/td\u003E\n\u003Ctd\u003ESession creation, token format, credential issuance\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERefresh\u003C/td\u003E\n\u003Ctd\u003EDetermine whether authentication may continue\u003C/td\u003E\n\u003Ctd\u003ESession touch, session validation, token rotation, renewal state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ELogout\u003C/td\u003E\n\u003Ctd\u003EEnd or discard the applicable authentication context\u003C/td\u003E\n\u003Ctd\u003ESession revocation, refresh-token invalidation, client credential cleanup\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERequest authentication\u003C/td\u003E\n\u003Ctd\u003EEstablish trusted identity from presented credentials\u003C/td\u003E\n\u003Ctd\u003ESession validation, opaque token resolution, JWT validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA flow must never introduce a session dependency merely because another authentication mode requires one.\u003C/p\u003E\n\u003Cp\u003EIn particular, PureJwt must remain independent of mandatory session persistence and validation.\u003C/p\u003E\n\u003Cp\u003EFor the complete mode definitions and their guarantees, see \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-controlled-decisions\u0022\u003EServer-controlled decisions\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth treats authentication as a server-controlled process.\u003C/p\u003E\n\u003Cp\u003EThe client can request an operation and provide the evidence needed to perform it.\u003C/p\u003E\n\u003Cp\u003EHowever, the client does not independently determine:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhether the authentication evidence is acceptable.\u003C/li\u003E\n\u003Cli\u003EWhich security policies can be bypassed.\u003C/li\u003E\n\u003Cli\u003EWhether an expired or revoked authentication context may continue.\u003C/li\u003E\n\u003Cli\u003EWhether a new session or token may be issued.\u003C/li\u003E\n\u003Cli\u003EWhether a particular authentication mode is permitted.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese decisions belong to trusted framework and application components operating under the effective server configuration.\u003C/p\u003E\n\u003Cp\u003EThis does not mean every authentication decision requires a database lookup.\u003C/p\u003E\n\u003Cp\u003EFor stateless JWT authentication, the server can establish identity through cryptographic verification and the applicable token-validation rules.\u003C/p\u003E\n\u003Cp\u003EFor session-authoritative authentication, the server must also validate the required session state.\u003C/p\u003E\n\u003Cp\u003EServer authority describes \u003Cstrong\u003Ewho controls the rules\u003C/strong\u003E, not whether every request must consult persistent storage.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022separation-of-responsibilities\u0022\u003ESeparation of responsibilities\u003C/h2\u003E\n\u003Cp\u003EFlow-based authentication is also a separation-of-concerns model.\u003C/p\u003E\n\u003Cp\u003EThe components participating in an authentication operation have distinct responsibilities.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERequest and context resolution\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EEstablish the trusted execution environment, including tenant, mode, client profile, and effective policies.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication validation\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EEvaluate the evidence required by the operation and authentication mode.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESecurity decisions\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EDetermine whether the requested operation is permitted under the applicable rules and authoritative state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOrchestration\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ECoordinate the services and state transitions needed to complete the operation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EPersistence and credential issuance\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ECreate, update, or revoke the relevant authentication state and credentials when required.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EResponse delivery\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ECommunicate the result through the appropriate cookie, header, redirect, or other supported delivery mechanism.\u003C/p\u003E\n\u003Cp\u003ESeparating these responsibilities prevents transport behavior from becoming authentication policy.\u003C/p\u003E\n\u003Cp\u003EFor example, whether an access token is delivered through a cookie or header should not change the security requirements used to issue it.\u003C/p\u003E\n\u003Cp\u003EThe technical components and request pipeline implementing these responsibilities are documented in \u003Ca href=\u0022/docs/auth-flows/overview\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flows-across-client-runtimes\u0022\u003EFlows across client runtimes\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports different application environments through a common authentication architecture.\u003C/p\u003E\n\u003Cp\u003EA browser application, Blazor Server application, Blazor WebAssembly application, mobile client, and resource API may interact with authentication differently.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA browser application may rely on secure cookies.\u003C/li\u003E\n\u003Cli\u003EA mobile application may manage credentials through secure device storage.\u003C/li\u003E\n\u003Cli\u003EA resource API may validate bearer access tokens.\u003C/li\u003E\n\u003Cli\u003EA Blazor Server application may use a server-controlled session without managing refresh-token rotation.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese differences affect client integration and credential transport.\u003C/p\u003E\n\u003Cp\u003EThey do not require independent implementations of the fundamental authentication model.\u003C/p\u003E\n\u003Cp\u003EClient Profiles provide runtime-appropriate behavior and defaults, while authentication modes define the validation and lifecycle strategy.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/client-profiles\u0022\u003EClient Profiles\u003C/a\u003E for how runtime characteristics influence authentication configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022predictable-outcomes\u0022\u003EPredictable outcomes\u003C/h2\u003E\n\u003Cp\u003EAn authentication flow should produce an explicit result that reflects what the operation actually accomplished.\u003C/p\u003E\n\u003Cp\u003EFor example, a refresh operation may:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EContinue an existing session without changing persistent state.\u003C/li\u003E\n\u003Cli\u003EUpdate session activity or expiration when permitted.\u003C/li\u003E\n\u003Cli\u003ERotate refresh credentials and issue replacement tokens.\u003C/li\u003E\n\u003Cli\u003EReject continuation and require reauthentication.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese outcomes are not interchangeable.\u003C/p\u003E\n\u003Cp\u003EA successful operation should not imply that a token was issued when no token was needed.\u003C/p\u003E\n\u003Cp\u003ELikewise, an unsuccessful refresh must not silently create a new authenticated context or bypass the original authentication requirements.\u003C/p\u003E\n\u003Cp\u003EExplicit outcomes allow the client SDK and server components to respond consistently without assuming that every mode behaves the same way.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extensibility-and-security-boundaries\u0022\u003EExtensibility and security boundaries\u003C/h2\u003E\n\u003Cp\u003EA flow-based architecture provides controlled extension points for authentication behavior.\u003C/p\u003E\n\u003Cp\u003EDepending on the supported features and configured services, applications may integrate additional requirements such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMulti-factor authentication.\u003C/li\u003E\n\u003Cli\u003EAdditional credential verification.\u003C/li\u003E\n\u003Cli\u003ETenant-specific authentication policies.\u003C/li\u003E\n\u003Cli\u003EDevice-related security checks.\u003C/li\u003E\n\u003Cli\u003ECustom authentication proof providers.\u003C/li\u003E\n\u003Cli\u003EAdditional security decisions before issuing or continuing access.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EExtensions must preserve the security guarantees of the effective authentication mode.\u003C/p\u003E\n\u003Cp\u003EFor example, a custom flow must not bypass mandatory session validation in Hybrid.\u003C/p\u003E\n\u003Cp\u003ESimilarly, an extension must not make session persistence an undocumented prerequisite for PureJwt.\u003C/p\u003E\n\u003Cp\u003EFlexibility must not silently change the meaning of an authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-based-does-not-mean-session-based\u0022\u003EFlow-based does not mean session-based\u003C/h2\u003E\n\u003Cp\u003EAn important distinction is that authentication flows and authentication lineage are related but separate concepts.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication flows\u003C/strong\u003E describe controlled operations.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication lineage\u003C/strong\u003E describes the Root, Chain, and Session relationships used by session-backed authentication.\u003C/p\u003E\n\u003Cp\u003EA flow may operate on that lineage when required.\u003C/p\u003E\n\u003Cp\u003EHowever, not every flow or mode requires persisted Root, Chain, or Session state.\u003C/p\u003E\n\u003Cp\u003EThis separation allows UltimateAuth to support both stateful and stateless authentication without maintaining two unrelated authentication architectures.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/auth-model\u0022\u003EAuthentication Model\u003C/a\u003E for the lineage model and its role in authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses authentication flows to coordinate security-sensitive operations through a consistent, trusted execution context.\u003C/p\u003E\n\u003Cp\u003EA flow:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERepresents an explicit authentication intention.\u003C/li\u003E\n\u003Cli\u003EOperates under a resolved and stable \u003Ccode\u003EAuthFlowContext\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EApplies the validation and security rules required by the effective mode.\u003C/li\u003E\n\u003Cli\u003ECoordinates only the state transitions and credential operations necessary for its purpose.\u003C/li\u003E\n\u003Cli\u003EProduces an explicit outcome.\u003C/li\u003E\n\u003Cli\u003EDelivers that outcome according to the applicable client and response configuration.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe architecture does not require every mode to use the same credentials, persistence model, or validation strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EFlows define the operation. Modes define its authentication behavior. Client Profiles adapt its integration with the runtime.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003ENext step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E to understand how PureOpaque, Hybrid, SemiHybrid, and PureJwt implement different authentication and session lifecycle strategies.\u003C/p\u003E\n", "Headings": [ { "Id": "why-authentication-flows-matter", @@ -89,6 +89,6 @@ "Level": 0 } ], - "SourceHash": "6371366D09791BCDFD207180BECD70AE6AE37CEC2D3979ED0510C4F47C56B4AB", + "SourceHash": "3ED10C9D7BF82FBBD7E924E2937D960A350455E2BFD7CF2653256D199D5D3790", "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credentials.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credentials.json index 7d5d0681..8cf3676f 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credentials.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credentials.json @@ -1,7 +1,7 @@ { "Slug": "plugin-domains/credentials", "Title": "Credentials", - "Html": "\n\u003Cp\u003EAuthentication establishes whether an entity can be trusted to act as a particular identity. A credential provides the evidence used to establish that trust.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth treats credentials as a distinct domain rather than embedding authentication secrets directly into user records or authentication flows.\u003C/p\u003E\n\u003Cp\u003EThe \u003Cstrong\u003ECredentials Plugin Domain\u003C/strong\u003E manages the lifecycle, verification, and security of authentication credentials while allowing the authentication engine to remain independent of a particular credential type.\u003C/p\u003E\n\u003Cp\u003EThe current reference implementation supports password credentials. Its architecture establishes the separation of responsibilities needed to accommodate additional credential mechanisms without redefining the underlying authentication model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-credentials\u0022\u003EUnderstanding Credentials\u003C/h2\u003E\n\u003Cp\u003EA credential represents authentication material or a mechanism through which an identity can demonstrate possession of an accepted authentication factor.\u003C/p\u003E\n\u003Cp\u003ECredentials may take different forms:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECredential mechanism\u003C/th\u003E\n\u003Cth\u003EAuthentication evidence\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPassword\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EKnowledge of a secret verified against a protected representation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EOne-time code\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EPossession of a time-limited or single-use authentication value\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECryptographic credential\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EProof of control over a private key or authenticator\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EExternal identity provider\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EA trusted authentication assertion established through an external protocol\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese mechanisms do not necessarily share the same storage, verification, or lifecycle requirements.\u003C/p\u003E\n\u003Cp\u003EFor example, a password requires secure secret verification, while a cryptographic credential may require signature validation and public-key metadata. An external provider may require validating protocol assertions rather than storing a reusable secret.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s credential architecture is intended to accommodate these differences through explicit credential-specific behavior.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ECurrent implementation:\u003C/strong\u003E Password credentials are supported. The other mechanisms illustrate the broader credential model and its intended extension boundaries; they are not presented as implemented credential types.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentials-and-user-identity\u0022\u003ECredentials and User Identity\u003C/h2\u003E\n\u003Cp\u003EA user identity and a credential are separate concepts.\u003C/p\u003E\n\u003Cp\u003EThe Users domain manages identity ownership, identifiers, and account lifecycle. The Credentials domain manages the authentication material associated with an identity.\u003C/p\u003E\n\u003Cp\u003EThis separation means:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA user can exist independently of a particular credential.\u003C/li\u003E\n\u003Cli\u003ECredential state can change without redefining the user\u0027s identity.\u003C/li\u003E\n\u003Cli\u003EAn identity can conceptually be associated with multiple authentication mechanisms.\u003C/li\u003E\n\u003Cli\u003ECredential verification does not replace user lifecycle or account eligibility checks.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA username or email address is generally an \u003Cstrong\u003Eidentifier\u003C/strong\u003E, not a credential.\u003C/p\u003E\n\u003Cp\u003EAn identifier helps resolve which identity is involved in an authentication attempt. A credential provides evidence that the actor is entitled to authenticate as that identity.\u003C/p\u003E\n\u003Cp\u003EFor a password-based login, these responsibilities work together:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EThe submitted identifier is resolved to an identity.\u003C/li\u003E\n\u003Cli\u003EThe applicable password credential is located.\u003C/li\u003E\n\u003Cli\u003EThe submitted secret is verified.\u003C/li\u003E\n\u003Cli\u003EAuthentication policies and relevant security conditions are evaluated.\u003C/li\u003E\n\u003Cli\u003EThe authentication engine establishes authentication state if the flow succeeds.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe credential domain participates in establishing identity proof, but it does not independently determine the entire authentication outcome.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022responsibilities-across-the-framework\u0022\u003EResponsibilities Across the Framework\u003C/h2\u003E\n\u003Cp\u003ECredential support spans several architectural layers, each with a different responsibility.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ELayer\u003C/th\u003E\n\u003Cth\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECore\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EFoundational authentication contracts and security concepts\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EServer\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication flow execution, orchestration, and authentication-state management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials Bridge\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EContracts connecting credential capabilities to the authentication server\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials Contracts\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EShared credential operation models and results\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials Reference Implementation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential management, validation, recovery, and lifecycle behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPersistence\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EStorage of credential records and related security state\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe distinction is between \u003Cstrong\u003Eusing credentials to authenticate\u003C/strong\u003E and \u003Cstrong\u003Emanaging credentials as domain resources\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe authentication engine coordinates authentication flows. The Credentials domain owns the rules governing credential records, their usability, and their lifecycle.\u003C/p\u003E\n\u003Cp\u003EAn application may integrate a custom credential system through the relevant authentication contracts without necessarily adopting the reference credential management implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022password-credentials\u0022\u003EPassword Credentials\u003C/h2\u003E\n\u003Cp\u003EPassword is the first supported credential mechanism in UltimateAuth.\u003C/p\u003E\n\u003Cp\u003EA password credential represents a password-based authentication factor associated with an identity.\u003C/p\u003E\n\u003Cp\u003EThe reference implementation separates the submitted secret from its protected representation and the credential\u0027s operational state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022secret-protection\u0022\u003ESecret Protection\u003C/h3\u003E\n\u003Cp\u003EPasswords must not be stored as plaintext or returned through credential management responses.\u003C/p\u003E\n\u003Cp\u003EThe password verification process operates against a protected password representation using the configured password hashing infrastructure.\u003C/p\u003E\n\u003Cp\u003ECredential management APIs should expose only the information required for their intended operations, not the underlying secret.\u003C/p\u003E\n\u003Cp\u003EThis separation also allows password verification and password management to evolve without changing the user identity model.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-security-state\u0022\u003ECredential Security State\u003C/h3\u003E\n\u003Cp\u003EA credential is not necessarily usable merely because a matching secret has been supplied.\u003C/p\u003E\n\u003Cp\u003ECredential validation must also account for relevant lifecycle and security conditions, such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhether the credential is active or revoked.\u003C/li\u003E\n\u003Cli\u003EWhether an applicable expiration condition has been reached.\u003C/li\u003E\n\u003Cli\u003EWhether the credential belongs to the intended identity.\u003C/li\u003E\n\u003Cli\u003EWhether the requested operation is permitted.\u003C/li\u003E\n\u003Cli\u003EWhether the credential is eligible for the authentication mechanism being executed.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ESecret verification and credential usability are related but distinct decisions.\u003C/p\u003E\n\u003Cp\u003EA correctly supplied password must not make a revoked or otherwise unusable credential valid.\u003C/p\u003E\n\u003Cp\u003EPassword strength, common-password detection, user similarity, and password reuse are governed by configurable security policies.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/security/password-policy\u0022\u003EPassword Policy \u0026amp; History\u003C/a\u003E for the built-in policy levels, defaults, and configuration options.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-validation\u0022\u003ECredential Validation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates credential-specific verification from the broader authentication flow.\u003C/p\u003E\n\u003Cp\u003EFor password authentication, credential validation includes evaluating the supplied secret against the stored protected representation and enforcing the relevant credential conditions.\u003C/p\u003E\n\u003Cp\u003EThe authentication server then uses the result within its authentication flow.\u003C/p\u003E\n\u003Cp\u003EThis boundary matters because credential verification is not equivalent to session creation or token issuance.\u003C/p\u003E\n\u003Cp\u003EA successful credential check establishes evidence for authentication. The authentication engine remains responsible for determining whether the flow can complete and what authentication state should be created.\u003C/p\u003E\n\u003Cp\u003EThe same architectural distinction applies to other credential mechanisms.\u003C/p\u003E\n\u003Cp\u003EA future cryptographic credential validator, for example, would verify a different kind of proof, but it would still participate in a broader authentication decision rather than independently issuing authentication state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-lifecycle\u0022\u003ECredential Lifecycle\u003C/h2\u003E\n\u003Cp\u003ECredentials are managed resources with their own lifecycle.\u003C/p\u003E\n\u003Cp\u003EThe reference domain provides operations around credential creation, secret changes, revocation, and recovery.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\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\u003Cstrong\u003EAdd\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERegister a credential for an identity\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EChange secret\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EReplace authentication material through an authorized operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoke\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EMake a credential unusable\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EBegin reset\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EInitiate a controlled credential recovery process\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EComplete reset\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EReplace a secret after satisfying recovery requirements\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese operations have different security requirements.\u003C/p\u003E\n\u003Cp\u003EChanging a password while authenticated is not equivalent to recovering access without the existing password. Likewise, revoking a credential is not the same as invalidating every authentication session previously established using that credential.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth treats these distinctions explicitly rather than assuming that every credential mutation has the same security effect.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-ownership-and-operation-scope\u0022\u003ECredential Ownership and Operation Scope\u003C/h2\u003E\n\u003Cp\u003ECredential management operations must be evaluated in the context of the actor, the target identity, and the requested operation.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth distinguishes between self-service, administrative, and anonymous recovery scenarios.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self-service-operations\u0022\u003ESelf-Service Operations\u003C/h3\u003E\n\u003Cp\u003EAn authenticated user may manage credentials associated with their own identity, subject to the applicable permissions and validation rules.\u003C/p\u003E\n\u003Cp\u003EChanging a secret may require proving knowledge of the existing secret or satisfying another supported security requirement.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-operations\u0022\u003EAdministrative Operations\u003C/h3\u003E\n\u003Cp\u003EAdministrative credential management is performed against an explicitly identified target.\u003C/p\u003E\n\u003Cp\u003EAdministrative authority does not eliminate the requirement to preserve target identity consistency.\u003C/p\u003E\n\u003Cp\u003EFor example, a credential recovery operation must not modify a different user\u0027s credential merely because a supplied identifier resolves to another account.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022anonymous-recovery\u0022\u003EAnonymous Recovery\u003C/h3\u003E\n\u003Cp\u003EAnonymous recovery allows an identity to regain access without an existing authenticated session.\u003C/p\u003E\n\u003Cp\u003EBecause the actor is not authenticated, the recovery process must establish sufficient proof before allowing credential modification.\u003C/p\u003E\n\u003Cp\u003EThese operation scopes are security boundaries, not merely different API routes.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-recovery\u0022\u003ECredential Recovery\u003C/h2\u003E\n\u003Cp\u003ECredential recovery is one of the most security-sensitive responsibilities of the Credentials domain.\u003C/p\u003E\n\u003Cp\u003EThe password reset process is designed as a controlled, multi-stage operation rather than an unrestricted secret update.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022beginning-recovery\u0022\u003EBeginning Recovery\u003C/h3\u003E\n\u003Cp\u003EA recovery request identifies the intended account and initiates a proof-based process.\u003C/p\u003E\n\u003Cp\u003EDepending on the supported recovery configuration, proof may take the form of a token or code delivered through an appropriate notification mechanism.\u003C/p\u003E\n\u003Cp\u003EThe proof must be bound to the intended recovery context and subject to server-controlled validity rules.\u003C/p\u003E\n\u003Cp\u003ERecovery material must not be exposed to an anonymous caller simply because that caller initiated the request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022completing-recovery\u0022\u003ECompleting Recovery\u003C/h3\u003E\n\u003Cp\u003ECompleting recovery requires validating the supplied proof and its associated security conditions before applying a new secret.\u003C/p\u003E\n\u003Cp\u003EThe reference recovery model accounts for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EProof validity and expiration.\u003C/li\u003E\n\u003Cli\u003ESingle-use recovery material.\u003C/li\u003E\n\u003Cli\u003EConsistency between the recovery target and intended identity.\u003C/li\u003E\n\u003Cli\u003ERejection of invalid or previously consumed proof.\u003C/li\u003E\n\u003Cli\u003EProtection against unauthorized credential replacement.\u003C/li\u003E\n\u003Cli\u003ESecurity-state updates following successful recovery.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA completed recovery must not leave the same proof available for another successful reset.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022enumeration-resistance\u0022\u003EEnumeration Resistance\u003C/h3\u003E\n\u003Cp\u003EAnonymous recovery endpoints must avoid unnecessarily revealing whether an identifier belongs to an existing account.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth separates externally observable recovery responses from the internal decisions required to locate and manage the account.\u003C/p\u003E\n\u003Cp\u003EThis reduces account enumeration opportunities while preserving the ability to enforce recovery rules.\u003C/p\u003E\n\u003Cp\u003ERecovery behavior should also be combined with appropriate abuse protection and operational monitoring.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentials-and-authentication-sessions\u0022\u003ECredentials and Authentication Sessions\u003C/h2\u003E\n\u003Cp\u003EChanging or recovering a credential may require action against authentication sessions that were established before the change.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Cstrong\u003Ecredential lifecycle and session lifecycle are separate responsibilities\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports security policies that determine how credential changes and resets affect existing sessions.\u003C/p\u003E\n\u003Cp\u003EThe available policy model includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPolicy\u003C/th\u003E\n\u003Cth\u003EIntended behavior\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EKeepAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve existing sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EKeepCurrent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve the current session while revoking other applicable sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke all applicable sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe appropriate policy depends on the operation and the application\u0027s security requirements.\u003C/p\u003E\n\u003Cp\u003EA password reset performed through anonymous recovery may warrant different treatment from an authenticated password change.\u003C/p\u003E\n\u003Cp\u003EEnforcement also depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, revocation can be reflected through server-side session validation. In JWT-based modes, previously issued access tokens may remain usable until expiration unless additional validation mechanisms are involved.\u003C/p\u003E\n\u003Cp\u003ECredential changes therefore initiate the applicable security response; they do not imply identical immediate token invalidation across every mode.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E for the underlying differences.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022integration-with-the-users-domain\u0022\u003EIntegration with the Users Domain\u003C/h2\u003E\n\u003Cp\u003EThe Credentials domain does not need to own user records to manage authentication material associated with an identity.\u003C/p\u003E\n\u003Cp\u003EUser creation, deletion, or other lifecycle changes may require corresponding credential operations.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports coordination through integration contracts rather than requiring the Users and Credentials implementations to depend directly on one another.\u003C/p\u003E\n\u003Cp\u003EFor example, an application may coordinate initial credential enrollment with user creation or perform credential cleanup as part of account removal.\u003C/p\u003E\n\u003Cp\u003EThese behaviors belong to explicit integration workflows.\u003C/p\u003E\n\u003Cp\u003ECreating a user does not inherently require creating a password credential, and deleting a user should not rely on an undocumented side effect to maintain credential consistency.\u003C/p\u003E\n\u003Cp\u003EThis distinction keeps identity lifecycle and credential lifecycle independently understandable.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extending-credential-capabilities\u0022\u003EExtending Credential Capabilities\u003C/h2\u003E\n\u003Cp\u003ECredential mechanisms differ substantially in how they establish trust.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s architecture is intended to support additional mechanisms by separating credential-specific verification and management from authentication flow orchestration.\u003C/p\u003E\n\u003Cp\u003ESuch extensions may require their own:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredential representation and metadata.\u003C/li\u003E\n\u003Cli\u003EEnrollment and verification procedures.\u003C/li\u003E\n\u003Cli\u003ESecurity and expiration rules.\u003C/li\u003E\n\u003Cli\u003ERecovery or revocation behavior.\u003C/li\u003E\n\u003Cli\u003EPersistence and integration contracts.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ENot every credential mechanism needs to implement password-style secret changes or recovery.\u003C/p\u003E\n\u003Cp\u003EThe goal is to preserve consistent authentication boundaries while allowing each credential mechanism to enforce its own security requirements.\u003C/p\u003E\n\u003Cp\u003EFor most applications using password authentication, the reference implementation provides the recommended starting point.\u003C/p\u003E\n\u003Cp\u003EApplications with specialized requirements can customize supported services and integrations or provide alternative implementations through the relevant contracts.\u003C/p\u003E\n\u003Cp\u003EDetailed extension and replacement strategies belong in the dedicated customization documentation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EThe Credentials domain establishes an important distinction within UltimateAuth:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA user identifies the account. A credential provides authentication evidence. The authentication engine decides how that evidence establishes authentication state.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EKeeping these responsibilities separate allows the framework to support identity management without tying authentication to one user entity, one secret format, or one credential mechanism.\u003C/p\u003E\n\u003Cp\u003EThe current password implementation provides the operational foundation. The broader architecture allows credential capabilities to evolve without turning the authentication engine into a monolithic credential management system.\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/plugin-domains/users\u0022\u003EUsers\u003C/a\u003E \u2014 Understand identity ownership, identifiers, and account lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Explore how authenticated identities are evaluated against access policies.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains\u0022\u003EPlugin Domains\u003C/a\u003E \u2014 Review the architecture and responsibilities of Plugin Domains.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor the authentication flow that consumes credential verification results, see \u003Ca href=\u0022/docs/fundamentals/flow-based-authentication\u0022\u003EFlow-Based Authentication\u003C/a\u003E.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EAuthentication establishes whether an entity can be trusted to act as a particular identity. A credential provides the evidence used to establish that trust.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth treats credentials as a distinct domain rather than embedding authentication secrets directly into user records or authentication flows.\u003C/p\u003E\n\u003Cp\u003EThe \u003Cstrong\u003ECredentials Plugin Domain\u003C/strong\u003E manages the lifecycle, verification, and security of authentication credentials while allowing the authentication engine to remain independent of a particular credential type.\u003C/p\u003E\n\u003Cp\u003EThe current reference implementation supports password credentials. Its architecture establishes the separation of responsibilities needed to accommodate additional credential mechanisms without redefining the underlying authentication model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-credentials\u0022\u003EUnderstanding Credentials\u003C/h2\u003E\n\u003Cp\u003EA credential represents authentication material or a mechanism through which an identity can demonstrate possession of an accepted authentication factor.\u003C/p\u003E\n\u003Cp\u003ECredentials may take different forms:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECredential mechanism\u003C/th\u003E\n\u003Cth\u003EAuthentication evidence\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPassword\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EKnowledge of a secret verified against a protected representation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EOne-time code\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EPossession of a time-limited or single-use authentication value\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECryptographic credential\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EProof of control over a private key or authenticator\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EExternal identity provider\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EA trusted authentication assertion established through an external protocol\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese mechanisms do not necessarily share the same storage, verification, or lifecycle requirements.\u003C/p\u003E\n\u003Cp\u003EFor example, a password requires secure secret verification, while a cryptographic credential may require signature validation and public-key metadata. An external provider may require validating protocol assertions rather than storing a reusable secret.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s credential architecture is intended to accommodate these differences through explicit credential-specific behavior.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ECurrent implementation:\u003C/strong\u003E Password credentials are supported. The other mechanisms illustrate the broader credential model and its intended extension boundaries; they are not presented as implemented credential types.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentials-and-user-identity\u0022\u003ECredentials and User Identity\u003C/h2\u003E\n\u003Cp\u003EA user identity and a credential are separate concepts.\u003C/p\u003E\n\u003Cp\u003EThe Users domain manages identity ownership, identifiers, and account lifecycle. The Credentials domain manages the authentication material associated with an identity.\u003C/p\u003E\n\u003Cp\u003EThis separation means:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA user can exist independently of a particular credential.\u003C/li\u003E\n\u003Cli\u003ECredential state can change without redefining the user\u0027s identity.\u003C/li\u003E\n\u003Cli\u003EAn identity can conceptually be associated with multiple authentication mechanisms.\u003C/li\u003E\n\u003Cli\u003ECredential verification does not replace user lifecycle or account eligibility checks.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA username or email address is generally an \u003Cstrong\u003Eidentifier\u003C/strong\u003E, not a credential.\u003C/p\u003E\n\u003Cp\u003EAn identifier helps resolve which identity is involved in an authentication attempt. A credential provides evidence that the actor is entitled to authenticate as that identity.\u003C/p\u003E\n\u003Cp\u003EFor a password-based login, these responsibilities work together:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EThe submitted identifier is resolved to an identity.\u003C/li\u003E\n\u003Cli\u003EThe applicable password credential is located.\u003C/li\u003E\n\u003Cli\u003EThe submitted secret is verified.\u003C/li\u003E\n\u003Cli\u003EAuthentication policies and relevant security conditions are evaluated.\u003C/li\u003E\n\u003Cli\u003EThe authentication engine establishes authentication state if the flow succeeds.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe credential domain participates in establishing identity proof, but it does not independently determine the entire authentication outcome.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022responsibilities-across-the-framework\u0022\u003EResponsibilities Across the Framework\u003C/h2\u003E\n\u003Cp\u003ECredential support spans several architectural layers, each with a different responsibility.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ELayer\u003C/th\u003E\n\u003Cth\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECore\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EFoundational authentication contracts and security concepts\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EServer\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication flow execution, orchestration, and authentication-state management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials Bridge\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EContracts connecting credential capabilities to the authentication server\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials Contracts\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EShared credential operation models and results\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials Reference Implementation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential management, validation, recovery, and lifecycle behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPersistence\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EStorage of credential records and related security state\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe distinction is between \u003Cstrong\u003Eusing credentials to authenticate\u003C/strong\u003E and \u003Cstrong\u003Emanaging credentials as domain resources\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe authentication engine coordinates authentication flows. The Credentials domain owns the rules governing credential records, their usability, and their lifecycle.\u003C/p\u003E\n\u003Cp\u003EAn application may integrate a custom credential system through the relevant authentication contracts without necessarily adopting the reference credential management implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022password-credentials\u0022\u003EPassword Credentials\u003C/h2\u003E\n\u003Cp\u003EPassword is the first supported credential mechanism in UltimateAuth.\u003C/p\u003E\n\u003Cp\u003EA password credential represents a password-based authentication factor associated with an identity.\u003C/p\u003E\n\u003Cp\u003EThe reference implementation separates the submitted secret from its protected representation and the credential\u0027s operational state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022secret-protection\u0022\u003ESecret Protection\u003C/h3\u003E\n\u003Cp\u003EPasswords must not be stored as plaintext or returned through credential management responses.\u003C/p\u003E\n\u003Cp\u003EThe password verification process operates against a protected password representation using the configured password hashing infrastructure.\u003C/p\u003E\n\u003Cp\u003ECredential management APIs should expose only the information required for their intended operations, not the underlying secret.\u003C/p\u003E\n\u003Cp\u003EThis separation also allows password verification and password management to evolve without changing the user identity model.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-security-state\u0022\u003ECredential Security State\u003C/h3\u003E\n\u003Cp\u003EA credential is not necessarily usable merely because a matching secret has been supplied.\u003C/p\u003E\n\u003Cp\u003ECredential validation must also account for relevant lifecycle and security conditions, such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhether the credential is active or revoked.\u003C/li\u003E\n\u003Cli\u003EWhether an applicable expiration condition has been reached.\u003C/li\u003E\n\u003Cli\u003EWhether the credential belongs to the intended identity.\u003C/li\u003E\n\u003Cli\u003EWhether the requested operation is permitted.\u003C/li\u003E\n\u003Cli\u003EWhether the credential is eligible for the authentication mechanism being executed.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ESecret verification and credential usability are related but distinct decisions.\u003C/p\u003E\n\u003Cp\u003EA correctly supplied password must not make a revoked or otherwise unusable credential valid.\u003C/p\u003E\n\u003Cp\u003EPassword strength, common-password detection, user similarity, and password reuse are governed by configurable security policies.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/security/password-policy\u0022\u003EPassword Policy \u0026amp; History\u003C/a\u003E for the built-in policy levels, defaults, and configuration options.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-validation\u0022\u003ECredential Validation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates credential-specific verification from the broader authentication flow.\u003C/p\u003E\n\u003Cp\u003EFor password authentication, credential validation includes evaluating the supplied secret against the stored protected representation and enforcing the relevant credential conditions.\u003C/p\u003E\n\u003Cp\u003EThe authentication server then uses the result within its authentication flow.\u003C/p\u003E\n\u003Cp\u003EThis boundary matters because credential verification is not equivalent to session creation or token issuance.\u003C/p\u003E\n\u003Cp\u003EA successful credential check establishes evidence for authentication. The authentication engine remains responsible for determining whether the flow can complete and what authentication state should be created.\u003C/p\u003E\n\u003Cp\u003EThe same architectural distinction applies to other credential mechanisms.\u003C/p\u003E\n\u003Cp\u003EA future cryptographic credential validator, for example, would verify a different kind of proof, but it would still participate in a broader authentication decision rather than independently issuing authentication state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-lifecycle\u0022\u003ECredential Lifecycle\u003C/h2\u003E\n\u003Cp\u003ECredentials are managed resources with their own lifecycle.\u003C/p\u003E\n\u003Cp\u003EThe reference domain provides operations around credential creation, secret changes, revocation, and recovery.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\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\u003Cstrong\u003EAdd\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERegister a credential for an identity\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EChange secret\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EReplace authentication material through an authorized operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoke\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EMake a credential unusable\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EBegin reset\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EInitiate a controlled credential recovery process\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EComplete reset\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EReplace a secret after satisfying recovery requirements\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese operations have different security requirements.\u003C/p\u003E\n\u003Cp\u003EChanging a password while authenticated is not equivalent to recovering access without the existing password. Likewise, revoking a credential is not the same as invalidating every authentication session previously established using that credential.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth treats these distinctions explicitly rather than assuming that every credential mutation has the same security effect.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-ownership-and-operation-scope\u0022\u003ECredential Ownership and Operation Scope\u003C/h2\u003E\n\u003Cp\u003ECredential management operations must be evaluated in the context of the actor, the target identity, and the requested operation.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth distinguishes between self-service, administrative, and anonymous recovery scenarios.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self-service-operations\u0022\u003ESelf-Service Operations\u003C/h3\u003E\n\u003Cp\u003EAn authenticated user may manage credentials associated with their own identity, subject to the applicable permissions and validation rules.\u003C/p\u003E\n\u003Cp\u003EChanging a secret may require proving knowledge of the existing secret or satisfying another supported security requirement.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-operations\u0022\u003EAdministrative Operations\u003C/h3\u003E\n\u003Cp\u003EAdministrative credential management is performed against an explicitly identified target.\u003C/p\u003E\n\u003Cp\u003EAdministrative authority does not eliminate the requirement to preserve target identity consistency.\u003C/p\u003E\n\u003Cp\u003EFor example, a credential recovery operation must not modify a different user\u0027s credential merely because a supplied identifier resolves to another account.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022anonymous-recovery\u0022\u003EAnonymous Recovery\u003C/h3\u003E\n\u003Cp\u003EAnonymous recovery allows an identity to regain access without an existing authenticated session.\u003C/p\u003E\n\u003Cp\u003EBecause the actor is not authenticated, the recovery process must establish sufficient proof before allowing credential modification.\u003C/p\u003E\n\u003Cp\u003EThese operation scopes are security boundaries, not merely different API routes.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-recovery\u0022\u003ECredential Recovery\u003C/h2\u003E\n\u003Cp\u003ECredential recovery is one of the most security-sensitive responsibilities of the Credentials domain.\u003C/p\u003E\n\u003Cp\u003EThe password reset process is designed as a controlled, multi-stage operation rather than an unrestricted secret update.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022beginning-recovery\u0022\u003EBeginning Recovery\u003C/h3\u003E\n\u003Cp\u003EA recovery request identifies the intended account and initiates a proof-based process.\u003C/p\u003E\n\u003Cp\u003EDepending on the supported recovery configuration, proof may take the form of a token or code delivered through an appropriate notification mechanism.\u003C/p\u003E\n\u003Cp\u003EThe proof must be bound to the intended recovery context and subject to server-controlled validity rules.\u003C/p\u003E\n\u003Cp\u003ERecovery material must not be exposed to an anonymous caller simply because that caller initiated the request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022completing-recovery\u0022\u003ECompleting Recovery\u003C/h3\u003E\n\u003Cp\u003ECompleting recovery requires validating the supplied proof and its associated security conditions before applying a new secret.\u003C/p\u003E\n\u003Cp\u003EThe reference recovery model accounts for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EProof validity and expiration.\u003C/li\u003E\n\u003Cli\u003ESingle-use recovery material.\u003C/li\u003E\n\u003Cli\u003EConsistency between the recovery target and intended identity.\u003C/li\u003E\n\u003Cli\u003ERejection of invalid or previously consumed proof.\u003C/li\u003E\n\u003Cli\u003EProtection against unauthorized credential replacement.\u003C/li\u003E\n\u003Cli\u003ESecurity-state updates following successful recovery.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA completed recovery must not leave the same proof available for another successful reset.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022enumeration-resistance\u0022\u003EEnumeration Resistance\u003C/h3\u003E\n\u003Cp\u003EAnonymous recovery endpoints must avoid unnecessarily revealing whether an identifier belongs to an existing account.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth separates externally observable recovery responses from the internal decisions required to locate and manage the account.\u003C/p\u003E\n\u003Cp\u003EThis reduces account enumeration opportunities while preserving the ability to enforce recovery rules.\u003C/p\u003E\n\u003Cp\u003ERecovery behavior should also be combined with appropriate abuse protection and operational monitoring.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentials-and-authentication-sessions\u0022\u003ECredentials and Authentication Sessions\u003C/h2\u003E\n\u003Cp\u003EChanging or recovering a credential may require action against authentication sessions that were established before the change.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Cstrong\u003Ecredential lifecycle and session lifecycle are separate responsibilities\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports security policies that determine how credential changes and resets affect existing sessions.\u003C/p\u003E\n\u003Cp\u003EThe available policy model includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPolicy\u003C/th\u003E\n\u003Cth\u003EIntended behavior\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EKeepAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve existing sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EKeepCurrent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve the current session while revoking other applicable sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke all applicable sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe appropriate policy depends on the operation and the application\u0027s security requirements.\u003C/p\u003E\n\u003Cp\u003EA password reset performed through anonymous recovery may warrant different treatment from an authenticated password change.\u003C/p\u003E\n\u003Cp\u003EEnforcement also depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, revocation can be reflected through server-side session validation. In JWT-based modes, previously issued access tokens may remain usable until expiration unless additional validation mechanisms are involved.\u003C/p\u003E\n\u003Cp\u003ECredential changes therefore initiate the applicable security response; they do not imply identical immediate token invalidation across every mode.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E for the underlying differences.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022integration-with-the-users-domain\u0022\u003EIntegration with the Users Domain\u003C/h2\u003E\n\u003Cp\u003EThe Credentials domain does not need to own user records to manage authentication material associated with an identity.\u003C/p\u003E\n\u003Cp\u003EUser creation, deletion, or other lifecycle changes may require corresponding credential operations.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports coordination through integration contracts rather than requiring the Users and Credentials implementations to depend directly on one another.\u003C/p\u003E\n\u003Cp\u003EFor example, an application may coordinate initial credential enrollment with user creation or perform credential cleanup as part of account removal.\u003C/p\u003E\n\u003Cp\u003EThese behaviors belong to explicit integration workflows.\u003C/p\u003E\n\u003Cp\u003ECreating a user does not inherently require creating a password credential, and deleting a user should not rely on an undocumented side effect to maintain credential consistency.\u003C/p\u003E\n\u003Cp\u003EThis distinction keeps identity lifecycle and credential lifecycle independently understandable.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extending-credential-capabilities\u0022\u003EExtending Credential Capabilities\u003C/h2\u003E\n\u003Cp\u003ECredential mechanisms differ substantially in how they establish trust.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s architecture is intended to support additional mechanisms by separating credential-specific verification and management from authentication flow orchestration.\u003C/p\u003E\n\u003Cp\u003ESuch extensions may require their own:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredential representation and metadata.\u003C/li\u003E\n\u003Cli\u003EEnrollment and verification procedures.\u003C/li\u003E\n\u003Cli\u003ESecurity and expiration rules.\u003C/li\u003E\n\u003Cli\u003ERecovery or revocation behavior.\u003C/li\u003E\n\u003Cli\u003EPersistence and integration contracts.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ENot every credential mechanism needs to implement password-style secret changes or recovery.\u003C/p\u003E\n\u003Cp\u003EThe goal is to preserve consistent authentication boundaries while allowing each credential mechanism to enforce its own security requirements.\u003C/p\u003E\n\u003Cp\u003EFor most applications using password authentication, the reference implementation provides the recommended starting point.\u003C/p\u003E\n\u003Cp\u003EApplications with specialized requirements can customize supported services and integrations or provide alternative implementations through the relevant contracts.\u003C/p\u003E\n\u003Cp\u003EDetailed extension and replacement strategies belong in the dedicated customization documentation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EThe Credentials domain establishes an important distinction within UltimateAuth:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA user identifies the account. A credential provides authentication evidence. The authentication engine decides how that evidence establishes authentication state.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EKeeping these responsibilities separate allows the framework to support identity management without tying authentication to one user entity, one secret format, or one credential mechanism.\u003C/p\u003E\n\u003Cp\u003EThe current password implementation provides the operational foundation. The broader architecture allows credential capabilities to evolve without turning the authentication engine into a monolithic credential management system.\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/plugin-domains/users\u0022\u003EUsers\u003C/a\u003E \u2014 Understand identity ownership, identifiers, and account lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Explore how authenticated identities are evaluated against access policies.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/overview\u0022\u003EPlugin Domains\u003C/a\u003E \u2014 Review the architecture and responsibilities of Plugin Domains.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor the authentication flow that consumes credential verification results, see \u003Ca href=\u0022/docs/fundamentals/flow-based-auth\u0022\u003EFlow-Based Authentication\u003C/a\u003E.\u003C/p\u003E\n", "Headings": [ { "Id": "understanding-credentials", @@ -109,6 +109,6 @@ "Level": 0 } ], - "SourceHash": "5EEC9DEC644EEAB9AFBF7958DBA283FA11077DADF5ABBFCF3FB7493087B17106", + "SourceHash": "E53D4D20E053D0FF3D3EB37C8DF7A8DD3FACA8F2D9959442B958318742B5AED1", "BuilderVersion": "1" } \ No newline at end of file From 92aebd4e3cec3e3159701d88bed8c66a57f3abbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Tue, 3 Nov 2026 00:34:24 +0300 Subject: [PATCH 2/2] One More Fix --- docs/content/client-sdk/authorization.md | 2 +- .../wwwroot/docs/client-sdk/authorization.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/client-sdk/authorization.md b/docs/content/client-sdk/authorization.md index fa74571f..886f9532 100644 --- a/docs/content/client-sdk/authorization.md +++ b/docs/content/client-sdk/authorization.md @@ -1544,7 +1544,7 @@ This allows applications to provide flexible administrative interfaces while kee ## Next Steps -- [Policies & Access Control](/docs/plugin-domains/policies-access-control) — Understand application policies and access rules. +- [Policies & Access Control](/docs/plugin-domains/policies) — Understand application policies and access rules. - [Authorization Pipeline Internals](/docs/security/authorization-pipeline) — Explore how authorization decisions are evaluated. - [User Management](/docs/client-sdk/user-management) — Manage user accounts and lifecycle. - [Session Management](/docs/client-sdk/session-management) — Manage authenticated sessions and devices. diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authorization.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authorization.json index 1ea8da64..7d7fb69e 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authorization.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authorization.json @@ -1,7 +1,7 @@ { "Slug": "client-sdk/authorization", "Title": "Authorization", - "Html": "\n\u003Cp\u003EUltimateAuth provides a unified client API for managing roles, assigning permissions, controlling user-role relationships, and requesting authorization decisions from the server.\u003C/p\u003E\n\u003Cp\u003EThrough \u003Ccode\u003EUAuthClient.Authorization\u003C/code\u003E, applications can build administrative authorization interfaces and check whether the current user is allowed to perform a particular action.\u003C/p\u003E\n\u003Cp\u003EAuthorization is enforced on the server. Client-side permission checks are useful for improving the user experience, but they do not replace authorization at the endpoint or application-service level.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the current \u003Ccode\u003EIAuthorizationClient\u003C/code\u003E API, practical role management workflows, permission patterns, and secure client integration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-authorization\u0022\u003E1. Understanding Authorization\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth authorization combines several concepts:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcept\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\u003ERoles\u003C/td\u003E\n\u003Ctd\u003ENamed collections of permissions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPermissions\u003C/td\u003E\n\u003Ctd\u003EFine-grained capabilities represented by normalized values\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUser-role assignments\u003C/td\u003E\n\u003Ctd\u003EAssociations between users and roles\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPolicies\u003C/td\u003E\n\u003Ctd\u003EServer-side rules that participate in access decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthorization context\u003C/td\u003E\n\u003Ctd\u003EInformation about the actor, resource, action, and current security state\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA typical authorization relationship looks like this:\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\u003EUser\n |\n \u002B-- Manager\n | |\n | \u002B-- products.read\n | \u002B-- products.create\n | \u002B-- products.update\n |\n \u002B-- Auditor\n |\n \u002B-- reports.read\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA user can have multiple roles, and each role can contain multiple permissions.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Cstrong\u003Ehaving a permission does not automatically guarantee access\u003C/strong\u003E. The server\u0027s authorization pipeline may apply additional requirements, such as resource ownership, session validity, tenant isolation, or application policies.\u003C/p\u003E\n\u003Cp\u003EFor the internal decision model, see \u003Ca href=\u0022/docs/security/authorization-pipeline\u0022\u003EAuthorization Pipeline Internals\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-authorization-client\u0022\u003E2. Accessing the Authorization Client\u003C/h2\u003E\n\u003Cp\u003EInject \u003Ccode\u003EIUAuthClient\u003C/code\u003E into a Blazor 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\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAuthorization operations are available through:\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\u003EUAuthClient.Authorization\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis property exposes \u003Ccode\u003EIAuthorizationClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe interface provides three main groups of operations:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthorization checks:\u003C/strong\u003E Evaluate whether the current user may perform an action.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERole management:\u003C/strong\u003E Create, query, rename, configure, and delete roles.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUser-role management:\u003C/strong\u003E Retrieve, assign, and remove user roles.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe SDK sends requests to the corresponding UltimateAuth endpoints and returns structured \u003Ccode\u003EUAuthResult\u003C/code\u003E values.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-permissions\u0022\u003E3. Understanding Permissions\u003C/h2\u003E\n\u003Cp\u003EA permission is represented by the \u003Ccode\u003EPermission\u003C/code\u003E value 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\u003Evar permission = Permission.From(\u0026quot;products.create\u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe current implementation normalizes permission values by trimming surrounding whitespace and converting the value to lowercase.\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\u003Evar permission = Permission.From(\u0026quot; PRODUCTS.CREATE \u0026quot;);\n\nConsole.WriteLine(permission.Value);\n// products.create\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe constructor rejects null, empty, and whitespace-only values.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-patterns\u0022\u003EPermission Patterns\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth recognizes three important forms:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPattern\u003C/th\u003E\n\u003Cth\u003EExample\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\u003EExact\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eproducts.create\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EA specific permission\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPrefix wildcard\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eproducts.*\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPermissions within the corresponding prefix\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EGlobal wildcard\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E*\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUnrestricted permission matching within the applicable authorization rules\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ECreate them using:\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 exact = Permission.From(\u0026quot;products.create\u0026quot;);\n\nvar prefix = Permission.From(\u0026quot;products.*\u0026quot;);\n\nvar global = Permission.Wildcard;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe type also exposes:\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\u003Epermission.IsWildcard;\npermission.IsPrefix;\npermission.IsValid;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-normalization\u0022\u003EPermission Normalization\u003C/h3\u003E\n\u003Cp\u003EThe following values normalize to the same representation:\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\u003EPermission.From(\u0026quot;Products.Create\u0026quot;);\nPermission.From(\u0026quot;products.create\u0026quot;);\nPermission.From(\u0026quot; PRODUCTS.CREATE \u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAll produce:\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\u003Eproducts.create\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse consistent permission names across application services, policies, and role configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022wildcards-are-powerful\u0022\u003EWildcards Are Powerful\u003C/h3\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\u003EPermission.From(\u0026quot;users.*\u0026quot;)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Emay grant broad access to operations within the \u003Ccode\u003Eusers\u003C/code\u003E permission prefix.\u003C/p\u003E\n\u003Cp\u003EThe global wildcard:\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\u003EPermission.Wildcard\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eis especially sensitive.\u003C/p\u003E\n\u003Cp\u003EAvoid assigning broad wildcard permissions merely to simplify development. Use least-privilege permissions for ordinary application roles.\u003C/p\u003E\n\u003Cp\u003EA wildcard is not a substitute for satisfying independent security policies, authentication requirements, or tenant restrictions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-authorization\u0022\u003E4. Check Authorization\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E evaluates whether the current authenticated user is authorized to perform a specific action against a resource.\u003C/p\u003E\n\u003Cp\u003EIt provides an \u003Cstrong\u003Eon-demand, server-side authorization decision\u003C/strong\u003E based on the current authentication context, permissions, roles, and applicable authorization policies.\u003C/p\u003E\n\u003Cp\u003EUnlike operations that perform protected actions, \u003Ccode\u003ECheckAsync\u003C/code\u003E only evaluates access. It does not execute the requested operation, modify permissions, grant access, or reserve authorization for a future request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-to-use-checkasync\u0022\u003EWhen to Use CheckAsync\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECheckAsync\u003C/code\u003E when your application needs to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDetermine whether an authenticated user can perform an action before presenting it in the UI.\u003C/li\u003E\n\u003Cli\u003EEnable, disable, or hide controls based on the current authorization decision.\u003C/li\u003E\n\u003Cli\u003EExplain why an operation is unavailable, using the returned denial reason where appropriate.\u003C/li\u003E\n\u003Cli\u003EDetermine whether an operation requires reauthentication.\u003C/li\u003E\n\u003Cli\u003EEvaluate resource-specific access when the applicable policies support it.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, an application may check whether the current user can delete a particular order before displaying the Delete button.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-checkasync-does-not-guarantee\u0022\u003EWhat CheckAsync Does Not Guarantee\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EA successful authorization check is not a guarantee that a subsequent operation will succeed.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAuthorization is evaluated against the security context available at the time of the check. That context may change before the actual operation is performed.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn administrator may revoke the user\u0027s permissions.\u003C/li\u003E\n\u003Cli\u003EThe user\u0027s session may expire or become invalid.\u003C/li\u003E\n\u003Cli\u003EThe target resource may be deleted or modified.\u003C/li\u003E\n\u003Cli\u003EThe resource\u0027s ownership or tenant context may change.\u003C/li\u003E\n\u003Cli\u003EAdditional business rules may prevent the operation.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe protected operation must independently enforce authorization using its current context.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ENever use a previous \u003Ccode\u003ECheckAsync\u003C/code\u003E result as proof of authorization for a later request.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E is primarily a developer-experience and user-experience API. It helps applications understand and present authorization decisions without replacing the security enforcement performed by protected endpoints and application services.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECheckAsync\u003C/code\u003E to ask the server whether the current user is authorized to perform an action.\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;read\u0026quot;,\n Resource = \u0026quot;products\u0026quot;\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsAllowed: true })\n{\n // The server authorized this action.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;AuthorizationResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorizationcheckrequest\u0022\u003EAuthorizationCheckRequest\u003C/h3\u003E\n\u003Cp\u003EThe request contains:\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\u003EType\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\u003EAction\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResource\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResource being accessed\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResourceId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional identifier for a specific resource instance\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EBoth \u003Ccode\u003EAction\u003C/code\u003E and \u003Ccode\u003EResource\u003C/code\u003E are required.\u003C/p\u003E\n\u003Cp\u003EThe action and resource values must follow the conventions understood by your server-side authorization configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-a-specific-resource\u0022\u003ECheck a Specific Resource\u003C/h3\u003E\n\u003Cp\u003EFor example, check access to a particular product:\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;update\u0026quot;,\n Resource = \u0026quot;products\u0026quot;,\n ResourceId = productId.ToString()\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EProviding \u003Ccode\u003EResourceId\u003C/code\u003E allows the server to evaluate resource-specific rules where supported by the configured authorization pipeline.\u003C/p\u003E\n\u003Cp\u003EFor example, the application may distinguish between updating any product and updating a product owned by the current user.\u003C/p\u003E\n\u003Cp\u003EDo not assume that sending \u003Ccode\u003EResourceId\u003C/code\u003E automatically enables ownership checking. The relevant server-side policy must implement the required rule.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-authorization-results\u0022\u003E5. Understanding Authorization Results\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E returns an \u003Ccode\u003EAuthorizationResult\u003C/code\u003E with 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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAllowed\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the request was authorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequiresReauthentication\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the decision indicates reauthentication is required\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDenyReason\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional denial reason code\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=\u0022authorization-decisions-vs.http-results\u0022\u003EAuthorization Decisions vs. HTTP Results\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E separates the outcome of the API request from the authorization decision itself.\u003C/p\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003EUAuthResult\u0026lt;AuthorizationResult\u0026gt;\u003C/code\u003E:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthResult.IsSuccess\u003C/code\u003E indicates whether the authorization check request completed successfully.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAuthorizationResult.IsAllowed\u003C/code\u003E indicates whether the requested action is currently authorized.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAuthorizationResult.RequiresReauthentication\u003C/code\u003E indicates whether additional authentication is required.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAuthorizationResult.DenyReason\u003C/code\u003E provides an optional reason for denial.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA denied authorization decision is a valid result of a successful authorization check.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EScenario\u003C/th\u003E\n\u003Cth\u003EHTTP Status\u003C/th\u003E\n\u003Cth\u003EAuthorization Result\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAction allowed\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E200 OK\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAllowed = true\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAction denied\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E200 OK\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAllowed = false\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReauthentication required\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E200 OK\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequiresReauthentication = true\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECaller not authenticated\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E401 Unauthorized\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo authorization decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInvalid request\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E400 Bad Request\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo authorization decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EServer-side evaluation failure\u003C/td\u003E\n\u003Ctd\u003EAppropriate error status\u003C/td\u003E\n\u003Ctd\u003ENo authorization decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EHTTP 200 means the authorization evaluation completed successfully. It does not mean access was granted.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EOnly authenticated callers can use the current \u003Ccode\u003ECheckAsync\u003C/code\u003E endpoint. However, callers do not need permission to perform the action they are checking.\u003C/p\u003E\n\u003Cp\u003EThis distinction is intentional: the purpose of \u003Ccode\u003ECheckAsync\u003C/code\u003E is to determine whether the action is permitted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-denied-decisions\u0022\u003EHandling Denied Decisions\u003C/h3\u003E\n\u003Cp\u003EApplications should distinguish between authorization denial, reauthentication requirements, and request failures.\u003C/p\u003E\n\u003Cp\u003EA denied decision may be used to disable an action or display an appropriate message.\u003C/p\u003E\n\u003Cp\u003EA reauthentication requirement may be used to initiate the application\u0027s supported reauthentication flow.\u003C/p\u003E\n\u003Cp\u003EA failed request must not be interpreted as permission to proceed.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EFail closed:\u003C/strong\u003E If the authorization decision is unavailable, treat the operation as unavailable in the UI and rely on server-side authorization when the actual operation is requested.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-all-decision-outcomes\u0022\u003EHandle All Decision Outcomes\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;delete\u0026quot;,\n Resource = \u0026quot;products\u0026quot;,\n ResourceId = productId.ToString()\n });\n\nif (!result.IsSuccess)\n{\n // The authorization request itself failed.\n}\nelse if (result.Value is { RequiresReauthentication: true })\n{\n // Ask the user to authenticate again.\n}\nelse if (result.Value is { IsAllowed: true })\n{\n // The operation is allowed.\n}\nelse\n{\n // The operation was denied.\n var reason = result.Value?.DenyReason;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn unsuccessful API request and an explicit authorization denial are different outcomes.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003Eresult.IsSuccess == false\u003C/code\u003E means the authorization request did not complete successfully at the API level.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eresult.Value.IsAllowed == false\u003C/code\u003E means the server returned a denial decision.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eresult.Value.RequiresReauthentication == true\u003C/code\u003E indicates that the caller needs to reestablish authentication.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ETreat failed or unavailable authorization checks as \u003Cstrong\u003Enot authorized\u003C/strong\u003E in the UI.\u003C/p\u003E\n\u003Cp\u003ENever grant access because an authorization request failed.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-authorization-checks-in-blazor\u0022\u003E6. Use Authorization Checks in Blazor\u003C/h2\u003E\n\u003Cp\u003EA common use case is controlling whether an action button should be displayed.\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.Authorization.Contracts\n@inject IUAuthClient UAuthClient\n\n@if (_canCreate)\n{\n \u0026lt;button @onclick=\u0026quot;CreateProductAsync\u0026quot;\u0026gt;Create Product\u0026lt;/button\u0026gt;\n}\n\n@code {\n private bool _canCreate;\n\n protected override async Task OnInitializedAsync()\n {\n var result = await UAuthClient.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;create\u0026quot;,\n Resource = \u0026quot;products\u0026quot;\n });\n\n _canCreate = result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsAllowed: true };\n }\n\n private Task CreateProductAsync()\n {\n // Invoke the protected application operation.\n return Task.CompletedTask;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis improves the interface by hiding unavailable actions.\u003C/p\u003E\n\u003Cp\u003EHowever, the endpoint handling product creation must still enforce authorization independently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-client-checks-are-not-security-boundaries\u0022\u003EWhy Client Checks Are Not Security Boundaries\u003C/h3\u003E\n\u003Cp\u003EA user can bypass a hidden button and send a request directly to an endpoint.\u003C/p\u003E\n\u003Cp\u003ETherefore:\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\u003EClient authorization check\n |\n v\nImprove the user experience\n |\n v\nUser submits an operation\n |\n v\nServer independently authorizes the operation\n |\n v\nExecute or deny\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EClient authorization checks should never be the only protection for sensitive operations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-my-roles\u0022\u003E7. Get My Roles\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMyRolesAsync\u003C/code\u003E to retrieve roles assigned to the current authenticated 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.Authorization.GetMyRolesAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n foreach (var role in data.Roles.Items)\n {\n Console.WriteLine(role.Name);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;UserRolesGetResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userrolesgetresult\u0022\u003EUserRolesGetResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser whose roles were requested\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoles\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPaginated collection of role assignments\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EEach entry is a \u003Ccode\u003EUserRoleInfo\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userroleinfo\u0022\u003EUserRoleInfo\u003C/h3\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant associated with the assignment\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssigned user\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssigned role\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole name\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAssignedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssignment timestamp\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=\u0022pagination\u0022\u003EPagination\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.Authorization.GetMyRolesAsync(\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe returned role collection is paginated.\u003C/p\u003E\n\u003Cp\u003EDo not assume that the first page contains all assigned roles.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-my-permissions\u0022\u003E8. Get My Permissions\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMyPermissionsAsync()\u003C/code\u003E to retrieve the permissions assigned to the currently authenticated user through their roles.\u003C/p\u003E\n\u003Cp\u003EUnlike \u003Ccode\u003EGetMyRolesAsync()\u003C/code\u003E, which returns role assignments, this method returns the resulting permission collection.\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.Authorization.GetMyPermissionsAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n foreach (var permission in data.Permissions)\n {\n Console.WriteLine(permission.Value);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method is self-service and does not require administrative permission.\u003C/p\u003E\n\u003Cp\u003EThe user identity and tenant context are derived from the authenticated session. No user identifier or tenant identifier is accepted as a request parameter.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userpermissionsresult\u0022\u003EUserPermissionsResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains the user\u0027s role-derived permissions:\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 UserPermissionsResult\n{\n public required IReadOnlyList\u0026lt;Permission\u0026gt; Permissions { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe returned collection:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECombines permissions from the user\u0027s assigned roles.\u003C/li\u003E\n\u003Cli\u003ERemoves duplicate permissions.\u003C/li\u003E\n\u003Cli\u003EUses deterministic ordering.\u003C/li\u003E\n\u003Cli\u003EPreserves wildcard permissions such as \u003Ccode\u003Eusers.*\u003C/code\u003E and \u003Ccode\u003E*\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EReturns an empty collection when no permissions are assigned.\u003C/li\u003E\n\u003Cli\u003EReflects the current permissions resolved from the configured authorization stores.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, a user assigned multiple roles may receive:\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\u003Ereports.*\nusers.read.admin\nusers.update.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe result does not identify which role contributed each permission.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assigned-permissions-vs.authorization-decisions\u0022\u003EAssigned Permissions vs. Authorization Decisions\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EAssigned permissions are not final authorization decisions.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA user may possess a permission but still be denied access because of an applicable policy, tenant boundary, target restriction, or other security requirement.\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\u003Evar permissions = await UAuthClient.Authorization.GetMyPermissionsAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis retrieves the user\u0027s assigned permissions.\u003C/p\u003E\n\u003Cp\u003ETo determine whether a specific operation is currently authorized, use \u003Ccode\u003ECheckAsync()\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;users.delete.admin\u0026quot;,\n Resource = \u0026quot;users\u0026quot;,\n ResourceId = targetUserKey.Value\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } decision)\n{\n if (decision.IsAllowed)\n {\n // The authorization check allowed this operation.\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEven a successful client-side authorization check does not replace authorization enforcement at the server-side operation boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022wildcard-permissions\u0022\u003EWildcard Permissions\u003C/h3\u003E\n\u003Cp\u003EWildcard permissions are returned without expansion.\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\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\u003Eusers.*\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eremains \u003Ccode\u003Eusers.*\u003C/code\u003E rather than being expanded into individual user-management actions.\u003C/p\u003E\n\u003Cp\u003EThis preserves the stored permission semantics and avoids depending on a potentially incomplete action catalog.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003ESecurity Considerations\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EGetMyPermissionsAsync()\u003C/code\u003E is intended for permission inspection, user interfaces, and administrative transparency.\u003C/p\u003E\n\u003Cp\u003EIt must not be used as the sole mechanism for protecting sensitive operations.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for evaluating authorization policies and enforcing access at the actual execution boundary.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/client-sdk/authorization#4-check-authorization\u0022\u003ECheck Authorization\u003C/a\u003E for policy-aware authorization checks.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-another-users-roles\u0022\u003E9. Get Another User\u0027s Roles\u003C/h2\u003E\n\u003Cp\u003EAdministrative interfaces can retrieve the roles assigned to a specific 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.Authorization.GetUserRolesAsync(userKey,\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n foreach (var role in data.Roles.Items)\n {\n Console.WriteLine($\u0026quot;{role.Name} \u2014 {role.AssignedAt}\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis operation requires appropriate administrative authorization.\u003C/p\u003E\n\u003Cp\u003EThe \u003Ccode\u003EUserKey\u003C/code\u003E identifies the target user, not the current actor.\u003C/p\u003E\n\u003Cp\u003EThe server determines whether the current actor may inspect that user\u0027s role assignments.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022query-roles\u0022\u003E10. Query Roles\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EQueryRolesAsync\u003C/code\u003E to retrieve roles with pagination and optional filtering.\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var role in page.Items)\n {\n Console.WriteLine(role.Name);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;PagedResult\u0026lt;RoleInfo\u0026gt;\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rolequery\u0022\u003ERoleQuery\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ERoleQuery\u003C/code\u003E inherits from \u003Ccode\u003EPageRequest\u003C/code\u003E and adds:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESearch\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional role-name search\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIncludeDeleted\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EInclude deleted roles when supported and authorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPageNumber\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested page\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPageSize\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested page size\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=\u0022search-roles\u0022\u003ESearch Roles\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n Search = \u0026quot;manager\u0026quot;,\n PageNumber = 1,\n PageSize = 10\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022include-deleted-roles\u0022\u003EInclude Deleted Roles\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n IncludeDeleted = true,\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003EIncludeDeleted\u003C/code\u003E only where the application needs administrative access to deleted role records.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for enforcing the query\u0027s permissions and lifecycle rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-roleinfo\u0022\u003E11. Understanding RoleInfo\u003C/h2\u003E\n\u003Cp\u003EEach role returned by the SDK is represented by \u003Ccode\u003ERoleInfo\u003C/code\u003E.\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\u003EType\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\u003EId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole name\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPermissions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIReadOnlyCollection\u0026lt;Permission\u0026gt;\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssigned permissions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreation timestamp\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUpdatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELast update timestamp\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var role in page.Items)\n {\n Console.WriteLine($\u0026quot;Role: {role.Name}\u0026quot;);\n\n foreach (var permission in role.Permissions)\n {\n Console.WriteLine($\u0026quot; {permission.Value}\u0026quot;);\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ERoleId\u003C/code\u003E is a strongly typed identifier rather than a plain \u003Ccode\u003EGuid\u003C/code\u003E.\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\u003ERoleId roleId = RoleId.From(existingGuid);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERoleId\u003C/code\u003E for role management requests.\u003C/p\u003E\n\u003Cp\u003EDo not confuse it with \u003Ccode\u003EUserKey\u003C/code\u003E or other identifier types.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-role\u0022\u003E12. Create a Role\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECreateRoleAsync\u003C/code\u003E to create a role.\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } role)\n{\n Console.WriteLine($\u0026quot;Created: {role.Name}\u0026quot;);\n Console.WriteLine($\u0026quot;Role ID: {role.Id}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;RoleInfo\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERole names must be unique within the relevant tenant according to the server\u0027s role-name rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-role-with-permissions\u0022\u003ECreate a Role with Permissions\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECreateRoleRequest\u003C/code\u003E supports an optional initial permission collection.\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;ProductManager\u0026quot;,\n Permissions =\n [\n Permission.From(\u0026quot;products.read\u0026quot;),\n Permission.From(\u0026quot;products.create\u0026quot;),\n Permission.From(\u0026quot;products.update\u0026quot;)\n ]\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows applications to create and configure a role in one operation.\u003C/p\u003E\n\u003Cp\u003EThe server validates the requested permissions and whether the caller is authorized to create the role.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-read-only-role\u0022\u003ECreate a Read-Only Role\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;Auditor\u0026quot;,\n Permissions =\n [\n Permission.From(\u0026quot;reports.read\u0026quot;),\n Permission.From(\u0026quot;products.read\u0026quot;)\n ]\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse role names that describe the responsibility or capability represented by the role.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rename-a-role\u0022\u003E13. Rename a Role\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERenameRoleAsync\u003C/code\u003E to change a role\u0027s name.\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.Authorization.RenameRoleAsync(\n new RenameRoleRequest\n {\n Id = roleId,\n Name = \u0026quot;SeniorManager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role renamed.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ERenameRoleRequest\u003C/code\u003E requires:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EId\u003C/code\u003E: The role\u0027s \u003Ccode\u003ERoleId\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EName\u003C/code\u003E: The new role name.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe operation may fail if another role already uses the requested name.\u003C/p\u003E\n\u003Cp\u003ERole renaming does not mean that permissions are automatically changed.\u003C/p\u003E\n\u003Cp\u003EAfter a successful rename, the SDK publishes an \u003Ccode\u003EAuthorizationChanged\u003C/code\u003E state event.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022set-role-permissions\u0022\u003E14. Set Role Permissions\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E to configure a role\u0027s permissions.\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.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = roleId,\n Permissions =\n [\n Permission.From(\u0026quot;products.read\u0026quot;),\n Permission.From(\u0026quot;products.create\u0026quot;),\n Permission.From(\u0026quot;products.update\u0026quot;)\n ]\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole to modify\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPermissions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENew permission collection\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=\u0022important-this-is-a-replacement-operation\u0022\u003EImportant: This Is a Replacement Operation\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E replaces the role\u0027s existing permission collection.\u003C/p\u003E\n\u003Cp\u003EIt does not append permissions to the current collection.\u003C/p\u003E\n\u003Cp\u003EFor example, suppose a role currently contains:\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\u003Eproducts.read\nproducts.create\nproducts.update\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ESubmitting:\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\u003EPermissions =\n[\n Permission.From(\u0026quot;products.read\u0026quot;)\n]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Emeans the intended new permission collection contains only:\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\u003Eproducts.read\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe other permissions are removed from the role.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022safely-add-a-permission-to-an-existing-role\u0022\u003ESafely Add a Permission to an Existing Role\u003C/h3\u003E\n\u003Cp\u003ETo preserve existing permissions, first retrieve the role and construct the complete desired collection.\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 query = await UAuthClient.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n Search = \u0026quot;ProductManager\u0026quot;,\n PageNumber = 1,\n PageSize = 50\n });\n\nif (query.IsSuccess \u0026amp;\u0026amp; query.Value is { } page)\n{\n var role = page.Items.FirstOrDefault(x =\u0026gt; x.Name == \u0026quot;ProductManager\u0026quot;);\n\n if (role is not null)\n {\n var permissions = role.Permissions.Append(Permission.From(\u0026quot;products.delete\u0026quot;)).Distinct().ToArray();\n\n var result =\n await UAuthClient.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = role.Id,\n Permissions = permissions\n });\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is a read-modify-write pattern, not an atomic append operation.\u003C/p\u003E\n\u003Cp\u003EIf multiple administrators edit the same role concurrently, the last successful replacement may overwrite another administrator\u0027s changes.\u003C/p\u003E\n\u003Cp\u003EApplications requiring concurrency-safe editing should implement suitable coordination or version checking at the application level.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-a-permission\u0022\u003ERemove a Permission\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 updatedPermissions = role.Permissions.Where(x =\u0026gt; x.Value != \u0026quot;products.delete\u0026quot;).ToArray();\n\nvar result = await UAuthClient.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = role.Id,\n Permissions = updatedPermissions\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAgain, submit the complete desired permission set.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022clear-all-permissions\u0022\u003EClear All Permissions\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.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = roleId,\n Permissions = []\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn empty collection requests that the role have no permissions.\u003C/p\u003E\n\u003Cp\u003EThe server determines whether the operation is permitted.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-a-role-to-a-user\u0022\u003E15. Assign a Role to a User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EAssignRoleToUserAsync\u003C/code\u003E to assign an existing role to a 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.Authorization.AssignRoleToUserAsync(\n new AssignRoleRequest\n {\n UserKey = userKey,\n RoleName = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role assigned.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETarget user\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExisting role name\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe role must exist, and the current actor must be authorized to assign it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-multiple-roles\u0022\u003EAssign Multiple Roles\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\u003Eforeach (var roleName in new[] { \u0026quot;Manager\u0026quot;, \u0026quot;Auditor\u0026quot; })\n{\n var result = await UAuthClient.Authorization.AssignRoleToUserAsync(\n new AssignRoleRequest\n {\n UserKey = userKey,\n RoleName = roleName\n });\n\n if (!result.IsSuccess)\n {\n // Handle the failed assignment.\n break;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach call is a separate operation.\u003C/p\u003E\n\u003Cp\u003EThis example does not provide transactional, all-or-nothing behavior across the entire collection.\u003C/p\u003E\n\u003Cp\u003EFor complex provisioning workflows, consider whether the application needs a server-side orchestration operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-a-role-from-a-user\u0022\u003E16. Remove a Role from a User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERemoveRoleFromUserAsync\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.Authorization.RemoveRoleFromUserAsync(\n new RemoveRoleRequest\n {\n UserKey = userKey,\n RoleName = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role removed.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERemoving a role changes the user\u0027s role assignments.\u003C/p\u003E\n\u003Cp\u003EIt does not delete the role itself.\u003C/p\u003E\n\u003Cp\u003EOther users assigned to that role are unaffected by this particular operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-effective-permissions-may-remain\u0022\u003EImportant: Effective Permissions May Remain\u003C/h3\u003E\n\u003Cp\u003EA user may receive the same permission through multiple roles.\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\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\u003EUser\n |\n \u002B-- Manager\n | \u002B-- products.read\n |\n \u002B-- Auditor\n \u002B-- products.read\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERemoving \u003Ccode\u003EManager\u003C/code\u003E does not necessarily remove the user\u0027s ability to read products because \u003Ccode\u003EAuditor\u003C/code\u003E still provides \u003Ccode\u003Eproducts.read\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ETo determine whether the user may perform an action, rely on the server\u0027s authorization decision rather than inspecting one role assignment in isolation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-a-role\u0022\u003E17. Delete a Role\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EDeleteRoleAsync\u003C/code\u003E to delete a role.\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.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Soft\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request supports a deletion mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hard-delete\u0022\u003EHard Delete\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.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Hard\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe availability and effect of each deletion mode are determined by server-side lifecycle rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022roledeleteresult\u0022\u003ERoleDeleteResult\u003C/h3\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;RoleDeleteResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe response contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDeleted role\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERemovedAssignments\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENumber of user-role assignments removed\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EApplied deletion mode\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeletedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDeletion timestamp\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-the-result\u0022\u003EInspect the Result\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.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Soft\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } deleted)\n{\n Console.WriteLine($\u0026quot;Removed assignments: {deleted.RemovedAssignments}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDeleting a role removes its assignments from users.\u003C/p\u003E\n\u003Cp\u003EThis can affect multiple users at once.\u003C/p\u003E\n\u003Cp\u003EFor that reason, role deletion should be presented as a sensitive administrative operation with appropriate confirmation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-role-management-page\u0022\u003E18. Build a Role Management Page\u003C/h2\u003E\n\u003Cp\u003EThe following Blazor example demonstrates querying roles, creating a role, and deleting a selected role.\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.Authorization.Contracts\n@using CodeBeam.UltimateAuth.Core.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Role Management\u0026lt;/h3\u0026gt;\n\n\u0026lt;input @bind=\u0026quot;_search\u0026quot; placeholder=\u0026quot;Search roles\u0026quot; /\u0026gt;\n\n\u0026lt;button @onclick=\u0026quot;LoadRolesAsync\u0026quot;\u0026gt;Search\u0026lt;/button\u0026gt;\n\n@if (_loading)\n{\n \u0026lt;p\u0026gt;Loading roles...\u0026lt;/p\u0026gt;\n}\nelse\n{\n @foreach (var role in _roles)\n {\n \u0026lt;div @key=\u0026quot;role.Id\u0026quot;\u0026gt;\n \u0026lt;strong\u0026gt;@role.Name\u0026lt;/strong\u0026gt;\n \u0026lt;span\u0026gt;@role.Permissions.Count permissions\u0026lt;/span\u0026gt;\n \u0026lt;button @onclick=\u0026quot;() =\u0026gt; DeleteRoleAsync(role.Id)\u0026quot;\u0026gt;Delete\u0026lt;/button\u0026gt;\n \u0026lt;/div\u0026gt;\n }\n}\n\n\u0026lt;h4\u0026gt;Create Role\u0026lt;/h4\u0026gt;\n\u0026lt;input @bind=\u0026quot;_newRoleName\u0026quot; placeholder=\u0026quot;Role name\u0026quot; /\u0026gt;\n\u0026lt;button @onclick=\u0026quot;CreateRoleAsync\u0026quot; disabled=\u0026quot;@_busy\u0026quot;\u0026gt;Create\u0026lt;/button\u0026gt;\n\n@if (_message is not null)\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private IReadOnlyList\u0026lt;RoleInfo\u0026gt; _roles = [];\n\n private string _search = \u0026quot;\u0026quot;;\n private string _newRoleName = \u0026quot;\u0026quot;;\n private string? _message;\n\n private bool _loading = true;\n private bool _busy;\n\n protected override async Task OnInitializedAsync()\n {\n await LoadRolesAsync();\n }\n\n private async Task LoadRolesAsync()\n {\n _loading = true;\n\n try\n {\n var result = await UAuthClient.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n Search = _search,\n PageNumber = 1,\n PageSize = 50\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n {\n _roles = page.Items;\n }\n else\n {\n _message = \u0026quot;Unable to load roles.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _message = \u0026quot;The authorization service is unavailable.\u0026quot;;\n }\n finally\n {\n _loading = false;\n }\n }\n\n private async Task CreateRoleAsync()\n {\n if (_busy || string.IsNullOrWhiteSpace(_newRoleName))\n return;\n\n _busy = true;\n _message = null;\n\n try\n {\n var result = await UAuthClient.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = _newRoleName\n });\n\n if (result.IsSuccess)\n {\n _newRoleName = \u0026quot;\u0026quot;;\n await LoadRolesAsync();\n }\n else\n {\n _message = \u0026quot;Unable to create role.\u0026quot;;\n }\n }\n finally\n {\n _busy = false;\n }\n }\n\n private async Task DeleteRoleAsync(RoleId roleId)\n {\n // Add a confirmation dialog before deletion in production.\n\n var result = await UAuthClient.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Soft\n });\n\n if (result.IsSuccess)\n {\n await LoadRolesAsync();\n }\n else\n {\n _message = \u0026quot;Unable to delete role.\u0026quot;;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example demonstrates the SDK integration rather than a complete production administration interface.\u003C/p\u003E\n\u003Cp\u003EA production implementation should include pagination, role assignment controls, permission editing, deletion confirmation, and appropriate loading and error handling for every operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-user-role-assignment-interface\u0022\u003E19. Build a User Role Assignment Interface\u003C/h2\u003E\n\u003Cp\u003EAn administrative user details page can display assigned roles and allow administrators to add or remove them.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022load-user-roles\u0022\u003ELoad User Roles\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.Authorization.GetUserRolesAsync(userKey,\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 50\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n var assignedRoles = data.Roles.Items.ToList();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-a-selected-role\u0022\u003EAssign a Selected Role\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.Authorization.AssignRoleToUserAsync(\n new AssignRoleRequest\n {\n UserKey = userKey,\n RoleName = selectedRoleName\n });\n\nif (result.IsSuccess)\n{\n // Reload the user\u0027s assigned roles.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-a-selected-role\u0022\u003ERemove a Selected Role\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.Authorization.RemoveRoleFromUserAsync(\n new RemoveRoleRequest\n {\n UserKey = userKey,\n RoleName = selectedRoleName\n });\n\nif (result.IsSuccess)\n{\n // Reload the user\u0027s assigned roles.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis pattern works well alongside \u003Ccode\u003EUAuthClient.Users\u003C/code\u003E for building a unified user administration interface.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/client-sdk/user-management\u0022\u003EUser Management\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-state-events\u0022\u003E20. Authorization State Events\u003C/h2\u003E\n\u003Cp\u003EThe current \u003Ccode\u003EUAuthAuthorizationClient\u003C/code\u003E publishes:\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\u003EUAuthStateEvent.AuthorizationChanged\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eafter successful authorization mutations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EPublishes \u003Ccode\u003EAuthorizationChanged\u003C/code\u003E\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAssignRoleToUserAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERemoveRoleFromUserAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERenameRoleAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeleteRoleAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreateRoleAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EQueryRolesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EGetMyRolesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EGetUserRolesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe event follows the configured client state event handling mode.\u003C/p\u003E\n\u003Cp\u003EIt can be used to refresh navigation menus, role assignment views, or permission-sensitive UI.\u003C/p\u003E\n\u003Cp\u003EHowever, it is a \u003Cstrong\u003Elocal client event\u003C/strong\u003E, not a guarantee that all active clients or distributed caches have immediately observed the change.\u003C/p\u003E\n\u003Cp\u003EFor example, an administrator assigning a role to another user does not automatically mean that user\u0027s open browser receives the administrator\u0027s local event.\u003C/p\u003E\n\u003Cp\u003EApplications requiring cross-client updates should provide an appropriate synchronization mechanism.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-changes-and-active-sessions\u0022\u003E21. Authorization Changes and Active Sessions\u003C/h2\u003E\n\u003Cp\u003ERole and permission changes may affect active users.\u003C/p\u003E\n\u003Cp\u003EHowever, the timing of those changes depends on how authorization data is resolved and cached by the server.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EAn administrator removes a role.\u003C/li\u003E\n\u003Cli\u003EThe server persists the new role assignment state.\u003C/li\u003E\n\u003Cli\u003EA subsequent authorization evaluation uses the effective authorization state available to the server.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EIf the application caches authorization information, changes may not become visible everywhere immediately.\u003C/p\u003E\n\u003Cp\u003EDo not assume that a client event invalidates every active session or refreshes every distributed authorization cache.\u003C/p\u003E\n\u003Cp\u003EFor sensitive operations, authorization should be evaluated against sufficiently current server-side state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022roles-are-not-sessions\u0022\u003ERoles Are Not Sessions\u003C/h3\u003E\n\u003Cp\u003ERemoving a role and revoking a session are different operations.\u003C/p\u003E\n\u003Cp\u003ERemoving a role changes authorization data.\u003C/p\u003E\n\u003Cp\u003ERevoking a session changes whether an authentication session remains valid.\u003C/p\u003E\n\u003Cp\u003EApplications may combine these operations when security policy requires immediate termination of access, but the concepts should remain separate.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/client-sdk/session-management\u0022\u003ESession Management\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022multi-tenant-authorization\u0022\u003E22. Multi-Tenant Authorization\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth authorization operations are scoped to the current tenant context.\u003C/p\u003E\n\u003Cp\u003ERole names are unique within the relevant tenant, and role assignments belong to the applicable tenant.\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\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\u003ETenant A\n |\n \u002B-- Manager\n | \u002B-- products.read\n |\n \u002B-- User A\n\nTenant B\n |\n \u002B-- Manager\n | \u002B-- reports.read\n |\n \u002B-- User B\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same role name may represent different permission sets in different tenants.\u003C/p\u003E\n\u003Cp\u003EThe client does not gain cross-tenant access merely by providing another user\u0027s \u003Ccode\u003EUserKey\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe server must enforce tenant isolation and administrative authorization.\u003C/p\u003E\n\u003Cp\u003EWhen implementing multi-tenant administration, do not assume role names or permission configurations are globally shared.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-authorization-errors\u0022\u003E23. Handling Authorization Errors\u003C/h2\u003E\n\u003Cp\u003EAuthorization methods return \u003Ccode\u003EUAuthResult\u003C/code\u003E or \u003Ccode\u003EUAuthResult\u0026lt;T\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-role-creation-errors\u0022\u003EHandle Role Creation Errors\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role created.\n}\nelse if (result.Http.IsConflict)\n{\n // A conflicting role may already exist.\n}\nelse if (result.Http.IsForbidden)\n{\n // Caller lacks permission.\n}\nelse if (result.Http.IsBadRequest)\n{\n // Invalid request.\n}\nelse\n{\n // Handle another failure.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-authorization-check-failures\u0022\u003EHandle Authorization Check Failures\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;update\u0026quot;,\n Resource = \u0026quot;products\u0026quot;\n });\n\nvar allowed = result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsAllowed: true };\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis fail-closed pattern is appropriate when determining whether the UI should expose a protected operation.\u003C/p\u003E\n\u003Cp\u003EUse the server\u0027s structured error information for diagnostics where appropriate.\u003C/p\u003E\n\u003Cp\u003EDo not expose sensitive policy internals unnecessarily.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations-1\u0022\u003E24. Security Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-authorization-is-authoritative\u0022\u003EServer Authorization Is Authoritative\u003C/h3\u003E\n\u003Cp\u003EClient-side checks improve usability but cannot protect an endpoint by themselves.\u003C/p\u003E\n\u003Cp\u003EEvery protected operation must enforce authorization on the server.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022follow-least-privilege\u0022\u003EFollow Least Privilege\u003C/h3\u003E\n\u003Cp\u003EAssign only the permissions a role actually needs.\u003C/p\u003E\n\u003Cp\u003EAvoid using \u003Ccode\u003E*\u003C/code\u003E or broad prefix wildcards for ordinary users.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-administrative-operations\u0022\u003EProtect Administrative Operations\u003C/h3\u003E\n\u003Cp\u003ERole creation, permission replacement, role assignment, and role deletion are privileged operations.\u003C/p\u003E\n\u003Cp\u003EThe server must authorize each mutation independently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022treat-permission-replacement-carefully\u0022\u003ETreat Permission Replacement Carefully\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E replaces the entire permission collection.\u003C/p\u003E\n\u003Cp\u003EAlways inspect the existing role configuration before submitting changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-reauthentication-requirements\u0022\u003EHandle Reauthentication Requirements\u003C/h3\u003E\n\u003Cp\u003EAn authorization decision can indicate \u003Ccode\u003ERequiresReauthentication\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EApplications should handle this state explicitly rather than treating it as an ordinary permission denial.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022respect-tenant-boundaries\u0022\u003ERespect Tenant Boundaries\u003C/h3\u003E\n\u003Cp\u003ERoles and assignments must remain within their authorized tenant context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-not-infer-effective-access-from-one-role\u0022\u003EDo Not Infer Effective Access from One Role\u003C/h3\u003E\n\u003Cp\u003EA user can have multiple roles, overlapping permissions, and additional policy restrictions.\u003C/p\u003E\n\u003Cp\u003EUse the server\u0027s authorization decision when determining whether an action is permitted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022account-for-cached-authorization-state\u0022\u003EAccount for Cached Authorization State\u003C/h3\u003E\n\u003Cp\u003EPermission changes may not propagate instantly to every active client or cached authorization context.\u003C/p\u003E\n\u003Cp\u003EDesign sensitive operations around server-side enforcement and appropriate cache invalidation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022putting-everything-together\u0022\u003E25. Putting Everything Together\u003C/h2\u003E\n\u003Cp\u003EA typical administrative authorization workflow may look like this:\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\u003ECreate role\n |\n v\nConfigure permissions\n |\n v\nAssign role to user\n |\n v\nUser requests protected action\n |\n v\nServer evaluates authorization\n |\n \u002B-- Allow --\u0026gt; Execute operation\n |\n \u002B-- Deny --\u0026gt; Reject operation\n |\n \u002B-- Reauthentication required\n |\n v\n Authenticate again\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUltimateAuth separates role management from authorization enforcement.\u003C/p\u003E\n\u003Cp\u003EThis allows applications to provide flexible administrative interfaces while keeping access decisions under server control.\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/plugin-domains/policies-access-control\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E \u2014 Understand application policies and access rules.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/authorization-pipeline\u0022\u003EAuthorization Pipeline Internals\u003C/a\u003E \u2014 Explore how authorization decisions are evaluated.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client-sdk/user-management\u0022\u003EUser Management\u003C/a\u003E \u2014 Manage user accounts and lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client-sdk/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Manage authenticated sessions and devices.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand session validity and security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth provides a unified client API for managing roles, assigning permissions, controlling user-role relationships, and requesting authorization decisions from the server.\u003C/p\u003E\n\u003Cp\u003EThrough \u003Ccode\u003EUAuthClient.Authorization\u003C/code\u003E, applications can build administrative authorization interfaces and check whether the current user is allowed to perform a particular action.\u003C/p\u003E\n\u003Cp\u003EAuthorization is enforced on the server. Client-side permission checks are useful for improving the user experience, but they do not replace authorization at the endpoint or application-service level.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the current \u003Ccode\u003EIAuthorizationClient\u003C/code\u003E API, practical role management workflows, permission patterns, and secure client integration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-authorization\u0022\u003E1. Understanding Authorization\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth authorization combines several concepts:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcept\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\u003ERoles\u003C/td\u003E\n\u003Ctd\u003ENamed collections of permissions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPermissions\u003C/td\u003E\n\u003Ctd\u003EFine-grained capabilities represented by normalized values\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUser-role assignments\u003C/td\u003E\n\u003Ctd\u003EAssociations between users and roles\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPolicies\u003C/td\u003E\n\u003Ctd\u003EServer-side rules that participate in access decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthorization context\u003C/td\u003E\n\u003Ctd\u003EInformation about the actor, resource, action, and current security state\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA typical authorization relationship looks like this:\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\u003EUser\n |\n \u002B-- Manager\n | |\n | \u002B-- products.read\n | \u002B-- products.create\n | \u002B-- products.update\n |\n \u002B-- Auditor\n |\n \u002B-- reports.read\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA user can have multiple roles, and each role can contain multiple permissions.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Cstrong\u003Ehaving a permission does not automatically guarantee access\u003C/strong\u003E. The server\u0027s authorization pipeline may apply additional requirements, such as resource ownership, session validity, tenant isolation, or application policies.\u003C/p\u003E\n\u003Cp\u003EFor the internal decision model, see \u003Ca href=\u0022/docs/security/authorization-pipeline\u0022\u003EAuthorization Pipeline Internals\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-authorization-client\u0022\u003E2. Accessing the Authorization Client\u003C/h2\u003E\n\u003Cp\u003EInject \u003Ccode\u003EIUAuthClient\u003C/code\u003E into a Blazor 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\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAuthorization operations are available through:\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\u003EUAuthClient.Authorization\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis property exposes \u003Ccode\u003EIAuthorizationClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe interface provides three main groups of operations:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthorization checks:\u003C/strong\u003E Evaluate whether the current user may perform an action.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERole management:\u003C/strong\u003E Create, query, rename, configure, and delete roles.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUser-role management:\u003C/strong\u003E Retrieve, assign, and remove user roles.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe SDK sends requests to the corresponding UltimateAuth endpoints and returns structured \u003Ccode\u003EUAuthResult\u003C/code\u003E values.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-permissions\u0022\u003E3. Understanding Permissions\u003C/h2\u003E\n\u003Cp\u003EA permission is represented by the \u003Ccode\u003EPermission\u003C/code\u003E value 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\u003Evar permission = Permission.From(\u0026quot;products.create\u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe current implementation normalizes permission values by trimming surrounding whitespace and converting the value to lowercase.\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\u003Evar permission = Permission.From(\u0026quot; PRODUCTS.CREATE \u0026quot;);\n\nConsole.WriteLine(permission.Value);\n// products.create\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe constructor rejects null, empty, and whitespace-only values.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-patterns\u0022\u003EPermission Patterns\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth recognizes three important forms:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPattern\u003C/th\u003E\n\u003Cth\u003EExample\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\u003EExact\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eproducts.create\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EA specific permission\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPrefix wildcard\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eproducts.*\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPermissions within the corresponding prefix\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EGlobal wildcard\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E*\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUnrestricted permission matching within the applicable authorization rules\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ECreate them using:\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 exact = Permission.From(\u0026quot;products.create\u0026quot;);\n\nvar prefix = Permission.From(\u0026quot;products.*\u0026quot;);\n\nvar global = Permission.Wildcard;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe type also exposes:\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\u003Epermission.IsWildcard;\npermission.IsPrefix;\npermission.IsValid;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-normalization\u0022\u003EPermission Normalization\u003C/h3\u003E\n\u003Cp\u003EThe following values normalize to the same representation:\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\u003EPermission.From(\u0026quot;Products.Create\u0026quot;);\nPermission.From(\u0026quot;products.create\u0026quot;);\nPermission.From(\u0026quot; PRODUCTS.CREATE \u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAll produce:\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\u003Eproducts.create\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse consistent permission names across application services, policies, and role configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022wildcards-are-powerful\u0022\u003EWildcards Are Powerful\u003C/h3\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\u003EPermission.From(\u0026quot;users.*\u0026quot;)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Emay grant broad access to operations within the \u003Ccode\u003Eusers\u003C/code\u003E permission prefix.\u003C/p\u003E\n\u003Cp\u003EThe global wildcard:\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\u003EPermission.Wildcard\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eis especially sensitive.\u003C/p\u003E\n\u003Cp\u003EAvoid assigning broad wildcard permissions merely to simplify development. Use least-privilege permissions for ordinary application roles.\u003C/p\u003E\n\u003Cp\u003EA wildcard is not a substitute for satisfying independent security policies, authentication requirements, or tenant restrictions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-authorization\u0022\u003E4. Check Authorization\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E evaluates whether the current authenticated user is authorized to perform a specific action against a resource.\u003C/p\u003E\n\u003Cp\u003EIt provides an \u003Cstrong\u003Eon-demand, server-side authorization decision\u003C/strong\u003E based on the current authentication context, permissions, roles, and applicable authorization policies.\u003C/p\u003E\n\u003Cp\u003EUnlike operations that perform protected actions, \u003Ccode\u003ECheckAsync\u003C/code\u003E only evaluates access. It does not execute the requested operation, modify permissions, grant access, or reserve authorization for a future request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-to-use-checkasync\u0022\u003EWhen to Use CheckAsync\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECheckAsync\u003C/code\u003E when your application needs to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDetermine whether an authenticated user can perform an action before presenting it in the UI.\u003C/li\u003E\n\u003Cli\u003EEnable, disable, or hide controls based on the current authorization decision.\u003C/li\u003E\n\u003Cli\u003EExplain why an operation is unavailable, using the returned denial reason where appropriate.\u003C/li\u003E\n\u003Cli\u003EDetermine whether an operation requires reauthentication.\u003C/li\u003E\n\u003Cli\u003EEvaluate resource-specific access when the applicable policies support it.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, an application may check whether the current user can delete a particular order before displaying the Delete button.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-checkasync-does-not-guarantee\u0022\u003EWhat CheckAsync Does Not Guarantee\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EA successful authorization check is not a guarantee that a subsequent operation will succeed.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAuthorization is evaluated against the security context available at the time of the check. That context may change before the actual operation is performed.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn administrator may revoke the user\u0027s permissions.\u003C/li\u003E\n\u003Cli\u003EThe user\u0027s session may expire or become invalid.\u003C/li\u003E\n\u003Cli\u003EThe target resource may be deleted or modified.\u003C/li\u003E\n\u003Cli\u003EThe resource\u0027s ownership or tenant context may change.\u003C/li\u003E\n\u003Cli\u003EAdditional business rules may prevent the operation.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe protected operation must independently enforce authorization using its current context.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ENever use a previous \u003Ccode\u003ECheckAsync\u003C/code\u003E result as proof of authorization for a later request.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E is primarily a developer-experience and user-experience API. It helps applications understand and present authorization decisions without replacing the security enforcement performed by protected endpoints and application services.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECheckAsync\u003C/code\u003E to ask the server whether the current user is authorized to perform an action.\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;read\u0026quot;,\n Resource = \u0026quot;products\u0026quot;\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsAllowed: true })\n{\n // The server authorized this action.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;AuthorizationResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorizationcheckrequest\u0022\u003EAuthorizationCheckRequest\u003C/h3\u003E\n\u003Cp\u003EThe request contains:\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\u003EType\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\u003EAction\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResource\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResource being accessed\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResourceId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional identifier for a specific resource instance\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EBoth \u003Ccode\u003EAction\u003C/code\u003E and \u003Ccode\u003EResource\u003C/code\u003E are required.\u003C/p\u003E\n\u003Cp\u003EThe action and resource values must follow the conventions understood by your server-side authorization configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-a-specific-resource\u0022\u003ECheck a Specific Resource\u003C/h3\u003E\n\u003Cp\u003EFor example, check access to a particular product:\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;update\u0026quot;,\n Resource = \u0026quot;products\u0026quot;,\n ResourceId = productId.ToString()\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EProviding \u003Ccode\u003EResourceId\u003C/code\u003E allows the server to evaluate resource-specific rules where supported by the configured authorization pipeline.\u003C/p\u003E\n\u003Cp\u003EFor example, the application may distinguish between updating any product and updating a product owned by the current user.\u003C/p\u003E\n\u003Cp\u003EDo not assume that sending \u003Ccode\u003EResourceId\u003C/code\u003E automatically enables ownership checking. The relevant server-side policy must implement the required rule.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-authorization-results\u0022\u003E5. Understanding Authorization Results\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E returns an \u003Ccode\u003EAuthorizationResult\u003C/code\u003E with 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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAllowed\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the request was authorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequiresReauthentication\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the decision indicates reauthentication is required\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDenyReason\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional denial reason code\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=\u0022authorization-decisions-vs.http-results\u0022\u003EAuthorization Decisions vs. HTTP Results\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E separates the outcome of the API request from the authorization decision itself.\u003C/p\u003E\n\u003Cp\u003EThe method returns \u003Ccode\u003EUAuthResult\u0026lt;AuthorizationResult\u0026gt;\u003C/code\u003E:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthResult.IsSuccess\u003C/code\u003E indicates whether the authorization check request completed successfully.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAuthorizationResult.IsAllowed\u003C/code\u003E indicates whether the requested action is currently authorized.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAuthorizationResult.RequiresReauthentication\u003C/code\u003E indicates whether additional authentication is required.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAuthorizationResult.DenyReason\u003C/code\u003E provides an optional reason for denial.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA denied authorization decision is a valid result of a successful authorization check.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EScenario\u003C/th\u003E\n\u003Cth\u003EHTTP Status\u003C/th\u003E\n\u003Cth\u003EAuthorization Result\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAction allowed\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E200 OK\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAllowed = true\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAction denied\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E200 OK\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsAllowed = false\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReauthentication required\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E200 OK\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequiresReauthentication = true\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECaller not authenticated\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E401 Unauthorized\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo authorization decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInvalid request\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E400 Bad Request\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo authorization decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EServer-side evaluation failure\u003C/td\u003E\n\u003Ctd\u003EAppropriate error status\u003C/td\u003E\n\u003Ctd\u003ENo authorization decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EHTTP 200 means the authorization evaluation completed successfully. It does not mean access was granted.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EOnly authenticated callers can use the current \u003Ccode\u003ECheckAsync\u003C/code\u003E endpoint. However, callers do not need permission to perform the action they are checking.\u003C/p\u003E\n\u003Cp\u003EThis distinction is intentional: the purpose of \u003Ccode\u003ECheckAsync\u003C/code\u003E is to determine whether the action is permitted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-denied-decisions\u0022\u003EHandling Denied Decisions\u003C/h3\u003E\n\u003Cp\u003EApplications should distinguish between authorization denial, reauthentication requirements, and request failures.\u003C/p\u003E\n\u003Cp\u003EA denied decision may be used to disable an action or display an appropriate message.\u003C/p\u003E\n\u003Cp\u003EA reauthentication requirement may be used to initiate the application\u0027s supported reauthentication flow.\u003C/p\u003E\n\u003Cp\u003EA failed request must not be interpreted as permission to proceed.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EFail closed:\u003C/strong\u003E If the authorization decision is unavailable, treat the operation as unavailable in the UI and rely on server-side authorization when the actual operation is requested.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-all-decision-outcomes\u0022\u003EHandle All Decision Outcomes\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;delete\u0026quot;,\n Resource = \u0026quot;products\u0026quot;,\n ResourceId = productId.ToString()\n });\n\nif (!result.IsSuccess)\n{\n // The authorization request itself failed.\n}\nelse if (result.Value is { RequiresReauthentication: true })\n{\n // Ask the user to authenticate again.\n}\nelse if (result.Value is { IsAllowed: true })\n{\n // The operation is allowed.\n}\nelse\n{\n // The operation was denied.\n var reason = result.Value?.DenyReason;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn unsuccessful API request and an explicit authorization denial are different outcomes.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003Eresult.IsSuccess == false\u003C/code\u003E means the authorization request did not complete successfully at the API level.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eresult.Value.IsAllowed == false\u003C/code\u003E means the server returned a denial decision.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eresult.Value.RequiresReauthentication == true\u003C/code\u003E indicates that the caller needs to reestablish authentication.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ETreat failed or unavailable authorization checks as \u003Cstrong\u003Enot authorized\u003C/strong\u003E in the UI.\u003C/p\u003E\n\u003Cp\u003ENever grant access because an authorization request failed.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-authorization-checks-in-blazor\u0022\u003E6. Use Authorization Checks in Blazor\u003C/h2\u003E\n\u003Cp\u003EA common use case is controlling whether an action button should be displayed.\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.Authorization.Contracts\n@inject IUAuthClient UAuthClient\n\n@if (_canCreate)\n{\n \u0026lt;button @onclick=\u0026quot;CreateProductAsync\u0026quot;\u0026gt;Create Product\u0026lt;/button\u0026gt;\n}\n\n@code {\n private bool _canCreate;\n\n protected override async Task OnInitializedAsync()\n {\n var result = await UAuthClient.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;create\u0026quot;,\n Resource = \u0026quot;products\u0026quot;\n });\n\n _canCreate = result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsAllowed: true };\n }\n\n private Task CreateProductAsync()\n {\n // Invoke the protected application operation.\n return Task.CompletedTask;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis improves the interface by hiding unavailable actions.\u003C/p\u003E\n\u003Cp\u003EHowever, the endpoint handling product creation must still enforce authorization independently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-client-checks-are-not-security-boundaries\u0022\u003EWhy Client Checks Are Not Security Boundaries\u003C/h3\u003E\n\u003Cp\u003EA user can bypass a hidden button and send a request directly to an endpoint.\u003C/p\u003E\n\u003Cp\u003ETherefore:\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\u003EClient authorization check\n |\n v\nImprove the user experience\n |\n v\nUser submits an operation\n |\n v\nServer independently authorizes the operation\n |\n v\nExecute or deny\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EClient authorization checks should never be the only protection for sensitive operations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-my-roles\u0022\u003E7. Get My Roles\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMyRolesAsync\u003C/code\u003E to retrieve roles assigned to the current authenticated 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.Authorization.GetMyRolesAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n foreach (var role in data.Roles.Items)\n {\n Console.WriteLine(role.Name);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;UserRolesGetResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userrolesgetresult\u0022\u003EUserRolesGetResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser whose roles were requested\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoles\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPaginated collection of role assignments\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EEach entry is a \u003Ccode\u003EUserRoleInfo\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userroleinfo\u0022\u003EUserRoleInfo\u003C/h3\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant associated with the assignment\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssigned user\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssigned role\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole name\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAssignedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssignment timestamp\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=\u0022pagination\u0022\u003EPagination\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.Authorization.GetMyRolesAsync(\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe returned role collection is paginated.\u003C/p\u003E\n\u003Cp\u003EDo not assume that the first page contains all assigned roles.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-my-permissions\u0022\u003E8. Get My Permissions\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EGetMyPermissionsAsync()\u003C/code\u003E to retrieve the permissions assigned to the currently authenticated user through their roles.\u003C/p\u003E\n\u003Cp\u003EUnlike \u003Ccode\u003EGetMyRolesAsync()\u003C/code\u003E, which returns role assignments, this method returns the resulting permission collection.\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.Authorization.GetMyPermissionsAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n foreach (var permission in data.Permissions)\n {\n Console.WriteLine(permission.Value);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method is self-service and does not require administrative permission.\u003C/p\u003E\n\u003Cp\u003EThe user identity and tenant context are derived from the authenticated session. No user identifier or tenant identifier is accepted as a request parameter.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userpermissionsresult\u0022\u003EUserPermissionsResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains the user\u0027s role-derived permissions:\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 UserPermissionsResult\n{\n public required IReadOnlyList\u0026lt;Permission\u0026gt; Permissions { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe returned collection:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECombines permissions from the user\u0027s assigned roles.\u003C/li\u003E\n\u003Cli\u003ERemoves duplicate permissions.\u003C/li\u003E\n\u003Cli\u003EUses deterministic ordering.\u003C/li\u003E\n\u003Cli\u003EPreserves wildcard permissions such as \u003Ccode\u003Eusers.*\u003C/code\u003E and \u003Ccode\u003E*\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EReturns an empty collection when no permissions are assigned.\u003C/li\u003E\n\u003Cli\u003EReflects the current permissions resolved from the configured authorization stores.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, a user assigned multiple roles may receive:\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\u003Ereports.*\nusers.read.admin\nusers.update.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe result does not identify which role contributed each permission.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assigned-permissions-vs.authorization-decisions\u0022\u003EAssigned Permissions vs. Authorization Decisions\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EAssigned permissions are not final authorization decisions.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA user may possess a permission but still be denied access because of an applicable policy, tenant boundary, target restriction, or other security requirement.\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\u003Evar permissions = await UAuthClient.Authorization.GetMyPermissionsAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis retrieves the user\u0027s assigned permissions.\u003C/p\u003E\n\u003Cp\u003ETo determine whether a specific operation is currently authorized, use \u003Ccode\u003ECheckAsync()\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;users.delete.admin\u0026quot;,\n Resource = \u0026quot;users\u0026quot;,\n ResourceId = targetUserKey.Value\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } decision)\n{\n if (decision.IsAllowed)\n {\n // The authorization check allowed this operation.\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEven a successful client-side authorization check does not replace authorization enforcement at the server-side operation boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022wildcard-permissions\u0022\u003EWildcard Permissions\u003C/h3\u003E\n\u003Cp\u003EWildcard permissions are returned without expansion.\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\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\u003Eusers.*\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eremains \u003Ccode\u003Eusers.*\u003C/code\u003E rather than being expanded into individual user-management actions.\u003C/p\u003E\n\u003Cp\u003EThis preserves the stored permission semantics and avoids depending on a potentially incomplete action catalog.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003ESecurity Considerations\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EGetMyPermissionsAsync()\u003C/code\u003E is intended for permission inspection, user interfaces, and administrative transparency.\u003C/p\u003E\n\u003Cp\u003EIt must not be used as the sole mechanism for protecting sensitive operations.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for evaluating authorization policies and enforcing access at the actual execution boundary.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/client-sdk/authorization#4-check-authorization\u0022\u003ECheck Authorization\u003C/a\u003E for policy-aware authorization checks.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-another-users-roles\u0022\u003E9. Get Another User\u0027s Roles\u003C/h2\u003E\n\u003Cp\u003EAdministrative interfaces can retrieve the roles assigned to a specific 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.Authorization.GetUserRolesAsync(userKey,\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n foreach (var role in data.Roles.Items)\n {\n Console.WriteLine($\u0026quot;{role.Name} \u2014 {role.AssignedAt}\u0026quot;);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis operation requires appropriate administrative authorization.\u003C/p\u003E\n\u003Cp\u003EThe \u003Ccode\u003EUserKey\u003C/code\u003E identifies the target user, not the current actor.\u003C/p\u003E\n\u003Cp\u003EThe server determines whether the current actor may inspect that user\u0027s role assignments.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022query-roles\u0022\u003E10. Query Roles\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EQueryRolesAsync\u003C/code\u003E to retrieve roles with pagination and optional filtering.\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var role in page.Items)\n {\n Console.WriteLine(role.Name);\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;PagedResult\u0026lt;RoleInfo\u0026gt;\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rolequery\u0022\u003ERoleQuery\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ERoleQuery\u003C/code\u003E inherits from \u003Ccode\u003EPageRequest\u003C/code\u003E and adds:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESearch\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional role-name search\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIncludeDeleted\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EInclude deleted roles when supported and authorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPageNumber\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested page\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPageSize\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequested page size\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=\u0022search-roles\u0022\u003ESearch Roles\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n Search = \u0026quot;manager\u0026quot;,\n PageNumber = 1,\n PageSize = 10\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022include-deleted-roles\u0022\u003EInclude Deleted Roles\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n IncludeDeleted = true,\n PageNumber = 1,\n PageSize = 20\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003EIncludeDeleted\u003C/code\u003E only where the application needs administrative access to deleted role records.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for enforcing the query\u0027s permissions and lifecycle rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-roleinfo\u0022\u003E11. Understanding RoleInfo\u003C/h2\u003E\n\u003Cp\u003EEach role returned by the SDK is represented by \u003Ccode\u003ERoleInfo\u003C/code\u003E.\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\u003EType\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\u003EId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole name\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPermissions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIReadOnlyCollection\u0026lt;Permission\u0026gt;\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAssigned permissions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreation timestamp\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUpdatedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EDateTimeOffset?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELast update timestamp\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.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n PageNumber = 1,\n PageSize = 20\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n{\n foreach (var role in page.Items)\n {\n Console.WriteLine($\u0026quot;Role: {role.Name}\u0026quot;);\n\n foreach (var permission in role.Permissions)\n {\n Console.WriteLine($\u0026quot; {permission.Value}\u0026quot;);\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ERoleId\u003C/code\u003E is a strongly typed identifier rather than a plain \u003Ccode\u003EGuid\u003C/code\u003E.\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\u003ERoleId roleId = RoleId.From(existingGuid);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERoleId\u003C/code\u003E for role management requests.\u003C/p\u003E\n\u003Cp\u003EDo not confuse it with \u003Ccode\u003EUserKey\u003C/code\u003E or other identifier types.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-role\u0022\u003E12. Create a Role\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECreateRoleAsync\u003C/code\u003E to create a role.\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } role)\n{\n Console.WriteLine($\u0026quot;Created: {role.Name}\u0026quot;);\n Console.WriteLine($\u0026quot;Role ID: {role.Id}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;RoleInfo\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERole names must be unique within the relevant tenant according to the server\u0027s role-name rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-role-with-permissions\u0022\u003ECreate a Role with Permissions\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECreateRoleRequest\u003C/code\u003E supports an optional initial permission collection.\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;ProductManager\u0026quot;,\n Permissions =\n [\n Permission.From(\u0026quot;products.read\u0026quot;),\n Permission.From(\u0026quot;products.create\u0026quot;),\n Permission.From(\u0026quot;products.update\u0026quot;)\n ]\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows applications to create and configure a role in one operation.\u003C/p\u003E\n\u003Cp\u003EThe server validates the requested permissions and whether the caller is authorized to create the role.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-read-only-role\u0022\u003ECreate a Read-Only Role\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;Auditor\u0026quot;,\n Permissions =\n [\n Permission.From(\u0026quot;reports.read\u0026quot;),\n Permission.From(\u0026quot;products.read\u0026quot;)\n ]\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse role names that describe the responsibility or capability represented by the role.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rename-a-role\u0022\u003E13. Rename a Role\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERenameRoleAsync\u003C/code\u003E to change a role\u0027s name.\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.Authorization.RenameRoleAsync(\n new RenameRoleRequest\n {\n Id = roleId,\n Name = \u0026quot;SeniorManager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role renamed.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ERenameRoleRequest\u003C/code\u003E requires:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EId\u003C/code\u003E: The role\u0027s \u003Ccode\u003ERoleId\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EName\u003C/code\u003E: The new role name.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe operation may fail if another role already uses the requested name.\u003C/p\u003E\n\u003Cp\u003ERole renaming does not mean that permissions are automatically changed.\u003C/p\u003E\n\u003Cp\u003EAfter a successful rename, the SDK publishes an \u003Ccode\u003EAuthorizationChanged\u003C/code\u003E state event.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022set-role-permissions\u0022\u003E14. Set Role Permissions\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E to configure a role\u0027s permissions.\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.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = roleId,\n Permissions =\n [\n Permission.From(\u0026quot;products.read\u0026quot;),\n Permission.From(\u0026quot;products.create\u0026quot;),\n Permission.From(\u0026quot;products.update\u0026quot;)\n ]\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERole to modify\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPermissions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENew permission collection\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=\u0022important-this-is-a-replacement-operation\u0022\u003EImportant: This Is a Replacement Operation\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E replaces the role\u0027s existing permission collection.\u003C/p\u003E\n\u003Cp\u003EIt does not append permissions to the current collection.\u003C/p\u003E\n\u003Cp\u003EFor example, suppose a role currently contains:\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\u003Eproducts.read\nproducts.create\nproducts.update\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ESubmitting:\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\u003EPermissions =\n[\n Permission.From(\u0026quot;products.read\u0026quot;)\n]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Emeans the intended new permission collection contains only:\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\u003Eproducts.read\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe other permissions are removed from the role.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022safely-add-a-permission-to-an-existing-role\u0022\u003ESafely Add a Permission to an Existing Role\u003C/h3\u003E\n\u003Cp\u003ETo preserve existing permissions, first retrieve the role and construct the complete desired collection.\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 query = await UAuthClient.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n Search = \u0026quot;ProductManager\u0026quot;,\n PageNumber = 1,\n PageSize = 50\n });\n\nif (query.IsSuccess \u0026amp;\u0026amp; query.Value is { } page)\n{\n var role = page.Items.FirstOrDefault(x =\u0026gt; x.Name == \u0026quot;ProductManager\u0026quot;);\n\n if (role is not null)\n {\n var permissions = role.Permissions.Append(Permission.From(\u0026quot;products.delete\u0026quot;)).Distinct().ToArray();\n\n var result =\n await UAuthClient.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = role.Id,\n Permissions = permissions\n });\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is a read-modify-write pattern, not an atomic append operation.\u003C/p\u003E\n\u003Cp\u003EIf multiple administrators edit the same role concurrently, the last successful replacement may overwrite another administrator\u0027s changes.\u003C/p\u003E\n\u003Cp\u003EApplications requiring concurrency-safe editing should implement suitable coordination or version checking at the application level.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-a-permission\u0022\u003ERemove a Permission\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 updatedPermissions = role.Permissions.Where(x =\u0026gt; x.Value != \u0026quot;products.delete\u0026quot;).ToArray();\n\nvar result = await UAuthClient.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = role.Id,\n Permissions = updatedPermissions\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAgain, submit the complete desired permission set.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022clear-all-permissions\u0022\u003EClear All Permissions\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.Authorization.SetRolePermissionsAsync(\n new SetRolePermissionsRequest\n {\n RoleId = roleId,\n Permissions = []\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn empty collection requests that the role have no permissions.\u003C/p\u003E\n\u003Cp\u003EThe server determines whether the operation is permitted.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-a-role-to-a-user\u0022\u003E15. Assign a Role to a User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EAssignRoleToUserAsync\u003C/code\u003E to assign an existing role to a 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.Authorization.AssignRoleToUserAsync(\n new AssignRoleRequest\n {\n UserKey = userKey,\n RoleName = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role assigned.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserKey\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETarget user\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExisting role name\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe role must exist, and the current actor must be authorized to assign it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-multiple-roles\u0022\u003EAssign Multiple Roles\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\u003Eforeach (var roleName in new[] { \u0026quot;Manager\u0026quot;, \u0026quot;Auditor\u0026quot; })\n{\n var result = await UAuthClient.Authorization.AssignRoleToUserAsync(\n new AssignRoleRequest\n {\n UserKey = userKey,\n RoleName = roleName\n });\n\n if (!result.IsSuccess)\n {\n // Handle the failed assignment.\n break;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach call is a separate operation.\u003C/p\u003E\n\u003Cp\u003EThis example does not provide transactional, all-or-nothing behavior across the entire collection.\u003C/p\u003E\n\u003Cp\u003EFor complex provisioning workflows, consider whether the application needs a server-side orchestration operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-a-role-from-a-user\u0022\u003E16. Remove a Role from a User\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERemoveRoleFromUserAsync\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.Authorization.RemoveRoleFromUserAsync(\n new RemoveRoleRequest\n {\n UserKey = userKey,\n RoleName = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role removed.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERemoving a role changes the user\u0027s role assignments.\u003C/p\u003E\n\u003Cp\u003EIt does not delete the role itself.\u003C/p\u003E\n\u003Cp\u003EOther users assigned to that role are unaffected by this particular operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-effective-permissions-may-remain\u0022\u003EImportant: Effective Permissions May Remain\u003C/h3\u003E\n\u003Cp\u003EA user may receive the same permission through multiple roles.\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\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\u003EUser\n |\n \u002B-- Manager\n | \u002B-- products.read\n |\n \u002B-- Auditor\n \u002B-- products.read\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERemoving \u003Ccode\u003EManager\u003C/code\u003E does not necessarily remove the user\u0027s ability to read products because \u003Ccode\u003EAuditor\u003C/code\u003E still provides \u003Ccode\u003Eproducts.read\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ETo determine whether the user may perform an action, rely on the server\u0027s authorization decision rather than inspecting one role assignment in isolation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-a-role\u0022\u003E17. Delete a Role\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EDeleteRoleAsync\u003C/code\u003E to delete a role.\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.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Soft\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request supports a deletion mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hard-delete\u0022\u003EHard Delete\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.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Hard\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe availability and effect of each deletion mode are determined by server-side lifecycle rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022roledeleteresult\u0022\u003ERoleDeleteResult\u003C/h3\u003E\n\u003Cp\u003EThe method 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\u003EUAuthResult\u0026lt;RoleDeleteResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe response contains:\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERoleId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDeleted role\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERemovedAssignments\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENumber of user-role assignments removed\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EApplied deletion mode\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeletedAt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDeletion timestamp\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-the-result\u0022\u003EInspect the Result\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.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Soft\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } deleted)\n{\n Console.WriteLine($\u0026quot;Removed assignments: {deleted.RemovedAssignments}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDeleting a role removes its assignments from users.\u003C/p\u003E\n\u003Cp\u003EThis can affect multiple users at once.\u003C/p\u003E\n\u003Cp\u003EFor that reason, role deletion should be presented as a sensitive administrative operation with appropriate confirmation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-role-management-page\u0022\u003E18. Build a Role Management Page\u003C/h2\u003E\n\u003Cp\u003EThe following Blazor example demonstrates querying roles, creating a role, and deleting a selected role.\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.Authorization.Contracts\n@using CodeBeam.UltimateAuth.Core.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Role Management\u0026lt;/h3\u0026gt;\n\n\u0026lt;input @bind=\u0026quot;_search\u0026quot; placeholder=\u0026quot;Search roles\u0026quot; /\u0026gt;\n\n\u0026lt;button @onclick=\u0026quot;LoadRolesAsync\u0026quot;\u0026gt;Search\u0026lt;/button\u0026gt;\n\n@if (_loading)\n{\n \u0026lt;p\u0026gt;Loading roles...\u0026lt;/p\u0026gt;\n}\nelse\n{\n @foreach (var role in _roles)\n {\n \u0026lt;div @key=\u0026quot;role.Id\u0026quot;\u0026gt;\n \u0026lt;strong\u0026gt;@role.Name\u0026lt;/strong\u0026gt;\n \u0026lt;span\u0026gt;@role.Permissions.Count permissions\u0026lt;/span\u0026gt;\n \u0026lt;button @onclick=\u0026quot;() =\u0026gt; DeleteRoleAsync(role.Id)\u0026quot;\u0026gt;Delete\u0026lt;/button\u0026gt;\n \u0026lt;/div\u0026gt;\n }\n}\n\n\u0026lt;h4\u0026gt;Create Role\u0026lt;/h4\u0026gt;\n\u0026lt;input @bind=\u0026quot;_newRoleName\u0026quot; placeholder=\u0026quot;Role name\u0026quot; /\u0026gt;\n\u0026lt;button @onclick=\u0026quot;CreateRoleAsync\u0026quot; disabled=\u0026quot;@_busy\u0026quot;\u0026gt;Create\u0026lt;/button\u0026gt;\n\n@if (_message is not null)\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private IReadOnlyList\u0026lt;RoleInfo\u0026gt; _roles = [];\n\n private string _search = \u0026quot;\u0026quot;;\n private string _newRoleName = \u0026quot;\u0026quot;;\n private string? _message;\n\n private bool _loading = true;\n private bool _busy;\n\n protected override async Task OnInitializedAsync()\n {\n await LoadRolesAsync();\n }\n\n private async Task LoadRolesAsync()\n {\n _loading = true;\n\n try\n {\n var result = await UAuthClient.Authorization.QueryRolesAsync(\n new RoleQuery\n {\n Search = _search,\n PageNumber = 1,\n PageSize = 50\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } page)\n {\n _roles = page.Items;\n }\n else\n {\n _message = \u0026quot;Unable to load roles.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _message = \u0026quot;The authorization service is unavailable.\u0026quot;;\n }\n finally\n {\n _loading = false;\n }\n }\n\n private async Task CreateRoleAsync()\n {\n if (_busy || string.IsNullOrWhiteSpace(_newRoleName))\n return;\n\n _busy = true;\n _message = null;\n\n try\n {\n var result = await UAuthClient.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = _newRoleName\n });\n\n if (result.IsSuccess)\n {\n _newRoleName = \u0026quot;\u0026quot;;\n await LoadRolesAsync();\n }\n else\n {\n _message = \u0026quot;Unable to create role.\u0026quot;;\n }\n }\n finally\n {\n _busy = false;\n }\n }\n\n private async Task DeleteRoleAsync(RoleId roleId)\n {\n // Add a confirmation dialog before deletion in production.\n\n var result = await UAuthClient.Authorization.DeleteRoleAsync(\n new DeleteRoleRequest\n {\n Id = roleId,\n Mode = DeleteMode.Soft\n });\n\n if (result.IsSuccess)\n {\n await LoadRolesAsync();\n }\n else\n {\n _message = \u0026quot;Unable to delete role.\u0026quot;;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example demonstrates the SDK integration rather than a complete production administration interface.\u003C/p\u003E\n\u003Cp\u003EA production implementation should include pagination, role assignment controls, permission editing, deletion confirmation, and appropriate loading and error handling for every operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-user-role-assignment-interface\u0022\u003E19. Build a User Role Assignment Interface\u003C/h2\u003E\n\u003Cp\u003EAn administrative user details page can display assigned roles and allow administrators to add or remove them.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022load-user-roles\u0022\u003ELoad User Roles\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.Authorization.GetUserRolesAsync(userKey,\n new PageRequest\n {\n PageNumber = 1,\n PageSize = 50\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } data)\n{\n var assignedRoles = data.Roles.Items.ToList();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-a-selected-role\u0022\u003EAssign a Selected Role\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.Authorization.AssignRoleToUserAsync(\n new AssignRoleRequest\n {\n UserKey = userKey,\n RoleName = selectedRoleName\n });\n\nif (result.IsSuccess)\n{\n // Reload the user\u0027s assigned roles.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-a-selected-role\u0022\u003ERemove a Selected Role\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.Authorization.RemoveRoleFromUserAsync(\n new RemoveRoleRequest\n {\n UserKey = userKey,\n RoleName = selectedRoleName\n });\n\nif (result.IsSuccess)\n{\n // Reload the user\u0027s assigned roles.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis pattern works well alongside \u003Ccode\u003EUAuthClient.Users\u003C/code\u003E for building a unified user administration interface.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/client-sdk/user-management\u0022\u003EUser Management\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-state-events\u0022\u003E20. Authorization State Events\u003C/h2\u003E\n\u003Cp\u003EThe current \u003Ccode\u003EUAuthAuthorizationClient\u003C/code\u003E publishes:\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\u003EUAuthStateEvent.AuthorizationChanged\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eafter successful authorization mutations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EPublishes \u003Ccode\u003EAuthorizationChanged\u003C/code\u003E\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAssignRoleToUserAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERemoveRoleFromUserAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERenameRoleAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeleteRoleAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECreateRoleAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EQueryRolesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EGetMyRolesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EGetUserRolesAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECheckAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe event follows the configured client state event handling mode.\u003C/p\u003E\n\u003Cp\u003EIt can be used to refresh navigation menus, role assignment views, or permission-sensitive UI.\u003C/p\u003E\n\u003Cp\u003EHowever, it is a \u003Cstrong\u003Elocal client event\u003C/strong\u003E, not a guarantee that all active clients or distributed caches have immediately observed the change.\u003C/p\u003E\n\u003Cp\u003EFor example, an administrator assigning a role to another user does not automatically mean that user\u0027s open browser receives the administrator\u0027s local event.\u003C/p\u003E\n\u003Cp\u003EApplications requiring cross-client updates should provide an appropriate synchronization mechanism.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-changes-and-active-sessions\u0022\u003E21. Authorization Changes and Active Sessions\u003C/h2\u003E\n\u003Cp\u003ERole and permission changes may affect active users.\u003C/p\u003E\n\u003Cp\u003EHowever, the timing of those changes depends on how authorization data is resolved and cached by the server.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EAn administrator removes a role.\u003C/li\u003E\n\u003Cli\u003EThe server persists the new role assignment state.\u003C/li\u003E\n\u003Cli\u003EA subsequent authorization evaluation uses the effective authorization state available to the server.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EIf the application caches authorization information, changes may not become visible everywhere immediately.\u003C/p\u003E\n\u003Cp\u003EDo not assume that a client event invalidates every active session or refreshes every distributed authorization cache.\u003C/p\u003E\n\u003Cp\u003EFor sensitive operations, authorization should be evaluated against sufficiently current server-side state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022roles-are-not-sessions\u0022\u003ERoles Are Not Sessions\u003C/h3\u003E\n\u003Cp\u003ERemoving a role and revoking a session are different operations.\u003C/p\u003E\n\u003Cp\u003ERemoving a role changes authorization data.\u003C/p\u003E\n\u003Cp\u003ERevoking a session changes whether an authentication session remains valid.\u003C/p\u003E\n\u003Cp\u003EApplications may combine these operations when security policy requires immediate termination of access, but the concepts should remain separate.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/client-sdk/session-management\u0022\u003ESession Management\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022multi-tenant-authorization\u0022\u003E22. Multi-Tenant Authorization\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth authorization operations are scoped to the current tenant context.\u003C/p\u003E\n\u003Cp\u003ERole names are unique within the relevant tenant, and role assignments belong to the applicable tenant.\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\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\u003ETenant A\n |\n \u002B-- Manager\n | \u002B-- products.read\n |\n \u002B-- User A\n\nTenant B\n |\n \u002B-- Manager\n | \u002B-- reports.read\n |\n \u002B-- User B\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same role name may represent different permission sets in different tenants.\u003C/p\u003E\n\u003Cp\u003EThe client does not gain cross-tenant access merely by providing another user\u0027s \u003Ccode\u003EUserKey\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe server must enforce tenant isolation and administrative authorization.\u003C/p\u003E\n\u003Cp\u003EWhen implementing multi-tenant administration, do not assume role names or permission configurations are globally shared.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-authorization-errors\u0022\u003E23. Handling Authorization Errors\u003C/h2\u003E\n\u003Cp\u003EAuthorization methods return \u003Ccode\u003EUAuthResult\u003C/code\u003E or \u003Ccode\u003EUAuthResult\u0026lt;T\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-role-creation-errors\u0022\u003EHandle Role Creation Errors\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.Authorization.CreateRoleAsync(\n new CreateRoleRequest\n {\n Name = \u0026quot;Manager\u0026quot;\n });\n\nif (result.IsSuccess)\n{\n // Role created.\n}\nelse if (result.Http.IsConflict)\n{\n // A conflicting role may already exist.\n}\nelse if (result.Http.IsForbidden)\n{\n // Caller lacks permission.\n}\nelse if (result.Http.IsBadRequest)\n{\n // Invalid request.\n}\nelse\n{\n // Handle another failure.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-authorization-check-failures\u0022\u003EHandle Authorization Check Failures\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.Authorization.CheckAsync(\n new AuthorizationCheckRequest\n {\n Action = \u0026quot;update\u0026quot;,\n Resource = \u0026quot;products\u0026quot;\n });\n\nvar allowed = result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsAllowed: true };\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis fail-closed pattern is appropriate when determining whether the UI should expose a protected operation.\u003C/p\u003E\n\u003Cp\u003EUse the server\u0027s structured error information for diagnostics where appropriate.\u003C/p\u003E\n\u003Cp\u003EDo not expose sensitive policy internals unnecessarily.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations-1\u0022\u003E24. Security Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-authorization-is-authoritative\u0022\u003EServer Authorization Is Authoritative\u003C/h3\u003E\n\u003Cp\u003EClient-side checks improve usability but cannot protect an endpoint by themselves.\u003C/p\u003E\n\u003Cp\u003EEvery protected operation must enforce authorization on the server.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022follow-least-privilege\u0022\u003EFollow Least Privilege\u003C/h3\u003E\n\u003Cp\u003EAssign only the permissions a role actually needs.\u003C/p\u003E\n\u003Cp\u003EAvoid using \u003Ccode\u003E*\u003C/code\u003E or broad prefix wildcards for ordinary users.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-administrative-operations\u0022\u003EProtect Administrative Operations\u003C/h3\u003E\n\u003Cp\u003ERole creation, permission replacement, role assignment, and role deletion are privileged operations.\u003C/p\u003E\n\u003Cp\u003EThe server must authorize each mutation independently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022treat-permission-replacement-carefully\u0022\u003ETreat Permission Replacement Carefully\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ESetRolePermissionsAsync\u003C/code\u003E replaces the entire permission collection.\u003C/p\u003E\n\u003Cp\u003EAlways inspect the existing role configuration before submitting changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-reauthentication-requirements\u0022\u003EHandle Reauthentication Requirements\u003C/h3\u003E\n\u003Cp\u003EAn authorization decision can indicate \u003Ccode\u003ERequiresReauthentication\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EApplications should handle this state explicitly rather than treating it as an ordinary permission denial.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022respect-tenant-boundaries\u0022\u003ERespect Tenant Boundaries\u003C/h3\u003E\n\u003Cp\u003ERoles and assignments must remain within their authorized tenant context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-not-infer-effective-access-from-one-role\u0022\u003EDo Not Infer Effective Access from One Role\u003C/h3\u003E\n\u003Cp\u003EA user can have multiple roles, overlapping permissions, and additional policy restrictions.\u003C/p\u003E\n\u003Cp\u003EUse the server\u0027s authorization decision when determining whether an action is permitted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022account-for-cached-authorization-state\u0022\u003EAccount for Cached Authorization State\u003C/h3\u003E\n\u003Cp\u003EPermission changes may not propagate instantly to every active client or cached authorization context.\u003C/p\u003E\n\u003Cp\u003EDesign sensitive operations around server-side enforcement and appropriate cache invalidation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022putting-everything-together\u0022\u003E25. Putting Everything Together\u003C/h2\u003E\n\u003Cp\u003EA typical administrative authorization workflow may look like this:\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\u003ECreate role\n |\n v\nConfigure permissions\n |\n v\nAssign role to user\n |\n v\nUser requests protected action\n |\n v\nServer evaluates authorization\n |\n \u002B-- Allow --\u0026gt; Execute operation\n |\n \u002B-- Deny --\u0026gt; Reject operation\n |\n \u002B-- Reauthentication required\n |\n v\n Authenticate again\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUltimateAuth separates role management from authorization enforcement.\u003C/p\u003E\n\u003Cp\u003EThis allows applications to provide flexible administrative interfaces while keeping access decisions under server control.\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/plugin-domains/policies\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E \u2014 Understand application policies and access rules.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/authorization-pipeline\u0022\u003EAuthorization Pipeline Internals\u003C/a\u003E \u2014 Explore how authorization decisions are evaluated.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client-sdk/user-management\u0022\u003EUser Management\u003C/a\u003E \u2014 Manage user accounts and lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client-sdk/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Manage authenticated sessions and devices.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand session validity and security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "understanding-authorization", @@ -364,6 +364,6 @@ "Level": 0 } ], - "SourceHash": "BCD3447FF02C9E98B5D0102E7B99CAB9C7A205F4EEE828339D99D9BFB9801A2F", + "SourceHash": "D14F973C8368A06A394A9B3DE6F13B191FBD8730DA9E9B990023C548FE4F1518", "BuilderVersion": "1" } \ No newline at end of file