From f0109b4a1db658c91f6ab877960f2440c9a1ad80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Fri, 16 Oct 2026 11:32:03 +0300 Subject: [PATCH 1/7] docs: revamp content and improve documentation UI --- docs/content/fundamentals/auth-model.md | 439 +++++++++--- docs/content/fundamentals/auth-modes.md | 651 +++++++++++++++--- docs/content/fundamentals/client-profiles.md | 442 +++++++++--- docs/content/fundamentals/flow-based-auth.md | 396 +++++++---- .../content/fundamentals/request-lifecycle.md | 393 ++++++++--- .../fundamentals/runtime-architecture.md | 608 +++++++++++++--- .../Pages/DocsContent.razor | 13 +- .../docs/auth-flows/device-management.json | 2 +- .../wwwroot/docs/auth-flows/index.json | 2 +- .../wwwroot/docs/auth-flows/login-flow.json | 2 +- .../wwwroot/docs/auth-flows/logout-flow.json | 2 +- .../wwwroot/docs/auth-flows/refresh-flow.json | 2 +- .../docs/auth-flows/session-lifecycle.json | 2 +- .../docs/auth-flows/token-behavior.json | 2 +- .../wwwroot/docs/client/authentication.json | 2 +- .../wwwroot/docs/client/authorization.json | 2 +- .../wwwroot/docs/client/credentials.json | 2 +- .../wwwroot/docs/client/identifiers.json | 2 +- .../wwwroot/docs/client/index.json | 2 +- .../docs/client/session-management.json | 2 +- .../wwwroot/docs/client/user-management.json | 2 +- .../configuration/advanced-configuration.json | 2 +- .../docs/configuration/client-options.json | 2 +- .../configuration/configuration-overview.json | 2 +- .../configuration/configuration-sources.json | 2 +- .../wwwroot/docs/configuration/index.json | 2 +- .../docs/configuration/server-options.json | 2 +- .../wwwroot/docs/docs-index.json | 2 +- .../wwwroot/docs/fundamentals/auth-model.json | 140 +++- .../wwwroot/docs/fundamentals/auth-modes.json | 158 ++++- .../docs/fundamentals/client-profiles.json | 129 +++- .../docs/fundamentals/flow-based-auth.json | 89 ++- .../docs/fundamentals/request-lifecycle.json | 99 +-- .../fundamentals/runtime-architecture.json | 147 +++- .../docs/getting-started/ecosystem.json | 2 +- .../wwwroot/docs/getting-started/index.json | 2 +- .../docs/getting-started/quickstart.json | 2 +- .../getting-started/real-world-setup.json | 2 +- .../plugin-domains/authorization-domain.json | 2 +- .../docs/security/access-token-behavior.json | 2 +- .../docs/security/policy-pipeline.json | 2 +- .../docs/security/refresh-rotation.json | 2 +- .../docs/security/session-security-model.json | 2 +- .../Components/App.razor | 10 +- .../wwwroot/app.css | 280 ++++++++ .../wwwroot/docs.js | 36 + .../Program.cs | 89 +++ 47 files changed, 3265 insertions(+), 914 deletions(-) diff --git a/docs/content/fundamentals/auth-model.md b/docs/content/fundamentals/auth-model.md index 0616637f..7dfb4953 100644 --- a/docs/content/fundamentals/auth-model.md +++ b/docs/content/fundamentals/auth-model.md @@ -4,186 +4,405 @@ order: 2 group: fundamentals --- +# Authentication Model -# 🧠 Authentication Model +UltimateAuth is built around a fundamental principle: **authentication is a trusted process for establishing identity, not simply the presence of a token, cookie, or session identifier.** -UltimateAuth is built around a simple but powerful idea: +An authentication credential provides evidence of identity. The server must evaluate that evidence according to the active authentication mode before treating a request as authenticated. -> Authentication is not just a token. -> It is a **structured, server-controlled session model**. +UltimateAuth provides a common authentication architecture that supports both server-side sessions and cryptographically verifiable tokens. -At the core of this model are three concepts: +The authentication mode determines how identity is established, what state must be validated, and which lifecycle guarantees apply. -- **Root** -- **Chain** -- **Session** +## Authentication and identity -Together, they form what we call: +Authentication answers a specific question: -πŸ‘‰ **Authentication Lineage** +**Who is making this request, and what evidence establishes that identity?** -
+An authentication operation may involve credentials such as passwords, existing sessions, access tokens, refresh tokens, or other supported authentication proofs. -## πŸ”‘ The Big Picture +These credentials have different purposes. -Instead of treating authentication as a single token or cookie, UltimateAuth models it as a hierarchy: +A password can establish a new authenticated context. An access token can represent an already established identity. A refresh token can continue an existing authentication context without repeating the original login. +None of these credentials should be trusted merely because a client presents them. + +UltimateAuth evaluates authentication evidence through framework-controlled services and produces a trusted identity context only after the applicable validation succeeds. + +Authorization is a separate responsibility. It determines what an authenticated actor is allowed to do. + +## Server authority + +UltimateAuth is server-authoritative. + +This means the server defines the authentication rules and determines whether the presented evidence satisfies them. + +Server authority does not require every request to access a server-side session store. + +For example: + +- In PureOpaque, the server validates the session represented by the client's opaque session credential. +- In Hybrid, the server resolves an opaque access token and validates its associated session. +- In SemiHybrid, normal requests authenticate through JWT validation, while server-side sessions govern authentication continuation and lifecycle operations. +- In PureJwt, authentication can be established through JWT validation without requiring session persistence or lookup. + +In all cases, the effective authentication mode, validation requirements, and security policies remain under trusted server control. + +The client cannot establish its own authenticated identity merely by supplying claims, identifiers, or an arbitrary authentication mode. + +## Authentication state + +Authentication state can be represented in different ways. + +**Server-side authentication state** is maintained by the authentication system and evaluated when required. It enables centralized session management and immediate session-based revocation. + +**Token-based authentication state** can be represented by cryptographically verifiable credentials. A JWT can carry sufficient information to establish identity without a session lookup, provided its signature and applicable validation rules are satisfied. + +**Combined authentication state** uses both approaches for different purposes. For example, an application can validate JWTs independently while retaining server-side state for refresh and session management. + +These are different authentication strategies, not separate authentication architectures. + +UltimateAuth preserves common trust boundaries while allowing the effective mode to determine how authentication state is represented and evaluated. + +## Authentication lineage + +For session-backed authentication, UltimateAuth models authentication state through three related concepts: + +- **Root:** User-level authentication authority within a tenant. +- **Chain:** A grouping of related authentication sessions, typically associated with a device or client context. +- **Session:** An individual authenticated session. + +Together, these form the authentication lineage. + +```text +Root + | + +-- Chain A + | | + | +-- Session 1 + | | + | +-- Session 2 + | + +-- Chain B + | + +-- Session 3 ``` -Root (user authority) -β”œβ”€β”€ Chain (device context) -β”‚ β”œβ”€β”€ Session (login instance) -β”‚ β”œβ”€β”€ Session -β”‚ -β”œβ”€β”€ Chain -β”‚ β”œβ”€β”€ Session (login instance) + +The lineage provides a structured way to manage authentication state at different scopes. + +It allows the system to distinguish between terminating one session, terminating sessions associated with a particular chain, and applying a broader user-level security change. + +The lineage is central to session-backed authentication. It must not be interpreted as a mandatory persistence requirement for every authentication mode. + +In particular, PureJwt does not require Root, Chain, or Session records to authenticate requests. + +## Root: user-level authentication authority + +Root represents the user-level authority within a tenant's session-backed authentication lineage. + +It provides a common security boundary for related chains and sessions. + +Depending on the configured policies and supported operations, Root can participate in: + +- Tracking user-level authentication security state. +- Coordinating security changes affecting multiple sessions. +- Applying broader session invalidation. +- Maintaining a security version or epoch used by session-backed validation. + +For example, a security-sensitive credential change may trigger a policy that invalidates existing sessions. + +Rather than treating each session as unrelated, the lineage provides a common authority through which the affected authentication state can be managed. + +### Security versions + +A security version, sometimes called a security epoch, can be used to determine whether an existing session still belongs to the current authentication security state. + +A session may retain the version associated with its creation. When the relevant authority changes, validation can reject sessions associated with an outdated version. + +This mechanism is meaningful when authentication validation consults the relevant server-side state. + +It does not automatically invalidate a previously issued JWT that is validated without a server-side lookup. + +The exact invalidation behavior depends on the authentication mode and configured security policies. + +## Chain: related session context + +Chain groups related authentication sessions under a common lifecycle context. + +A chain commonly represents a device or client context, but its purpose is broader than storing a device identifier. + +It provides a boundary for managing related authentication activity. + +A chain can support: + +- Grouping related sessions. +- Device-oriented session management. +- Chain-level expiration and invalidation. +- Coordinating refresh-token lifecycle where applicable. +- Applying security policies to related authentication sessions. + +For example, an application may allow a user to terminate authentication associated with a particular device without terminating every session belonging to the user. + +A chain provides a suitable scope for that operation. + +A chain is not itself proof of authentication. Its validity and relationship to the relevant session must be evaluated according to the active mode. + +## Session: an authenticated instance + +Session represents an individual authenticated instance maintained by the server. + +It can contain or reference the information required to manage that instance, such as: + +- The authenticated user and tenant. +- Its relationship to a chain. +- Creation and expiration information. +- Revocation or lifecycle status. +- Applicable security-version information. +- Session metadata required by supported policies. + +The role of Session differs by authentication mode. + +### Session-authoritative authentication + +In PureOpaque and Hybrid, session validity is part of the authentication decision. + +A request cannot be treated as authenticated merely because it presents a structurally valid credential. The associated session must also satisfy the applicable validation requirements. + +In these modes, session invalidation affects subsequent authentication evaluations. + +### Session-managed authentication + +In SemiHybrid, sessions support lifecycle operations such as refresh, logout, device management, and session revocation. + +However, normal JWT authentication does not require session validation. + +Consequently, revoking a session can prevent future refresh operations without automatically invalidating an already issued JWT. + +### Session-independent authentication + +PureJwt does not require a Session record. + +Authentication relies on cryptographic token validation and applicable claims and token-validation policies. + +There is no mandatory session authority or session lookup in its authentication pipeline. + +## Establishing a new authentication context + +Authentication begins when the system receives evidence sufficient to establish a new authenticated identity. + +A typical login operation follows these conceptual steps: + +1. Resolve the effective authentication context and security policies. +2. Validate the presented authentication proof. +3. Establish the authenticated identity. +4. Create the authentication state required by the selected mode. +5. Issue the appropriate authentication credentials. +6. Deliver those credentials through the configured transport mechanism. + +The fourth and fifth steps are mode-dependent. + +For session-backed modes, establishing authentication can involve creating or resolving the relevant Root, Chain, and Session state. + +For PureJwt, issuing a JWT must not require creating a server-side session. + +The shared authentication pipeline does not imply that every mode performs identical persistence operations. + +## Authenticating subsequent requests + +After login, a client presents the credential appropriate for its authentication mode. + +The request pipeline validates that credential and establishes the trusted authentication state. + +```text +Incoming Request + | + v +Effective Authentication Context + | + v +Credential Resolution + | + v +Mode-Specific Authentication Validation + | + +-- PureOpaque: Session validation + | + +-- Hybrid: Opaque token + session validation + | + +-- SemiHybrid: JWT validation + | + +-- PureJwt: JWT validation + | + v +Authenticated Identity + | + v +AccessContext + | + v +Authorization + | + v +Protected Operation ``` -Each level has a distinct responsibility. +The authentication mode determines which validation steps are required. -
+The resulting identity must satisfy the same framework-level trust requirements regardless of the credential format. -## 🧩 Root β€” The Authority +An application must not construct a trusted identity directly from unvalidated client input. -**Root** represents the authentication authority of a user within a tenant. +## Authentication context and authorization context -- There is **only one active Root per user per tenant** -- It defines the **global security state** -- It controls all chains and sessions +UltimateAuth distinguishes the context used to execute an authentication flow from the context used to evaluate authorization. -### What Root Does +### AuthFlowContext -- Tracks security version (security epoch) -- Invalidates all sessions when needed -- Acts as the **source of truth** +`AuthFlowContext` represents the effective authentication context for an operation. -### Example +It includes the relevant mode, client characteristics, tenant context, and other applicable runtime information. -If a user changes their password: +The framework establishes this context through controlled boundaries. -πŸ‘‰ Root is updated -πŸ‘‰ All existing sessions can become invalid +Once established, the effective context remains stable throughout the operation. -
+Application code and individual domain services must not independently replace or reconstruct it to change the security rules. -## πŸ”— Chain β€” The Device Context +### AccessContext -**Chain** represents a device or client context. +`AccessContext` represents the trusted actor and authorization context after authentication has been evaluated. -- Each device typically has its own chain -- Multiple logins from the same device belong to the same chain +It provides the identity and contextual information needed for authorization and policy decisions. -πŸ‘‰ Think of Chain as: +`AccessContext` does not authenticate credentials. -> β€œWhere is the user logged in from?” +Authentication must be resolved before a trusted authorization context is established. -### What Chain Does +This separation allows the authentication mechanism to vary without requiring a different authorization architecture for each mode. -- Groups sessions by device -- Enables **device-level control** -- Allows actions like: +## Authentication lifecycle - - Logout from one device - - Revoke a specific device +Authentication does not end when credentials are issued. -
+Depending on the mode, an authenticated context may be continued, renewed, expired, or revoked. -## 🧾 Session β€” The Authentication Instance +UltimateAuth distinguishes several lifecycle operations. -**Session** is the actual authentication instance. +### Authentication -- Created when the user logs in -- Represents a single authenticated state -- Carries a snapshot of the Root security version +Establishes a new authenticated context after the required proof has been validated. -πŸ‘‰ This is what gets validated on each request. +### Refresh -### What Session Does +Continues an existing authentication context when its continuation requirements are satisfied. -- Proves the user is authenticated -- Can be refreshed, revoked, or expired -- Is tied to a specific chain +Refresh is mode-dependent. -
+PureOpaque uses session continuation and touch semantics rather than refresh-token rotation. -## πŸ”„ How They Work Together +Hybrid and SemiHybrid use refresh-token rotation together with the applicable session validation requirements. -When a user logs in: +PureJwt may support session-independent token renewal when refresh tokens are enabled. -1. Root is resolved (or created) -2. A Chain is identified (device context) -3. A new Session is created +### Logout -`Login β†’ Root β†’ Chain β†’ Session` +Ends or discards the relevant authentication context according to the mode. +In session-authoritative modes, server-side session revocation can prevent subsequent authentication. -On each request: +In JWT-based modes, removing credentials from the client does not by itself invalidate already issued JWTs. -1. Session is validated -2. Chain context is checked -3. Root security version is verified +### Revocation -πŸ‘‰ If any level is invalid β†’ authentication fails +Invalidates authentication state or credentials according to the guarantees supported by the selected mode. -
+Session revocation and JWT revocation are not equivalent operations. -## πŸ›‘ Why This Model Matters +A server-side session can be revoked immediately, but a JWT validated without session lookup can remain valid until expiration. -Traditional systems rely on: +Applications requiring stronger access-token invalidation guarantees must select a mode and validation strategy that explicitly provides them. -- Cookies -- JWT tokens -- Stateless validation +## Authentication security boundaries -These approaches have limitations: +UltimateAuth's authentication architecture relies on several principles that apply across modes. -- No real session control -- Weak revocation -- No device awareness +### Credentials are evidence, not authority ---- +The presence of a token, cookie, or session identifier does not automatically establish identity. + +The required validation must succeed first. + +### Authentication modes have explicit guarantees + +A mode must not silently weaken its validation or revocation guarantees because of client runtime, transport selection, or performance optimization. + +### Session authority is mode-dependent + +Server-side sessions are authoritative where the mode requires them. + +Stateless authentication does not become session-backed merely because other parts of the framework support session management. + +### Client context is not inherently trusted + +Device identifiers, tenant identifiers, and client-provided runtime information must be handled according to the applicable trust boundaries. + +They must not allow a client to bypass authentication requirements. + +### Authentication and authorization remain separate + +A valid identity does not automatically imply permission to access a resource. + +Authorization policies must still evaluate the authenticated actor's access rights. + +### Credential protection remains essential + +Opaque session credentials, opaque access tokens, JWTs, and refresh tokens are all sensitive. + +Their safe use depends on appropriate transport, storage, expiration, and validation policies. + +## How the model relates to authentication modes -UltimateAuth solves this by: +The authentication model establishes the shared principles and boundaries. -### βœ” Server-Controlled Authentication +Authentication modes determine how those principles are executed. -- Sessions are always validated server-side -- No blind trust in tokens +| Mode | Authentication authority | Role of session state | +|---|---|---| +| PureOpaque | Server-side session | Required for authentication and lifecycle | +| Hybrid | Server-side session, accessed through an opaque token | Required for authentication and lifecycle | +| SemiHybrid | Cryptographically validated JWT for normal requests | Required for session lifecycle, not default request validation | +| PureJwt | Cryptographically validated JWT | Not required | -### βœ” Instant Revocation +This distinction is essential to understanding UltimateAuth. -- Revoke a session β†’ immediate effect -- Revoke a chain β†’ device logged out -- Revoke root β†’ disable user (global logout) +Root, Chain, and Session provide structured lifecycle control where server-side authentication state is required. -### βœ” Device Awareness +They are not universal prerequisites for authenticating a request. -- Each device has its own chain -- Sessions are bound to context +Likewise, JWT authentication does not eliminate server authority. The server still defines which tokens are acceptable and which validation rules must be enforced. -### βœ” Strong Security Model +## Summary -- Session carries Root security version -- Old sessions automatically become invalid +UltimateAuth defines authentication as a controlled process that establishes trusted identity from validated evidence. -
+Its architecture separates: -## 🧠 Mental Model +- Authentication proof and credential validation. +- Authentication state and lifecycle management. +- Authentication context and authorization context. +- Shared security boundaries and mode-specific execution strategies. -If you remember only one thing, remember this: +Root, Chain, and Session form the authentication lineage used by session-backed modes. -πŸ‘‰ **Root = authority** -πŸ‘‰ **Chain = device** -πŸ‘‰ **Session = login** +PureOpaque and Hybrid rely on that lineage as part of their authentication authority. -## πŸ“Œ Key Takeaways +SemiHybrid retains session lifecycle management while allowing JWT authentication without request-time session lookup. -- Authentication is not just a token -- Sessions are first-class citizens -- The server always remains in control -- Device and security context are built-in +PureJwt does not require session persistence or validation. -## ➑️ Next Step +These distinctions allow UltimateAuth to support different authentication strategies without changing the fundamental trust and authorization model. -Now that you understand the core model: +## Next step -πŸ‘‰ Continue to **Flow-Based Authentication** +Continue to **Flow-Based Authentication** to understand how UltimateAuth establishes and executes authentication operations through its controlled flow pipeline. \ No newline at end of file diff --git a/docs/content/fundamentals/auth-modes.md b/docs/content/fundamentals/auth-modes.md index 756c202f..0c6dadfd 100644 --- a/docs/content/fundamentals/auth-modes.md +++ b/docs/content/fundamentals/auth-modes.md @@ -4,164 +4,597 @@ order: 4 group: fundamentals --- +# Auth Modes -> Note: SemiHybrid and PureJwt modes will be available on future releases. For now you can safely use PureOpaque and Hybrid modes. +UltimateAuth provides four authentication modes: **PureOpaque**, **Hybrid**, **SemiHybrid**, and **PureJwt**. -# πŸ” Authentication Modes +Each mode defines how authentication credentials are issued, validated, renewed, and revoked. The modes share the same authentication and authorization architecture, but differ in their reliance on server-side sessions, credential formats, and lifecycle guarantees. -UltimateAuth supports multiple authentication modes. +The choice of authentication mode affects more than performance. It determines where authentication state is maintained, whether a server-side session must be consulted during an authenticated request, and how quickly previously issued credentials can be invalidated. -Each mode represents a different balance between: +> **Availability** +> +> PureOpaque and Hybrid are supported in the initial release. SemiHybrid and PureJwt are defined by the UltimateAuth architecture but are not yet available as completed, production-supported modes. Their behavior is documented here to explain the intended authentication model. -- Security -- Performance -- Control -- Client capabilities +## Auth credentials -πŸ‘‰ You don’t always choose a single model. -UltimateAuth can adapt based on context. +UltimateAuth distinguishes three credential concepts. -
+**Session credential** -## 🧩 Available Modes +A session credential identifies an authenticated session maintained by the server. In PureOpaque, it is the credential presented by the client to authenticate requests. -### PureOpaque -Fully server-controlled session model. +A session credential may be represented by an opaque, randomly generated value. It does not need to expose the internal database identifier of the session. -### Hybrid -Combines session control with token-based access. +**Access token** -### SemiHybrid -JWT-based with server-side metadata awareness. +An access token is a credential used to authenticate requests to protected resources. -### PureJwt -Fully stateless token-based authentication. +Depending on the mode, it may be: -
+- An opaque token resolved and validated by the server. +- A JWT validated cryptographically without a session lookup. -## βš–οΈ Mode Comparison +An access token is not necessarily a session identifier. In Hybrid, the opaque access token is a separate credential associated with a server-side session. -| Feature | PureOpaque | Hybrid | SemiHybrid | PureJwt | -|----------------|------------|------------|----------------|--------------| -| SessionId | Required | Required | Metadata only | None | -| Access Token | ❌ | βœ” | βœ” | βœ” | -| Refresh Token | ❌ | βœ” | βœ” | βœ” | -| Revocation | Immediate | Immediate | Metadata-based | Not immediate| -| Statefulness | Full | Hybrid | Semi | Stateless | -| Server Control | Full | High | Medium | Low | -| Performance* | Medium | Medium | High | Highest | -| Offline Support| ❌ | Partial | βœ” | βœ” | +**Refresh token** -> ⚑ **Performance Note** -> -> All modes in UltimateAuth are designed for production use and are highly optimized. -> -> The differences here are about **trade-offs**, not absolute speed: -> -> πŸ‘‰ Even the most server-controlled mode is performant enough for real-world applications. +A refresh token allows an existing authentication context to continue by obtaining replacement credentials without requiring the user to authenticate again. + +Refresh tokens have their own validation, expiration, rotation, and replay-protection requirements. + +Not every authentication mode requires all three credential types. + +## Mode comparison + +| Characteristic | PureOpaque | Hybrid | SemiHybrid | PureJwt | +|---|---|---|---|---| +| Client authentication credential | Opaque session credential | Opaque access token | JWT access token | JWT access token | +| Separate access token | No | Yes | Yes | Yes | +| Access token format | Not applicable | Opaque | JWT | JWT | +| Refresh token | No | Yes | Yes | Optional | +| Server-side session | Required | Required | Required | Not required | +| Session lookup on authenticated requests | Required | Required | Not required by default | Not required | +| Session role | Authentication authority | Authentication authority | Lifecycle management | None | +| Access token validation | Session validation | Token and session validation | Cryptographic JWT validation | Cryptographic JWT validation | +| Session continuation | Session touch | Refresh token rotation and session policies | Refresh token rotation with session validation | Independent token renewal, if enabled | +| Immediate session revocation | Yes | Yes | Prevents continuation; does not automatically invalidate issued JWTs | Not applicable | +| Immediate access-token revocation | Through session invalidation | Through session invalidation | Requires an additional revocation check | Not supported by the pure stateless model | +| Device/session management | Yes | Yes | Yes | No session-based device management | +| Server-side state for authentication | Required | Required | Required for lifecycle, not normal JWT validation | Not required | +| Main advantage | Simple, centrally controlled sessions | Separate token lifecycle with session authority | Stateless access validation with session management | Minimal authentication-state dependencies | +| Main trade-off | Session lookup on each authenticated request | More credentials and lifecycle complexity | Existing JWTs can remain valid after session revocation | No immediate server-side session revocation | + +The comparison describes authentication behavior, not guaranteed throughput rankings. Actual performance depends on token validation costs, storage infrastructure, caching, network latency, request volume, and application architecture. + +## PureOpaque + +### Fully session-based authentication + +PureOpaque is UltimateAuth's simplest stateful authentication mode. + +Authentication is represented by a server-side session. The client receives an opaque session credential and presents it on subsequent authenticated requests. + +The server resolves the credential, validates the session and its applicable security policies, and establishes the authenticated identity. + +```text +Client + | + | Session Credential + v +Session Resolution + | + v +Session Validation + | + v +Authenticated Identity + | + v +AccessContext +``` + +PureOpaque does not issue a separate access token or refresh token. + +The session credential itself is sufficient to identify the authentication context, provided the server determines that the session remains valid. + +### Session continuation + +PureOpaque does not use refresh-token rotation. + +Instead, an existing session may continue through session touch operations, subject to the configured session policies. + +Depending on the configuration, these policies may include: + +- Session lifetime. +- Sliding expiration. +- Idle timeout. +- Touch interval. +- Absolute expiration limits. +- Session revocation. + +A touch operation does not necessarily extend the session lifetime. It updates session state only when the applicable policy requires it. + +An expired or revoked session cannot be restored merely by touching it. + +### Advantages + +**Minimal credential complexity** + +There is no separate access-token lifecycle, refresh-token storage, or refresh-token rotation. + +**Immediate session revocation** + +When a session is revoked, subsequent authentication evaluations that validate that session reject the credential. + +**Centralized authentication control** + +The server remains authoritative for session validity and can enforce current session policies. + +**Natural integration with server-rendered applications** + +Applications already maintaining server-side authentication state can use a single session credential without introducing additional token-management mechanisms. + +### Limitations + +**Session validation is required** + +Every authenticated request must validate the server-side session. Depending on the deployment, this can involve a database or distributed cache lookup. + +**Availability depends on session infrastructure** + +A distributed deployment needs reliable access to shared session state or an equivalent session-validation mechanism. + +**The session credential is sensitive** + +Possession of a valid session credential may be sufficient to authenticate as the session owner. It must be protected against disclosure, replay, and session fixation. + +**Long-lived connections require special handling** + +In environments such as Blazor Server, an established SignalR circuit may outlive the authentication state initially used to establish it. Appropriate revalidation and authorization checks remain necessary. + +### Recommended applications + +PureOpaque is generally a strong default for: + +- Blazor Server applications. +- ASP.NET Core MVC and Razor Pages applications. +- Internal applications using centrally managed sessions. +- Browser applications using secure, HttpOnly session cookies. +- Systems that prioritize simple session lifecycle management and immediate server-side revocation. + +PureOpaque is not restricted to server-to-server communication. It can also be used safely with browser and mobile clients when credentials are stored, transported, and validated appropriately. + +## Hybrid + +### Opaque access tokens with authoritative sessions + +Hybrid combines separate access and refresh tokens with mandatory server-side session validation. + +Unlike PureOpaque, the client does not use the session credential as its primary authentication credential. + +Instead, the client receives an **opaque access token** associated with a server-side session. + +The access token contains no readable JWT claims. The server resolves the token and validates the associated session before establishing the authenticated identity. + +```text +Client + | + | Opaque Access Token + v +Access Token Resolution + | + v +Associated Session + | + v +Session Validation + | + v +Authenticated Identity + | + v +AccessContext +``` + +The opaque access token and internal session identifier represent different concepts. + +The access token is a client-facing authentication credential with its own lifetime. The session represents the server-authoritative authentication state. + +### Refresh and session lifecycle + +Hybrid uses refresh tokens to continue authentication without requiring a new login. + +A successful refresh validates the relevant session and refresh-token state, applies the configured session policies, and issues replacement credentials. + +Access-token lifetime and session lifetime can therefore be managed separately. + +For example, an application may use a short-lived opaque access token while maintaining a longer-lived session, subject to refresh-token expiration and revocation rules. + +A valid refresh token must not be allowed to restore an expired or revoked session. + +### Advantages + +**Separate credential lifetimes** + +Access tokens can expire independently of the underlying session. + +**Immediate session-based revocation** + +Because session validity is enforced during authentication, revoking the session prevents associated access tokens from authenticating on subsequent validations. + +**Opaque access credentials** + +The access token does not disclose identity claims, roles, tenant information, or other application data through a readable token payload. + +**Centralized security policies** + +Session status and related security conditions can be enforced as part of authentication. + +**Suitable for API-oriented clients** + +Applications can use access and refresh tokens while retaining server-side control over the authenticated session. + +### Limitations + +**More complex lifecycle** + +Hybrid requires access-token issuance, refresh-token management, rotation, replay protection, and session lifecycle coordination. + +**Server-side validation remains mandatory** + +Opaque access tokens do not provide stateless JWT validation. The server must resolve the token and enforce the associated session state. + +**Additional infrastructure dependencies** + +Token resolution and session validation may require storage or cache access. + +**Credential theft remains a risk** + +Opaque tokens conceal claims but remain bearer credentials. Their format does not prevent unauthorized use if they are stolen. + +### Recommended applications + +Hybrid is generally appropriate for: + +- Blazor WebAssembly applications communicating with an API. +- Web applications with a separate backend API. +- Mobile and desktop applications requiring refresh-token continuity. +- Full-stack applications needing centrally controlled authentication. +- Systems requiring separate access-token expiration and immediate session revocation. + +Hybrid is particularly useful when the application benefits from access/refresh token semantics but does not want to sacrifice server-authoritative session validation. + +## SemiHybrid + +### Stateless JWT validation with server-side session lifecycle + +SemiHybrid combines JWT access tokens with server-side session management. + +Its defining characteristic is the separation between **access-token validation** and **session lifecycle operations**. + +For normal authenticated requests, the server validates the JWT cryptographically. A session lookup is not required by default. + +```text +Client + | + | JWT Access Token + v +JWT Validation + | + v +Authenticated Identity + | + v +AccessContext +``` + +The server-side session is not consulted during this normal authentication path. + +However, the session remains relevant when the authentication lifecycle must be managed. + +### What session metadata is used for + +SemiHybrid maintains server-side session state for operations such as: + +- Refresh-token validation and rotation. +- Logout and session termination. +- Revoking an individual session. +- Revoking all sessions belonging to a user. +- Listing active sessions or devices. +- Enforcing session expiration and continuation policies. +- Recording session lifecycle information. + +This state is a **lifecycle control mechanism**, not a mandatory source of identity validation on every API request. + +For example, when a user logs out, the server can revoke the session and prevent subsequent refresh operations from continuing it. + +However, a previously issued JWT may remain valid until its expiration because normal JWT validation does not consult that session. + +### Revocation semantics + +SemiHybrid must distinguish two separate guarantees. + +**Session revocation** + +Revoking the session prevents authentication continuation through that session, including refresh operations that require it. + +**Access-token revocation** + +Revoking the session does not automatically invalidate an already issued JWT. + +Immediate rejection of an issued JWT requires an additional mechanism, such as a revocation lookup against a distributed cache. + +Such a mechanism can be supported as an explicit policy, but it introduces an additional request-time dependency and validation cost. + +Without that policy, JWT expiration remains the boundary for an already issued access token. + +### Advantages + +**Stateless normal request validation** + +Protected API requests can authenticate JWTs without consulting the session store. + +**Centralized lifecycle management** + +Applications retain the ability to manage sessions, devices, refresh operations, and future authentication continuity. + +**Reduced request-time storage dependencies** + +Session infrastructure is not part of the default JWT validation path. + +**Configurable revocation trade-offs** + +Applications can choose whether to introduce additional request-time revocation enforcement when stronger invalidation guarantees are required. + +### Limitations + +**Session revocation does not automatically revoke issued JWTs** + +An access token can remain valid after logout until its expiration unless an additional revocation mechanism is enforced. + +**Lifecycle state is still required** + +SemiHybrid is not a session-free mode. Refresh and session management depend on server-side state. + +**More complex expiration semantics** + +JWT expiration, refresh-token expiration, session expiration, and session activity policies must be coordinated. + +**Optional revocation checks reduce statelessness** + +If immediate access-token revocation is enabled, the request pipeline may need an additional cache or store lookup. + +### Recommended applications + +SemiHybrid is intended for: + +- High-throughput APIs that still require centralized session management. +- Distributed applications using short-lived JWT access tokens. +- Applications offering device and active-session management. +- Systems that need JWT validation performance without giving up refresh and session lifecycle control. +- Architectures where access-token expiration is an acceptable default revocation boundary. + +SemiHybrid should not be described as providing immediate JWT revocation unless an additional enforcement mechanism is enabled. + +## PureJwt + +### Session-independent JWT authentication + +PureJwt is UltimateAuth's fully stateless JWT authentication mode. + +It does not require server-side session creation, persistence, resolution, or validation. + +The client receives a JWT access token. Protected resources validate the token using its signature and applicable validation rules, including issuer, audience, expiration, and other security-relevant claims. + +```text +Client + | + | JWT Access Token + v +Cryptographic JWT Validation + | + v +Authenticated Identity + | + v +AccessContext +``` + +No session lookup is involved. + +PureJwt must not require session infrastructure indirectly through token issuance, validation, or other authentication pipeline components. + +### Token renewal + +PureJwt can operate without refresh tokens. + +When refresh tokens are enabled, their lifecycle must remain independent of server-side sessions. + +Secure refresh-token rotation and replay protection may require dedicated refresh-token state. This does not imply a dependency on the session model. + +The distinction is important: + +- **Session independence** means that authentication does not require server-side sessions. +- **Stateless access validation** means that protected requests do not require server-side token or session lookups. +- **Refresh-token persistence**, when enabled, is a separate lifecycle concern. + +An application seeking a completely stateless credential lifecycle can disable refresh tokens and require reauthentication when access tokens expire. + +### Revocation semantics + +PureJwt does not provide session-based immediate revocation. + +A correctly signed, unexpired JWT remains valid under the configured validation rules unless a relevant validation condition changes, such as the availability of its signing key. + +PureJwt does not maintain a mandatory blacklist or server-side session revocation mechanism. + +Applications requiring immediate per-session access revocation should consider Hybrid, or SemiHybrid with an explicit revocation enforcement strategy. + +### Advantages + +**No mandatory session infrastructure** + +Authentication does not depend on session stores or session-management services. + +**Independent resource validation** + +Services with the necessary verification keys and validation configuration can validate access tokens locally. + +**Suitable for distributed architectures** + +Protected services do not need to contact a central authentication session store for each request. + +**Reduced lifecycle complexity when refresh is disabled** + +Applications can avoid session management and refresh-token rotation entirely. + +**Predictable validation behavior** + +Authentication is determined by the token and the applicable cryptographic and claim-validation rules. + +### Limitations + +**No immediate per-session revocation** + +An issued JWT can remain usable until expiration. + +**Token lifetime becomes particularly important** + +Long-lived access tokens increase the period during which a stolen token may remain usable. + +**Claims can become stale** + +Identity or authorization-related claims embedded in a JWT do not automatically change when server-side user data changes. + +**JWT payloads are readable** + +Signed JWTs are not encrypted by default. Sensitive information should not be placed in their payloads. + +**Key management is critical** + +Signing-key protection, key rotation, issuer and audience validation, and accepted algorithm configuration are essential. + +### Recommended applications + +PureJwt is intended for: + +- Stateless resource APIs. +- Distributed services and microservices. +- External API integrations. +- Workloads requiring independent JWT validation. +- Systems that do not require centralized session management. +- Applications prioritizing minimal authentication-state dependencies. + +PureJwt is not automatically the fastest mode in every deployment. Cryptographic validation has a cost, while well-cached stateful authentication can also be efficient. Its primary architectural advantage is eliminating mandatory session dependencies. + +## Choosing a mode + +The most useful selection criterion is not the client technology alone, but the authentication guarantees the application requires. + +| Application or requirement | Recommended mode | Reason | +|---|---|---| +| Blazor Server | PureOpaque | Server-controlled sessions without a separate access/refresh token lifecycle | +| ASP.NET Core MVC or Razor Pages | PureOpaque | Simple cookie-based session authentication | +| Internal administrative application | PureOpaque or Hybrid | Centralized session control and immediate revocation | +| Blazor WebAssembly with a separate API | Hybrid | Access/refresh tokens with authoritative session validation | +| Mobile or desktop client | Hybrid | Independent token lifetimes with centralized session control | +| Web application with separate frontend and API | Hybrid | Token-based access without losing server-side revocation | +| High-throughput API with session management | SemiHybrid | Stateless JWT validation alongside managed session lifecycle | +| Multi-device application with session controls | SemiHybrid or Hybrid | Centralized device/session management; different access-validation costs | +| Distributed resource APIs | PureJwt | Independent JWT validation without mandatory session lookup | +| External service integration | PureJwt | Stateless token validation across service boundaries | +| Immediate access revocation is essential | PureOpaque or Hybrid | Every authentication evaluation enforces server-side session validity | +| No session infrastructure is desired | PureJwt | Authentication does not require session persistence | + +These are recommendations, not restrictions. + +For example, Blazor WebAssembly can use PureOpaque through a secure cookie-based backend-for-frontend architecture. Similarly, a server-rendered application can use JWT authentication when its broader system requirements justify it. + +The runtime does not inherently determine the security model. + +## Client Profiles and authentication modes + +UltimateAuth supports different client environments through Client Profiles. + +A Client Profile can provide suitable defaults for a runtime, including credential delivery and authentication-mode selection. + +However, a Client Profile and an authentication mode represent different concerns. + +**Client Profile** + +Describes how the application interacts with the authentication system, including runtime characteristics and credential transport. + +**Authentication Mode** -
+Defines the authentication credential model, session dependencies, validation strategy, and lifecycle guarantees. -## 🧠 How to Think About Auth Modes -It’s important to understand that authentication modes in UltimateAuth are not rigid system-wide choices. +A Blazor Server profile may default to PureOpaque, while a Blazor WebAssembly profile may default to Hybrid. -πŸ‘‰ You are not expected to pick a single mode and use it everywhere. +These defaults do not mean the modes are exclusive to those environments. -Instead: +Applications can override the effective mode through supported configuration and context-resolution mechanisms. -- Different clients can use different modes on a single UAuthHub -- The mode can change **per request** -- UltimateAuth selects the most appropriate mode based on **Client Profile and runtime context** +When different clients share the same UAuthHub, the server must resolve the effective mode through trusted configuration and framework-controlled context. An untrusted client must not be allowed to select a weaker authentication policy arbitrarily. -
+The effective mode must remain stable throughout a single authentication operation. -### πŸ”„ Runtime-Driven Behavior -In a typical application: +## Security considerations -- Blazor Server β†’ PureOpaque -- Blazor WASM β†’ Hybrid -- API β†’ PureJwt +All four modes can support secure applications when implemented and configured correctly. None should be described as universally more secure than the others. -πŸ‘‰ All can coexist in the same system. +Their security guarantees differ. -You don’t split your architecture β€” UltimateAuth adapts automatically. +### Credential confidentiality -### βš™οΈ You Can Override Everything -UltimateAuth provides **safe defaults**, but nothing is locked. +Opaque credentials do not expose readable claims, but possession of a valid bearer credential may still permit authentication. -You can: +JWT signatures protect integrity and authenticity; they do not conceal the payload. -- Force a specific auth mode -- Customize behavior per client -- Replace default strategies +All credentials require appropriate transport and storage protections. -πŸ‘‰ The system is designed to be flexible without sacrificing safety. +### Revocation guarantees -### πŸ›‘ Safe by Default -The comparison table shows trade-offs β€” not risks. +PureOpaque and Hybrid enforce server-side session validity during authenticated requests. -- All modes are **valid and supported** -- Choosing a different mode will not β€œbreak” security -- Incompatible configurations will **fail fast** +SemiHybrid enforces session validity during lifecycle operations but does not require it during normal JWT validation. -πŸ‘‰ You are always operating within a safe boundary. +PureJwt does not depend on sessions and does not provide session-based revocation. -### πŸ’‘ Mental Model +These differences must be considered when selecting token lifetimes and logout behavior. -Think of auth modes as: +### Browser security -> Different execution strategies β€” not different systems. +When authentication credentials are delivered through cookies, appropriate `HttpOnly`, `Secure`, and `SameSite` settings should be applied. -UltimateAuth remains consistent. -Only the **behavior adapts**. +Cookie-based authentication also requires appropriate CSRF protections. -
+When tokens are accessible to browser application code, XSS and token-exfiltration risks require particular attention. -## πŸ” PureOpaque -- Fully session-based -- Every request validated on server -- Maximum security -- Touch semantics instead of refresh rotation +The authentication mode does not eliminate these browser security requirements. -πŸ‘‰ Best for: +### Authorization remains separate -- Blazor Server -- Internal apps +Authentication establishes the identity of the current actor. -## ⚑ Hybrid -- Access token as opaque session id -- Refresh token with rotate semantics -- Server control with API performance +Authorization determines what that actor is permitted to do. -πŸ‘‰ Best for: +All modes must produce trusted authentication state before authorization policies are evaluated. -- Blazor WASM -- Web + API systems -- Full-stack apps +Choosing a stateless mode does not remove the need for authorization checks or appropriate handling of stale authorization claims. -## πŸš€ SemiHybrid -- JWT-based access -- Server-side metadata control +## Summary -πŸ‘‰ Best for: +UltimateAuth's four authentication modes represent four distinct strategies: -- High-performance APIs -- Zero-trust systems +**PureOpaque** uses a server-side session as the authentication authority and avoids a separate access/refresh token lifecycle. -## 🌐 PureJwt -- Fully stateless -- No server-side session control +**Hybrid** uses separate opaque access and refresh tokens while requiring authoritative session validation for authenticated requests. -πŸ‘‰ Best for: +**SemiHybrid** uses JWT access tokens for stateless request authentication while retaining server-side sessions for refresh, logout, revocation, and device management. -- External APIs -- Microservices +**PureJwt** uses JWT access tokens without requiring server-side session infrastructure. -## 🎯 Which Mode Should You Use? +The correct mode is the one whose validation, lifecycle, revocation, and operational guarantees match the application's requirements. -| Scenario | Recommended Mode | -|------------------------|------------------| -| Blazor Server | PureOpaque | -| Web + API | Hybrid | -| High-scale API | SemiHybrid | -| External microservices | PureJwt | +UltimateAuth preserves a consistent authentication and authorization architecture while allowing these underlying strategies to differ. \ No newline at end of file diff --git a/docs/content/fundamentals/client-profiles.md b/docs/content/fundamentals/client-profiles.md index cfa72dd0..ca3d4869 100644 --- a/docs/content/fundamentals/client-profiles.md +++ b/docs/content/fundamentals/client-profiles.md @@ -4,148 +4,396 @@ order: 5 group: fundamentals --- +# Client Profiles -# 🧩 Client Profiles -UltimateAuth adapts its authentication behavior based on the client. +UltimateAuth is designed to support different application environments through a unified authentication architecture. -πŸ‘‰ This is powered by **Client Profiles**. +A Blazor Server application, a browser-based WebAssembly application, a native mobile client, and a server-side application do not interact with authentication credentials in exactly the same way. -
+They may differ in how credentials are stored, how requests are sent, whether browser cookies are available, and how authentication results are delivered. -## πŸ”‘ What is a Client Profile? -A Client Profile defines how authentication behaves for a specific client type. +**Client Profiles allow UltimateAuth to account for these runtime differences without requiring separate authentication architectures for each client type.** -It affects: +A Client Profile describes the environment in which a client operates. It helps the framework resolve suitable authentication behavior and defaults, while the server remains responsible for enforcing the effective security policy. -- Authentication mode -- Flow behavior -- Token usage -- Security constraints +## What is a Client Profile? -Unlike traditional systems: +A Client Profile identifies a supported client environment and its relevant runtime characteristics. -πŸ‘‰ You don’t configure authentication globally -πŸ‘‰ You let the system adapt per client +It can influence how UltimateAuth resolves: -## 🧠 The Key Idea -> Authentication behavior is not static -> It is determined **per client and per request** +- Default authentication mode. +- Credential delivery and transport. +- Client-specific authentication flow behavior. +- Browser and non-browser integration requirements. +- Applicable server configuration and compatibility constraints. -
+A profile does not authenticate the client or grant permissions. -## πŸ”„ Runtime Detection -By default, UltimateAuth automatically detects the client profile. +For example, identifying a request as originating from a Blazor WebAssembly application does not establish the identity of its user. -This is done using runtime signals such as: +Likewise, a client declaring itself to be a server-side application must not automatically receive privileges associated with a trusted server. -- Loaded assemblies -- Hosting environment -- Registered services +The distinction is fundamental: -### Example Detection Logic -- MAUI assemblies β†’ `Maui` -- WebAssembly runtime β†’ `BlazorWasm` -- Server components β†’ `BlazorServer` -- UAuthHub marker β†’ `UAuthHub` -- Fallback β†’ `WebServer` +**Client Profile describes the environment. Authentication Mode defines the authentication strategy. Server policy determines what is permitted.** -πŸ‘‰ Detection happens inside the client at startup. +## Why Client Profiles exist -
+Consider an application that includes a browser frontend, a native mobile application, and a server-rendered administrative interface. -## πŸ“‘ Client β†’ Server Propagation -The detected client profile is sent to the server on each request. +All three clients may authenticate against the same UAuthHub. -This can happen via: +However, their runtime characteristics differ. -- Request headers -- Form fields (for flow-based operations) +A browser application can use cookies managed by the browser. A native application may need to manage credentials through platform-specific secure storage. A server-rendered application may be able to maintain authentication through a server-controlled session without introducing a separate refresh-token lifecycle. -```text -Client β†’ (ClientProfile) β†’ Server +Without a client abstraction, these differences tend to spread throughout application code. + +Developers may need separate configuration paths, credential-handling logic, and authentication integration code for every runtime. + +Client Profiles provide a consistent way to describe those differences. + +They allow UltimateAuth to select appropriate integration behavior while preserving the same underlying authentication and authorization architecture. + +## Built-in Client Profiles + +UltimateAuth defines profiles for its supported client environments. + +| Profile | Environment | Typical characteristics | +|---|---|---| +| `BlazorServer` | Blazor Server applications | Server-side execution, browser connection, server-managed authentication context | +| `BlazorWasm` | Blazor WebAssembly applications | Browser execution, HTTP-based API communication, browser credential constraints | +| `Maui` | .NET MAUI applications | Native execution, platform-specific credential storage and HTTP communication | +| `WebServer` | ASP.NET Core MVC, Razor Pages, and other server-hosted web applications | Server-side execution with browser-oriented authentication | +| `Api` | API-oriented integrations | HTTP request authentication without assuming a browser UI | +| `UAuthHub` | UltimateAuth authentication host | Authentication-host-specific runtime integration | +| `NotSpecified` | No explicit profile resolved | Requires the applicable server-side fallback or resolution policy | + +These profiles describe runtime environments rather than independent authentication systems. + +For example, `BlazorWasm` does not inherently mean JWT authentication, and `BlazorServer` does not inherently prohibit access tokens. + +A profile may provide a recommended default, but the authentication mode remains a separate decision. + +### API profile versus resource API + +An API-oriented client and an API acting as a protected resource are not necessarily the same thing. + +A protected resource API may receive requests from browser applications, mobile applications, or other services. + +The calling client's profile should not be confused with the hosting environment of the resource API. + +Resource authentication must follow the applicable server-side configuration and token-validation requirements, regardless of what an untrusted caller claims its runtime to be. + +## Automatic profile detection + +UltimateAuth supports automatic Client Profile detection in compatible client integrations. + +Detection is intended to reduce configuration requirements for common application environments. + +Depending on the client implementation, the runtime can provide information such as: + +- The current execution environment. +- Available framework capabilities. +- Hosting-specific registrations. +- Runtime and platform characteristics. + +For example, a Blazor WebAssembly client can identify that it is executing in a browser environment, while a native MAUI client can identify its native application context. + +This resolution occurs within the supported client integration. + +It should not be confused with a server independently discovering the true identity or trust level of a remote application. + +### Detection is a convenience, not a security boundary + +Runtime detection helps choose appropriate defaults. + +It is not an authentication proof. + +A malicious HTTP client can imitate headers, request parameters, and other client-provided metadata. + +Consequently, the server must not grant additional authentication privileges solely because a request claims a particular Client Profile. + +The server remains responsible for validating the profile against its own configuration and security requirements. + +## Client-to-server propagation + +For supported operations, the client can communicate its resolved profile to the authentication server. + +The transport may depend on the integration and operation, including request metadata such as headers or flow-specific form fields. + +```text id="0mrrkv" +Client Runtime + | + v +Client Profile Detection + | + v +Profile Metadata + | + v +UAuthHub + | + v +Server-Side Profile Resolution + | + v +Effective Authentication Context ``` -On the server: -- The profile is read from the request -- If not provided β†’ NotSpecified -- Server applies defaults or resolves behavior +The important distinction is between a **declared profile** and an **effective profile**. + +A declared profile is information supplied by the client. + +An effective profile is the result accepted or resolved by the server under its configured rules. + +The server must not treat arbitrary client-provided profile values as authority to weaken authentication requirements. + +### Missing profile information + +Some requests may not provide an explicit Client Profile. + +In those cases, UltimateAuth can represent the unresolved client profile as `NotSpecified` and apply the relevant server-side resolution or fallback behavior. -
+A missing profile must not automatically imply a privileged or less restrictive environment. + +If the server cannot establish a permitted configuration for the operation, it should reject the operation rather than inventing an unsafe authentication context. + +## Automatic and explicit configuration + +Client Profile resolution can be automatic or explicitly configured, depending on the supported client integration. + +### Automatic configuration + +For supported Blazor client registrations, automatic detection can reduce the need to specify the runtime manually. -## βš™οΈ Automatic vs Manual Configuration -### Automatic (Default) ```csharp builder.Services.AddUltimateAuthClientBlazor(); ``` -It means: -- AutoDetectClientProfile = true -- Profile is resolved automatically -### Manual Override -You can explicitly set the client profile: +The client integration can resolve the appropriate profile according to its runtime detection configuration. + +This is generally the preferred approach when the application uses a standard, supported hosting environment. + +### Explicit configuration + +Applications can also configure the profile directly when their integration supports it. + ```csharp -builder.Services.AddUltimateAuthClientBlazor(o => +builder.Services.AddUltimateAuthClientBlazor(options => { - o.ClientProfile = UAuthClientProfile.Maui; - o.AutoDetectClientProfile = false; // optional + options.ClientProfile = UAuthClientProfile.BlazorWasm; + options.AutoDetectClientProfile = false; }); ``` -πŸ‘‰ This is useful when: -- Running in custom hosting environments -- Detection is ambiguous -- You want full control +Explicit configuration may be useful when: + +- An application uses a custom hosting environment. +- Runtime detection is ambiguous. +- A shared integration requires a specific client profile. +- The application needs predictable profile selection across environments. + +Explicitly configuring a client profile does not override the server's authentication policies. + +It only determines what profile the client integration declares or uses locally. + +The server must still resolve and enforce the effective authentication context. + +## Client Profiles and Authentication Modes + +Client Profiles and Authentication Modes are related but independent concepts. + +A Client Profile describes the execution environment and its integration requirements. + +An Authentication Mode defines how authentication credentials are issued, validated, continued, and revoked. + +For example, a Blazor Server application commonly benefits from PureOpaque because it can use server-controlled sessions without managing separate access and refresh tokens. + +A Blazor WebAssembly application communicating with a separate API may benefit from Hybrid because it can use access and refresh tokens while retaining server-authoritative session validation. + +These are architectural recommendations, not inherent limitations of the runtimes. + +### Recommended defaults + +| Client environment | Recommended mode | Reason | +|---|---|---| +| Blazor Server | PureOpaque | Simple session-based authentication without separate token rotation | +| Blazor WebAssembly with API | Hybrid | Access/refresh credentials with authoritative session control | +| .NET MAUI | Hybrid | Token lifecycle suitable for native clients | +| MVC / Razor Pages | PureOpaque | Natural integration with secure browser cookies and server-side sessions | +| API-oriented service client | PureJwt, when stateless authentication is appropriate | Independent JWT validation without mandatory session infrastructure | +| UAuthHub | Depends on its configured role and operation | Hosting the authentication system is not itself an authentication mode | + +These recommendations describe suitable architectural choices. + +They are not a guarantee that every listed mode is the current implementation default for that profile. + +In particular, the `UAuthHub` profile should not be interpreted as an authentication mode. UAuthHub is the authentication host, and its supported operations may serve clients using different modes. + +For the full comparison of validation and lifecycle guarantees, see [Authentication Modes](./authentication-modes.md). + +## Profile resolution and request context + +UltimateAuth can serve clients with different profiles through the same authentication infrastructure. + +For example: + +```text id="yktx8s" +Blazor Server Client + | + | Profile: BlazorServer + | + +--------------------+ + | +Blazor WASM Client | + | | + | Profile: BlazorWasm | + | v + +---------------> UAuthHub + | +MAUI Client | + | | + | Profile: Maui | + | | + +--------------------+ + | + v + AuthFlowContext + | + v + Effective Mode + Effective Policy + Response Behavior +``` + +The server resolves the effective authentication context for each applicable operation. + +This can include the Client Profile, authentication mode, tenant, security options, and credential delivery requirements. + +Different clients can therefore operate under different authentication strategies without requiring separate authentication architectures. + +### Per-request resolution does not mean arbitrary mode switching + +The effective profile and mode may differ between requests from different clients. + +However, they must remain consistent throughout an individual authentication operation. + +A request must not begin under one authentication policy and complete under another because different components interpreted its profile differently. + +Likewise, a client must not be allowed to change its declared profile to bypass validation requirements associated with an existing credential or session. + +Authentication continuation must preserve the relevant security relationships established by the original authentication context. + +The framework-controlled `AuthFlowContext` provides a consistent boundary for these decisions. + +See [Flow-Based Authentication](./flow-based-authentication.md) for the conceptual model and [Auth Flows](../auth-flows/) for the technical request pipeline. + +## Credential delivery and runtime capabilities + +Different clients have different capabilities for receiving and storing authentication credentials. + +Client Profiles help the framework account for those differences. + +### Browser environments + +Browser applications can use cookies for authentication. + +When cookies carry sensitive authentication credentials, appropriate `HttpOnly`, `Secure`, and `SameSite` policies should be applied. + +Cookie-based authentication also requires appropriate CSRF protection. + +In browser environments, a credential stored in an HttpOnly cookie is not directly readable by JavaScript or WebAssembly application code, although the browser can attach it to applicable requests. + +### Native applications + +Native applications generally communicate through HTTP clients and can use platform-specific secure credential storage. + +Their credential transport and storage requirements differ from those of a browser. + +A native Client Profile can help select appropriate integration behavior, but secure storage remains an application and platform responsibility. + +### Server-hosted applications + +Server-hosted applications can manage authentication state without exposing all authentication implementation details to browser application code. + +This often makes session-oriented authentication attractive. + +However, server execution alone does not guarantee security. Cookie protection, session validation, authorization, and long-lived connection behavior must still be handled correctly. + +### Resource APIs + +Resource APIs validate the credentials presented by callers. + +Their authentication requirements must be enforced by trusted server configuration. + +A caller's declared Client Profile must not determine whether token signatures, session validity, tenant boundaries, or authorization requirements can be bypassed. + +## Security and trust boundaries + +Client Profiles must preserve a clear separation between runtime adaptation and security authority. + +### Profile declarations are not trusted identity + +A Client Profile does not prove that the caller is a particular application, device, or user. + +Additional authentication mechanisms are required when an application needs to establish client identity. + +### Mode selection is server-controlled + +A client may provide information relevant to mode resolution, but the server determines which authentication modes are permitted. + +A profile must not provide a path to downgrade authentication requirements. + +### Credential validity is independent of profile labels + +A valid-looking credential must still satisfy the validation requirements of its authentication mode. + +Changing a profile label must not turn an invalid credential into a valid one. + +### Transport configuration is not authorization + +Selecting cookies, headers, or another supported delivery mechanism does not grant access to protected resources. + +Authentication and authorization decisions remain separate from credential transport. + +### Defaults are not universal guarantees + +A secure default depends on the application's deployment, runtime, authentication mode, and security requirements. + +UltimateAuth can provide suitable defaults, but developers remain responsible for selecting and configuring the policies appropriate to their applications. + +## Custom environments and extensibility + +Not every application fits a predefined runtime category. -
+A custom host, embedded client, hybrid application, or specialized integration may need explicit profile configuration or additional integration behavior. -## 🧩 Built-in Profiles +The purpose of Client Profiles is to keep these differences at a defined architectural boundary. -UltimateAuth includes predefined profiles: -| Profile | Description | -| ------------ | ---------------------------- | -| BlazorServer | Server-rendered apps | -| BlazorWasm | Browser-based WASM apps | -| Maui | Native mobile apps | -| WebServer | MVC / Razor / generic server | -| Api | API-only backend | -| UAuthHub | Central auth server | +New integrations should reuse the existing authentication model and flow infrastructure wherever possible, rather than introducing independent authentication implementations. -### πŸ›‘ Safe Defaults -If no profile is specified (and auto detection is false): +Custom profile resolution and integration behavior must continue to respect server-controlled mode selection and authentication security policies. -- Client β†’ NotSpecified β†’ Server resolves safely -- Defaults are applied -- Unsafe combinations are prevented -- System remains consistent +## Summary -### πŸ” Why This Matters -Client Profiles enable: +Client Profiles allow UltimateAuth to adapt authentication integration to different application environments. -- Multi-client systems (Web + Mobile + API) -- Runtime adaptation -- Safe defaults without manual configuration +They provide a consistent way to describe runtime characteristics and influence suitable defaults without turning client-provided metadata into a security authority. -Without Client Profiles You would need: -- Separate auth setups per client -- Complex branching logic -- Manual security handling +The fundamental distinctions are: -## 🧠 Mental Model +**Client Profile** describes the runtime environment and integration requirements. -If you remember one thing: +**Authentication Mode** defines credential validation, session dependencies, and authentication lifecycle guarantees. -πŸ‘‰ Client defines behavior -πŸ‘‰ Server enforces rules +**AuthFlowContext** establishes the effective security context for an authentication operation. -## πŸ“Œ Key Takeaways -- Client Profiles are automatically detected -- They are sent to the server on each request -- Behavior adapts per request -- You can override everything when needed -- Safe defaults are always applied +**Server policy** determines which behavior is permitted. -## ➑️ Next Step +Together, these concepts allow different clients to share the same UltimateAuth infrastructure while preserving explicit authentication boundaries. -Now that you understand runtime behavior: +## Next step -πŸ‘‰ Continue to Runtime Architecture +Continue to [Runtime Architecture](./runtime-architecture.md) to understand how UltimateAuth's client, server, and hosting components work together across different application environments. \ 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 6e60d06a..d7a36fea 100644 --- a/docs/content/fundamentals/flow-based-auth.md +++ b/docs/content/fundamentals/flow-based-auth.md @@ -1,205 +1,335 @@ --- -title: Flow Based Authentication +title: Flow-Based Authentication order: 3 group: fundamentals --- +# Flow-Based Authentication -# πŸ”„ Flow-Based Authentication -UltimateAuth is not cookie-based or token-based. +UltimateAuth organizes authentication around explicit, controlled operations called **authentication flows**. -πŸ‘‰ It is **flow-based**. +A flow represents an authentication intention, such as establishing access, continuing an existing authentication context, or terminating it. -
+Rather 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. -## πŸ”‘ What Does β€œFlow-Based” Mean? -In traditional systems, authentication is treated as: +This approach is independent of the credential format. -- A cookie -- A JWT token +A 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. -Once issued, the system simply checks: +**Flow-based authentication is not an alternative to session-based or token-based authentication. It is the architecture that coordinates those strategies.** -> β€œIs this token valid?” +## Why authentication flows matter -
+Authentication involves more than issuing and validating credentials. -UltimateAuth takes a different approach: +A complete authentication operation may need to determine: -πŸ‘‰ Authentication is a **series of controlled flows**, not a static artifact. +- Which tenant the operation belongs to. +- Which authentication mode is permitted. +- Which client profile and runtime characteristics apply. +- What authentication proof is required. +- Whether an existing authentication context can continue. +- Which security policies must be enforced. +- Whether server-side state must be created, updated, or revoked. +- Which credentials or other results should be returned to the client. -
+These decisions should not be independently reconstructed by every endpoint, client SDK, or authentication service. -## 🧭 Authentication as Flows -Every authentication operation is an explicit flow: +UltimateAuth establishes a shared context for the operation and delegates the relevant decisions to the components responsible for them. -- **Login** -- **Logout** -- **Validate** -- **Refresh** -- **Re-authentication** +The result is a predictable authentication process whose behavior can vary by mode without changing its fundamental security boundaries. -Each flow: +## What is an authentication flow? -- Is initiated intentionally -- Is processed on the server -- Produces a controlled result +An authentication flow is a controlled operation with a defined purpose, applicable security requirements, and an explicit outcome. -
+Three primary flows illustrate the model. -## πŸ” Example: Login Flow -Instead of: +### Login -> β€œGenerate a token and store it” +Login establishes a new authenticated context after the required authentication proof has been accepted. -UltimateAuth does: -``` -Login Request -β†’ Validate credentials -β†’ Resolve Root -β†’ Resolve or create Chain -β†’ Create Session -β†’ Issue authentication grant -``` +Depending on the effective mode, the operation may create server-side authentication state, issue access credentials, or perform both actions. -πŸ‘‰ Login is not a single step β€” it is a **managed process** +### Refresh -
+Refresh attempts to continue an existing authenticated context without requiring the original authentication proof again. -## πŸ”„ Example: Refresh Flow -Traditional systems: +Its behavior depends on the mode. -> Refresh = issue new token +For example, PureOpaque can continue a session through touch semantics, while token-based modes may issue replacement credentials. -UltimateAuth: -``` -Refresh Request -β†’ Validate session -β†’ Check security constraints -β†’ Apply policies (if any) -β†’ Optionally rotate tokens -β†’ Update session state (if needed) -``` +Refresh must not bypass expiration, revocation, or other continuation requirements. -πŸ‘‰ The server decides what actually happens +### Logout -
+Logout terminates or discards the applicable authentication context. -## πŸ” Example: Validate Flow -On each request: -``` -Incoming Request -β†’ Extract session/token -β†’ Validate session -β†’ Check chain (device context) -β†’ Verify root security version -β†’ Build auth state +In session-authoritative modes, this can involve revoking server-side sessions. + +In stateless JWT authentication, client-side credential removal does not automatically invalidate an already issued access token. + +These operations share architectural boundaries but do not perform identical work. + +For their implementation details, see [Auth Flows](../auth-flows/). + +## The flow execution model + +At a conceptual level, an authentication flow follows a common progression: + +```text +Authentication Intent + | + v +Resolve Effective Security Context + | + v +Validate Required Evidence + | + v +Evaluate Applicable Security Rules + | + v +Execute Mode-Specific Operation + | + v +Produce Authentication Outcome + | + v +Deliver Result to Client ``` -πŸ‘‰ Validation is not just β€œtoken valid?” +This diagram represents responsibilities, not a fixed implementation sequence. -
+For example, credential validation, authority decisions, and state transitions may involve different components or ordering depending on the operation. -## ⚠️ Why Token-Based Thinking Falls Short -Token-based systems assume: +Some flows create sessions. Others update existing state, rotate tokens, revoke credentials, or perform no persistent state change. -- The token contains truth -- The server trusts the token +The important guarantee is that each operation executes within a controlled context and follows the security rules applicable to its purpose and authentication mode. -This leads to: +## AuthFlowContext -- Weak revocation -- No device awareness -- Limited control +A central concept in this architecture is `AuthFlowContext`. -
+`AuthFlowContext` represents the effective authentication environment in which an operation executes. -## βœ… UltimateAuth Approach -UltimateAuth treats tokens as: +It brings together the information required to apply consistent authentication behavior, including: -πŸ‘‰ **transport artifacts**, not sources of truth +- The intended authentication flow. +- The effective authentication mode. +- The resolved tenant context. +- The applicable client profile. +- Effective server options and security policies. +- Relevant device and session context, when applicable. +- Credential delivery and response behavior. -The real authority is: +The context is established through framework-controlled boundaries. -- Root -- Chain -- Session +Once established, it must remain stable throughout the operation. Individual services and endpoints must not independently replace it or reconstruct it using different assumptions. -
+This matters particularly in applications serving multiple tenants, client runtimes, and authentication modes. -## 🧠 Key Idea -> Tokens carry data -> Flows enforce rules +A flow must not begin under one security policy and complete under another merely because different components interpreted the request differently. -
+### Context is not authorization -## πŸ” Server-Controlled by Design +`AuthFlowContext` describes the environment and rules under which an operation executes. -All flows are: +Its existence does not mean that the operation has been authorized or that the presented credentials are valid. -- Executed on the server -- Evaluated against policies -- Subject to security constraints +The relevant authentication evidence and security requirements must still be evaluated. -πŸ‘‰ The client does not control authentication state +After authentication has established trusted identity, authorization can operate through the appropriate `AccessContext`. -
+The distinction between these contexts is explained in [Authentication Model](./authentication-model.md). -## βš™οΈ Flow Examples in Code +## Flows and authentication modes -Using `IUAuthClient`: +Authentication flows describe **what operation is being performed**. -```csharp -await UAuthClient.Flows.LoginAsync(request); -await UAuthClient.Flows.RefreshAsync(); -await UAuthClient.Flows.LogoutAsync(); -``` -πŸ‘‰ Each method represents a server-driven flow +Authentication modes determine **how the operation establishes or continues trust**. + +Consider login. + +In PureOpaque, a successful login establishes a server-side session and provides a session credential. + +In Hybrid, login establishes server-side session authority and issues separate opaque access and refresh tokens. + +In SemiHybrid, login establishes the session lifecycle state required by the mode and issues JWT-based access credentials. + +In PureJwt, login issues JWT credentials without requiring a server-side session. + +All four are login operations, but their persistence and credential-issuance requirements differ. + +The same principle applies to refresh and logout. + +| Flow | What remains consistent | What varies by mode | +|---|---|---| +| Login | Establish authenticated access from validated evidence | Session creation, token format, credential issuance | +| Refresh | Determine whether authentication may continue | Session touch, session validation, token rotation, renewal state | +| Logout | End or discard the applicable authentication context | Session revocation, refresh-token invalidation, client credential cleanup | +| Request authentication | Establish trusted identity from presented credentials | Session validation, opaque token resolution, JWT validation | + +A flow must never introduce a session dependency merely because another authentication mode requires one. + +In particular, PureJwt must remain independent of mandatory session persistence and validation. + +For the complete mode definitions and their guarantees, see [Authentication Modes](./authentication-modes.md). + +## Server-controlled decisions + +UltimateAuth treats authentication as a server-controlled process. + +The client can request an operation and provide the evidence needed to perform it. + +However, the client does not independently determine: + +- Whether the authentication evidence is acceptable. +- Which security policies can be bypassed. +- Whether an expired or revoked authentication context may continue. +- Whether a new session or token may be issued. +- Whether a particular authentication mode is permitted. + +These decisions belong to trusted framework and application components operating under the effective server configuration. + +This does not mean every authentication decision requires a database lookup. + +For stateless JWT authentication, the server can establish identity through cryptographic verification and the applicable token-validation rules. + +For session-authoritative authentication, the server must also validate the required session state. + +Server authority describes **who controls the rules**, not whether every request must consult persistent storage. + +## Separation of responsibilities + +Flow-based authentication is also a separation-of-concerns model. + +The components participating in an authentication operation have distinct responsibilities. + +**Request and context resolution** + +Establish the trusted execution environment, including tenant, mode, client profile, and effective policies. + +**Authentication validation** + +Evaluate the evidence required by the operation and authentication mode. + +**Security decisions** + +Determine whether the requested operation is permitted under the applicable rules and authoritative state. + +**Orchestration** + +Coordinate the services and state transitions needed to complete the operation. + +**Persistence and credential issuance** + +Create, update, or revoke the relevant authentication state and credentials when required. + +**Response delivery** + +Communicate the result through the appropriate cookie, header, redirect, or other supported delivery mechanism. + +Separating these responsibilities prevents transport behavior from becoming authentication policy. + +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/). + +## Flows across client runtimes + +UltimateAuth supports different application environments through a common authentication architecture. + +A browser application, Blazor Server application, Blazor WebAssembly application, mobile client, and resource API may interact with authentication differently. + +For example: + +- A browser application may rely on secure cookies. +- A mobile application may manage credentials through secure device storage. +- A resource API may validate bearer access tokens. +- A Blazor Server application may use a server-controlled session without managing refresh-token rotation. + +These differences affect client integration and credential transport. + +They do not require independent implementations of the fundamental authentication model. + +Client Profiles provide runtime-appropriate behavior and defaults, while authentication modes define the validation and lifecycle strategy. + +See [Client Profiles](./client-profiles.md) for how runtime characteristics influence authentication configuration. + +## Predictable outcomes + +An authentication flow should produce an explicit result that reflects what the operation actually accomplished. + +For example, a refresh operation may: + +- Continue an existing session without changing persistent state. +- Update session activity or expiration when permitted. +- Rotate refresh credentials and issue replacement tokens. +- Reject continuation and require reauthentication. + +These outcomes are not interchangeable. + +A successful operation should not imply that a token was issued when no token was needed. + +Likewise, an unsuccessful refresh must not silently create a new authenticated context or bypass the original authentication requirements. + +Explicit outcomes allow the client SDK and server components to respond consistently without assuming that every mode behaves the same way. + +## Extensibility and security boundaries + +A flow-based architecture provides controlled extension points for authentication behavior. + +Depending on the supported features and configured services, applications may integrate additional requirements such as: + +- Multi-factor authentication. +- Additional credential verification. +- Tenant-specific authentication policies. +- Device-related security checks. +- Custom authentication proof providers. +- Additional security decisions before issuing or continuing access. + +Extensions must preserve the security guarantees of the effective authentication mode. + +For example, a custom flow must not bypass mandatory session validation in Hybrid. + +Similarly, an extension must not make session persistence an undocumented prerequisite for PureJwt. -
+Flexibility must not silently change the meaning of an authentication mode. -## 🧩 How This Changes Development -Instead of thinking: +## Flow-based does not mean session-based -❌ β€œI need to manage tokens” +An important distinction is that authentication flows and authentication lineage are related but separate concepts. -You think: +**Authentication flows** describe controlled operations. -βœ… β€œI need to trigger flows” +**Authentication lineage** describes the Root, Chain, and Session relationships used by session-backed authentication. -
+A flow may operate on that lineage when required. -## πŸ“Œ Benefits of Flow-Based Authentication -### βœ” Predictable Behavior -- Every action is explicit and controlled. +However, not every flow or mode requires persisted Root, Chain, or Session state. -### βœ” Better Security -- No blind token trust -- Server-side validation -- Policy-driven decisions +This separation allows UltimateAuth to support both stateful and stateless authentication without maintaining two unrelated authentication architectures. -### βœ” Extensibility -Flows can be extended with: +See [Authentication Model](./authentication-model.md) for the lineage model and its role in authentication. -- MFA -- Risk-based checks -- Custom policies +## Summary -### βœ” Consistent Across Clients -Same flows work for: -- Blazor Server -- WASM (PKCE) -- APIs +UltimateAuth uses authentication flows to coordinate security-sensitive operations through a consistent, trusted execution context. -
+A flow: -## 🧠 Mental Model -If you remember one thing: +- Represents an explicit authentication intention. +- Operates under a resolved and stable `AuthFlowContext`. +- Applies the validation and security rules required by the effective mode. +- Coordinates only the state transitions and credential operations necessary for its purpose. +- Produces an explicit outcome. +- Delivers that outcome according to the applicable client and response configuration. -πŸ‘‰ Authentication is not a token β€” it is a process +The architecture does not require every mode to use the same credentials, persistence model, or validation strategy. -## ➑️ Next Step +**Flows define the operation. Modes define its authentication behavior. Client Profiles adapt its integration with the runtime.** -Now that you understand flows: +## Next step -πŸ‘‰ Continue to Auth Modes +Continue to [Authentication Modes](./authentication-modes.md) to understand how PureOpaque, Hybrid, SemiHybrid, and PureJwt implement different authentication and session lifecycle strategies. \ No newline at end of file diff --git a/docs/content/fundamentals/request-lifecycle.md b/docs/content/fundamentals/request-lifecycle.md index 94ca5f76..7bb443a7 100644 --- a/docs/content/fundamentals/request-lifecycle.md +++ b/docs/content/fundamentals/request-lifecycle.md @@ -4,160 +4,339 @@ order: 7 group: fundamentals --- +# Request Lifecycle -# πŸ”„ Request Lifecycle -This section explains what happens when a request enters UltimateAuth. +UltimateAuth distinguishes between establishing authentication, validating an existing identity, and authorizing access to protected operations. -πŸ‘‰ From the moment an HTTP request arrives -πŸ‘‰ Until authentication state is established or a flow is executed +These responsibilities are related, but they do not necessarily execute through the same pipeline. -
+A login request may establish a new authentication context. A protected API request may only need to validate an existing credential. A server-side application operation may execute without making an additional HTTP request. -## 🧠 Two Types of Requests -UltimateAuth processes requests in two different ways: +Understanding these differences is essential when integrating UltimateAuth into real applications. -### 1. Passive Requests -Regular application requests (page load, API call) +This page focuses on **how authentication is evaluated during application requests and where the security boundaries are enforced**. -### 2. Active Flow Requests -Authentication flows (login, refresh, logout) +For authentication flow orchestration, see [Auth Flows](/docs/auth-flows/index). -πŸ‘‰ Both share the same foundation, but diverge at the flow level. +## Two request categories -
+At a conceptual level, requests interacting with UltimateAuth fall into two categories. -## 🧩 Middleware Pipeline -Every request passes through the UltimateAuth middleware pipeline: +| Category | Purpose | Typical examples | +|---|---|---| +| Authentication operations | Establish, continue, or terminate authentication | Login, refresh, logout | +| Protected resource operations | Access application functionality using an existing identity | API calls, protected pages, application commands | -``` -Tenant β†’ Session Resolution β†’ (Validation) β†’ User Resolution -``` +The distinction matters because an authentication operation may change authentication state, while a protected resource operation generally consumes an existing authentication context. + +Both require appropriate security checks, but they do not need to execute identical flows. -### 🏒 Tenant Resolution -The system determines the tenant: +## Authentication operations -- Multi-tenant β†’ resolved via `ITenantResolver` -- Single-tenant β†’ default context applied +Authentication operations are explicitly initiated through supported authentication endpoints or application integrations. -πŸ‘‰ If tenant cannot be resolved β†’ request fails early +Examples include login, refresh, and logout. -### πŸ” Session Resolution -The system attempts to extract a session: +For HTTP authentication endpoints, UltimateAuth uses its flow infrastructure to resolve the effective authentication context and execute the requested operation. -- From headers, cookies, or tokens -- Converted into a `SessionContext` +Conceptually: +```text +Authentication Request + | + v +Authentication Flow Boundary + | + v +Effective AuthFlowContext + | + v +Operation-Specific Validation + | + v +Authentication Operation + | + v +Authentication Result ``` -SessionId β†’ SessionContext + +The operation may create or update sessions, issue credentials, rotate refresh tokens, revoke authentication state, or produce a result without persistent changes. + +These details depend on the flow and authentication mode. + +The internal execution pipeline is described in [Auth Flows](/docs/auth-flows/index), while individual operations are documented in their respective flow guides. + +## Protected resource operations + +Most application requests do not initiate a login, refresh, or logout flow. + +Instead, they attempt to access a protected resource using an existing authentication credential or established server-side authentication context. + +A typical protected HTTP request follows this conceptual lifecycle: + +```text +Incoming Request + | + v +Resolve Applicable Authentication Scheme + | + v +Extract Authentication Credential + | + v +Validate Credential and Required State + | + v +Establish Trusted Identity + | + v +Evaluate Authorization + | + +---- Denied + | + v +Execute Protected Operation + | + v +Return Response ``` -πŸ‘‰ If no session is found β†’ request becomes anonymous +The exact framework components and ordering depend on the application integration. -### βœ” Session Validation (Resource APIs) -For API scenarios: +However, the security distinction remains the same: -- Session is validated immediately -- Device context is considered -- A validation result is attached to the request +**Authentication establishes identity. Authorization determines whether that identity may perform the requested operation.** -πŸ‘‰ This enables stateless or semi-stateful validation +A successfully authenticated request is not automatically authorized. -### πŸ‘€ User Resolution -The system resolves the current user: +## Credential validation -- Based on validated session -- Using `IUserAccessor` +The most important mode-dependent difference in the request lifecycle is how authentication credentials are validated. -πŸ‘‰ This produces the final user context +UltimateAuth supports different authentication strategies. -
+### PureOpaque -## πŸ”„ Passive Request Flow -For normal requests: +The client presents an opaque session credential. -``` -Request -β†’ Middleware pipeline -β†’ Session resolved -β†’ User resolved -β†’ Application executes -``` +The server resolves the associated session and evaluates the applicable session, chain, and security requirements. -πŸ‘‰ No flow execution happens +The session remains authoritative for authentication. -
+A revoked or expired session must not continue to authenticate subsequent requests. -## πŸ” Active Flow Requests -For auth endpoints (login, refresh, etc.): +### Hybrid -The lifecycle continues beyond middleware. +The client presents an opaque access token. -### Step 1: Flow Detection -``` -Endpoint β†’ FlowType -``` +The server resolves the token and validates its relationship to the authoritative server-side session. -### Step 2: Context Creation -An `AuthFlowContext` is created. +The access token and session are separate concepts. -It includes: +Authentication requires both a valid access credential and the applicable session validation requirements to succeed. -- Client profile -- Effective mode -- Tenant -- Device -- Session -- Response configuration +### SemiHybrid -πŸ‘‰ This defines the execution environment +The client presents a JWT access token. -### Step 3: Flow Execution -``` -AuthFlowContext β†’ Flow Service β†’ Orchestrator β†’ Authority -``` +Normal resource authentication validates the JWT cryptographically and applies the relevant token-validation rules. -### Step 4: State Mutation -Depending on the flow: +A server-side session remains relevant for lifecycle operations such as refresh and session management, but normal JWT validation does not require a session lookup. -- Session may be created, updated, or revoked -- Tokens may be issued -- Security state may change +Revoking a session therefore does not automatically invalidate an already issued JWT. -### Step 5: Response Generation -The system writes the response: +### PureJwt -- SessionId -- Access token -- Refresh token -- Redirect (if needed) +The client presents a JWT access token. -
+The resource validates the token cryptographically and applies the configured issuer, audience, lifetime, and other relevant validation requirements. -## πŸ” Example: Login Request -``` -HTTP Request -β†’ Tenant resolved -β†’ Session resolved (anonymous) -β†’ Flow detected (Login) -β†’ AuthFlowContext created -β†’ Credentials validated -β†’ Session created -β†’ Tokens issued -β†’ Response returned -``` +Authentication does not require a server-side session. + +Previously issued JWTs may remain valid until expiration unless an additional invalidation mechanism is explicitly configured. + +For the complete guarantees and tradeoffs, see [Authentication Modes](/docs/fundamentals/auth-modes). + +## When identity becomes trusted + +An incoming request may contain user identifiers, claims, tokens, cookies, device information, or tenant-related metadata. + +These values must not automatically be treated as a trusted identity. + +The relevant authentication mechanism must validate the presented evidence before the application relies on it. + +In session-authoritative modes, this includes the required server-side session checks. + +In JWT-based modes, it includes the applicable cryptographic and token-validation checks. + +Only after successful authentication should the application treat the resulting identity as authenticated. + +A failed authentication attempt must not leave behind an identity that appears successfully authenticated to downstream application code. + +### Anonymous requests + +Not every application request requires authentication. + +Public endpoints may allow anonymous access. + +A request without valid authentication credentials can remain anonymous when the endpoint permits it. + +For protected endpoints, missing or invalid authentication must prevent access according to the applicable authentication and authorization configuration. + +An anonymous request is not necessarily an error. It becomes an access problem when the requested operation requires an authenticated identity. + +## Authentication context and AccessContext + +UltimateAuth separates authentication-flow execution from authorization context. + +`AuthFlowContext` is associated with authentication operations and describes the effective environment in which those operations execute. + +It includes the applicable mode, client profile, tenant, and other flow-related information. + +`AccessContext` represents the trusted actor and contextual information used for access and authorization decisions. + +These contexts serve different purposes. + +An application request does not need to execute a login flow merely to establish an identity from an already valid authentication credential. + +Likewise, the existence of an `AuthFlowContext` does not mean the operation has already been authorized. + +The authentication model and these trust boundaries are introduced in [Authentication Model](/docs/fundamentals/auth-model). + +## Authorization boundary + +After authentication establishes the caller's identity, the application must determine whether the requested operation is permitted. + +Authorization may depend on: + +- The authenticated actor. +- The requested resource or operation. +- Tenant boundaries. +- Roles and permissions. +- Applicable application policies. +- Resource ownership or other domain-specific requirements. + +UltimateAuth integrates with the relevant server-side authorization mechanisms, but authentication and authorization remain separate responsibilities. + +For ASP.NET Core applications, endpoint and policy-based authorization can protect HTTP resources. + +However, an authorization attribute applied to an HTTP endpoint does not automatically protect arbitrary direct calls to the underlying application service. + +Security requirements must be enforced at the boundary through which the operation is actually accessible. + +## HTTP requests and in-process operations + +Not every protected application operation crosses an HTTP boundary. + +This distinction is especially important for server-hosted applications. + +### HTTP resource requests + +A remote client sends an HTTP request to a protected application or resource API. + +The receiving application must authenticate the request and enforce authorization before performing the protected operation. + +Client-side authentication state alone is insufficient. + +### In-process application operations + +A server-hosted application may invoke application services directly without making an HTTP request. + +For example, a Blazor Server component may call a registered service within the same process. + +In this case, the application must not assume that HTTP middleware or endpoint authorization filters execute automatically. + +The service invocation must operate with an appropriately established identity and enforce the relevant authorization requirements. + +This is an architectural distinction, not a separate authentication mode. + +See [Runtime Architecture](/docs/fundamentals/runtime-architecture) for integrated and distributed deployment models. + +## Long-lived connections + +Some application environments maintain connections or execution contexts that outlive individual HTTP requests. + +Blazor Server is an important example. + +An interactive circuit may remain active after its initial authentication state was established. + +During that time, the underlying authentication state may change. + +A session may expire, be revoked, or become invalid because of a security-sensitive operation. + +Consequently, applications must account for authentication-state revalidation and authorization during long-lived interactions. + +An initially authenticated connection must not be treated as permanent proof that every later operation remains authorized. + +The appropriate mechanism depends on the runtime integration and authentication mode. + +## Authentication failures and authorization failures + +Authentication and authorization failures represent different conditions. + +| Condition | Meaning | Typical HTTP outcome | +|---|---|---| +| Missing required authentication | No acceptable authenticated identity was established | 401 Unauthorized | +| Invalid or expired credential | Presented evidence failed authentication | 401 Unauthorized | +| Authenticated but insufficient permission | Identity is valid, but access is denied | 403 Forbidden | +| Public endpoint accessed anonymously | Authentication is not required | Request proceeds | + +These are typical HTTP semantics, not guarantees that every integration returns identical status codes. + +Browser applications may use redirects or other configured challenge behavior. + +Application services operating outside HTTP may express failures through typed results or exceptions rather than HTTP responses. + +The essential distinction is that authentication failure and authorization denial must not be confused. + +## Request isolation + +Authentication state must be associated with the correct execution context. + +A request must not inherit another request's authenticated identity, tenant context, or authorization state. + +This matters in multi-tenant systems, applications with concurrent requests, and server-side environments where services may have different lifetimes. + +Request-specific context must be resolved and propagated through the appropriate framework boundaries. + +Long-lived or singleton services must not accidentally retain request-specific authentication information. + +The same principle applies to direct application service calls and background operations. + +A background operation without an authenticated HTTP request must establish its execution and authorization context through an explicitly supported mechanism rather than assuming a user identity exists. + +## What the request lifecycle does not guarantee + +The request lifecycle should not be interpreted as a promise that every UltimateAuth operation: + +- Creates an `AuthFlowContext`. +- Resolves a server-side session. +- Queries authentication persistence. +- Rotates or issues tokens. +- Executes the same orchestrator or authority method. +- Passes through an HTTP endpoint. +- Uses identical authorization mechanisms. + +Those requirements depend on the operation, authentication mode, and runtime integration. + +The shared guarantee is architectural: authentication evidence must be validated according to the applicable security model, and protected operations must enforce their authorization requirements. + +## Summary + +UltimateAuth distinguishes between authentication operations and requests that consume an existing authenticated identity. + +Authentication operations establish, continue, or terminate authentication through controlled flows. + +Protected resource operations validate existing authentication evidence, establish trusted identity, and enforce authorization before allowing access. -
+The validation strategy depends on the authentication mode. -## πŸ” Flow Execution Boundary -Authentication flows are only executed for endpoints explicitly marked with flow metadata. +The execution boundary depends on the runtime and deployment architecture. -- Regular requests do not create an AuthFlowContext -- AuthFlowContext is only created during flow execution +**A request is not trusted because it contains a credential. It becomes authenticated only when the required validation succeeds, and it becomes authorized only when the applicable access rules permit the operation.** -πŸ‘‰ This ensures that authentication logic does not interfere with normal application behavior. +## Next step -
+You have completed the Fundamentals section. -## 🧠 Mental Model -πŸ‘‰ Middleware prepares the request -πŸ‘‰ Flows change the state +Continue to [Auth Flows](/docs/auth-flows/index) to explore the technical authentication pipeline and the individual login, refresh, and logout operations. \ No newline at end of file diff --git a/docs/content/fundamentals/runtime-architecture.md b/docs/content/fundamentals/runtime-architecture.md index 992ed5a7..498eb8cb 100644 --- a/docs/content/fundamentals/runtime-architecture.md +++ b/docs/content/fundamentals/runtime-architecture.md @@ -4,162 +4,554 @@ order: 6 group: fundamentals --- +# Runtime Architecture -# πŸ— Runtime Architecture +UltimateAuth is designed to provide a unified authentication architecture across different .NET application environments. -UltimateAuth processes authentication through a structured execution pipeline. +A Blazor Server application, a standalone Blazor WebAssembly application, a native MAUI application, and a resource API may have different execution models, but they should not require entirely separate authentication systems. -πŸ‘‰ It is not just middleware-based authentication -πŸ‘‰ It is a **flow-driven execution system** +UltimateAuth addresses this through a separation between **authentication infrastructure, client integration, hosting, and protected resources**. -## 🧠 The Big Picture +This separation allows authentication behavior to remain consistent while adapting to the capabilities and responsibilities of each runtime. -When a request reaches an auth endpoint: +## Architectural overview + +At a high level, UltimateAuth consists of several cooperating layers: + +- **Core:** Shared authentication contracts, models, and foundational abstractions. +- **Server:** Authentication operations, security decisions, credential management, and persistence coordination. +- **Client:** Client-side integration, authentication requests, and credential lifecycle handling. +- **UAuthHub:** The application hosting UltimateAuth's authentication endpoints and services. +- **Resource applications:** Applications and APIs that consume authenticated identity and enforce authorization. + +These are architectural responsibilities, not necessarily separate processes or deployments. + +A single application may host multiple responsibilities. Alternatively, they can be distributed across independently deployed services. + +```text + Application Clients + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ β”‚ + Blazor WASM MAUI Other Clients + β”‚ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”‚ HTTP + β–Ό + UAuthHub + β”‚ + UltimateAuth Server + β”‚ + Authentication Services + β”‚ + Stores / Providers + β”‚ + β–Ό + Persistent State + + + Protected Resources + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ β”‚ + Resource API Web App Other Services + β”‚ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + Authentication Validation + β”‚ + Authorization +``` + +This diagram illustrates the separation of responsibilities. + +It does not imply that every authenticated resource request must pass through UAuthHub. + +Whether a protected resource needs to consult server-side authentication infrastructure depends on the effective authentication mode and its validation requirements. + +## Core: shared authentication foundations + +The Core layer defines the common concepts used throughout UltimateAuth. + +Its responsibility is to provide stable contracts and abstractions without depending on a particular client runtime or application host. + +These foundations include concepts such as: + +- Authentication modes. +- Authentication and security contexts. +- Authentication results and contracts. +- Domain models and shared abstractions. +- Security-related options and definitions. + +The Core layer provides the common vocabulary used by client integrations, server services, and other framework components. + +It should not contain assumptions that authentication always occurs through an HTTP endpoint, a browser cookie, or a particular hosting model. + +This separation is important because UltimateAuth supports both stateful and stateless authentication strategies. + +A shared authentication contract must not implicitly require session persistence when the selected mode does not use sessions. + +## Server: authentication execution and lifecycle + +The Server layer implements the server-side authentication capabilities. + +It coordinates the operations required to establish, continue, validate, and terminate authentication according to the effective mode and security policies. + +Its responsibilities include: + +- Authentication flow execution. +- Credential validation and management. +- Session and token lifecycle operations. +- Authentication security decisions. +- Coordination with persistence providers. +- Credential issuance and validation. +- Integration with server-side authorization infrastructure. + +The Server layer contains multiple cooperating services rather than one universal authentication engine responsible for every decision. + +For example, session lifecycle operations, credential verification, and authorization may follow different execution paths while sharing common security contracts. + +The architecture separates orchestration from the underlying services that perform domain-specific work. + +For the detailed execution pipeline, see [Auth Flows](../auth-flows/). + +## Client: runtime integration + +The Client layer provides the integration between an application and UltimateAuth's authentication services. + +Its purpose is to reduce the amount of authentication infrastructure that application developers need to implement themselves. + +Depending on the supported runtime and configuration, client integration may handle: + +- Initiating authentication flows. +- Communicating with UAuthHub. +- Processing authentication outcomes. +- Managing supported credential delivery mechanisms. +- Coordinating authentication state with the application. +- Integrating authenticated requests with protected resources. + +The client SDK does not replace server-side authentication validation. + +It provides an application-facing interface to the authentication infrastructure. + +### Client integration is runtime-aware + +Different client runtimes have different capabilities. + +A browser-based WebAssembly application executes within browser security restrictions. + +A native MAUI application has access to platform-specific capabilities. + +A Blazor Server application executes application logic on the server while maintaining an interactive connection with the browser. + +UltimateAuth accounts for these differences through runtime-specific integrations and Client Profiles. + +The authentication model remains shared even when the implementation of credential transport or application state differs. + +See [Client Profiles](./client-profiles.md). + +## UAuthHub: the authentication host + +UAuthHub is the application hosting UltimateAuth's server-side authentication infrastructure. + +It provides the environment in which authentication endpoints and the required server services are available. + +Depending on the deployment, UAuthHub can be responsible for: + +- Accepting authentication requests. +- Resolving the applicable tenant and authentication context. +- Executing authentication flows. +- Managing credentials and sessions. +- Issuing authentication credentials. +- Applying configured authentication policies. +- Exposing the supported authentication endpoints. + +UAuthHub is a hosting role, not an authentication mode. + +A single UAuthHub can support multiple client environments and authentication strategies, subject to its configuration. + +### Centralized hosting + +In a centralized deployment, several applications can use the same UAuthHub. + +```text +Blazor WASM Application ──┐ + β”‚ +MAUI Application ─────────┼──► UAuthHub + β”‚ β”‚ +Web Application β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β–Ό + Authentication Services + β”‚ + β–Ό + Authentication Storage +``` + +This allows applications to share authentication infrastructure without requiring each application to implement its own credential management and authentication lifecycle. + +However, centralized authentication hosting does not mean that all protected resource requests must be routed through UAuthHub. + +The resource authentication strategy remains mode-dependent. + +## Resource applications and APIs + +A resource application is an application or service that exposes functionality requiring authentication and authorization. + +Examples include: + +- A protected Web API. +- An administrative web application. +- A business application backend. +- An independently deployed service. +- A server-rendered application with protected operations. + +A resource application must establish the caller's authenticated identity before allowing access to protected functionality. + +It must then enforce the relevant authorization policies. + +### Authentication validation depends on the mode + +In PureOpaque and Hybrid, authentication requires server-side session authority. + +The resource authentication path must therefore have access to the infrastructure necessary to enforce the relevant session validation requirements. + +In SemiHybrid and PureJwt, normal access-token authentication uses JWT validation without a mandatory session lookup. + +This can allow a resource API to validate access tokens locally when it has the required verification keys and token-validation configuration. + +The authentication mode therefore affects the resource application's infrastructure dependencies. + +### Authorization remains local to the protected operation + +Authentication establishes who the caller is. + +Authorization determines whether that caller may perform the requested operation. + +A valid authentication credential does not automatically grant permission to every resource. + +Resource applications must continue to enforce their authorization requirements through the applicable framework and application policies. + +UltimateAuth's authentication integration should preserve these boundaries rather than replacing resource authorization with client-side assumptions. + +## Deployment topology + +UltimateAuth separates architectural responsibilities from physical deployment boundaries. + +This means the same authentication architecture can support different deployment topologies. + +### Integrated deployment + +An integrated deployment hosts authentication infrastructure and application functionality within the same server application. + +```text +ASP.NET Core Application + β”‚ + β”œβ”€β”€ UltimateAuth Server + β”‚ + β”œβ”€β”€ Authentication Services + β”‚ + β”œβ”€β”€ Application Services + β”‚ + └── Protected Endpoints +``` + +In this model, authentication services and protected application functionality may share the same dependency injection container and process. + +Where supported, internal operations can interact with server-side services directly without requiring an HTTP call to the same application. + +This can simplify deployment and avoid unnecessary network communication. + +However, direct service access must still respect the required authentication and authorization boundaries. + +### Distributed deployment + +A distributed deployment separates the authentication host from one or more resource applications. ```text -Request - β†’ Endpoint Filter - β†’ AuthFlowContext - β†’ Endpoint Handler - β†’ Flow Service - β†’ Orchestrator - β†’ Authority - β†’ Stores / Issuers - β†’ Response + UAuthHub + β”‚ + β”‚ Authentication + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ + Web Application Mobile Client + β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + Resource API + β”‚ + β–Ό + Application Services ``` -Each step has a clearly defined responsibility. -## πŸ”„ Request Entry Point -Authentication begins at the endpoint level. +In this model, clients communicate with UAuthHub for the applicable authentication operations and access resource APIs using the supported credentials. + +Resource APIs validate those credentials according to the configured authentication mode. + +This architecture supports independently deployed applications while maintaining shared authentication policies and identity infrastructure. + +### Choosing a topology + +Integrated and distributed deployments are not different authentication modes. -An endpoint filter detects the flow: +They describe where application components execute and how they communicate. -`Endpoint β†’ FlowType (Login, Refresh, Logout…)` +The appropriate topology depends on: -πŸ‘‰ The system knows which flow is being executed before any logic runs. +- Application boundaries. +- Deployment and operational requirements. +- Network and infrastructure constraints. +- Authentication mode dependencies. +- Service ownership and scaling requirements. -
+A distributed deployment is not inherently more secure or more scalable than an integrated deployment. -## 🧾 AuthFlowContext β€” The Core State -Before any operation starts, UltimateAuth creates an AuthFlowContext. +Both require correct authentication validation, authorization enforcement, and infrastructure configuration. -This is the central object that carries: +## Blazor Server architecture -- Client profile -- Effective authentication mode -- Tenant information -- Device context -- Session state -- Response configuration +Blazor Server has a distinct execution model. -πŸ‘‰ This context defines the entire execution environment +The browser maintains an interactive connection to an application whose component logic executes on the server. -
+This makes it possible to keep many application operations and authentication-related services within the server environment. -## βš™οΈ Flow Service β€” Entry Layer -After the context is created, the request is passed to the Flow Service. +```text +Browser + β”‚ + β”‚ Interactive Connection + β–Ό +Blazor Server Application + β”‚ + β”œβ”€β”€ UI Components + β”‚ + β”œβ”€β”€ Application Services + β”‚ + β”œβ”€β”€ UltimateAuth Integration + β”‚ + └── Protected Operations +``` + +PureOpaque is a natural default for this environment because it provides server-controlled authentication without requiring a separate access/refresh token lifecycle. -The Flow Service: +However, the hosting model does not require PureOpaque exclusively. -- Acts as the entry point for all flows -- Normalizes execution -- Delegates work to orchestrators +### Direct service access -πŸ‘‰ It does not implement business logic directly +When authentication infrastructure and application services are hosted in the same process, server-side code may be able to use registered services directly. -
+This can avoid unnecessary HTTP requests between components of the same application. -## 🧭 Orchestrator β€” Flow Coordinator -The Orchestrator manages the execution of a flow. +The distinction is between an application service call and a remote API request. -- Coordinates multiple steps -- Ensures correct execution order -- Delegates decisions to Authority +A direct service call must not bypass security checks merely because it avoids HTTP. -πŸ‘‰ Think of it as the flow engine +Authorization requirements must still be enforced at the appropriate trusted boundary. -
+### Long-lived interactive connections -## πŸ” Authority β€” Decision Layer -The Authority is the most critical component. +Blazor Server introduces an additional consideration: authentication state may change while an interactive circuit remains active. -- Validates authentication state -- Applies security rules -- Approves or rejects operations +A session can expire or be revoked after the circuit was established. -πŸ‘‰ No sensitive operation bypasses Authority +Applications must therefore account for authentication-state revalidation and authorization of sensitive operations. -
+An authenticated circuit must not be treated as permanently authorized solely because its initial connection succeeded. -## βš™οΈ Services & Stores -Once decisions are made: +## Blazor WebAssembly architecture -- Services handle domain logic -- Stores handle persistence -- Issuers generate tokens or session artifacts +Blazor WebAssembly executes application code in the browser. -πŸ‘‰ These layers do not make security decisions +Unlike Blazor Server, it cannot directly access private server-side authentication services. -
+Authentication and protected application operations therefore cross an HTTP boundary. -## πŸ” End-to-End Example (Login) -Login Request +```text +Browser + β”‚ + └── Blazor WebAssembly + β”‚ + β”œβ”€β”€ UltimateAuth Client + β”‚ β”‚ + β”‚ β–Ό + β”‚ UAuthHub + β”‚ + └── Application API Client + β”‚ + β–Ό + Resource API ``` - β†’ Endpoint Filter (Login Flow) - β†’ AuthFlowContext created - β†’ Flow Service - β†’ Orchestrator - β†’ Authority validates credentials - β†’ Session created (Store) - β†’ Tokens issued (Issuer) - β†’ Response generated + +Hybrid is a suitable authentication strategy for applications that need separate access and refresh tokens while retaining server-authoritative session validation. + +However, a browser application can also use other modes when the corresponding transport and security architecture supports them. + +For example, a backend-for-frontend architecture may use secure session cookies rather than exposing bearer tokens to browser application code. + +The runtime determines which integration techniques are available; the authentication mode determines the security and lifecycle model. + +## Native application architecture + +Native applications, including .NET MAUI applications, execute outside the browser environment. + +They generally communicate with authentication hosts and resource APIs through HTTP. + +```text +MAUI Application + β”‚ + β”œβ”€β”€ UltimateAuth Client + β”‚ β”‚ + β”‚ β–Ό + β”‚ UAuthHub + β”‚ + β”œβ”€β”€ Secure Credential Storage + β”‚ + └── Resource API Client + β”‚ + β–Ό + Resource API ``` -
-## 🧠 Why This Architecture Matters -βœ” Centralized Decision Making -- Authority is always in control -- No scattered validation logic +Native applications can use platform-specific secure storage for sensitive credentials. + +Their credential delivery and persistence requirements differ from browser applications. + +Hybrid can provide a suitable model for applications requiring refresh-token continuity and centralized session control. + +The choice remains configurable according to the supported authentication modes and application requirements. + +## Shared components across runtimes + +A unified authentication architecture should minimize unnecessary duplication between client environments. + +UltimateAuth separates shared authentication behavior from runtime-specific integration. + +For example, Blazor Server and Blazor WebAssembly applications may share: + +- Authentication-related UI components. +- Login and logout interaction patterns. +- Request and response contracts. +- Application-facing client abstractions. +- Authentication-state presentation logic. + +At the same time, they may require different implementations for: + +- Credential storage. +- HTTP communication. +- Browser interaction. +- Server-side service access. +- Authentication-state synchronization. + +The goal is not to force every runtime to execute identical code. + +The goal is to share behavior and contracts where appropriate while preserving each runtime's security and execution requirements. + +## HTTP boundaries and direct execution + +An important architectural distinction is whether an operation crosses an HTTP boundary. + +### Remote execution + +A remote client communicates with an authentication host or resource API through HTTP. + +The server must independently validate the request, credentials, and authorization requirements. + +Client-side checks are not sufficient. + +### In-process execution + +An application may host UltimateAuth services and protected application services within the same process. + +In that case, some operations may use direct service calls instead of HTTP. + +This can reduce serialization and network overhead. + +However, HTTP middleware and endpoint authorization attributes do not automatically execute during arbitrary direct service calls. + +The application must ensure that equivalent security requirements are enforced through the appropriate service or authorization boundary. + +Direct execution is an optimization and integration choice, not permission to bypass authentication or authorization. + +## Infrastructure and persistence + +UltimateAuth uses abstractions to separate authentication behavior from infrastructure implementations. + +Depending on the configured mode and services, the system may require storage for: + +- User and credential information. +- Authentication security state. +- Session lineage. +- Refresh-token lifecycle. +- Device-associated authentication state. + +These dependencies vary by authentication mode. + +For example, PureOpaque and Hybrid require session infrastructure for authentication. + +SemiHybrid requires session infrastructure for lifecycle management but not for normal JWT validation. + +PureJwt does not require server-side session infrastructure. Optional refresh-token functionality may introduce independent renewal-state requirements. + +The architecture must preserve these distinctions. + +Infrastructure abstractions allow the authentication services to remain independent of a particular persistence implementation, while the selected mode determines which capabilities are required. + +## Architectural boundaries + +UltimateAuth's runtime architecture depends on several boundaries remaining explicit. + +### Client and server + +Clients initiate operations and present credentials. + +Servers establish trusted authentication state and enforce security requirements. + +### Authentication and authorization + +Authentication determines identity. + +Authorization determines whether an operation is permitted. + +### Hosting and authentication mode + +UAuthHub and resource applications describe hosting responsibilities. + +PureOpaque, Hybrid, SemiHybrid, and PureJwt describe authentication strategies. + +### Shared contracts and runtime integration + +Shared contracts define consistent behavior. + +Runtime integrations adapt that behavior to browser, native, and server-side environments. + +### Orchestration and infrastructure + +Orchestration coordinates authentication operations. -βœ” Predictable Execution -- Every flow follows the same pipeline -- No hidden behavior +Infrastructure services provide persistence, token operations, and other required capabilities. -βœ” Extensibility -- Replace stores -- Extend flows -- Customize orchestration +These boundaries allow UltimateAuth to evolve without requiring every client integration or authentication mode to duplicate the complete authentication implementation. -βœ” Security by Design -- No bypass of Authority -- Context-driven validation -- Flow-aware execution +## Summary -## πŸ”— Relation to Other Concepts -This architecture connects all previous concepts: +UltimateAuth's runtime architecture separates authentication responsibilities from application hosting and client execution environments. -- Auth Model (Root / Chain / Session) β†’ validated in Authority -- Auth Flows β†’ executed by Orchestrator -- Auth Modes β†’ applied via EffectiveMode -- Client Profiles β†’ influence behavior at runtime +Its main architectural components are: -## 🧠 Mental Model +- **Core**, which defines shared contracts and authentication foundations. +- **Server**, which implements authentication operations and lifecycle management. +- **Client**, which provides application-facing authentication integration. +- **UAuthHub**, which hosts the authentication infrastructure. +- **Resource applications**, which authenticate callers and enforce authorization. -If you remember one thing: +These components can operate within an integrated application or across distributed services. -πŸ‘‰ Flow defines what happens -πŸ‘‰ Context defines how it happens -πŸ‘‰ Authority decides if it happens +Different client runtimes can share authentication contracts and behavior while using integration mechanisms appropriate to their environments. -## πŸ“Œ Key Takeaways -Authentication is executed as a pipeline -AuthFlowContext carries execution state -Orchestrator coordinates flows -Authority enforces security -Services and Stores execute operations +Authentication modes determine the required validation and state dependencies. -## ➑️ Next Step +**The architecture is unified because its security contracts and responsibilities remain consistentβ€”not because every runtime, mode, or deployment follows an identical execution path.** -Now that you understand the execution model: +## Next step -πŸ‘‰ Continue to Request Lifecycle +Continue to [Request Lifecycle](./request-lifecycle.md) to understand how an incoming request moves through authentication validation, identity establishment, and authorization before reaching a protected operation. \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsContent.razor b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsContent.razor index 0f80a41e..6fe903b8 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsContent.razor +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsContent.razor @@ -41,10 +41,7 @@ else if (_doc is not null) { - - Previous - - + Previous @_prev.Title @@ -58,9 +55,7 @@ else if (_doc is not null) { - - Next - + Next @_next.Title @@ -138,9 +133,9 @@ else if (_doc is not null) protected override async Task OnAfterRenderAsync(bool firstRender) { - if (!_loading && _doc != null) + if (firstRender) { - // await JS.InvokeVoidAsync("docsEnhance", _contentRef); + await JS.InvokeVoidAsync("highlightDocsCode"); } } diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/device-management.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/device-management.json index 966fac57..dbb6e101 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/device-management.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/device-management.json @@ -1,7 +1,7 @@ { "Slug": "auth-flows/device-management", "Title": "Device Management", - "Html": "\n\u003Cp\u003EIn UltimateAuth, devices are not an afterthought.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 They are a \u003Cstrong\u003Efirst-class concept\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-device-matters\u0022\u003E\uD83E\uDDE0 Why Device Matters\u003C/h2\u003E\n\u003Cp\u003EMost authentication systems ignore devices.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA user logs in\u003C/li\u003E\n\u003Cli\u003EA token is issued\u003C/li\u003E\n\u003Cli\u003EEverything is treated the same\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This breaks down when you need:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMulti-device control\u003C/li\u003E\n\u003Cli\u003ESession visibility\u003C/li\u003E\n\u003Cli\u003ESecurity enforcement\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 UltimateAuth solves this with \u003Cstrong\u003Edevice-aware authentication\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-concept-chain-device\u0022\u003E\uD83E\uDDE9 Core Concept: Chain = Device\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 A \u003Cstrong\u003ESessionChain represents a device\u003C/strong\u003E\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EDevice \u2192 Chain \u2192 Sessions\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EEach chain:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EIs bound to a device\u003C/li\u003E\n\u003Cli\u003EGroups sessions\u003C/li\u003E\n\u003Cli\u003ETracks activity\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 A device is not inferred \u2014 it is explicitly modeled\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-defines-a-device\u0022\u003E\uD83D\uDD17 What Defines a Device?\u003C/h2\u003E\n\u003Cp\u003EA chain includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDeviceId\u003C/li\u003E\n\u003Cli\u003EPlatform (web, mobile, etc.)\u003C/li\u003E\n\u003Cli\u003EOperating System\u003C/li\u003E\n\u003Cli\u003EBrowser\u003C/li\u003E\n\u003Cli\u003EIP (optional binding)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This forms a \u003Cstrong\u003Edevice fingerprint\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-lifecycle\u0022\u003E\uD83D\uDD04 Device Lifecycle\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022first-login\u0022\u003E1\uFE0F\u20E3 First Login\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ENew device detected\u003C/li\u003E\n\u003Cli\u003ENew chain is created\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022subsequent-logins\u0022\u003E2\uFE0F\u20E3 Subsequent Logins\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESame device \u2192 reuse chain\u003C/li\u003E\n\u003Cli\u003ENew device \u2192 new chain\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Device continuity is preserved\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022activity-touch\u0022\u003E3\uFE0F\u20E3 Activity (Touch)\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EChain \u003Ccode\u003ELastSeenAt\u003C/code\u003E updated\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ETouchCount\u003C/code\u003E increases\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Tracks real usage\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-rotation\u0022\u003E4\uFE0F\u20E3 Token Rotation\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession changes\u003C/li\u003E\n\u003Cli\u003EChain remains\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ERotationCount\u003C/code\u003E increases\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Device identity stays stable\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003E5\uFE0F\u20E3 Logout\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession removed\u003C/li\u003E\n\u003Cli\u003EChain remains\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Device still trusted\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke\u0022\u003E6\uFE0F\u20E3 Revoke\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EChain invalidated\u003C/li\u003E\n\u003Cli\u003EAll sessions removed\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Device trust is reset\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-model\u0022\u003E\uD83D\uDD10 Security Model\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-binding\u0022\u003E\uD83D\uDD17 Device Binding\u003C/h3\u003E\n\u003Cp\u003ESessions and tokens are tied to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EChain\u003C/li\u003E\n\u003Cli\u003EDevice context\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Prevents cross-device reuse\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-tracking\u0022\u003E\uD83D\uDD01 Rotation Tracking\u003C/h3\u003E\n\u003Cp\u003EChains track:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERotationCount\u003C/li\u003E\n\u003Cli\u003ETouchCount\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Enables anomaly detection\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-cascade\u0022\u003E\uD83D\uDEA8 Revoke Cascade\u003C/h3\u003E\n\u003Cp\u003EIf a device is compromised:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEntire chain can be revoked\u003C/li\u003E\n\u003Cli\u003EAll sessions invalidated\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Immediate containment\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration\u0022\u003E\u2699\uFE0F Configuration\u003C/h2\u003E\n\u003Cp\u003EDevice behavior is configurable via session options:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMax chains per user\u003C/li\u003E\n\u003Cli\u003EMax sessions per chain\u003C/li\u003E\n\u003Cli\u003EPlatform-based limits\u003C/li\u003E\n\u003Cli\u003EDevice mismatch behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Fine-grained control for enterprise scenarios\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Device = Chain\u003Cbr /\u003E\n\uD83D\uDC49 Not just metadata\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EDevices are explicitly modeled\u003C/li\u003E\n\u003Cli\u003EEach device has its own chain\u003C/li\u003E\n\u003Cli\u003ESessions belong to chains\u003C/li\u003E\n\u003Cli\u003ESecurity is enforced per device\u003C/li\u003E\n\u003Cli\u003ELogout and revoke operate on device scope\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EConfiguration \u0026amp; Extensibility\u003C/strong\u003E\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EIn UltimateAuth, devices are not an afterthought.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 They are a \u003Cstrong\u003Efirst-class concept\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-device-matters\u0022\u003E\uD83E\uDDE0 Why Device Matters\u003C/h2\u003E\n\u003Cp\u003EMost authentication systems ignore devices.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA user logs in\u003C/li\u003E\n\u003Cli\u003EA token is issued\u003C/li\u003E\n\u003Cli\u003EEverything is treated the same\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This breaks down when you need:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMulti-device control\u003C/li\u003E\n\u003Cli\u003ESession visibility\u003C/li\u003E\n\u003Cli\u003ESecurity enforcement\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 UltimateAuth solves this with \u003Cstrong\u003Edevice-aware authentication\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-concept-chain-device\u0022\u003E\uD83E\uDDE9 Core Concept: Chain = Device\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 A \u003Cstrong\u003ESessionChain represents a device\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\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\u003EDevice \u2192 Chain \u2192 Sessions\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach chain:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EIs bound to a device\u003C/li\u003E\n\u003Cli\u003EGroups sessions\u003C/li\u003E\n\u003Cli\u003ETracks activity\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 A device is not inferred \u2014 it is explicitly modeled\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-defines-a-device\u0022\u003E\uD83D\uDD17 What Defines a Device?\u003C/h2\u003E\n\u003Cp\u003EA chain includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDeviceId\u003C/li\u003E\n\u003Cli\u003EPlatform (web, mobile, etc.)\u003C/li\u003E\n\u003Cli\u003EOperating System\u003C/li\u003E\n\u003Cli\u003EBrowser\u003C/li\u003E\n\u003Cli\u003EIP (optional binding)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This forms a \u003Cstrong\u003Edevice fingerprint\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-lifecycle\u0022\u003E\uD83D\uDD04 Device Lifecycle\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022first-login\u0022\u003E1\uFE0F\u20E3 First Login\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ENew device detected\u003C/li\u003E\n\u003Cli\u003ENew chain is created\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022subsequent-logins\u0022\u003E2\uFE0F\u20E3 Subsequent Logins\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESame device \u2192 reuse chain\u003C/li\u003E\n\u003Cli\u003ENew device \u2192 new chain\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Device continuity is preserved\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022activity-touch\u0022\u003E3\uFE0F\u20E3 Activity (Touch)\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EChain \u003Ccode\u003ELastSeenAt\u003C/code\u003E updated\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ETouchCount\u003C/code\u003E increases\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Tracks real usage\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-rotation\u0022\u003E4\uFE0F\u20E3 Token Rotation\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession changes\u003C/li\u003E\n\u003Cli\u003EChain remains\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ERotationCount\u003C/code\u003E increases\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Device identity stays stable\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003E5\uFE0F\u20E3 Logout\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession removed\u003C/li\u003E\n\u003Cli\u003EChain remains\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Device still trusted\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke\u0022\u003E6\uFE0F\u20E3 Revoke\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EChain invalidated\u003C/li\u003E\n\u003Cli\u003EAll sessions removed\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Device trust is reset\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-model\u0022\u003E\uD83D\uDD10 Security Model\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-binding\u0022\u003E\uD83D\uDD17 Device Binding\u003C/h3\u003E\n\u003Cp\u003ESessions and tokens are tied to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EChain\u003C/li\u003E\n\u003Cli\u003EDevice context\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Prevents cross-device reuse\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-tracking\u0022\u003E\uD83D\uDD01 Rotation Tracking\u003C/h3\u003E\n\u003Cp\u003EChains track:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERotationCount\u003C/li\u003E\n\u003Cli\u003ETouchCount\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Enables anomaly detection\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-cascade\u0022\u003E\uD83D\uDEA8 Revoke Cascade\u003C/h3\u003E\n\u003Cp\u003EIf a device is compromised:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEntire chain can be revoked\u003C/li\u003E\n\u003Cli\u003EAll sessions invalidated\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Immediate containment\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration\u0022\u003E\u2699\uFE0F Configuration\u003C/h2\u003E\n\u003Cp\u003EDevice behavior is configurable via session options:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMax chains per user\u003C/li\u003E\n\u003Cli\u003EMax sessions per chain\u003C/li\u003E\n\u003Cli\u003EPlatform-based limits\u003C/li\u003E\n\u003Cli\u003EDevice mismatch behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Fine-grained control for enterprise scenarios\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Device = Chain\u003Cbr\u003E\n\uD83D\uDC49 Not just metadata\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EDevices are explicitly modeled\u003C/li\u003E\n\u003Cli\u003EEach device has its own chain\u003C/li\u003E\n\u003Cli\u003ESessions belong to chains\u003C/li\u003E\n\u003Cli\u003ESecurity is enforced per device\u003C/li\u003E\n\u003Cli\u003ELogout and revoke operate on device scope\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EConfiguration \u0026amp; Extensibility\u003C/strong\u003E\u003C/p\u003E\n", "Headings": [ { "Id": "why-device-matters", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json index f31bce6a..9515fdd8 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json @@ -1,7 +1,7 @@ { "Slug": "auth-flows/index", "Title": "Auth Flows", - "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\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\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\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\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\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\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\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\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\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\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\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\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./login-flow.md\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./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./login-flow.md\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", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/login-flow.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/login-flow.json index f9f627e5..d60840a2 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/login-flow.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/login-flow.json @@ -1,7 +1,7 @@ { "Slug": "auth-flows/login-flow", "Title": "Login", - "Html": "\n\u003Cp\u003ELogging in is often described as a simple exchange: verify a password, issue a token, and consider the user authenticated. That description leaves out the part that matters most over time: \u003Cstrong\u003Ewhat exactly has been established and how can it be managed or revoked?\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EUltimateAuth treats login as a \u003Cstrong\u003Econtrolled session-establishment flow\u003C/strong\u003E. Credential verification is essential, but it is only one stage. A successful login connects an authenticated user to a tenant, a device context, a session hierarchy, and the security state that governs future requests.\u003C/p\u003E\n\u003Cp\u003EThe central idea is straightforward:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003ELogin establishes a session. Tokens, when enabled, are credentials issued in connection with that session\u2014not the session itself.\u003C/strong\u003E\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EThis distinction shapes how UltimateAuth handles device awareness, session continuity, token issuance, revocation, and authentication policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022from-a-login-response-to-an-authentication-lifecycle\u0022\u003EFrom a login response to an authentication lifecycle\u003C/h2\u003E\n\u003Cp\u003EIn a token-centric design, login is frequently treated as the moment a token is minted. The token then becomes the primary artifact developers reason about. This works for simple scenarios, but it can make questions about devices, concurrent logins, global sign-out, and session continuity harder to express consistently.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth starts with the authentication lifecycle instead. It separates four responsibilities:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003E\u003Cstrong\u003EIdentity resolution:\u003C/strong\u003E determine which user is attempting to authenticate.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthentication and security evaluation:\u003C/strong\u003E verify the credential and decide whether the attempt is allowed.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession establishment:\u003C/strong\u003E create a managed authentication instance in the appropriate device and user context.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ECredential delivery:\u003C/strong\u003E optionally issue access and refresh tokens and deliver the applicable credentials according to the server mode and client profile.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThese responsibilities are related, but they are not interchangeable. In particular, a valid password does not by itself create a session, and a token is not the sole source of authentication state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-session-hierarchy\u0022\u003EThe session hierarchy\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth models authentication through a hierarchy:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EUser\n\u2514\u2500\u2500 Root\n \u251C\u2500\u2500 Chain (Device A)\n \u2502 \u251C\u2500\u2500 Session 1\n \u2502 \u2514\u2500\u2500 Session 2\n \u2514\u2500\u2500 Chain (Device B)\n \u2514\u2500\u2500 Session 3\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EEach level answers a different question:\u003C/p\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ELevel\u003C/th\u003E\n\u003Cth\u003EResponsibility\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\u003Cstrong\u003EUser\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe identity being authenticated\u003C/td\u003E\n\u003Ctd\u003ELogin identifiers can change without changing the user\u0027s internal identity.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERoot\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe user-level session hierarchy\u003C/td\u003E\n\u003Ctd\u003EProvides a shared context for authentication across devices.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EChain\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EA device-associated sequence of sessions\u003C/td\u003E\n\u003Ctd\u003EAllows the system to reason about continuity and device-scoped authentication.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESession\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EA particular issued authentication instance\u003C/td\u003E\n\u003Ctd\u003EProvides the concrete session identity to which authentication credentials can be related.\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cp\u003EThe hierarchy is a \u003Cstrong\u003Elogical model\u003C/strong\u003E, not a claim that every login inserts a new root or chain. A login can continue an existing active chain for the same user and device. The session subsystem determines how the resulting session is issued and persisted.\u003C/p\u003E\n\u003Cp\u003EA chain is best understood as a \u003Cstrong\u003Edevice-associated authentication lifecycle\u003C/strong\u003E, rather than as the physical device itself. The quality and stability of device identification depend on the configured device context.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-a-login-proceeds\u0022\u003EHow a login proceeds\u003C/h2\u003E\n\u003Cp\u003EThe standard password login flow can be viewed as the following pipeline:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EClient request\n \u2502\n \u25BC\nResolve authentication flow context\n \u2502 Tenant \u00B7 device \u00B7 effective mode \u00B7 client profile\n \u25BC\nResolve identifier \u2192 UserKey\n \u2502\n \u25BC\nCheck user eligibility and security state\n \u2502\n \u25BC\nValidate credential using registered providers\n \u2502\n \u25BC\nFind an eligible active chain for this user/device\n \u2502\n \u25BC\nLoginAuthority decision\n \u251C\u2500\u2500 Deny \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25BA Failure / lockout handling\n \u251C\u2500\u2500 Challenge \u2500\u2500\u2500\u2500\u25BA Continuation required\n \u2514\u2500\u2500 Allow\n \u2502\n \u25BC\n Establish session\n \u2502\n \u25BC\n Optionally issue access/refresh tokens\n \u2502\n \u25BC\n Dispatch login event\n \u2502\n \u25BC\n Deliver credentials / redirect or OK\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis diagram shows the responsibilities in the current flow. Some security-state operations, such as recording failed attempts, happen as part of processing a rejected login. Therefore, \u003Cstrong\u003Ethe authority decision precedes session issuance, not every possible state mutation\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resolve-the-authentication-context\u0022\u003E1. Resolve the authentication context\u003C/h3\u003E\n\u003Cp\u003EBefore credential validation, UltimateAuth operates within an \u003Ccode\u003EAuthFlowContext\u003C/code\u003E. This context provides the effective tenant, device information, authentication mode, client profile, and applicable server options.\u003C/p\u003E\n\u003Cp\u003EThat context matters because the same logical login operation can have different delivery and session requirements depending on how the application is hosted and which kind of client is connecting. Login should not need to be reimplemented for each application style.\u003C/p\u003E\n\u003Cp\u003EA resolvable device ID is required by the current login orchestrator. If device identification cannot be resolved, login cannot proceed to normal session establishment.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resolve-the-login-identifier\u0022\u003E2. Resolve the login identifier\u003C/h3\u003E\n\u003Cp\u003EA user may identify themselves with a username, email address, or another identifier supported by the configured resolver. UltimateAuth translates that external identifier into an internal \u003Ccode\u003EUserKey\u003C/code\u003E through \u003Ccode\u003EILoginIdentifierResolver\u003C/code\u003E.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EUser-provided identifier\n \u2502\n \u25BC\nILoginIdentifierResolver\n \u2502\n \u25BC\n UserKey\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThe internal user key, not the original text entered into the login form, becomes the stable reference used by the rest of the authentication flow. This helps keep \u003Cstrong\u003Ehow a user signs in\u003C/strong\u003E separate from \u003Cstrong\u003Ewho that user is\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe identifiers actually accepted depend on the configured resolver and user data; support for a particular identifier format should not be assumed without configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-eligibility-and-security-state\u0022\u003E3. Check eligibility and security state\u003C/h3\u003E\n\u003Cp\u003EOnce the user is resolved, the login orchestrator retrieves their runtime state. The current implementation requires the user to exist, be permitted to authenticate (\u003Ccode\u003ECanAuthenticate\u003C/code\u003E), and not be deleted (\u003Ccode\u003EIsDeleted\u003C/code\u003E).\u003C/p\u003E\n\u003Cp\u003EIt also examines two security scopes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EAccount state:\u003C/strong\u003E security conditions associated with the account as a whole.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EFactor state:\u003C/strong\u003E security conditions associated with the credential type being used.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAn existing account or factor lock can stop the attempt before credential validation proceeds. Failed attempts, lockout duration, failure windows, and lock extension are governed by login options.\u003C/p\u003E\n\u003Cp\u003EThis separation is intentional: a credential can be correct while the account is not currently eligible to authenticate.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validate-the-credential\u0022\u003E4. Validate the credential\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth delegates credential verification to registered credential providers rather than embedding password comparison directly into the login orchestrator.\u003C/p\u003E\n\u003Cp\u003EFor the current standard HTTP login endpoint, the credential type is \u003Cstrong\u003EPassword\u003C/strong\u003E. The orchestrator also rejects unsupported credential types in this path. The provider architecture is extensible, but that extensibility should not be confused with a promise that OTP, passkeys, or external identity providers all use this exact password-login endpoint in v0.1.0.\u003C/p\u003E\n\u003Cp\u003EFor eligible credentials, the orchestrator checks that the credential matches the requested type, has not been deleted, is usable under its security rules, and validates the submitted secret through its provider.\u003C/p\u003E\n\u003Cp\u003EThis gives the login flow a stable orchestration model while keeping credential-specific verification behind dedicated abstractions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resolve-device-associated-session-continuity\u0022\u003E5. Resolve device-associated session continuity\u003C/h3\u003E\n\u003Cp\u003EBefore the authority decision, the orchestrator looks for a session chain associated with the resolved user and device. If a chain exists and its state is active under the configured idle timeout, its \u003Ccode\u003EChainId\u003C/code\u003E can be carried into session issuance.\u003C/p\u003E\n\u003Cp\u003EConceptually:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ESame user \u002B known device \u002B active chain\n \u2502\n \u25BC\n Continue with that ChainId\n\nNo eligible active chain\n \u2502\n \u25BC\n Session subsystem establishes\n the appropriate new chain\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis enables device-aware continuity without reducing a login to \u201Cissue another unrelated token.\u201D The session subsystem remains responsible for the exact creation and persistence behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022make-the-authority-decision\u0022\u003E6. Make the authority decision\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ELoginAuthority\u003C/code\u003E evaluates the login attempt and returns one of three outcomes:\u003C/p\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EDecision\u003C/th\u003E\n\u003Cth\u003EMeaning\u003C/th\u003E\n\u003Cth\u003ESession issued?\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAllow\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe attempt may proceed to session establishment.\u003C/td\u003E\n\u003Ctd\u003EYes, if the remaining issuance steps succeed.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EDeny\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication is rejected.\u003C/td\u003E\n\u003Ctd\u003ENo.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EChallenge\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAn additional step or continuation is required.\u003C/td\u003E\n\u003Ctd\u003ENo, not from this login attempt.\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cp\u003EThe current default authority considers user existence, lockout, reauthentication requirements, and credential validity. The broader flow also performs eligibility and lockout checks before invoking the authority.\u003C/p\u003E\n\u003Cp\u003EA challenge is represented through a \u003Ccode\u003ELoginContinuation\u003C/code\u003E; the current orchestrator uses an MFA continuation type for its challenge branch. This represents a requirement to continue authentication, \u003Cstrong\u003Enot proof that an MFA exchange has already completed\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EFor rejected attempts, the orchestrator may record a failed factor attempt and return lockout or remaining-attempt information when the corresponding disclosure option is enabled. Applications should treat these details as policy-controlled rather than universally available.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022establish-the-session\u0022\u003E7. Establish the session\u003C/h3\u003E\n\u003Cp\u003EOnly an allowed, successfully validated \u003Cstrong\u003Ecommit\u003C/strong\u003E login proceeds to session issuance.\u003C/p\u003E\n\u003Cp\u003EThe orchestrator first records a successful factor authentication (unless explicitly suppressed by an internal execution option), then obtains user claims through \u003Ccode\u003EIUserClaimsProvider\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIt constructs a \u003Ccode\u003ESessionIssuanceContext\u003C/code\u003E containing the tenant, user key, device, claims, effective mode, session options, and any eligible existing chain ID. The context is passed to \u003Ccode\u003EISessionOrchestrator\u003C/code\u003E using a login-session creation command.\u003C/p\u003E\n\u003Cp\u003EThis is the decisive transition:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EThe user has not merely supplied a valid secret; the system has established a managed authentication session.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EThe resulting session has its own identity. That identity is available to the later token-issuance and response-delivery stages.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022issue-tokens-when-permitted\u0022\u003E8. Issue tokens when permitted\u003C/h3\u003E\n\u003Cp\u003EAfter session issuance, access and refresh tokens may be requested from the token issuer. The current orchestrator issues them only when \u003Ccode\u003ELoginRequest.RequestTokens\u003C/code\u003E is true.\u003C/p\u003E\n\u003Cp\u003EFor the HTTP login endpoint, however, the server derives that setting from \u003Ccode\u003EAuthFlowContext.AllowsTokenIssuance\u003C/code\u003E. A client cannot force token issuance merely by placing \u003Ccode\u003ERequestTokens: true\u003C/code\u003E in its submitted payload.\u003C/p\u003E\n\u003Cp\u003EWhen issuance is enabled:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe access token is issued in the context of the authenticated user and issued session.\u003C/li\u003E\n\u003Cli\u003ERefresh-token issuance is requested with persistence enabled.\u003C/li\u003E\n\u003Cli\u003EBoth issuance paths operate with the tenant, user key, session ID, chain ID, and claims context.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EDepending on the effective mode and issuer behavior, an individual token type may be absent. \u003Cstrong\u003ESuccessful login and token issuance are deliberately separate concepts.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022dispatch-the-login-event-and-deliver-the-response\u0022\u003E9. Dispatch the login event and deliver the response\u003C/h3\u003E\n\u003Cp\u003EFollowing successful session and optional token issuance, UltimateAuth dispatches a \u003Ccode\u003EUserLoggedInContext\u003C/code\u003E event containing the tenant, user key, time, device, and session ID.\u003C/p\u003E\n\u003Cp\u003EThe HTTP endpoint then delegates credential delivery to \u003Ccode\u003EICredentialResponseWriter\u003C/code\u003E. Session ID, access token, and refresh token are handled as distinct grant kinds. Delivery is resolved using the effective mode, authentication flow, client profile, and server configuration.\u003C/p\u003E\n\u003Cp\u003EDepending on configuration, the HTTP response may complete with \u003Ccode\u003E200 OK\u003C/code\u003E or a success redirect. A rejected login may produce \u003Ccode\u003E401 Unauthorized\u003C/code\u003E or a configured failure redirect.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant for v0.1.0:\u003C/strong\u003E \u003Ccode\u003ETokenResponseMode.Body\u003C/code\u003E is currently unsupported and rejected during response resolution. Do not assume that the HTTP login endpoint returns a JSON object containing the issued tokens. Consult the configured delivery modes and client profile instead.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022trylogin-evaluate-without-establishing-a-session\u0022\u003ETryLogin: evaluate without establishing a session\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes \u003Cstrong\u003Epreviewing a login\u003C/strong\u003E from \u003Cstrong\u003Ecommitting a login\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003ETryLogin\u003C/code\u003E runs the authentication evaluation in preview mode. It can indicate whether credentials would pass the current checks, report permitted failure information, and return a continuation requirement. It does \u003Cstrong\u003Enot\u003C/strong\u003E issue a session or tokens.\u003C/p\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EBehavior\u003C/th\u003E\n\u003Cth\u003ETryLogin (Preview)\u003C/th\u003E\n\u003Cth\u003ELogin (Commit)\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EResolve identifier and evaluate credentials\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EApply applicable failure-attempt handling\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReset factor failure state on success\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003EYes, normally\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECreate a session\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003EYes, on success\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIssue tokens\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003EOptional\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReturn a preview receipt\u003C/td\u003E\n\u003Ctd\u003EPossible\u003C/td\u003E\n\u003Ctd\u003EConsumes a matching receipt, if supplied\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cp\u003EOn a successful preview, the server can create a short-lived \u003Ccode\u003EPreviewReceipt\u003C/code\u003E. That receipt is associated with the user, tenant, credential type, device, identifier, client profile, and a fingerprint of the submitted login material.\u003C/p\u003E\n\u003Cp\u003EWhen a subsequent commit request includes a matching receipt, the server consumes it and can suppress a duplicate failed-attempt registration for that commit. \u003Cstrong\u003EThe receipt is not a replacement for the password\u003C/strong\u003E: the commit path still performs credential validation.\u003C/p\u003E\n\u003Cp\u003EThis separation is useful when an application needs to assess login readiness or guide a user through an authentication experience without establishing a session prematurely.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-awareness-and-session-management\u0022\u003EDevice awareness and session management\u003C/h2\u003E\n\u003Cp\u003EBecause login produces a managed session within a device-associated chain, UltimateAuth can model authentication beyond a single browser token:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EMultiple devices:\u003C/strong\u003E a user can have separate chains associated with distinct device contexts.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession continuity:\u003C/strong\u003E an active chain can be considered when the same user signs in again from the same device.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ETargeted session management:\u003C/strong\u003E session and chain identities give downstream operations a way to distinguish authentication instances and device-associated lifecycles.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EBroader revocation:\u003C/strong\u003E the hierarchy provides the foundation for operations that affect more than one session.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact scope and availability of logout and revocation operations are defined by their own APIs and policies. Login establishes the identities those operations can act upon; it does not by itself specify every revocation behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-is-part-of-the-flow-not-an-afterthought\u0022\u003ESecurity is part of the flow, not an afterthought\u003C/h2\u003E\n\u003Cp\u003ESeveral security properties follow from this design:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EUser eligibility is separate from credential correctness.\u003C/strong\u003E A valid secret does not override a disabled, deleted, or locked account.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EFailed attempts are tracked as security state.\u003C/strong\u003E Lockout behavior is configurable and can be evaluated before session issuance.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDevice context participates in session establishment.\u003C/strong\u003E The system can distinguish device-associated chains rather than treating all logins as anonymous token issuance events.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESession issuance follows an explicit decision.\u003C/strong\u003E A denied or challenged login does not proceed to normal session creation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EResponse delivery is policy-driven.\u003C/strong\u003E Whether a session credential or token is delivered\u2014and by which supported mechanism\u2014is determined by the effective server flow, not solely by a client request.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe hierarchy supports later lifecycle enforcement.\u003C/strong\u003E Token validation, session validity, security-version checks, refresh behavior, and revocation are related concerns, but their exact enforcement belongs to those respective flows. They should not be mistaken for checks all performed inside the login method itself.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-useful-mental-model\u0022\u003EA useful mental model\u003C/h2\u003E\n\u003Cp\u003EThink of login as the moment UltimateAuth establishes a \u003Cstrong\u003Emanaged, device-aware authentication relationship\u003C/strong\u003E between a user and an application.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EIdentity \u002B Valid Credential \u002B Security Decision\n \u2502\n \u25BC\n Managed Session\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u25BC \u25BC\n Session identity Optional tokens\n \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u25BC\n Ongoing auth lifecycle\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThe session is the managed authentication instance. Tokens, where used, are credentials associated with that instance. They can be issued, refreshed, and validated without redefining what a login means.\u003C/p\u003E\n\u003Cp\u003EThis is the shift UltimateAuth is designed to make: \u003Cstrong\u003Efrom login as token generation to login as lifecycle-aware session establishment\u003C/strong\u003E.\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\u003ELogin is a flow, not a single credential-check function.\u003C/strong\u003E Identity resolution, eligibility, security evaluation, session issuance, and delivery are separate stages.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EThe hierarchy is \u003Ccode\u003EUser \u2192 Root \u2192 Chain \u2192 Session\u003C/code\u003E.\u003C/strong\u003E It expresses user-level, device-associated, and individual session lifecycles.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EA successful login establishes a session.\u003C/strong\u003E It does not necessarily create a new root or chain each time.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ETokens are optional.\u003C/strong\u003E Issuance is governed by the effective mode and server policy.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ETryLogin is not Login.\u003C/strong\u003E Preview evaluates an attempt without issuing a session; commit establishes one.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EThe authority controls admission to session issuance.\u003C/strong\u003E Security-state handling can also occur before and after that decision.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice context matters.\u003C/strong\u003E Session continuity and downstream session management are built around it.\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\u003EContinue to \u003Cstrong\u003ERefresh Flow\u003C/strong\u003E to learn how UltimateAuth extends or rotates authentication credentials while preserving the relationship to the managed session lifecycle.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003ELogging in is often described as a simple exchange: verify a password, issue a token, and consider the user authenticated. That description leaves out the part that matters most over time: \u003Cstrong\u003Ewhat exactly has been established and how can it be managed or revoked?\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EUltimateAuth treats login as a \u003Cstrong\u003Econtrolled session-establishment flow\u003C/strong\u003E. Credential verification is essential, but it is only one stage. A successful login connects an authenticated user to a tenant, a device context, a session hierarchy, and the security state that governs future requests.\u003C/p\u003E\n\u003Cp\u003EThe central idea is straightforward:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003ELogin establishes a session. Tokens, when enabled, are credentials issued in connection with that session\u2014not the session itself.\u003C/strong\u003E\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EThis distinction shapes how UltimateAuth handles device awareness, session continuity, token issuance, revocation, and authentication policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022from-a-login-response-to-an-authentication-lifecycle\u0022\u003EFrom a login response to an authentication lifecycle\u003C/h2\u003E\n\u003Cp\u003EIn a token-centric design, login is frequently treated as the moment a token is minted. The token then becomes the primary artifact developers reason about. This works for simple scenarios, but it can make questions about devices, concurrent logins, global sign-out, and session continuity harder to express consistently.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth starts with the authentication lifecycle instead. It separates four responsibilities:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003E\u003Cstrong\u003EIdentity resolution:\u003C/strong\u003E determine which user is attempting to authenticate.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthentication and security evaluation:\u003C/strong\u003E verify the credential and decide whether the attempt is allowed.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession establishment:\u003C/strong\u003E create a managed authentication instance in the appropriate device and user context.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ECredential delivery:\u003C/strong\u003E optionally issue access and refresh tokens and deliver the applicable credentials according to the server mode and client profile.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThese responsibilities are related, but they are not interchangeable. In particular, a valid password does not by itself create a session, and a token is not the sole source of authentication state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-session-hierarchy\u0022\u003EThe session hierarchy\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth models authentication through a hierarchy:\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 \u251C\u2500\u2500 Chain (Device A)\n \u2502 \u251C\u2500\u2500 Session 1\n \u2502 \u2514\u2500\u2500 Session 2\n \u2514\u2500\u2500 Chain (Device B)\n \u2514\u2500\u2500 Session 3\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach level answers a different question:\u003C/p\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\u003Cth\u003EWhy it matters\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EUser\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe identity being authenticated\u003C/td\u003E\n\u003Ctd\u003ELogin identifiers can change without changing the user\u0027s internal identity.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERoot\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe user-level session hierarchy\u003C/td\u003E\n\u003Ctd\u003EProvides a shared context for authentication across devices.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EChain\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EA device-associated sequence of sessions\u003C/td\u003E\n\u003Ctd\u003EAllows the system to reason about continuity and device-scoped authentication.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESession\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EA particular issued authentication instance\u003C/td\u003E\n\u003Ctd\u003EProvides the concrete session identity to which authentication credentials can be related.\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe hierarchy is a \u003Cstrong\u003Elogical model\u003C/strong\u003E, not a claim that every login inserts a new root or chain. A login can continue an existing active chain for the same user and device. The session subsystem determines how the resulting session is issued and persisted.\u003C/p\u003E\n\u003Cp\u003EA chain is best understood as a \u003Cstrong\u003Edevice-associated authentication lifecycle\u003C/strong\u003E, rather than as the physical device itself. The quality and stability of device identification depend on the configured device context.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-a-login-proceeds\u0022\u003EHow a login proceeds\u003C/h2\u003E\n\u003Cp\u003EThe standard password login flow can be viewed as the following pipeline:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\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 request\n \u2502\n \u25BC\nResolve authentication flow context\n \u2502 Tenant \u00B7 device \u00B7 effective mode \u00B7 client profile\n \u25BC\nResolve identifier \u2192 UserKey\n \u2502\n \u25BC\nCheck user eligibility and security state\n \u2502\n \u25BC\nValidate credential using registered providers\n \u2502\n \u25BC\nFind an eligible active chain for this user/device\n \u2502\n \u25BC\nLoginAuthority decision\n \u251C\u2500\u2500 Deny \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25BA Failure / lockout handling\n \u251C\u2500\u2500 Challenge \u2500\u2500\u2500\u2500\u25BA Continuation required\n \u2514\u2500\u2500 Allow\n \u2502\n \u25BC\n Establish session\n \u2502\n \u25BC\n Optionally issue access/refresh tokens\n \u2502\n \u25BC\n Dispatch login event\n \u2502\n \u25BC\n Deliver credentials / redirect or OK\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis diagram shows the responsibilities in the current flow. Some security-state operations, such as recording failed attempts, happen as part of processing a rejected login. Therefore, \u003Cstrong\u003Ethe authority decision precedes session issuance, not every possible state mutation\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resolve-the-authentication-context\u0022\u003E1. Resolve the authentication context\u003C/h3\u003E\n\u003Cp\u003EBefore credential validation, UltimateAuth operates within an \u003Ccode\u003EAuthFlowContext\u003C/code\u003E. This context provides the effective tenant, device information, authentication mode, client profile, and applicable server options.\u003C/p\u003E\n\u003Cp\u003EThat context matters because the same logical login operation can have different delivery and session requirements depending on how the application is hosted and which kind of client is connecting. Login should not need to be reimplemented for each application style.\u003C/p\u003E\n\u003Cp\u003EA resolvable device ID is required by the current login orchestrator. If device identification cannot be resolved, login cannot proceed to normal session establishment.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resolve-the-login-identifier\u0022\u003E2. Resolve the login identifier\u003C/h3\u003E\n\u003Cp\u003EA user may identify themselves with a username, email address, or another identifier supported by the configured resolver. UltimateAuth translates that external identifier into an internal \u003Ccode\u003EUserKey\u003C/code\u003E through \u003Ccode\u003EILoginIdentifierResolver\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\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-provided identifier\n \u2502\n \u25BC\nILoginIdentifierResolver\n \u2502\n \u25BC\n UserKey\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe internal user key, not the original text entered into the login form, becomes the stable reference used by the rest of the authentication flow. This helps keep \u003Cstrong\u003Ehow a user signs in\u003C/strong\u003E separate from \u003Cstrong\u003Ewho that user is\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe identifiers actually accepted depend on the configured resolver and user data; support for a particular identifier format should not be assumed without configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022check-eligibility-and-security-state\u0022\u003E3. Check eligibility and security state\u003C/h3\u003E\n\u003Cp\u003EOnce the user is resolved, the login orchestrator retrieves their runtime state. The current implementation requires the user to exist, be permitted to authenticate (\u003Ccode\u003ECanAuthenticate\u003C/code\u003E), and not be deleted (\u003Ccode\u003EIsDeleted\u003C/code\u003E).\u003C/p\u003E\n\u003Cp\u003EIt also examines two security scopes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EAccount state:\u003C/strong\u003E security conditions associated with the account as a whole.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EFactor state:\u003C/strong\u003E security conditions associated with the credential type being used.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAn existing account or factor lock can stop the attempt before credential validation proceeds. Failed attempts, lockout duration, failure windows, and lock extension are governed by login options.\u003C/p\u003E\n\u003Cp\u003EThis separation is intentional: a credential can be correct while the account is not currently eligible to authenticate.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validate-the-credential\u0022\u003E4. Validate the credential\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth delegates credential verification to registered credential providers rather than embedding password comparison directly into the login orchestrator.\u003C/p\u003E\n\u003Cp\u003EFor the current standard HTTP login endpoint, the credential type is \u003Cstrong\u003EPassword\u003C/strong\u003E. The orchestrator also rejects unsupported credential types in this path. The provider architecture is extensible, but that extensibility should not be confused with a promise that OTP, passkeys, or external identity providers all use this exact password-login endpoint in v0.1.0.\u003C/p\u003E\n\u003Cp\u003EFor eligible credentials, the orchestrator checks that the credential matches the requested type, has not been deleted, is usable under its security rules, and validates the submitted secret through its provider.\u003C/p\u003E\n\u003Cp\u003EThis gives the login flow a stable orchestration model while keeping credential-specific verification behind dedicated abstractions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resolve-device-associated-session-continuity\u0022\u003E5. Resolve device-associated session continuity\u003C/h3\u003E\n\u003Cp\u003EBefore the authority decision, the orchestrator looks for a session chain associated with the resolved user and device. If a chain exists and its state is active under the configured idle timeout, its \u003Ccode\u003EChainId\u003C/code\u003E can be carried into session issuance.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003ESame user \u002B known device \u002B active chain\n \u2502\n \u25BC\n Continue with that ChainId\n\nNo eligible active chain\n \u2502\n \u25BC\n Session subsystem establishes\n the appropriate new chain\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis enables device-aware continuity without reducing a login to \u201Cissue another unrelated token.\u201D The session subsystem remains responsible for the exact creation and persistence behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022make-the-authority-decision\u0022\u003E6. Make the authority decision\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ELoginAuthority\u003C/code\u003E evaluates the login attempt and returns one of three outcomes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EDecision\u003C/th\u003E\n\u003Cth\u003EMeaning\u003C/th\u003E\n\u003Cth\u003ESession issued?\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAllow\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe attempt may proceed to session establishment.\u003C/td\u003E\n\u003Ctd\u003EYes, if the remaining issuance steps succeed.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EDeny\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication is rejected.\u003C/td\u003E\n\u003Ctd\u003ENo.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EChallenge\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAn additional step or continuation is required.\u003C/td\u003E\n\u003Ctd\u003ENo, not from this login attempt.\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe current default authority considers user existence, lockout, reauthentication requirements, and credential validity. The broader flow also performs eligibility and lockout checks before invoking the authority.\u003C/p\u003E\n\u003Cp\u003EA challenge is represented through a \u003Ccode\u003ELoginContinuation\u003C/code\u003E; the current orchestrator uses an MFA continuation type for its challenge branch. This represents a requirement to continue authentication, \u003Cstrong\u003Enot proof that an MFA exchange has already completed\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EFor rejected attempts, the orchestrator may record a failed factor attempt and return lockout or remaining-attempt information when the corresponding disclosure option is enabled. Applications should treat these details as policy-controlled rather than universally available.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022establish-the-session\u0022\u003E7. Establish the session\u003C/h3\u003E\n\u003Cp\u003EOnly an allowed, successfully validated \u003Cstrong\u003Ecommit\u003C/strong\u003E login proceeds to session issuance.\u003C/p\u003E\n\u003Cp\u003EThe orchestrator first records a successful factor authentication (unless explicitly suppressed by an internal execution option), then obtains user claims through \u003Ccode\u003EIUserClaimsProvider\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIt constructs a \u003Ccode\u003ESessionIssuanceContext\u003C/code\u003E containing the tenant, user key, device, claims, effective mode, session options, and any eligible existing chain ID. The context is passed to \u003Ccode\u003EISessionOrchestrator\u003C/code\u003E using a login-session creation command.\u003C/p\u003E\n\u003Cp\u003EThis is the decisive transition:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EThe user has not merely supplied a valid secret; the system has established a managed authentication session.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EThe resulting session has its own identity. That identity is available to the later token-issuance and response-delivery stages.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022issue-tokens-when-permitted\u0022\u003E8. Issue tokens when permitted\u003C/h3\u003E\n\u003Cp\u003EAfter session issuance, access and refresh tokens may be requested from the token issuer. The current orchestrator issues them only when \u003Ccode\u003ELoginRequest.RequestTokens\u003C/code\u003E is true.\u003C/p\u003E\n\u003Cp\u003EFor the HTTP login endpoint, however, the server derives that setting from \u003Ccode\u003EAuthFlowContext.AllowsTokenIssuance\u003C/code\u003E. A client cannot force token issuance merely by placing \u003Ccode\u003ERequestTokens: true\u003C/code\u003E in its submitted payload.\u003C/p\u003E\n\u003Cp\u003EWhen issuance is enabled:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe access token is issued in the context of the authenticated user and issued session.\u003C/li\u003E\n\u003Cli\u003ERefresh-token issuance is requested with persistence enabled.\u003C/li\u003E\n\u003Cli\u003EBoth issuance paths operate with the tenant, user key, session ID, chain ID, and claims context.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EDepending on the effective mode and issuer behavior, an individual token type may be absent. \u003Cstrong\u003ESuccessful login and token issuance are deliberately separate concepts.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022dispatch-the-login-event-and-deliver-the-response\u0022\u003E9. Dispatch the login event and deliver the response\u003C/h3\u003E\n\u003Cp\u003EFollowing successful session and optional token issuance, UltimateAuth dispatches a \u003Ccode\u003EUserLoggedInContext\u003C/code\u003E event containing the tenant, user key, time, device, and session ID.\u003C/p\u003E\n\u003Cp\u003EThe HTTP endpoint then delegates credential delivery to \u003Ccode\u003EICredentialResponseWriter\u003C/code\u003E. Session ID, access token, and refresh token are handled as distinct grant kinds. Delivery is resolved using the effective mode, authentication flow, client profile, and server configuration.\u003C/p\u003E\n\u003Cp\u003EDepending on configuration, the HTTP response may complete with \u003Ccode\u003E200 OK\u003C/code\u003E or a success redirect. A rejected login may produce \u003Ccode\u003E401 Unauthorized\u003C/code\u003E or a configured failure redirect.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant for v0.1.0:\u003C/strong\u003E \u003Ccode\u003ETokenResponseMode.Body\u003C/code\u003E is currently unsupported and rejected during response resolution. Do not assume that the HTTP login endpoint returns a JSON object containing the issued tokens. Consult the configured delivery modes and client profile instead.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022trylogin-evaluate-without-establishing-a-session\u0022\u003ETryLogin: evaluate without establishing a session\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes \u003Cstrong\u003Epreviewing a login\u003C/strong\u003E from \u003Cstrong\u003Ecommitting a login\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003ETryLogin\u003C/code\u003E runs the authentication evaluation in preview mode. It can indicate whether credentials would pass the current checks, report permitted failure information, and return a continuation requirement. It does \u003Cstrong\u003Enot\u003C/strong\u003E issue a session or tokens.\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\u003ETryLogin (Preview)\u003C/th\u003E\n\u003Cth\u003ELogin (Commit)\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EResolve identifier and evaluate credentials\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EApply applicable failure-attempt handling\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReset factor failure state on success\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003EYes, normally\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECreate a session\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003EYes, on success\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIssue tokens\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003EOptional\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReturn a preview receipt\u003C/td\u003E\n\u003Ctd\u003EPossible\u003C/td\u003E\n\u003Ctd\u003EConsumes a matching receipt, if supplied\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EOn a successful preview, the server can create a short-lived \u003Ccode\u003EPreviewReceipt\u003C/code\u003E. That receipt is associated with the user, tenant, credential type, device, identifier, client profile, and a fingerprint of the submitted login material.\u003C/p\u003E\n\u003Cp\u003EWhen a subsequent commit request includes a matching receipt, the server consumes it and can suppress a duplicate failed-attempt registration for that commit. \u003Cstrong\u003EThe receipt is not a replacement for the password\u003C/strong\u003E: the commit path still performs credential validation.\u003C/p\u003E\n\u003Cp\u003EThis separation is useful when an application needs to assess login readiness or guide a user through an authentication experience without establishing a session prematurely.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-awareness-and-session-management\u0022\u003EDevice awareness and session management\u003C/h2\u003E\n\u003Cp\u003EBecause login produces a managed session within a device-associated chain, UltimateAuth can model authentication beyond a single browser token:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EMultiple devices:\u003C/strong\u003E a user can have separate chains associated with distinct device contexts.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession continuity:\u003C/strong\u003E an active chain can be considered when the same user signs in again from the same device.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ETargeted session management:\u003C/strong\u003E session and chain identities give downstream operations a way to distinguish authentication instances and device-associated lifecycles.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EBroader revocation:\u003C/strong\u003E the hierarchy provides the foundation for operations that affect more than one session.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact scope and availability of logout and revocation operations are defined by their own APIs and policies. Login establishes the identities those operations can act upon; it does not by itself specify every revocation behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-is-part-of-the-flow-not-an-afterthought\u0022\u003ESecurity is part of the flow, not an afterthought\u003C/h2\u003E\n\u003Cp\u003ESeveral security properties follow from this design:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EUser eligibility is separate from credential correctness.\u003C/strong\u003E A valid secret does not override a disabled, deleted, or locked account.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EFailed attempts are tracked as security state.\u003C/strong\u003E Lockout behavior is configurable and can be evaluated before session issuance.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDevice context participates in session establishment.\u003C/strong\u003E The system can distinguish device-associated chains rather than treating all logins as anonymous token issuance events.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESession issuance follows an explicit decision.\u003C/strong\u003E A denied or challenged login does not proceed to normal session creation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EResponse delivery is policy-driven.\u003C/strong\u003E Whether a session credential or token is delivered\u2014and by which supported mechanism\u2014is determined by the effective server flow, not solely by a client request.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe hierarchy supports later lifecycle enforcement.\u003C/strong\u003E Token validation, session validity, security-version checks, refresh behavior, and revocation are related concerns, but their exact enforcement belongs to those respective flows. They should not be mistaken for checks all performed inside the login method itself.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-useful-mental-model\u0022\u003EA useful mental model\u003C/h2\u003E\n\u003Cp\u003EThink of login as the moment UltimateAuth establishes a \u003Cstrong\u003Emanaged, device-aware authentication relationship\u003C/strong\u003E between a user and an application.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\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\u003EIdentity \u002B Valid Credential \u002B Security Decision\n \u2502\n \u25BC\n Managed Session\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u25BC \u25BC\n Session identity Optional tokens\n \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u25BC\n Ongoing auth lifecycle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe session is the managed authentication instance. Tokens, where used, are credentials associated with that instance. They can be issued, refreshed, and validated without redefining what a login means.\u003C/p\u003E\n\u003Cp\u003EThis is the shift UltimateAuth is designed to make: \u003Cstrong\u003Efrom login as token generation to login as lifecycle-aware session establishment\u003C/strong\u003E.\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\u003ELogin is a flow, not a single credential-check function.\u003C/strong\u003E Identity resolution, eligibility, security evaluation, session issuance, and delivery are separate stages.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EThe hierarchy is \u003Ccode\u003EUser \u2192 Root \u2192 Chain \u2192 Session\u003C/code\u003E.\u003C/strong\u003E It expresses user-level, device-associated, and individual session lifecycles.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EA successful login establishes a session.\u003C/strong\u003E It does not necessarily create a new root or chain each time.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ETokens are optional.\u003C/strong\u003E Issuance is governed by the effective mode and server policy.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ETryLogin is not Login.\u003C/strong\u003E Preview evaluates an attempt without issuing a session; commit establishes one.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EThe authority controls admission to session issuance.\u003C/strong\u003E Security-state handling can also occur before and after that decision.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice context matters.\u003C/strong\u003E Session continuity and downstream session management are built around it.\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\u003EContinue to \u003Cstrong\u003ERefresh Flow\u003C/strong\u003E to learn how UltimateAuth extends or rotates authentication credentials while preserving the relationship to the managed session lifecycle.\u003C/p\u003E\n", "Headings": [ { "Id": "from-a-login-response-to-an-authentication-lifecycle", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/logout-flow.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/logout-flow.json index 8fb130d3..2abd4d91 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/logout-flow.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/logout-flow.json @@ -1,7 +1,7 @@ { "Slug": "auth-flows/logout-flow", "Title": "Logout", - "Html": "\n\u003Cp\u003EAuthentication does not end when a client deletes a cookie or forgets a token. In UltimateAuth, ending access is a \u003Cstrong\u003Eserver-side lifecycle decision\u003C/strong\u003E: the system determines which authentication state must stop being usable, at what scope, and whether the associated device relationship should survive a future login.\u003C/p\u003E\n\u003Cp\u003EThat distinction is central to UltimateAuth\u0027s design. \u003Cstrong\u003ELogout ends access; revoke permanently invalidates the selected trust boundary.\u003C/strong\u003E Both can prevent a session from being used, but they express different intentions and have different consequences for the session hierarchy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-is-more-than-clearing-credentials\u0022\u003ELogout is more than clearing credentials\u003C/h2\u003E\n\u003Cp\u003EA traditional application may implement logout by removing a browser cookie or deleting a locally stored token. That is useful client cleanup, but by itself it does not necessarily invalidate a credential already issued by the server. A copied token could remain usable until its expiration if the server has no corresponding invalidation mechanism.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth instead models authentication around a hierarchy:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EUser\n\u2514\u2500\u2500 Root Global authentication lineage\n \u251C\u2500\u2500 Chain Device-associated authentication lineage\n \u2502 \u251C\u2500\u2500 Session Individual authentication instance\n \u2502 \u2514\u2500\u2500 Session Another instance in the same chain\n \u2514\u2500\u2500 Chain Another device\n \u2514\u2500\u2500 Session\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis structure gives termination a meaningful scope. An application can end access on the current device, sign out other devices, sign out all devices, or explicitly revoke a session, chain, or root. The correct choice depends on the intent: ordinary sign-out, account security, device compromise, or global trust reset.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003EMental model:\u003C/strong\u003E Logout is a controlled exit from authentication. Revoke is a permanent invalidation of the selected authentication state. A later login may establish new state, but it does not undo a revocation.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-versus-revoke\u0022\u003ELogout versus revoke\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-end-access-without-necessarily-destroying-the-device-lineage\u0022\u003ELogout: end access without necessarily destroying the device lineage\u003C/h3\u003E\n\u003Cp\u003EAn ordinary logout is designed for situations such as a user selecting \u003Cstrong\u003ESign out\u003C/strong\u003E in an application. It ends the relevant authenticated access while allowing the device-associated chain to remain available for a subsequent login, subject to its state and lifetime rules.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EBefore logout\nRoot (active)\n\u2514\u2500\u2500 Chain (active)\n \u2514\u2500\u2500 Session (authenticated)\n\nAfter logout\nRoot (active)\n\u2514\u2500\u2500 Chain (retained)\n \u2514\u2500\u2500 Session (no longer usable)\n\nAfter a later successful login\nRoot (active)\n\u2514\u2500\u2500 Chain (reusable if still eligible)\n \u2514\u2500\u2500 New Session\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\u003Cstrong\u003ERetaining a chain does not mean retaining an authenticated session.\u003C/strong\u003E The user must authenticate again. Reuse is conditional: an expired, revoked, or otherwise ineligible chain must not be treated as active merely because it still exists in storage.\u003C/p\u003E\n\u003Cp\u003EA subtle but important implementation detail: the current HTTP logout flow resolves the authenticated session and invokes the flow service, whose logout command identifies and logs out the \u003Cstrong\u003Eassociated chain\u003C/strong\u003E. Consequently, the default logout endpoint should not be described as a guarantee that \u003Cem\u003Eonly one session record\u003C/em\u003E changes. Its practical boundary is the current device chain.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-permanently-invalidate-the-selected-trust-boundary\u0022\u003ERevoke: permanently invalidate the selected trust boundary\u003C/h3\u003E\n\u003Cp\u003ERevocation is a stronger security operation. A revoked session, chain, or root is not restored by a subsequent login, refresh, or cookie update.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EChain (active)\n\u2514\u2500\u2500 Session (active)\n\nRevoke chain\n\nChain (revoked \u2014 terminal state)\n\u2514\u2500\u2500 Sessions (invalidated)\n\nFuture authentication \u2192 new eligible chain, not restoration of the old one\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\u003Cstrong\u003ERevocation is irreversible for the revoked entity.\u003C/strong\u003E This does not mean the user is permanently locked out of the application. A legitimate user may authenticate again and establish a new session lineage if account policy allows it. What cannot happen is bringing the \u003Cem\u003Erevoked\u003C/em\u003E session or chain back to life.\u003C/p\u003E\n\u003Cp\u003EThis distinction is useful in security-sensitive scenarios: a lost device, suspected credential theft, administrator intervention, or a decision to invalidate all existing authentication relationships.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-the-termination-scope\u0022\u003EChoosing the termination scope\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EIntended scope\u003C/th\u003E\n\u003Cth\u003EWhat it means\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELogout current device\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECurrent session\u0027s chain\u003C/td\u003E\n\u003Ctd\u003EEnds authenticated access for the current device while preserving the possibility of later eligible chain reuse.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELogout a device\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ESelected chain\u003C/td\u003E\n\u003Ctd\u003EEnds access on the chosen device; can be initiated by the user or an authorized administrator.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELogout other devices\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOther device sessions\u003C/td\u003E\n\u003Ctd\u003EEnds access elsewhere while preserving the specified current device context.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELogout all devices\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAll user sessions\u003C/td\u003E\n\u003Ctd\u003ESigns the user out across devices without treating this as a root revocation.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoke session\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOne session\u003C/td\u003E\n\u003Ctd\u003EPermanently invalidates the selected session.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoke chain\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOne device lineage\u003C/td\u003E\n\u003Ctd\u003EPermanently invalidates that chain and its sessions.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoke other/all chains\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EMultiple device lineages\u003C/td\u003E\n\u003Ctd\u003EPermanently invalidates selected device relationships.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoke root\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EGlobal authentication lineage\u003C/td\u003E\n\u003Ctd\u003EInvalidates the root and cascades to its chains and sessions.\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cp\u003EThese are not interchangeable aliases. In particular, \u003Cstrong\u003Elogout all devices\u003C/strong\u003E and \u003Cstrong\u003Erevoke root\u003C/strong\u003E both disrupt broad access, but they do not express the same lifecycle intent.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-session-level-revoke-is-separate\u0022\u003EWhy session-level revoke is separate\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth exposes a session revocation operation distinct from the ordinary logout endpoint. A single session can be revoked without necessarily revoking its parent chain. This is useful when an application needs to invalidate a particular authentication instance rather than the entire device relationship.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-root-revoke-is-exceptional\u0022\u003EWhy root revoke is exceptional\u003C/h3\u003E\n\u003Cp\u003EThe root represents the user\u0027s active authentication lineage, not the user\u0027s existence in the user directory. Root revocation cascades through the associated authentication hierarchy. A later permitted login may establish a \u003Cstrong\u003Enew\u003C/strong\u003E active root; it does not reverse the old root\u0027s revocation.\u003C/p\u003E\n\u003Cp\u003ERoot revocation should therefore be reserved for an intentional global trust reset rather than ordinary sign-out.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-the-current-logout-flow-executes\u0022\u003EHow the current logout flow executes\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resolve-the-flow-context\u0022\u003E1. Resolve the flow context\u003C/h3\u003E\n\u003Cp\u003EThe HTTP endpoint obtains the current \u003Ccode\u003EAuthFlowContext\u003C/code\u003E. This carries the relevant authentication state and response configuration, including the resolved session when one is available.\u003C/p\u003E\n\u003Cp\u003EThe ordinary logout endpoint does not accept an arbitrary session ID from the browser and blindly invalidate it. It uses the session already resolved for the current flow.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022request-server-side-termination\u0022\u003E2. Request server-side termination\u003C/h3\u003E\n\u003Cp\u003EWhen a \u003Ccode\u003ESessionSecurityContext\u003C/code\u003E is present, the endpoint constructs a \u003Ccode\u003ELogoutRequest\u003C/code\u003E using that session ID and calls the flow service:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar request = new LogoutRequest\n{\n SessionId = session.SessionId\n};\n\nawait _client.Flows.LogoutAsync(request);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThe logout command resolves the session\u0027s chain and invokes the session issuer\u0027s \u003Ccode\u003ELogoutChainAsync\u003C/code\u003E operation. The session-store implementation performs the chain logout transition.\u003C/p\u003E\n\u003Cp\u003EThis is the authoritative part of logout. Removing a client cookie alone is not a substitute for this state transition.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022clear-cookies-configured-for-this-flow\u0022\u003E3. Clear cookies configured for this flow\u003C/h3\u003E\n\u003Cp\u003EAfter the server-side operation, the HTTP handler attempts to delete credentials whose \u003Cstrong\u003Eeffective delivery mode is Cookie\u003C/strong\u003E:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession ID cookie\u003C/li\u003E\n\u003Cli\u003ERefresh token cookie\u003C/li\u003E\n\u003Cli\u003EAccess token cookie\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe handler uses the configured cookie names through \u003Ccode\u003EIUAuthCookieManager\u003C/code\u003E. It does not assume that all three credentials are cookies, and it does not attempt to delete header-delivered credentials from a remote client\u0027s storage.\u003C/p\u003E\n\u003Cp\u003EWhen the flow has no resolved session, the endpoint can still perform cookie cleanup and return a successful logout response. This makes sign-out useful even when a browser holds stale credentials.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resolve-the-response-and-optional-redirect\u0022\u003E4. Resolve the response and optional redirect\u003C/h3\u003E\n\u003Cp\u003EOnce termination and cookie cleanup have been handled, UltimateAuth evaluates the logout response configuration. The result is either a redirect to the configured destination or a normal HTTP success response.\u003C/p\u003E\n\u003Cp\u003EWith redirect disabled, the ordinary endpoint returns:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-json\u0022\u003E{\n \u0026quot;isSuccess\u0026quot;: true\n}\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EWith redirect enabled, it returns an HTTP redirect to the resolved target instead. Applications should not expect the JSON success body in that case.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-redirect-matters\u0022\u003EWhy redirect matters\u003C/h2\u003E\n\u003Cp\u003EA redirect is \u003Cstrong\u003Enot required to invalidate authentication state\u003C/strong\u003E. It is a navigation concern that becomes important in browser-based authentication flows.\u003C/p\u003E\n\u003Cp\u003EConsider a server-rendered application with a dedicated sign-out page. Once the session has ended and cookies are cleared, the browser should often return to a public page, a login screen, or the page originally requested by the user. Redirecting completes that user journey without requiring the application to parse a logout response and navigate manually.\u003C/p\u003E\n\u003Cp\u003ERedirects can also help when the authentication endpoint and the client application are on different origins or when a centralized authentication service serves multiple clients. The server can perform the security operation and then send the browser back to the appropriate application destination.\u003C/p\u003E\n\u003Cp\u003EIn contrast, a mobile client or API consumer commonly prefers an ordinary HTTP result and controls its own navigation. \u003Cstrong\u003ERedirect is a response behavior, not a security primitive.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022redirect-resolution-in-ultimateauth\u0022\u003ERedirect resolution in UltimateAuth\u003C/h3\u003E\n\u003Cp\u003EThe response resolver selects the effective redirect settings for the logout flow. The redirect resolver then:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EChecks whether redirect is enabled.\u003C/li\u003E\n\u003Cli\u003EOn success, considers an allowed return URL override when configured.\u003C/li\u003E\n\u003Cli\u003EOtherwise uses the configured success path, resolved against the client base address.\u003C/li\u003E\n\u003Cli\u003EIf no valid destination is configured, leaves redirect disabled and allows a normal success response.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EFor absolute return URL overrides, the resolver restricts schemes to HTTP or HTTPS and validates the origin against configured allowed client origins when that allowlist is populated. Relative paths are resolved against the selected client base address.\u003C/p\u003E\n\u003Cp\u003EThis is why a return URL should be treated as \u003Cstrong\u003Evalidated navigation input\u003C/strong\u003E, never as an unrestricted arbitrary destination. Applications should configure allowed origins carefully, especially in multi-client or hosted scenarios.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E Do not use a redirect as proof that logout succeeded, or assume that clearing browser cookies invalidates every credential issued to every device. The server-side termination scope determines what access has actually ended.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-aware-logout\u0022\u003EDevice-aware logout\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth treats the device-associated chain as a first-class authentication boundary. That allows applications to build features such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ESign out of this device\u003C/strong\u003E \u2014 end the current device\u0027s authenticated access.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESign out of another device\u003C/strong\u003E \u2014 target a specific chain after the appropriate access checks.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESign out of other devices\u003C/strong\u003E \u2014 preserve the current device while ending other sessions.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESign out everywhere\u003C/strong\u003E \u2014 end all device sessions for the user.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERevoke a lost device\u003C/strong\u003E \u2014 permanently invalidate the compromised chain instead of merely signing it out.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese operations are exposed through distinct application-service methods, rather than overloading a single logout call with ambiguous behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self-service-and-administrative-actions\u0022\u003ESelf-service and administrative actions\u003C/h3\u003E\n\u003Cp\u003EDevice and all-device HTTP endpoints use access contexts carrying explicit action identifiers. They are executed through the access orchestrator, which enforces the applicable authorization decision before the requested state mutation.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003ELogoutDeviceSelfAsync\u003C/code\u003E targets the authenticated user\u0027s device, whereas \u003Ccode\u003ELogoutDeviceAdminAsync\u003C/code\u003E accepts a target user and executes under an administrative action context. Both ultimately call the device logout application service; authorization and target ownership are not left to the client.\u003C/p\u003E\n\u003Cp\u003EThe default current-device logout endpoint follows its own flow-based path. Do not assume every termination operation shares the same exact handler or authorization pipeline.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-properties-and-boundaries\u0022\u003ESecurity properties and boundaries\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-side-invalidation-comes-first\u0022\u003EServer-side invalidation comes first\u003C/h3\u003E\n\u003Cp\u003EThe server must stop recognizing the affected authentication state. Browser cleanup improves user experience and reduces accidental credential reuse, but it is not the source of truth.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoked-state-is-terminal\u0022\u003ERevoked state is terminal\u003C/h3\u003E\n\u003Cp\u003ERevocation cannot be undone by setting \u003Ccode\u003EIsRevoked\u003C/code\u003E back to false, presenting an old refresh token, or logging in again with the same device. A new authentication path must establish new eligible state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022scope-must-be-explicit\u0022\u003EScope must be explicit\u003C/h3\u003E\n\u003Cp\u003EA session revoke should not be casually described as a chain revoke. A logout-all operation should not be described as a root revoke. These distinctions determine whether existing device lineages are retained, ended, or permanently invalidated.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-and-ownership-matter\u0022\u003EAuthorization and ownership matter\u003C/h3\u003E\n\u003Cp\u003ESelf-service operations must stay within the authenticated user\u0027s permitted scope. Administrative operations must be evaluated under their corresponding action and target context. A client-provided chain identifier is not sufficient authorization to terminate someone else\u0027s session.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-delivery-affects-cleanup-not-the-termination-model\u0022\u003EToken delivery affects cleanup, not the termination model\u003C/h3\u003E\n\u003Cp\u003EWhether a client receives credentials through cookies or headers, the server-side hierarchy still governs session-backed authentication. Clients using non-cookie delivery must also clear their own local credentials after successful termination.\u003C/p\u003E\n\u003Cp\u003EThe precise behavior of already-issued, independently validated tokens depends on the active authentication mode and token validation strategy. Avoid assuming every token format provides instantaneous server-side revocation without checking its validation model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022practical-examples\u0022\u003EPractical examples\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-user-signs-out-at-the-end-of-the-day\u0022\u003EA user signs out at the end of the day\u003C/h3\u003E\n\u003Cp\u003EUse the ordinary logout flow. The current device\u0027s authenticated access ends, configured cookies are cleared, and the browser may be redirected to a public page. A later successful login can reuse the retained device chain if it remains eligible.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-user-loses-a-phone\u0022\u003EA user loses a phone\u003C/h3\u003E\n\u003Cp\u003EUse \u003Cstrong\u003Echain revoke\u003C/strong\u003E, not merely a soft device logout, when the goal is to permanently invalidate that device\u0027s existing trust lineage. The user may later authenticate on a replacement device, but the revoked chain stays revoked.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-user-wants-to-keep-this-device-but-sign-out-elsewhere\u0022\u003EA user wants to keep this device but sign out elsewhere\u003C/h3\u003E\n\u003Cp\u003EUse \u003Cstrong\u003Elogout other devices\u003C/strong\u003E. The current device\u0027s chain is excluded while other device sessions are terminated. This is different from permanently revoking every other chain.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022an-account-is-believed-to-be-compromised\u0022\u003EAn account is believed to be compromised\u003C/h3\u003E\n\u003Cp\u003EDepending on the security policy, revoke all chains or the root. This is a stronger response than ordinary logout because it deliberately invalidates existing authentication lineages. Credential rotation and additional account recovery steps may also be required; revocation alone does not prove that compromised credentials have been changed.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-misconceptions\u0022\u003ECommon misconceptions\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CLogout just deletes the cookie.\u201D\u003C/strong\u003E No. The ordinary endpoint performs server-side termination when a session is resolved, then clears applicable cookies.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CLogout and revoke are the same operation.\u201D\u003C/strong\u003E No. Both can stop access, but revocation is permanent for the selected entity; soft logout can retain eligible device lineage.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CLogout always means exactly one session.\u201D\u003C/strong\u003E Not in the current default HTTP flow. The resolved session identifies a chain, and the logout command operates on that chain.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CRevocation permanently bans the user.\u201D\u003C/strong\u003E No. It permanently invalidates existing authentication state at the selected scope. A permitted future login may create new state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CRedirect makes logout secure.\u201D\u003C/strong\u003E No. Redirect controls where the browser goes after logout; the session-store transition is what ends server-recognized access.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CLogout all devices is the same as revoke root.\u201D\u003C/strong\u003E No. They are different lifecycle operations with different implications for the authentication hierarchy.\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\u003ELogout is a lifecycle operation\u003C/strong\u003E, not merely client-side credential deletion.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERevoke is irreversible\u003C/strong\u003E for the revoked session, chain, or root; a future login never restores that entity.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERoot \u2192 Chain \u2192 Session\u003C/strong\u003E makes termination scope explicit and device-aware.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ECurrent-device logout operates through the associated chain\u003C/strong\u003E in the current server implementation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EOther-device and all-device logout\u003C/strong\u003E are separate from hard chain/root revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERedirect is optional navigation behavior\u003C/strong\u003E, especially useful for browser-based flows; it does not perform revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EServer-side state is authoritative\u003C/strong\u003E for session-backed access; client credential cleanup remains important.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003ESession Lifecycle\u003C/strong\u003E to understand how roots, chains, and sessions are created, rotated, expired, logged out, and revoked over time.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EAuthentication does not end when a client deletes a cookie or forgets a token. In UltimateAuth, ending access is a \u003Cstrong\u003Eserver-side lifecycle decision\u003C/strong\u003E: the system determines which authentication state must stop being usable, at what scope, and whether the associated device relationship should survive a future login.\u003C/p\u003E\n\u003Cp\u003EThat distinction is central to UltimateAuth\u0027s design. \u003Cstrong\u003ELogout ends access; revoke permanently invalidates the selected trust boundary.\u003C/strong\u003E Both can prevent a session from being used, but they express different intentions and have different consequences for the session hierarchy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-is-more-than-clearing-credentials\u0022\u003ELogout is more than clearing credentials\u003C/h2\u003E\n\u003Cp\u003EA traditional application may implement logout by removing a browser cookie or deleting a locally stored token. That is useful client cleanup, but by itself it does not necessarily invalidate a credential already issued by the server. A copied token could remain usable until its expiration if the server has no corresponding invalidation mechanism.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth instead models authentication around a hierarchy:\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 Global authentication lineage\n \u251C\u2500\u2500 Chain Device-associated authentication lineage\n \u2502 \u251C\u2500\u2500 Session Individual authentication instance\n \u2502 \u2514\u2500\u2500 Session Another instance in the same chain\n \u2514\u2500\u2500 Chain Another device\n \u2514\u2500\u2500 Session\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis structure gives termination a meaningful scope. An application can end access on the current device, sign out other devices, sign out all devices, or explicitly revoke a session, chain, or root. The correct choice depends on the intent: ordinary sign-out, account security, device compromise, or global trust reset.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003EMental model:\u003C/strong\u003E Logout is a controlled exit from authentication. Revoke is a permanent invalidation of the selected authentication state. A later login may establish new state, but it does not undo a revocation.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-versus-revoke\u0022\u003ELogout versus revoke\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-end-access-without-necessarily-destroying-the-device-lineage\u0022\u003ELogout: end access without necessarily destroying the device lineage\u003C/h3\u003E\n\u003Cp\u003EAn ordinary logout is designed for situations such as a user selecting \u003Cstrong\u003ESign out\u003C/strong\u003E in an application. It ends the relevant authenticated access while allowing the device-associated chain to remain available for a subsequent login, subject to its state and lifetime rules.\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\u003EBefore logout\nRoot (active)\n\u2514\u2500\u2500 Chain (active)\n \u2514\u2500\u2500 Session (authenticated)\n\nAfter logout\nRoot (active)\n\u2514\u2500\u2500 Chain (retained)\n \u2514\u2500\u2500 Session (no longer usable)\n\nAfter a later successful login\nRoot (active)\n\u2514\u2500\u2500 Chain (reusable if still eligible)\n \u2514\u2500\u2500 New Session\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003ERetaining a chain does not mean retaining an authenticated session.\u003C/strong\u003E The user must authenticate again. Reuse is conditional: an expired, revoked, or otherwise ineligible chain must not be treated as active merely because it still exists in storage.\u003C/p\u003E\n\u003Cp\u003EA subtle but important implementation detail: the current HTTP logout flow resolves the authenticated session and invokes the flow service, whose logout command identifies and logs out the \u003Cstrong\u003Eassociated chain\u003C/strong\u003E. Consequently, the default logout endpoint should not be described as a guarantee that \u003Cem\u003Eonly one session record\u003C/em\u003E changes. Its practical boundary is the current device chain.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-permanently-invalidate-the-selected-trust-boundary\u0022\u003ERevoke: permanently invalidate the selected trust boundary\u003C/h3\u003E\n\u003Cp\u003ERevocation is a stronger security operation. A revoked session, chain, or root is not restored by a subsequent login, refresh, or cookie update.\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\u003EChain (active)\n\u2514\u2500\u2500 Session (active)\n\nRevoke chain\n\nChain (revoked \u2014 terminal state)\n\u2514\u2500\u2500 Sessions (invalidated)\n\nFuture authentication \u2192 new eligible chain, not restoration of the old one\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003ERevocation is irreversible for the revoked entity.\u003C/strong\u003E This does not mean the user is permanently locked out of the application. A legitimate user may authenticate again and establish a new session lineage if account policy allows it. What cannot happen is bringing the \u003Cem\u003Erevoked\u003C/em\u003E session or chain back to life.\u003C/p\u003E\n\u003Cp\u003EThis distinction is useful in security-sensitive scenarios: a lost device, suspected credential theft, administrator intervention, or a decision to invalidate all existing authentication relationships.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-the-termination-scope\u0022\u003EChoosing the termination scope\u003C/h2\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\u003EIntended scope\u003C/th\u003E\n\u003Cth\u003EWhat it means\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELogout current device\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECurrent session\u0027s chain\u003C/td\u003E\n\u003Ctd\u003EEnds authenticated access for the current device while preserving the possibility of later eligible chain reuse.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELogout a device\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ESelected chain\u003C/td\u003E\n\u003Ctd\u003EEnds access on the chosen device; can be initiated by the user or an authorized administrator.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELogout other devices\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOther device sessions\u003C/td\u003E\n\u003Ctd\u003EEnds access elsewhere while preserving the specified current device context.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELogout all devices\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAll user sessions\u003C/td\u003E\n\u003Ctd\u003ESigns the user out across devices without treating this as a root revocation.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoke session\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOne session\u003C/td\u003E\n\u003Ctd\u003EPermanently invalidates the selected session.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoke chain\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOne device lineage\u003C/td\u003E\n\u003Ctd\u003EPermanently invalidates that chain and its sessions.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoke other/all chains\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EMultiple device lineages\u003C/td\u003E\n\u003Ctd\u003EPermanently invalidates selected device relationships.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoke root\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EGlobal authentication lineage\u003C/td\u003E\n\u003Ctd\u003EInvalidates the root and cascades to its chains and sessions.\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese are not interchangeable aliases. In particular, \u003Cstrong\u003Elogout all devices\u003C/strong\u003E and \u003Cstrong\u003Erevoke root\u003C/strong\u003E both disrupt broad access, but they do not express the same lifecycle intent.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-session-level-revoke-is-separate\u0022\u003EWhy session-level revoke is separate\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth exposes a session revocation operation distinct from the ordinary logout endpoint. A single session can be revoked without necessarily revoking its parent chain. This is useful when an application needs to invalidate a particular authentication instance rather than the entire device relationship.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-root-revoke-is-exceptional\u0022\u003EWhy root revoke is exceptional\u003C/h3\u003E\n\u003Cp\u003EThe root represents the user\u0027s active authentication lineage, not the user\u0027s existence in the user directory. Root revocation cascades through the associated authentication hierarchy. A later permitted login may establish a \u003Cstrong\u003Enew\u003C/strong\u003E active root; it does not reverse the old root\u0027s revocation.\u003C/p\u003E\n\u003Cp\u003ERoot revocation should therefore be reserved for an intentional global trust reset rather than ordinary sign-out.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-the-current-logout-flow-executes\u0022\u003EHow the current logout flow executes\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resolve-the-flow-context\u0022\u003E1. Resolve the flow context\u003C/h3\u003E\n\u003Cp\u003EThe HTTP endpoint obtains the current \u003Ccode\u003EAuthFlowContext\u003C/code\u003E. This carries the relevant authentication state and response configuration, including the resolved session when one is available.\u003C/p\u003E\n\u003Cp\u003EThe ordinary logout endpoint does not accept an arbitrary session ID from the browser and blindly invalidate it. It uses the session already resolved for the current flow.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022request-server-side-termination\u0022\u003E2. Request server-side termination\u003C/h3\u003E\n\u003Cp\u003EWhen a \u003Ccode\u003ESessionSecurityContext\u003C/code\u003E is present, the endpoint constructs a \u003Ccode\u003ELogoutRequest\u003C/code\u003E using that session ID and calls the flow service:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\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 LogoutRequest\n{\n SessionId = session.SessionId\n};\n\nawait _client.Flows.LogoutAsync(request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe logout command resolves the session\u0027s chain and invokes the session issuer\u0027s \u003Ccode\u003ELogoutChainAsync\u003C/code\u003E operation. The session-store implementation performs the chain logout transition.\u003C/p\u003E\n\u003Cp\u003EThis is the authoritative part of logout. Removing a client cookie alone is not a substitute for this state transition.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022clear-cookies-configured-for-this-flow\u0022\u003E3. Clear cookies configured for this flow\u003C/h3\u003E\n\u003Cp\u003EAfter the server-side operation, the HTTP handler attempts to delete credentials whose \u003Cstrong\u003Eeffective delivery mode is Cookie\u003C/strong\u003E:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession ID cookie\u003C/li\u003E\n\u003Cli\u003ERefresh token cookie\u003C/li\u003E\n\u003Cli\u003EAccess token cookie\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe handler uses the configured cookie names through \u003Ccode\u003EIUAuthCookieManager\u003C/code\u003E. It does not assume that all three credentials are cookies, and it does not attempt to delete header-delivered credentials from a remote client\u0027s storage.\u003C/p\u003E\n\u003Cp\u003EWhen the flow has no resolved session, the endpoint can still perform cookie cleanup and return a successful logout response. This makes sign-out useful even when a browser holds stale credentials.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resolve-the-response-and-optional-redirect\u0022\u003E4. Resolve the response and optional redirect\u003C/h3\u003E\n\u003Cp\u003EOnce termination and cookie cleanup have been handled, UltimateAuth evaluates the logout response configuration. The result is either a redirect to the configured destination or a normal HTTP success response.\u003C/p\u003E\n\u003Cp\u003EWith redirect disabled, the ordinary endpoint 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\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;isSuccess\u0026quot;: true\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWith redirect enabled, it returns an HTTP redirect to the resolved target instead. Applications should not expect the JSON success body in that case.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-redirect-matters\u0022\u003EWhy redirect matters\u003C/h2\u003E\n\u003Cp\u003EA redirect is \u003Cstrong\u003Enot required to invalidate authentication state\u003C/strong\u003E. It is a navigation concern that becomes important in browser-based authentication flows.\u003C/p\u003E\n\u003Cp\u003EConsider a server-rendered application with a dedicated sign-out page. Once the session has ended and cookies are cleared, the browser should often return to a public page, a login screen, or the page originally requested by the user. Redirecting completes that user journey without requiring the application to parse a logout response and navigate manually.\u003C/p\u003E\n\u003Cp\u003ERedirects can also help when the authentication endpoint and the client application are on different origins or when a centralized authentication service serves multiple clients. The server can perform the security operation and then send the browser back to the appropriate application destination.\u003C/p\u003E\n\u003Cp\u003EIn contrast, a mobile client or API consumer commonly prefers an ordinary HTTP result and controls its own navigation. \u003Cstrong\u003ERedirect is a response behavior, not a security primitive.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022redirect-resolution-in-ultimateauth\u0022\u003ERedirect resolution in UltimateAuth\u003C/h3\u003E\n\u003Cp\u003EThe response resolver selects the effective redirect settings for the logout flow. The redirect resolver then:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EChecks whether redirect is enabled.\u003C/li\u003E\n\u003Cli\u003EOn success, considers an allowed return URL override when configured.\u003C/li\u003E\n\u003Cli\u003EOtherwise uses the configured success path, resolved against the client base address.\u003C/li\u003E\n\u003Cli\u003EIf no valid destination is configured, leaves redirect disabled and allows a normal success response.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EFor absolute return URL overrides, the resolver restricts schemes to HTTP or HTTPS and validates the origin against configured allowed client origins when that allowlist is populated. Relative paths are resolved against the selected client base address.\u003C/p\u003E\n\u003Cp\u003EThis is why a return URL should be treated as \u003Cstrong\u003Evalidated navigation input\u003C/strong\u003E, never as an unrestricted arbitrary destination. Applications should configure allowed origins carefully, especially in multi-client or hosted scenarios.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E Do not use a redirect as proof that logout succeeded, or assume that clearing browser cookies invalidates every credential issued to every device. The server-side termination scope determines what access has actually ended.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-aware-logout\u0022\u003EDevice-aware logout\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth treats the device-associated chain as a first-class authentication boundary. That allows applications to build features such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ESign out of this device\u003C/strong\u003E \u2014 end the current device\u0027s authenticated access.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESign out of another device\u003C/strong\u003E \u2014 target a specific chain after the appropriate access checks.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESign out of other devices\u003C/strong\u003E \u2014 preserve the current device while ending other sessions.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESign out everywhere\u003C/strong\u003E \u2014 end all device sessions for the user.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERevoke a lost device\u003C/strong\u003E \u2014 permanently invalidate the compromised chain instead of merely signing it out.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese operations are exposed through distinct application-service methods, rather than overloading a single logout call with ambiguous behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self-service-and-administrative-actions\u0022\u003ESelf-service and administrative actions\u003C/h3\u003E\n\u003Cp\u003EDevice and all-device HTTP endpoints use access contexts carrying explicit action identifiers. They are executed through the access orchestrator, which enforces the applicable authorization decision before the requested state mutation.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003ELogoutDeviceSelfAsync\u003C/code\u003E targets the authenticated user\u0027s device, whereas \u003Ccode\u003ELogoutDeviceAdminAsync\u003C/code\u003E accepts a target user and executes under an administrative action context. Both ultimately call the device logout application service; authorization and target ownership are not left to the client.\u003C/p\u003E\n\u003Cp\u003EThe default current-device logout endpoint follows its own flow-based path. Do not assume every termination operation shares the same exact handler or authorization pipeline.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-properties-and-boundaries\u0022\u003ESecurity properties and boundaries\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-side-invalidation-comes-first\u0022\u003EServer-side invalidation comes first\u003C/h3\u003E\n\u003Cp\u003EThe server must stop recognizing the affected authentication state. Browser cleanup improves user experience and reduces accidental credential reuse, but it is not the source of truth.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoked-state-is-terminal\u0022\u003ERevoked state is terminal\u003C/h3\u003E\n\u003Cp\u003ERevocation cannot be undone by setting \u003Ccode\u003EIsRevoked\u003C/code\u003E back to false, presenting an old refresh token, or logging in again with the same device. A new authentication path must establish new eligible state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022scope-must-be-explicit\u0022\u003EScope must be explicit\u003C/h3\u003E\n\u003Cp\u003EA session revoke should not be casually described as a chain revoke. A logout-all operation should not be described as a root revoke. These distinctions determine whether existing device lineages are retained, ended, or permanently invalidated.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-and-ownership-matter\u0022\u003EAuthorization and ownership matter\u003C/h3\u003E\n\u003Cp\u003ESelf-service operations must stay within the authenticated user\u0027s permitted scope. Administrative operations must be evaluated under their corresponding action and target context. A client-provided chain identifier is not sufficient authorization to terminate someone else\u0027s session.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-delivery-affects-cleanup-not-the-termination-model\u0022\u003EToken delivery affects cleanup, not the termination model\u003C/h3\u003E\n\u003Cp\u003EWhether a client receives credentials through cookies or headers, the server-side hierarchy still governs session-backed authentication. Clients using non-cookie delivery must also clear their own local credentials after successful termination.\u003C/p\u003E\n\u003Cp\u003EThe precise behavior of already-issued, independently validated tokens depends on the active authentication mode and token validation strategy. Avoid assuming every token format provides instantaneous server-side revocation without checking its validation model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022practical-examples\u0022\u003EPractical examples\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-user-signs-out-at-the-end-of-the-day\u0022\u003EA user signs out at the end of the day\u003C/h3\u003E\n\u003Cp\u003EUse the ordinary logout flow. The current device\u0027s authenticated access ends, configured cookies are cleared, and the browser may be redirected to a public page. A later successful login can reuse the retained device chain if it remains eligible.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-user-loses-a-phone\u0022\u003EA user loses a phone\u003C/h3\u003E\n\u003Cp\u003EUse \u003Cstrong\u003Echain revoke\u003C/strong\u003E, not merely a soft device logout, when the goal is to permanently invalidate that device\u0027s existing trust lineage. The user may later authenticate on a replacement device, but the revoked chain stays revoked.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-user-wants-to-keep-this-device-but-sign-out-elsewhere\u0022\u003EA user wants to keep this device but sign out elsewhere\u003C/h3\u003E\n\u003Cp\u003EUse \u003Cstrong\u003Elogout other devices\u003C/strong\u003E. The current device\u0027s chain is excluded while other device sessions are terminated. This is different from permanently revoking every other chain.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022an-account-is-believed-to-be-compromised\u0022\u003EAn account is believed to be compromised\u003C/h3\u003E\n\u003Cp\u003EDepending on the security policy, revoke all chains or the root. This is a stronger response than ordinary logout because it deliberately invalidates existing authentication lineages. Credential rotation and additional account recovery steps may also be required; revocation alone does not prove that compromised credentials have been changed.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-misconceptions\u0022\u003ECommon misconceptions\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CLogout just deletes the cookie.\u201D\u003C/strong\u003E No. The ordinary endpoint performs server-side termination when a session is resolved, then clears applicable cookies.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CLogout and revoke are the same operation.\u201D\u003C/strong\u003E No. Both can stop access, but revocation is permanent for the selected entity; soft logout can retain eligible device lineage.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CLogout always means exactly one session.\u201D\u003C/strong\u003E Not in the current default HTTP flow. The resolved session identifies a chain, and the logout command operates on that chain.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CRevocation permanently bans the user.\u201D\u003C/strong\u003E No. It permanently invalidates existing authentication state at the selected scope. A permitted future login may create new state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CRedirect makes logout secure.\u201D\u003C/strong\u003E No. Redirect controls where the browser goes after logout; the session-store transition is what ends server-recognized access.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CLogout all devices is the same as revoke root.\u201D\u003C/strong\u003E No. They are different lifecycle operations with different implications for the authentication hierarchy.\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\u003ELogout is a lifecycle operation\u003C/strong\u003E, not merely client-side credential deletion.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERevoke is irreversible\u003C/strong\u003E for the revoked session, chain, or root; a future login never restores that entity.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERoot \u2192 Chain \u2192 Session\u003C/strong\u003E makes termination scope explicit and device-aware.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ECurrent-device logout operates through the associated chain\u003C/strong\u003E in the current server implementation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EOther-device and all-device logout\u003C/strong\u003E are separate from hard chain/root revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERedirect is optional navigation behavior\u003C/strong\u003E, especially useful for browser-based flows; it does not perform revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EServer-side state is authoritative\u003C/strong\u003E for session-backed access; client credential cleanup remains important.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003ESession Lifecycle\u003C/strong\u003E to understand how roots, chains, and sessions are created, rotated, expired, logged out, and revoked over time.\u003C/p\u003E\n", "Headings": [ { "Id": "logout-is-more-than-clearing-credentials", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/refresh-flow.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/refresh-flow.json index f7e92910..f67944ec 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/refresh-flow.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/refresh-flow.json @@ -1,7 +1,7 @@ { "Slug": "auth-flows/refresh-flow", "Title": "Refresh", - "Html": "\n\u003Cp\u003ERefresh is how an existing authentication context continues without asking the user to authenticate again. In UltimateAuth, it is not synonymous with issuing a new access token: the operation depends on the active authentication mode and on whether the context is represented by a server-side session, refresh credentials, or both.\u003C/p\u003E\n\u003Cp\u003EA successful refresh preserves the authenticated identity and its continuity. It does \u003Cstrong\u003Enot\u003C/strong\u003E prove the user\u0027s credentials again, create a new login, or necessarily extend every lifetime associated with the authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-model-continuity-not-a-second-login\u0022\u003EThe model: continuity, not a second login\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes three related operations:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthentication\u003C/strong\u003E establishes a new authenticated context after the required proof is accepted.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERefresh\u003C/strong\u003E attempts to continue an already established context, subject to its current validity and security policies.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EReauthentication\u003C/strong\u003E establishes trust again when continuation is unavailable or no longer permitted.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis distinction matters for both security and client behavior. A refresh request can fail because a session expired, a refresh credential is invalid or consumed, the device context does not match, or the underlying chain is no longer usable. The client should treat an unsuccessful refresh as a need to authenticate again, not as an invitation to retry the same credential indefinitely.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-dependent-strategies\u0022\u003EMode-dependent strategies\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth implements refresh through two building blocks: \u003Cstrong\u003Esession touch\u003C/strong\u003E and \u003Cstrong\u003Erefresh-token rotation\u003C/strong\u003E. A mode selects how they are combined.\u003C/p\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EAuthentication mode\u003C/th\u003E\n\u003Cth\u003ERequired refresh input\u003C/th\u003E\n\u003Cth\u003EStrategy\u003C/th\u003E\n\u003Cth\u003EResult\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession ID\u003C/td\u003E\n\u003Ctd\u003EValidate session; touch when policy requires\u003C/td\u003E\n\u003Ctd\u003EContinue the existing session; no new access or refresh token\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession ID \u003Cstrong\u003Eand\u003C/strong\u003E refresh token\u003C/td\u003E\n\u003Ctd\u003EValidate and conditionally touch session; rotate refresh token\u003C/td\u003E\n\u003Ctd\u003EContinue session and issue replacement tokens\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESemiHybrid\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession ID \u003Cstrong\u003Eand\u003C/strong\u003E refresh token\u003C/td\u003E\n\u003Ctd\u003EValidate session; rotate refresh token\u003C/td\u003E\n\u003Ctd\u003EIssue replacement tokens without a session touch\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureJwt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERefresh token\u003C/td\u003E\n\u003Ctd\u003EValidate and rotate refresh token\u003C/td\u003E\n\u003Ctd\u003EIssue replacement tokens; no session touch in the flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cp\u003E\u003Cstrong\u003ECurrent support status:\u003C/strong\u003E \u003Ccode\u003EPureOpaque\u003C/code\u003E and \u003Ccode\u003EHybrid\u003C/code\u003E are the supported modes for the initial release. The \u003Ccode\u003ESemiHybrid\u003C/code\u003E and \u003Ccode\u003EPureJwt\u003C/code\u003E branches are present in the refresh implementation but should not be presented as completed, generally supported configurations in v0.1.0.\u003C/p\u003E\n\u003Cp\u003EThe mode is resolved from the effective authentication context on the server. Clients do not select a refresh algorithm independently.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-touch\u0022\u003ESession touch\u003C/h2\u003E\n\u003Cp\u003EA session touch is a controlled update to existing server-side authentication state. It does not issue a replacement credential or create a new session.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ESession ID\n \u2192 Validate session, chain and device context\n \u2192 Recheck active state and chain/session relationship\n \u2192 Touch if policy requires\n \u2192 Continue with the same session ID\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EIn \u003Ccode\u003EPureOpaque\u003C/code\u003E, the refresh flow requires a session ID. The session validator checks the session against the current tenant, device context and session options. The touch service then reloads the session and chain from the store and checks that they are still active and consistent, including the chain\u0027s active session, tenant, user and chain identifiers.\u003C/p\u003E\n\u003Cp\u003ETouch behavior is governed by the effective session options:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ETouchInterval\u003C/code\u003E avoids writing activity state on every request. If the interval has not elapsed, the operation may succeed as \u003Cstrong\u003ENoOp\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ETouchMode\u003C/code\u003E is \u003Ccode\u003EIfNeeded\u003C/code\u003E by default; \u003Ccode\u003EForce\u003C/code\u003E requests a touch even when the normal interval has not elapsed.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ESlidingExpiration\u003C/code\u003E allows a touch to extend the session expiration using the configured \u003Ccode\u003ELifetime\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EAn absolute chain expiration remains an upper bound; sliding expiration cannot extend the session beyond it.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EIdleTimeout\u003C/code\u003E participates in chain-state validation. An idle or otherwise inactive chain cannot be revived merely by touching it.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA successful \u003Cstrong\u003ENoOp\u003C/strong\u003E is not a failed refresh: the existing state was valid, but no persistence update was necessary. Likewise, a touch is not an unconditional promise to extend the session\u0027s expiry; that depends on policy and the absolute limit.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-token-rotation\u0022\u003ERefresh-token rotation\u003C/h2\u003E\n\u003Cp\u003ERotation replaces a valid refresh credential while preserving its association with the existing authentication context.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ERefresh token\n \u2192 Validate token, tenant, device and expected session (if supplied)\n \u2192 Resolve user, session and chain\n \u2192 Prepare replacement refresh/access tokens\n \u2192 Atomically consume old refresh token and store replacement\n \u2192 Return new credentials\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThe rotation service first rejects modes or configurations that do not permit refresh-token issuance (\u003Ccode\u003EPureOpaque\u003C/code\u003E or \u003Ccode\u003EIssueRefresh = false\u003C/code\u003E). It then validates the presented credential and its context. The replacement refresh token and access token are prepared before the existing token is consumed. Persistence uses the refresh-token store\u0027s execution boundary and \u003Ccode\u003ETryConsumeAsync\u003C/code\u003E, so competing requests cannot both successfully consume the same old token.\u003C/p\u003E\n\u003Cp\u003ERotation preserves the validated user, session and chain association; it is \u003Cstrong\u003Enot\u003C/strong\u003E a new authentication or a new session. Claims for the replacement access token are obtained through the configured claims provider.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reuse-and-concurrent-requests\u0022\u003EReuse and concurrent requests\u003C/h3\u003E\n\u003Cp\u003EA consumed refresh token cannot be used again to obtain another replacement. UltimateAuth distinguishes two situations:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ELikely concurrent request:\u003C/strong\u003E if a consumed token has a recorded replacement and the second request falls within \u003Ccode\u003ERefreshTokenConcurrentRequestWindow\u003C/code\u003E, the second rotation is rejected without treating it as a confirmed replay.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EReplay outside that window:\u003C/strong\u003E the rotation service revokes the associated refresh-token chain, or tokens associated with the session when a chain identifier is unavailable.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis distinction reduces destructive responses to ordinary client-side concurrency while retaining a response to suspicious reuse. The concurrency window is \u003Cstrong\u003Enot\u003C/strong\u003E a grace period in which the old token may be exchanged again: the second request still fails.\u003C/p\u003E\n\u003Cp\u003EThe rotation implementation shown here revokes refresh-token state. Do not interpret that as an unconditional guarantee that every related server-side session is also revoked; session invalidation is a separate concern.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-each-mode-executes\u0022\u003EHow each mode executes\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque-session-continuation\u0022\u003EPureOpaque: session continuation\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E has no refresh-token rotation step. The endpoint resolves the existing session from the authentication flow context, validates it and performs a conditional touch. It returns \u003Ccode\u003ETouched\u003C/code\u003E when state was updated or \u003Ccode\u003ENoOp\u003C/code\u003E when the valid session required no update.\u003C/p\u003E\n\u003Cp\u003ENo access token or refresh token is issued. The existing session ID remains the credential representing the continued context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid-session-and-token-continuity\u0022\u003EHybrid: session and token continuity\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EHybrid\u003C/code\u003E requires \u003Cstrong\u003Eboth\u003C/strong\u003E a valid session ID and a refresh token. The flow:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EValidates the session and device context.\u003C/li\u003E\n\u003Cli\u003EPerforms the session touch operation according to policy.\u003C/li\u003E\n\u003Cli\u003EValidates and rotates the refresh token, requiring it to match the expected session.\u003C/li\u003E\n\u003Cli\u003EReturns the existing session ID together with replacement access and refresh tokens.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe order is important: \u003Cstrong\u003Esession touch happens before token rotation\u003C/strong\u003E in the current implementation. These operations are not presented as one cross-store transaction. If rotation subsequently fails, the refresh response fails, although a preceding session touch may already have been persisted. This should not be described as an all-or-nothing operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid-and-purejwt-implementation-branches\u0022\u003ESemiHybrid and PureJwt: implementation branches\u003C/h3\u003E\n\u003Cp\u003EThe current service includes branches for two additional modes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ESemiHybrid\u003C/code\u003E requires a session ID and refresh token, validates the session and rotates the token, but does not touch the session.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EPureJwt\u003C/code\u003E uses the refresh token for rotation without a session touch step in \u003Ccode\u003ERefreshFlowService\u003C/code\u003E.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese describe the existing code paths, \u003Cstrong\u003Enot a promise of v0.1.0 production support\u003C/strong\u003E. Configuration validation and supported-mode documentation remain authoritative for which modes applications can enable.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022request-resolution-and-response-delivery\u0022\u003ERequest resolution and response delivery\u003C/h2\u003E\n\u003Cp\u003EThe refresh endpoint uses the server\u0027s \u003Ccode\u003EAuthFlowContext\u003C/code\u003E. It obtains the session ID from \u003Ccode\u003Eflow.Session\u003C/code\u003E, the device from \u003Ccode\u003Eflow.Device\u003C/code\u003E, and the refresh token through \u003Ccode\u003ERefreshTokenResolver\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor refresh-token input, the resolver checks:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EThe explicit \u003Ccode\u003EX-Refresh-Token\u003C/code\u003E header, if present.\u003C/li\u003E\n\u003Cli\u003EThe configured refresh cookie, when cookie delivery is active.\u003C/li\u003E\n\u003Cli\u003EThe configured refresh header, when header delivery is active.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe explicit header takes precedence. Header values may be supplied with a \u003Ccode\u003EBearer \u003C/code\u003E prefix, which the resolver strips.\u003C/p\u003E\n\u003Cp\u003EOn success, the endpoint chooses a \u003Cstrong\u003Eprimary credential\u003C/strong\u003E according to the refresh response policy, rather than automatically writing every credential returned by the flow. The refresh token is written separately when permitted by the response policy and when a replacement exists. Actual cookie/header placement is determined by the effective response configuration and client profile.\u003C/p\u003E\n\u003Cp\u003EThe HTTP endpoint returns \u003Cstrong\u003E\u003Ccode\u003E204 No Content\u003C/code\u003E\u003C/strong\u003E on successful refresh and \u003Cstrong\u003E\u003Ccode\u003E401 Unauthorized\u003C/code\u003E\u003C/strong\u003E when continuation fails. Clients should obtain credentials from the configured response delivery mechanism rather than expecting a JSON token payload. When \u003Ccode\u003EDiagnostics.EnableRefreshDetails\u003C/code\u003E is enabled, the endpoint also writes the refresh outcome through its diagnostic response writer.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EResponse delivery customization is not fully independent in v0.1.0: mode templates and client-profile adaptation constrain the effective behavior. Do not assume every configured credential-delivery override is honored.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022outcomes-and-client-expectations\u0022\u003EOutcomes and client expectations\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOutcome\u003C/th\u003E\n\u003Cth\u003EMeaning\u003C/th\u003E\n\u003Cth\u003EClient interpretation\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENoOp\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExisting session is valid; no touch was necessary\u003C/td\u003E\n\u003Ctd\u003EContinue normally\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETouched\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExisting session activity was updated\u003C/td\u003E\n\u003Ctd\u003EContinue normally\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERotated\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERefresh token was consumed and replacement credentials issued\u003C/td\u003E\n\u003Ctd\u003EReplace stored credentials with the new values\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReauthRequired\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EThe flow could not safely continue\u003C/td\u003E\n\u003Ctd\u003EStop using the failed refresh credential and require authentication\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cp\u003E\u003Ccode\u003ERefreshFlowResult\u003C/code\u003E also recognizes \u003Ccode\u003ESuccess\u003C/code\u003E as a successful outcome, although the mode-specific branches described above return \u003Ccode\u003ENoOp\u003C/code\u003E, \u003Ccode\u003ETouched\u003C/code\u003E, \u003Ccode\u003ERotated\u003C/code\u003E or \u003Ccode\u003EReauthRequired\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EClients should serialize refresh attempts where practical. Parallel refresh requests using the same token do not produce multiple valid replacements, even when they fall within the concurrency window.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-boundaries\u0022\u003ESecurity boundaries\u003C/h2\u003E\n\u003Cp\u003ERefresh continues existing trust only while the relevant state remains valid:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession and chain checks:\u003C/strong\u003E session-based paths validate active state, tenant, user, chain and session relationships.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice context:\u003C/strong\u003E session and refresh-token validation receive the current device context; mismatches can prevent continuation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESingle-use refresh tokens:\u003C/strong\u003E successful rotation consumes the old credential and persists a replacement.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EReplay response:\u003C/strong\u003E suspicious reuse can revoke refresh-token state associated with the chain or session.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EExpiration:\u003C/strong\u003E idle, session, chain and refresh-token validity rules can end continuation. Refresh does not bypass those limits.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EIssuance policy:\u003C/strong\u003E \u003Ccode\u003EIssueRefresh = false\u003C/code\u003E prevents rotation before the old token is consumed.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese controls complement one another. A refresh token should be treated as a sensitive credential, not as a harmless request to generate another access token.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EThe useful distinction is not simply \u003Cem\u003Estateful versus stateless\u003C/em\u003E. It is \u003Cstrong\u003Ewhat must remain valid for authentication to continue\u003C/strong\u003E:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E continues an existing server-side session, with conditional activity updates.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EHybrid\u003C/code\u003E requires both session validity and successful refresh-token rotation.\u003C/li\u003E\n\u003Cli\u003EOther rotation branches exist, but are not part of the completed v0.1.0 supported-mode set.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ERefresh does not authenticate a user again. It extends the usefulness of an already established authentication context only within the boundaries of the selected mode and configured security policies.\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./logout-flow\u0022\u003ELogout Flow\u003C/a\u003E.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003ERefresh is how an existing authentication context continues without asking the user to authenticate again. In UltimateAuth, it is not synonymous with issuing a new access token: the operation depends on the active authentication mode and on whether the context is represented by a server-side session, refresh credentials, or both.\u003C/p\u003E\n\u003Cp\u003EA successful refresh preserves the authenticated identity and its continuity. It does \u003Cstrong\u003Enot\u003C/strong\u003E prove the user\u0027s credentials again, create a new login, or necessarily extend every lifetime associated with the authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-model-continuity-not-a-second-login\u0022\u003EThe model: continuity, not a second login\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes three related operations:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthentication\u003C/strong\u003E establishes a new authenticated context after the required proof is accepted.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERefresh\u003C/strong\u003E attempts to continue an already established context, subject to its current validity and security policies.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EReauthentication\u003C/strong\u003E establishes trust again when continuation is unavailable or no longer permitted.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis distinction matters for both security and client behavior. A refresh request can fail because a session expired, a refresh credential is invalid or consumed, the device context does not match, or the underlying chain is no longer usable. The client should treat an unsuccessful refresh as a need to authenticate again, not as an invitation to retry the same credential indefinitely.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-dependent-strategies\u0022\u003EMode-dependent strategies\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth implements refresh through two building blocks: \u003Cstrong\u003Esession touch\u003C/strong\u003E and \u003Cstrong\u003Erefresh-token rotation\u003C/strong\u003E. A mode selects how they are combined.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EAuthentication mode\u003C/th\u003E\n\u003Cth\u003ERequired refresh input\u003C/th\u003E\n\u003Cth\u003EStrategy\u003C/th\u003E\n\u003Cth\u003EResult\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession ID\u003C/td\u003E\n\u003Ctd\u003EValidate session; touch when policy requires\u003C/td\u003E\n\u003Ctd\u003EContinue the existing session; no new access or refresh token\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession ID \u003Cstrong\u003Eand\u003C/strong\u003E refresh token\u003C/td\u003E\n\u003Ctd\u003EValidate and conditionally touch session; rotate refresh token\u003C/td\u003E\n\u003Ctd\u003EContinue session and issue replacement tokens\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESemiHybrid\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession ID \u003Cstrong\u003Eand\u003C/strong\u003E refresh token\u003C/td\u003E\n\u003Ctd\u003EValidate session; rotate refresh token\u003C/td\u003E\n\u003Ctd\u003EIssue replacement tokens without a session touch\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureJwt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERefresh token\u003C/td\u003E\n\u003Ctd\u003EValidate and rotate refresh token\u003C/td\u003E\n\u003Ctd\u003EIssue replacement tokens; no session touch in the flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003ECurrent support status:\u003C/strong\u003E \u003Ccode\u003EPureOpaque\u003C/code\u003E and \u003Ccode\u003EHybrid\u003C/code\u003E are the supported modes for the initial release. The \u003Ccode\u003ESemiHybrid\u003C/code\u003E and \u003Ccode\u003EPureJwt\u003C/code\u003E branches are present in the refresh implementation but should not be presented as completed, generally supported configurations in v0.1.0.\u003C/p\u003E\n\u003Cp\u003EThe mode is resolved from the effective authentication context on the server. Clients do not select a refresh algorithm independently.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-touch\u0022\u003ESession touch\u003C/h2\u003E\n\u003Cp\u003EA session touch is a controlled update to existing server-side authentication state. It does not issue a replacement credential or create a new 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\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 ID\n \u2192 Validate session, chain and device context\n \u2192 Recheck active state and chain/session relationship\n \u2192 Touch if policy requires\n \u2192 Continue with the same session ID\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn \u003Ccode\u003EPureOpaque\u003C/code\u003E, the refresh flow requires a session ID. The session validator checks the session against the current tenant, device context and session options. The touch service then reloads the session and chain from the store and checks that they are still active and consistent, including the chain\u0027s active session, tenant, user and chain identifiers.\u003C/p\u003E\n\u003Cp\u003ETouch behavior is governed by the effective session options:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ETouchInterval\u003C/code\u003E avoids writing activity state on every request. If the interval has not elapsed, the operation may succeed as \u003Cstrong\u003ENoOp\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ETouchMode\u003C/code\u003E is \u003Ccode\u003EIfNeeded\u003C/code\u003E by default; \u003Ccode\u003EForce\u003C/code\u003E requests a touch even when the normal interval has not elapsed.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ESlidingExpiration\u003C/code\u003E allows a touch to extend the session expiration using the configured \u003Ccode\u003ELifetime\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EAn absolute chain expiration remains an upper bound; sliding expiration cannot extend the session beyond it.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EIdleTimeout\u003C/code\u003E participates in chain-state validation. An idle or otherwise inactive chain cannot be revived merely by touching it.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA successful \u003Cstrong\u003ENoOp\u003C/strong\u003E is not a failed refresh: the existing state was valid, but no persistence update was necessary. Likewise, a touch is not an unconditional promise to extend the session\u0027s expiry; that depends on policy and the absolute limit.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-token-rotation\u0022\u003ERefresh-token rotation\u003C/h2\u003E\n\u003Cp\u003ERotation replaces a valid refresh credential while preserving its association with the existing authentication 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\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\u003ERefresh token\n \u2192 Validate token, tenant, device and expected session (if supplied)\n \u2192 Resolve user, session and chain\n \u2192 Prepare replacement refresh/access tokens\n \u2192 Atomically consume old refresh token and store replacement\n \u2192 Return new credentials\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe rotation service first rejects modes or configurations that do not permit refresh-token issuance (\u003Ccode\u003EPureOpaque\u003C/code\u003E or \u003Ccode\u003EIssueRefresh = false\u003C/code\u003E). It then validates the presented credential and its context. The replacement refresh token and access token are prepared before the existing token is consumed. Persistence uses the refresh-token store\u0027s execution boundary and \u003Ccode\u003ETryConsumeAsync\u003C/code\u003E, so competing requests cannot both successfully consume the same old token.\u003C/p\u003E\n\u003Cp\u003ERotation preserves the validated user, session and chain association; it is \u003Cstrong\u003Enot\u003C/strong\u003E a new authentication or a new session. Claims for the replacement access token are obtained through the configured claims provider.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reuse-and-concurrent-requests\u0022\u003EReuse and concurrent requests\u003C/h3\u003E\n\u003Cp\u003EA consumed refresh token cannot be used again to obtain another replacement. UltimateAuth distinguishes two situations:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ELikely concurrent request:\u003C/strong\u003E if a consumed token has a recorded replacement and the second request falls within \u003Ccode\u003ERefreshTokenConcurrentRequestWindow\u003C/code\u003E, the second rotation is rejected without treating it as a confirmed replay.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EReplay outside that window:\u003C/strong\u003E the rotation service revokes the associated refresh-token chain, or tokens associated with the session when a chain identifier is unavailable.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis distinction reduces destructive responses to ordinary client-side concurrency while retaining a response to suspicious reuse. The concurrency window is \u003Cstrong\u003Enot\u003C/strong\u003E a grace period in which the old token may be exchanged again: the second request still fails.\u003C/p\u003E\n\u003Cp\u003EThe rotation implementation shown here revokes refresh-token state. Do not interpret that as an unconditional guarantee that every related server-side session is also revoked; session invalidation is a separate concern.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-each-mode-executes\u0022\u003EHow each mode executes\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque-session-continuation\u0022\u003EPureOpaque: session continuation\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E has no refresh-token rotation step. The endpoint resolves the existing session from the authentication flow context, validates it and performs a conditional touch. It returns \u003Ccode\u003ETouched\u003C/code\u003E when state was updated or \u003Ccode\u003ENoOp\u003C/code\u003E when the valid session required no update.\u003C/p\u003E\n\u003Cp\u003ENo access token or refresh token is issued. The existing session ID remains the credential representing the continued context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid-session-and-token-continuity\u0022\u003EHybrid: session and token continuity\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EHybrid\u003C/code\u003E requires \u003Cstrong\u003Eboth\u003C/strong\u003E a valid session ID and a refresh token. The flow:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EValidates the session and device context.\u003C/li\u003E\n\u003Cli\u003EPerforms the session touch operation according to policy.\u003C/li\u003E\n\u003Cli\u003EValidates and rotates the refresh token, requiring it to match the expected session.\u003C/li\u003E\n\u003Cli\u003EReturns the existing session ID together with replacement access and refresh tokens.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe order is important: \u003Cstrong\u003Esession touch happens before token rotation\u003C/strong\u003E in the current implementation. These operations are not presented as one cross-store transaction. If rotation subsequently fails, the refresh response fails, although a preceding session touch may already have been persisted. This should not be described as an all-or-nothing operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid-and-purejwt-implementation-branches\u0022\u003ESemiHybrid and PureJwt: implementation branches\u003C/h3\u003E\n\u003Cp\u003EThe current service includes branches for two additional modes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ESemiHybrid\u003C/code\u003E requires a session ID and refresh token, validates the session and rotates the token, but does not touch the session.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EPureJwt\u003C/code\u003E uses the refresh token for rotation without a session touch step in \u003Ccode\u003ERefreshFlowService\u003C/code\u003E.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese describe the existing code paths, \u003Cstrong\u003Enot a promise of v0.1.0 production support\u003C/strong\u003E. Configuration validation and supported-mode documentation remain authoritative for which modes applications can enable.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022request-resolution-and-response-delivery\u0022\u003ERequest resolution and response delivery\u003C/h2\u003E\n\u003Cp\u003EThe refresh endpoint uses the server\u0027s \u003Ccode\u003EAuthFlowContext\u003C/code\u003E. It obtains the session ID from \u003Ccode\u003Eflow.Session\u003C/code\u003E, the device from \u003Ccode\u003Eflow.Device\u003C/code\u003E, and the refresh token through \u003Ccode\u003ERefreshTokenResolver\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor refresh-token input, the resolver checks:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EThe explicit \u003Ccode\u003EX-Refresh-Token\u003C/code\u003E header, if present.\u003C/li\u003E\n\u003Cli\u003EThe configured refresh cookie, when cookie delivery is active.\u003C/li\u003E\n\u003Cli\u003EThe configured refresh header, when header delivery is active.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe explicit header takes precedence. Header values may be supplied with a \u003Ccode\u003EBearer \u003C/code\u003E prefix, which the resolver strips.\u003C/p\u003E\n\u003Cp\u003EOn success, the endpoint chooses a \u003Cstrong\u003Eprimary credential\u003C/strong\u003E according to the refresh response policy, rather than automatically writing every credential returned by the flow. The refresh token is written separately when permitted by the response policy and when a replacement exists. Actual cookie/header placement is determined by the effective response configuration and client profile.\u003C/p\u003E\n\u003Cp\u003EThe HTTP endpoint returns \u003Cstrong\u003E\u003Ccode\u003E204 No Content\u003C/code\u003E\u003C/strong\u003E on successful refresh and \u003Cstrong\u003E\u003Ccode\u003E401 Unauthorized\u003C/code\u003E\u003C/strong\u003E when continuation fails. Clients should obtain credentials from the configured response delivery mechanism rather than expecting a JSON token payload. When \u003Ccode\u003EDiagnostics.EnableRefreshDetails\u003C/code\u003E is enabled, the endpoint also writes the refresh outcome through its diagnostic response writer.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EResponse delivery customization is not fully independent in v0.1.0: mode templates and client-profile adaptation constrain the effective behavior. Do not assume every configured credential-delivery override is honored.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022outcomes-and-client-expectations\u0022\u003EOutcomes and client expectations\u003C/h2\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\u003Cth\u003EClient interpretation\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENoOp\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExisting session is valid; no touch was necessary\u003C/td\u003E\n\u003Ctd\u003EContinue normally\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETouched\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExisting session activity was updated\u003C/td\u003E\n\u003Ctd\u003EContinue normally\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERotated\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERefresh token was consumed and replacement credentials issued\u003C/td\u003E\n\u003Ctd\u003EReplace stored credentials with the new values\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReauthRequired\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EThe flow could not safely continue\u003C/td\u003E\n\u003Ctd\u003EStop using the failed refresh credential and require authentication\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ERefreshFlowResult\u003C/code\u003E also recognizes \u003Ccode\u003ESuccess\u003C/code\u003E as a successful outcome, although the mode-specific branches described above return \u003Ccode\u003ENoOp\u003C/code\u003E, \u003Ccode\u003ETouched\u003C/code\u003E, \u003Ccode\u003ERotated\u003C/code\u003E or \u003Ccode\u003EReauthRequired\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EClients should serialize refresh attempts where practical. Parallel refresh requests using the same token do not produce multiple valid replacements, even when they fall within the concurrency window.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-boundaries\u0022\u003ESecurity boundaries\u003C/h2\u003E\n\u003Cp\u003ERefresh continues existing trust only while the relevant state remains valid:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession and chain checks:\u003C/strong\u003E session-based paths validate active state, tenant, user, chain and session relationships.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice context:\u003C/strong\u003E session and refresh-token validation receive the current device context; mismatches can prevent continuation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESingle-use refresh tokens:\u003C/strong\u003E successful rotation consumes the old credential and persists a replacement.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EReplay response:\u003C/strong\u003E suspicious reuse can revoke refresh-token state associated with the chain or session.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EExpiration:\u003C/strong\u003E idle, session, chain and refresh-token validity rules can end continuation. Refresh does not bypass those limits.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EIssuance policy:\u003C/strong\u003E \u003Ccode\u003EIssueRefresh = false\u003C/code\u003E prevents rotation before the old token is consumed.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese controls complement one another. A refresh token should be treated as a sensitive credential, not as a harmless request to generate another access token.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EThe useful distinction is not simply \u003Cem\u003Estateful versus stateless\u003C/em\u003E. It is \u003Cstrong\u003Ewhat must remain valid for authentication to continue\u003C/strong\u003E:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E continues an existing server-side session, with conditional activity updates.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EHybrid\u003C/code\u003E requires both session validity and successful refresh-token rotation.\u003C/li\u003E\n\u003Cli\u003EOther rotation branches exist, but are not part of the completed v0.1.0 supported-mode set.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ERefresh does not authenticate a user again. It extends the usefulness of an already established authentication context only within the boundaries of the selected mode and configured security policies.\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./logout-flow\u0022\u003ELogout Flow\u003C/a\u003E.\u003C/p\u003E\n", "Headings": [ { "Id": "the-model-continuity-not-a-second-login", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/session-lifecycle.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/session-lifecycle.json index 96fc0cfb..4902cf5e 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/session-lifecycle.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/session-lifecycle.json @@ -1,7 +1,7 @@ { "Slug": "auth-flows/session-lifecycle", "Title": "Session Lifecycle", - "Html": "\n\u003Cp\u003EUltimateAuth is built around a structured session model.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Authentication is not a token\u003Cbr /\u003E\n\uD83D\uDC49 It is a \u003Cstrong\u003Ehierarchical session system\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-model\u0022\u003E\uD83E\uDDE0 Core Model\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth defines three core entities:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ERoot \u2192 Chain \u2192 Session\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-identity-authority\u0022\u003E\uD83D\uDD39 Root (Identity Authority)\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EOne per user\u003C/li\u003E\n\u003Cli\u003ERepresents global authentication state\u003C/li\u003E\n\u003Cli\u003EHolds security version\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Root defines \u003Cstrong\u003Ewho the user is\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-device-context\u0022\u003E\uD83D\uDCF1 Chain (Device Context)\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EOne per device\u003C/li\u003E\n\u003Cli\u003ERepresents a device-bound identity context\u003C/li\u003E\n\u003Cli\u003ETracks activity (LastSeenAt)\u003C/li\u003E\n\u003Cli\u003EManages session rotation and touch\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 A chain is a \u003Cstrong\u003Etrusted device boundary\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-authentication-instance\u0022\u003E\uD83D\uDD11 Session (Authentication Instance)\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ECreated on login\u003C/li\u003E\n\u003Cli\u003ERepresents a single authentication event\u003C/li\u003E\n\u003Cli\u003EHas expiration and revocation state\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 A session is a \u003Cstrong\u003Eproof of authentication\u003C/strong\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022relationship\u0022\u003E\uD83D\uDD17 Relationship\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode\u003EUser\n\u2514\u2500\u2500 Root\n\u2514\u2500\u2500 Chain (Device)\n\u2514\u2500\u2500 Session (Instance)\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Each level adds more specificity:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoot \u2192 identity\u003C/li\u003E\n\u003Cli\u003EChain \u2192 device\u003C/li\u003E\n\u003Cli\u003ESession \u2192 login instance\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifecycle-overview\u0022\u003E\uD83D\uDD04 Lifecycle Overview\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022creation-login\u0022\u003E1\uFE0F\u20E3 Creation (Login)\u003C/h3\u003E\n\u003Cp\u003EWhen a user logs in:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoot is created (if not exists)\u003C/li\u003E\n\u003Cli\u003EChain is resolved or created\u003C/li\u003E\n\u003Cli\u003ESession is issued\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022active-usage\u0022\u003E2\uFE0F\u20E3 Active Usage\u003C/h3\u003E\n\u003Cp\u003EDuring normal operation:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession is validated\u003C/li\u003E\n\u003Cli\u003EChain \u003Ccode\u003ELastSeenAt\u003C/code\u003E is updated (touch)\u003C/li\u003E\n\u003Cli\u003ESliding expiration may apply\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Activity updates the \u003Cstrong\u003Echain\u003C/strong\u003E, not just the session\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh\u0022\u003E3\uFE0F\u20E3 Refresh\u003C/h3\u003E\n\u003Cp\u003EDepending on mode:\u003C/p\u003E\n\u003Ch4 id=\u0022session-based-pureopaque\u0022\u003ESession-Based (PureOpaque)\u003C/h4\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession remains\u003C/li\u003E\n\u003Cli\u003EChain is touched\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch4 id=\u0022token-based-hybrid-jwt\u0022\u003EToken-Based (Hybrid / JWT)\u003C/h4\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession continues\u003C/li\u003E\n\u003Cli\u003ETokens are rotated\u003C/li\u003E\n\u003Cli\u003EChain rotation count increases\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Chain tracks behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERotationCount\u003C/li\u003E\n\u003Cli\u003ETouchCount\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022expiration\u0022\u003E4\uFE0F\u20E3 Expiration\u003C/h3\u003E\n\u003Cp\u003EA session may expire due to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELifetime expiration\u003C/li\u003E\n\u003Cli\u003EIdle timeout\u003C/li\u003E\n\u003Cli\u003EAbsolute expiration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Expired \u2260 revoked\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation\u0022\u003E5\uFE0F\u20E3 Revocation\u003C/h3\u003E\n\u003Cp\u003ERevocation can occur at multiple levels:\u003C/p\u003E\n\u003Ch4 id=\u0022session-revocation\u0022\u003ESession Revocation\u003C/h4\u003E\n\u003Cul\u003E\n\u003Cli\u003ESingle session invalidated\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch4 id=\u0022chain-revocation\u0022\u003EChain Revocation\u003C/h4\u003E\n\u003Cul\u003E\n\u003Cli\u003EAll sessions on device invalidated\u003C/li\u003E\n\u003Cli\u003EDevice trust reset\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr /\u003E\n\u003Ch4 id=\u0022root-revocation\u0022\u003ERoot Revocation\u003C/h4\u003E\n\u003Cul\u003E\n\u003Cli\u003EAll chains and sessions invalidated\u003C/li\u003E\n\u003Cli\u003ESecurity version increased\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Revocation is irreversible\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-model\u0022\u003E\uD83D\uDD10 Security Model\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-versioning\u0022\u003E\uD83D\uDD12 Security Versioning\u003C/h3\u003E\n\u003Cp\u003EEach root has:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ESecurityVersion\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EEach session stores:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ESecurityVersionAtCreation\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr /\u003E\n\u003Cp\u003E\uD83D\uDC49 If mismatch:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ESession becomes invalid\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-binding\u0022\u003E\uD83D\uDD17 Device Binding\u003C/h3\u003E\n\u003Cp\u003EEach chain is tied to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDeviceId\u003C/li\u003E\n\u003Cli\u003EPlatform\u003C/li\u003E\n\u003Cli\u003EOS\u003C/li\u003E\n\u003Cli\u003EBrowser\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Prevents cross-device misuse\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-tracking\u0022\u003E\uD83D\uDD01 Rotation Tracking\u003C/h3\u003E\n\u003Cp\u003EChains track:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERotationCount\u003C/li\u003E\n\u003Cli\u003ETouchCount\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Enables:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ereplay detection\u003C/li\u003E\n\u003Cli\u003Eanomaly tracking\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifecycle-configuration\u0022\u003E\u2699\uFE0F Lifecycle Configuration\u003C/h3\u003E\n\u003Cp\u003ESession behavior is configurable:\u003C/p\u003E\n\u003Cp\u003E\u23F1 Lifetime\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDefault session duration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDD04 Sliding Expiration\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EExtends session on activity\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDCA4 Idle Timeout\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalidates inactive sessions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDCF1 Device Limits\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMax chains per user\u003C/li\u003E\n\u003Cli\u003EMax sessions per chain\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 These are defined via UAuthSessionOptions\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Authentication is a living structure\n\uD83D\uDC49 Not a static token\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003ESessions are part of a hierarchy\u003C/li\u003E\n\u003Cli\u003EDevice (chain) is a first-class concept\u003C/li\u003E\n\u003Cli\u003ERoot controls global security\u003C/li\u003E\n\u003Cli\u003ESessions are short-lived proofs\u003C/li\u003E\n\u003Cli\u003EChains manage lifecycle and activity\u003C/li\u003E\n\u003Cli\u003ERevocation operates at multiple levels\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to Token Behavior\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth is built around a structured session model.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Authentication is not a token\u003Cbr\u003E\n\uD83D\uDC49 It is a \u003Cstrong\u003Ehierarchical session system\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-model\u0022\u003E\uD83E\uDDE0 Core Model\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth defines three core entities:\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\u003ERoot \u2192 Chain \u2192 Session\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-identity-authority\u0022\u003E\uD83D\uDD39 Root (Identity Authority)\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EOne per user\u003C/li\u003E\n\u003Cli\u003ERepresents global authentication state\u003C/li\u003E\n\u003Cli\u003EHolds security version\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Root defines \u003Cstrong\u003Ewho the user is\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-device-context\u0022\u003E\uD83D\uDCF1 Chain (Device Context)\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EOne per device\u003C/li\u003E\n\u003Cli\u003ERepresents a device-bound identity context\u003C/li\u003E\n\u003Cli\u003ETracks activity (LastSeenAt)\u003C/li\u003E\n\u003Cli\u003EManages session rotation and touch\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 A chain is a \u003Cstrong\u003Etrusted device boundary\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-authentication-instance\u0022\u003E\uD83D\uDD11 Session (Authentication Instance)\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ECreated on login\u003C/li\u003E\n\u003Cli\u003ERepresents a single authentication event\u003C/li\u003E\n\u003Cli\u003EHas expiration and revocation state\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 A session is a \u003Cstrong\u003Eproof of authentication\u003C/strong\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022relationship\u0022\u003E\uD83D\uDD17 Relationship\u003C/h2\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\u003EUser\n\u2514\u2500\u2500 Root\n\u2514\u2500\u2500 Chain (Device)\n\u2514\u2500\u2500 Session (Instance)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Each level adds more specificity:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoot \u2192 identity\u003C/li\u003E\n\u003Cli\u003EChain \u2192 device\u003C/li\u003E\n\u003Cli\u003ESession \u2192 login instance\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifecycle-overview\u0022\u003E\uD83D\uDD04 Lifecycle Overview\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022creation-login\u0022\u003E1\uFE0F\u20E3 Creation (Login)\u003C/h3\u003E\n\u003Cp\u003EWhen a user logs in:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoot is created (if not exists)\u003C/li\u003E\n\u003Cli\u003EChain is resolved or created\u003C/li\u003E\n\u003Cli\u003ESession is issued\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022active-usage\u0022\u003E2\uFE0F\u20E3 Active Usage\u003C/h3\u003E\n\u003Cp\u003EDuring normal operation:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession is validated\u003C/li\u003E\n\u003Cli\u003EChain \u003Ccode\u003ELastSeenAt\u003C/code\u003E is updated (touch)\u003C/li\u003E\n\u003Cli\u003ESliding expiration may apply\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Activity updates the \u003Cstrong\u003Echain\u003C/strong\u003E, not just the session\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh\u0022\u003E3\uFE0F\u20E3 Refresh\u003C/h3\u003E\n\u003Cp\u003EDepending on mode:\u003C/p\u003E\n\u003Ch4 id=\u0022session-based-pureopaque\u0022\u003ESession-Based (PureOpaque)\u003C/h4\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession remains\u003C/li\u003E\n\u003Cli\u003EChain is touched\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch4 id=\u0022token-based-hybrid-jwt\u0022\u003EToken-Based (Hybrid / JWT)\u003C/h4\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession continues\u003C/li\u003E\n\u003Cli\u003ETokens are rotated\u003C/li\u003E\n\u003Cli\u003EChain rotation count increases\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Chain tracks behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERotationCount\u003C/li\u003E\n\u003Cli\u003ETouchCount\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022expiration\u0022\u003E4\uFE0F\u20E3 Expiration\u003C/h3\u003E\n\u003Cp\u003EA session may expire due to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELifetime expiration\u003C/li\u003E\n\u003Cli\u003EIdle timeout\u003C/li\u003E\n\u003Cli\u003EAbsolute expiration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Expired \u2260 revoked\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation\u0022\u003E5\uFE0F\u20E3 Revocation\u003C/h3\u003E\n\u003Cp\u003ERevocation can occur at multiple levels:\u003C/p\u003E\n\u003Ch4 id=\u0022session-revocation\u0022\u003ESession Revocation\u003C/h4\u003E\n\u003Cul\u003E\n\u003Cli\u003ESingle session invalidated\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch4 id=\u0022chain-revocation\u0022\u003EChain Revocation\u003C/h4\u003E\n\u003Cul\u003E\n\u003Cli\u003EAll sessions on device invalidated\u003C/li\u003E\n\u003Cli\u003EDevice trust reset\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Ch4 id=\u0022root-revocation\u0022\u003ERoot Revocation\u003C/h4\u003E\n\u003Cul\u003E\n\u003Cli\u003EAll chains and sessions invalidated\u003C/li\u003E\n\u003Cli\u003ESecurity version increased\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Revocation is irreversible\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-model\u0022\u003E\uD83D\uDD10 Security Model\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-versioning\u0022\u003E\uD83D\uDD12 Security Versioning\u003C/h3\u003E\n\u003Cp\u003EEach root has:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ESecurityVersion\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EEach session stores:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ESecurityVersionAtCreation\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Cp\u003E\uD83D\uDC49 If mismatch:\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 becomes invalid\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-binding\u0022\u003E\uD83D\uDD17 Device Binding\u003C/h3\u003E\n\u003Cp\u003EEach chain is tied to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDeviceId\u003C/li\u003E\n\u003Cli\u003EPlatform\u003C/li\u003E\n\u003Cli\u003EOS\u003C/li\u003E\n\u003Cli\u003EBrowser\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Prevents cross-device misuse\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-tracking\u0022\u003E\uD83D\uDD01 Rotation Tracking\u003C/h3\u003E\n\u003Cp\u003EChains track:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERotationCount\u003C/li\u003E\n\u003Cli\u003ETouchCount\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Enables:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ereplay detection\u003C/li\u003E\n\u003Cli\u003Eanomaly tracking\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifecycle-configuration\u0022\u003E\u2699\uFE0F Lifecycle Configuration\u003C/h3\u003E\n\u003Cp\u003ESession behavior is configurable:\u003C/p\u003E\n\u003Cp\u003E\u23F1 Lifetime\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDefault session duration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDD04 Sliding Expiration\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EExtends session on activity\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDCA4 Idle Timeout\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalidates inactive sessions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDCF1 Device Limits\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMax chains per user\u003C/li\u003E\n\u003Cli\u003EMax sessions per chain\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 These are defined via UAuthSessionOptions\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Authentication is a living structure\n\uD83D\uDC49 Not a static token\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003ESessions are part of a hierarchy\u003C/li\u003E\n\u003Cli\u003EDevice (chain) is a first-class concept\u003C/li\u003E\n\u003Cli\u003ERoot controls global security\u003C/li\u003E\n\u003Cli\u003ESessions are short-lived proofs\u003C/li\u003E\n\u003Cli\u003EChains manage lifecycle and activity\u003C/li\u003E\n\u003Cli\u003ERevocation operates at multiple levels\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to Token Behavior\u003C/p\u003E\n", "Headings": [ { "Id": "core-model", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/token-behavior.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/token-behavior.json index cc2615db..1d7ebc7c 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/token-behavior.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/token-behavior.json @@ -1,7 +1,7 @@ { "Slug": "auth-flows/token-behavior", "Title": "Token Behavior", - "Html": "\n\u003Cp\u003EIn UltimateAuth, tokens are not the foundation of authentication.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 They are \u003Cstrong\u003Ederived artifacts of a session\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rethinking-tokens\u0022\u003E\uD83E\uDDE0 Rethinking Tokens\u003C/h2\u003E\n\u003Cp\u003EIn traditional systems:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EToken = identity\u003C/li\u003E\n\u003Cli\u003EToken = authentication\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIn UltimateAuth:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Session = identity\u003Cbr /\u003E\n\uD83D\uDC49 Token = transport mechanism\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003ETokens do not define identity\u003Cbr /\u003E\n\u2192 Sessions do\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-types\u0022\u003E\uD83E\uDDE9 Token Types\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports two main token types:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022opaque-tokens\u0022\u003E\uD83D\uDD39 Opaque Tokens\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ERandom, non-decodable values\u003C/li\u003E\n\u003Cli\u003EStored and validated on the server\u003C/li\u003E\n\u003Cli\u003ETypically reference a session\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Used in:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPureOpaque\u003C/li\u003E\n\u003Cli\u003EHybrid\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022jwt-json-web-tokens\u0022\u003E\uD83D\uDD39 JWT (JSON Web Tokens)\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESelf-contained tokens\u003C/li\u003E\n\u003Cli\u003EInclude claims and metadata\u003C/li\u003E\n\u003Cli\u003ESigned and verifiable without server lookup\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Used in:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESemiHybrid\u003C/li\u003E\n\u003Cli\u003EPureJwt\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-based-behavior\u0022\u003E\u2696\uFE0F Mode-Based Behavior\u003C/h2\u003E\n\u003Cp\u003EToken behavior depends on the authentication mode:\u003C/p\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EMode\u003C/th\u003E\n\u003Cth\u003EAccess Token\u003C/th\u003E\n\u003Cth\u003ERefresh Token\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\u003EPureOpaque\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003ESession-only\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EHybrid\u003C/td\u003E\n\u003Ctd\u003E\u2714 (opaque/JWT)\u003C/td\u003E\n\u003Ctd\u003E\u2714\u003C/td\u003E\n\u003Ctd\u003ESession \u002B token\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESemiHybrid\u003C/td\u003E\n\u003Ctd\u003E\u2714 (JWT)\u003C/td\u003E\n\u003Ctd\u003E\u2714\u003C/td\u003E\n\u003Ctd\u003EJWT \u002B session metadata\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPureJwt\u003C/td\u003E\n\u003Ctd\u003E\u2714 (JWT)\u003C/td\u003E\n\u003Ctd\u003E\u2714\u003C/td\u003E\n\u003Ctd\u003EFully stateless\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cp\u003E\uD83D\uDC49 UltimateAuth selects behavior automatically\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022access-tokens\u0022\u003E\uD83D\uDD11 Access Tokens\u003C/h2\u003E\n\u003Cp\u003EAccess tokens represent:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 A \u003Cstrong\u003Etemporary access grant\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022characteristics\u0022\u003ECharacteristics\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EShort-lived\u003C/li\u003E\n\u003Cli\u003EMode-dependent format\u003C/li\u003E\n\u003Cli\u003EMay contain session reference (\u003Ccode\u003Esid\u003C/code\u003E)\u003C/li\u003E\n\u003Cli\u003EMay include claims\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important\u0022\u003EImportant\u003C/h3\u003E\n\u003Cp\u003EAccess token is NOT the source of truth.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 It reflects session state, not replaces it\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-tokens\u0022\u003E\uD83D\uDD04 Refresh Tokens\u003C/h2\u003E\n\u003Cp\u003ERefresh tokens represent:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 A \u003Cstrong\u003Econtinuation mechanism\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022characteristics-1\u0022\u003ECharacteristics\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ELong-lived\u003C/li\u003E\n\u003Cli\u003EStored as hashed values\u003C/li\u003E\n\u003Cli\u003EBound to session and optionally chain\u003C/li\u003E\n\u003Cli\u003ERotated on use\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifecycle\u0022\u003ELifecycle\u003C/h3\u003E\n\u003Cp\u003EIssued \u2192 Used \u2192 Replaced \u2192 Revoked\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Old tokens are invalidated on rotation\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-model\u0022\u003E\uD83D\uDD10 Security Model\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation\u0022\u003E\uD83D\uDD01 Rotation\u003C/h3\u003E\n\u003Cp\u003EEach refresh:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalidates previous token\u003C/li\u003E\n\u003Cli\u003EIssues a new token\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Prevents replay attacks\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reuse-detection\u0022\u003E\uD83D\uDEA8 Reuse Detection\u003C/h3\u003E\n\u003Cp\u003EIf a token is reused:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EChain may be revoked\u003C/li\u003E\n\u003Cli\u003ESession may be revoked\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Strong forward security\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-binding\u0022\u003E\uD83D\uDD17 Session Binding\u003C/h3\u003E\n\u003Cp\u003ERefresh tokens include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESessionId\u003C/li\u003E\n\u003Cli\u003EChainId (optional)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Prevents cross-context usage\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hashed-storage\u0022\u003E\uD83D\uDD12 Hashed Storage\u003C/h3\u003E\n\u003Cp\u003ETokens are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ENever stored as plaintext\u003C/li\u003E\n\u003Cli\u003EHashed using secure algorithms\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Reduces breach impact\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-issuance\u0022\u003E\uD83D\uDD04 Token Issuance\u003C/h2\u003E\n\u003Cp\u003ETokens are issued during:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogin\u003C/li\u003E\n\u003Cli\u003ERefresh\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022access-token\u0022\u003EAccess Token\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EMay be opaque or JWT\u003C/li\u003E\n\u003Cli\u003EIncludes identity and optional session reference\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-token\u0022\u003ERefresh Token\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EAlways opaque\u003C/li\u003E\n\u003Cli\u003EPersisted in secure store\u003C/li\u003E\n\u003Cli\u003EUsed only for rotation\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Tokens are not identity\u003Cbr /\u003E\n\uD83D\uDC49 They are projections of a session\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession is the source of truth\u003C/li\u003E\n\u003Cli\u003ETokens are optional and mode-dependent\u003C/li\u003E\n\u003Cli\u003EOpaque tokens require server validation\u003C/li\u003E\n\u003Cli\u003EJWT tokens allow stateless access\u003C/li\u003E\n\u003Cli\u003ERefresh tokens enable secure continuation\u003C/li\u003E\n\u003Cli\u003EToken rotation ensures forward security\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to Device Management\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EIn UltimateAuth, tokens are not the foundation of authentication.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 They are \u003Cstrong\u003Ederived artifacts of a session\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rethinking-tokens\u0022\u003E\uD83E\uDDE0 Rethinking Tokens\u003C/h2\u003E\n\u003Cp\u003EIn traditional systems:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EToken = identity\u003C/li\u003E\n\u003Cli\u003EToken = authentication\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIn UltimateAuth:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Session = identity\u003Cbr\u003E\n\uD83D\uDC49 Token = transport mechanism\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003ETokens do not define identity\u003Cbr\u003E\n\u2192 Sessions do\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-types\u0022\u003E\uD83E\uDDE9 Token Types\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports two main token types:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022opaque-tokens\u0022\u003E\uD83D\uDD39 Opaque Tokens\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ERandom, non-decodable values\u003C/li\u003E\n\u003Cli\u003EStored and validated on the server\u003C/li\u003E\n\u003Cli\u003ETypically reference a session\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Used in:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPureOpaque\u003C/li\u003E\n\u003Cli\u003EHybrid\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022jwt-json-web-tokens\u0022\u003E\uD83D\uDD39 JWT (JSON Web Tokens)\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESelf-contained tokens\u003C/li\u003E\n\u003Cli\u003EInclude claims and metadata\u003C/li\u003E\n\u003Cli\u003ESigned and verifiable without server lookup\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Used in:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESemiHybrid\u003C/li\u003E\n\u003Cli\u003EPureJwt\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-based-behavior\u0022\u003E\u2696\uFE0F Mode-Based Behavior\u003C/h2\u003E\n\u003Cp\u003EToken behavior depends on the authentication mode:\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\u003EAccess Token\u003C/th\u003E\n\u003Cth\u003ERefresh Token\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\u003EPureOpaque\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003ESession-only\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EHybrid\u003C/td\u003E\n\u003Ctd\u003E\u2714 (opaque/JWT)\u003C/td\u003E\n\u003Ctd\u003E\u2714\u003C/td\u003E\n\u003Ctd\u003ESession \u002B token\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESemiHybrid\u003C/td\u003E\n\u003Ctd\u003E\u2714 (JWT)\u003C/td\u003E\n\u003Ctd\u003E\u2714\u003C/td\u003E\n\u003Ctd\u003EJWT \u002B session metadata\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPureJwt\u003C/td\u003E\n\u003Ctd\u003E\u2714 (JWT)\u003C/td\u003E\n\u003Ctd\u003E\u2714\u003C/td\u003E\n\u003Ctd\u003EFully stateless\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 UltimateAuth selects behavior automatically\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022access-tokens\u0022\u003E\uD83D\uDD11 Access Tokens\u003C/h2\u003E\n\u003Cp\u003EAccess tokens represent:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 A \u003Cstrong\u003Etemporary access grant\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022characteristics\u0022\u003ECharacteristics\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EShort-lived\u003C/li\u003E\n\u003Cli\u003EMode-dependent format\u003C/li\u003E\n\u003Cli\u003EMay contain session reference (\u003Ccode\u003Esid\u003C/code\u003E)\u003C/li\u003E\n\u003Cli\u003EMay include claims\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important\u0022\u003EImportant\u003C/h3\u003E\n\u003Cp\u003EAccess token is NOT the source of truth.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 It reflects session state, not replaces it\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-tokens\u0022\u003E\uD83D\uDD04 Refresh Tokens\u003C/h2\u003E\n\u003Cp\u003ERefresh tokens represent:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 A \u003Cstrong\u003Econtinuation mechanism\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022characteristics-1\u0022\u003ECharacteristics\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ELong-lived\u003C/li\u003E\n\u003Cli\u003EStored as hashed values\u003C/li\u003E\n\u003Cli\u003EBound to session and optionally chain\u003C/li\u003E\n\u003Cli\u003ERotated on use\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifecycle\u0022\u003ELifecycle\u003C/h3\u003E\n\u003Cp\u003EIssued \u2192 Used \u2192 Replaced \u2192 Revoked\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Old tokens are invalidated on rotation\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-model\u0022\u003E\uD83D\uDD10 Security Model\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation\u0022\u003E\uD83D\uDD01 Rotation\u003C/h3\u003E\n\u003Cp\u003EEach refresh:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalidates previous token\u003C/li\u003E\n\u003Cli\u003EIssues a new token\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Prevents replay attacks\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reuse-detection\u0022\u003E\uD83D\uDEA8 Reuse Detection\u003C/h3\u003E\n\u003Cp\u003EIf a token is reused:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EChain may be revoked\u003C/li\u003E\n\u003Cli\u003ESession may be revoked\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Strong forward security\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-binding\u0022\u003E\uD83D\uDD17 Session Binding\u003C/h3\u003E\n\u003Cp\u003ERefresh tokens include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESessionId\u003C/li\u003E\n\u003Cli\u003EChainId (optional)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Prevents cross-context usage\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hashed-storage\u0022\u003E\uD83D\uDD12 Hashed Storage\u003C/h3\u003E\n\u003Cp\u003ETokens are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ENever stored as plaintext\u003C/li\u003E\n\u003Cli\u003EHashed using secure algorithms\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Reduces breach impact\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-issuance\u0022\u003E\uD83D\uDD04 Token Issuance\u003C/h2\u003E\n\u003Cp\u003ETokens are issued during:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogin\u003C/li\u003E\n\u003Cli\u003ERefresh\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022access-token\u0022\u003EAccess Token\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EMay be opaque or JWT\u003C/li\u003E\n\u003Cli\u003EIncludes identity and optional session reference\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-token\u0022\u003ERefresh Token\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EAlways opaque\u003C/li\u003E\n\u003Cli\u003EPersisted in secure store\u003C/li\u003E\n\u003Cli\u003EUsed only for rotation\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Tokens are not identity\u003Cbr\u003E\n\uD83D\uDC49 They are projections of a session\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession is the source of truth\u003C/li\u003E\n\u003Cli\u003ETokens are optional and mode-dependent\u003C/li\u003E\n\u003Cli\u003EOpaque tokens require server validation\u003C/li\u003E\n\u003Cli\u003EJWT tokens allow stateless access\u003C/li\u003E\n\u003Cli\u003ERefresh tokens enable secure continuation\u003C/li\u003E\n\u003Cli\u003EToken rotation ensures forward security\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to Device Management\u003C/p\u003E\n", "Headings": [ { "Id": "rethinking-tokens", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json index 861594bd..64fbcd18 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json @@ -1,7 +1,7 @@ { "Slug": "client/authentication", "Title": "Authentication", - "Html": "\n\u003Cp\u003EThis section explains how to use the UltimateAuth client for authentication flows.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EAuthentication in UltimateAuth is \u003Cstrong\u003Eflow-based\u003C/strong\u003E, not endpoint-based.\u003C/p\u003E\n\u003Cp\u003EYou interact with:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003EFlowClient\u003C/code\u003E\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022login\u0022\u003E\uD83D\uDD11 Login\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic-login\u0022\u003EBasic Login\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.LoginAsync(new LoginRequest\n{\n Identifier = \u0026quot;user@ultimateauth.com\u0026quot;,\n Secret = \u0026quot;password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 This triggers a full login flow:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESends credentials\u003C/li\u003E\n\u003Cli\u003EHandles redirect\u003C/li\u003E\n\u003Cli\u003EEstablishes session\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022try-login-pre-check\u0022\u003E\u26A1 Try Login (Pre-check)\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.TryLoginAsync(\n new LoginRequest\n {\n Identifier = \u0026quot;user@mail.com\u0026quot;,\n Secret = \u0026quot;password\u0026quot;\n },\n UAuthSubmitMode.TryOnly\n);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022modes\u0022\u003EModes\u003C/h3\u003E\n\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\u003ETryOnly\u003C/td\u003E\n\u003Ctd\u003EValidate only\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDirectCommit\u003C/td\u003E\n\u003Ctd\u003ESkip validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ETryAndCommit\u003C/td\u003E\n\u003Ctd\u003EValidate then login if success\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cp\u003E\uD83D\uDC49 Use \u003Ccode\u003EDirectCommit\u003C/code\u003E when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need maximum performance while sacrificing interactive SPA capabilities.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Use \u003Ccode\u003ETryOnly\u003C/code\u003E when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need validation feedback\u003C/li\u003E\n\u003Cli\u003EYou want custom UI flows\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Use \u003Ccode\u003ETryAndCommit\u003C/code\u003E when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need completely interactive SPA experience.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003ETryAndCommit\u003C/code\u003E is the recommended mode for most applications.\u003C/p\u003E\n\u003Cp\u003EIt provides:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EValidation feedback\u003C/li\u003E\n\u003Cli\u003EAutomatic redirect on success\u003C/li\u003E\n\u003Cli\u003ESmooth SPA experience\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh\u0022\u003E\uD83D\uDD04 Refresh\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Flows.RefreshAsync();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022possible-outcomes\u0022\u003EPossible Outcomes\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESuccess \u2192 new tokens/session updated\u003C/li\u003E\n\u003Cli\u003ETouched \u2192 session extended\u003C/li\u003E\n\u003Cli\u003ERotated \u2192 refresh token rotated\u003C/li\u003E\n\u003Cli\u003ENoOp \u2192 nothing changed\u003C/li\u003E\n\u003Cli\u003EReauthRequired \u2192 login required\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Refresh behavior depends on auth mode:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPureOpaque \u2192 session touch\u003C/li\u003E\n\u003Cli\u003EHybrid/JWT \u2192 token rotation\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIn default, successful refresh returns success outcome. If you want to learn success detail such as no-op, touched or rotated, open it via server options:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(o =\u0026gt;\n{\n o.Diagnostics.EnableRefreshDetails = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003E\uD83D\uDEAA Logout\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.LogoutAsync();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 This:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnds current session\u003C/li\u003E\n\u003Cli\u003EClears authentication state\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-logout-variants\u0022\u003E\uD83D\uDCF1 Device Logout Variants\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.LogoutMyDeviceAsync(sessionId);\nawait UAuthClient.Flows.LogoutMyOtherDevicesAsync();\nawait UAuthClient.Flows.LogoutAllMyDevicesAsync();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 These operate on \u003Cstrong\u003Esession chains (devices)\u003C/strong\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pkce-flow-public-clients\u0022\u003E\uD83D\uDD10 PKCE Flow (Public Clients)\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022start-pkce\u0022\u003EStart PKCE\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.BeginPkceAsync();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-pkce\u0022\u003EComplete PKCE\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.CompletePkceLoginAsync(request);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EComplete PKCE also has try semantics the same as login flow. UltimateAuth suggests to use \u003Ccode\u003ETryCompletePkceLoginAsync\u003C/code\u003E for complete interactive experience.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003E\uD83D\uDC49 Required for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESPA\u003C/li\u003E\n\u003Cli\u003EBlazor WASM\u003C/li\u003E\n\u003Cli\u003EMobile apps\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-note\u0022\u003E\uD83D\uDEA8 Security Note\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EPublic clients MUST use PKCE\u003C/li\u003E\n\u003Cli\u003EServer clients MAY allow direct login\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EDirect credential posting disabled by default and throws exception when you directly call login. You can enable it via options. You should only use it for debugging and development purposes.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Login.AllowCredentialPost = true;\n});\n---\n\n## \uD83C\uDFAF Summary\n\nAuthentication in UltimateAuth:\n\n- is flow-driven\n- adapts to client type\n- enforces security by design\n\n---\n\n\uD83D\uDC49 Always use \u0060FlowClient\u0060 for authentication operations\n\u003C/code\u003E\u003C/pre\u003E\n", + "Html": "\n\u003Cp\u003EThis section explains how to use the UltimateAuth client for authentication flows.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EAuthentication in UltimateAuth is \u003Cstrong\u003Eflow-based\u003C/strong\u003E, not endpoint-based.\u003C/p\u003E\n\u003Cp\u003EYou interact with:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003EFlowClient\u003C/code\u003E\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022login\u0022\u003E\uD83D\uDD11 Login\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic-login\u0022\u003EBasic Login\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.LoginAsync(new LoginRequest\n{\n Identifier = \u0026quot;user@ultimateauth.com\u0026quot;,\n Secret = \u0026quot;password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This triggers a full login flow:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESends credentials\u003C/li\u003E\n\u003Cli\u003EHandles redirect\u003C/li\u003E\n\u003Cli\u003EEstablishes session\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022try-login-pre-check\u0022\u003E\u26A1 Try Login (Pre-check)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\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 = \u0026quot;user@mail.com\u0026quot;,\n Secret = \u0026quot;password\u0026quot;\n },\n UAuthSubmitMode.TryOnly\n);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022modes\u0022\u003EModes\u003C/h3\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\u003ETryOnly\u003C/td\u003E\n\u003Ctd\u003EValidate only\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDirectCommit\u003C/td\u003E\n\u003Ctd\u003ESkip validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ETryAndCommit\u003C/td\u003E\n\u003Ctd\u003EValidate then login if success\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Use \u003Ccode\u003EDirectCommit\u003C/code\u003E when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need maximum performance while sacrificing interactive SPA capabilities.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Use \u003Ccode\u003ETryOnly\u003C/code\u003E when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need validation feedback\u003C/li\u003E\n\u003Cli\u003EYou want custom UI flows\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Use \u003Ccode\u003ETryAndCommit\u003C/code\u003E when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need completely interactive SPA experience.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003ETryAndCommit\u003C/code\u003E is the recommended mode for most applications.\u003C/p\u003E\n\u003Cp\u003EIt provides:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EValidation feedback\u003C/li\u003E\n\u003Cli\u003EAutomatic redirect on success\u003C/li\u003E\n\u003Cli\u003ESmooth SPA experience\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh\u0022\u003E\uD83D\uDD04 Refresh\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\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\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022possible-outcomes\u0022\u003EPossible Outcomes\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESuccess \u2192 new tokens/session updated\u003C/li\u003E\n\u003Cli\u003ETouched \u2192 session extended\u003C/li\u003E\n\u003Cli\u003ERotated \u2192 refresh token rotated\u003C/li\u003E\n\u003Cli\u003ENoOp \u2192 nothing changed\u003C/li\u003E\n\u003Cli\u003EReauthRequired \u2192 login required\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Refresh behavior depends on auth mode:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPureOpaque \u2192 session touch\u003C/li\u003E\n\u003Cli\u003EHybrid/JWT \u2192 token rotation\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIn default, successful refresh returns success outcome. If you want to learn success detail such as no-op, touched or rotated, open it via server options:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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(o =\u0026gt;\n{\n o.Diagnostics.EnableRefreshDetails = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003E\uD83D\uDEAA Logout\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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\u003E\uD83D\uDC49 This:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnds current session\u003C/li\u003E\n\u003Cli\u003EClears authentication state\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-logout-variants\u0022\u003E\uD83D\uDCF1 Device Logout Variants\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.LogoutMyDeviceAsync(sessionId);\nawait UAuthClient.Flows.LogoutMyOtherDevicesAsync();\nawait UAuthClient.Flows.LogoutAllMyDevicesAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 These operate on \u003Cstrong\u003Esession chains (devices)\u003C/strong\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pkce-flow-public-clients\u0022\u003E\uD83D\uDD10 PKCE Flow (Public Clients)\u003C/h2\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\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-pkce\u0022\u003EComplete 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.CompletePkceLoginAsync(request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EComplete PKCE also has try semantics the same as login flow. UltimateAuth suggests to use \u003Ccode\u003ETryCompletePkceLoginAsync\u003C/code\u003E for complete interactive experience.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003E\uD83D\uDC49 Required for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESPA\u003C/li\u003E\n\u003Cli\u003EBlazor WASM\u003C/li\u003E\n\u003Cli\u003EMobile apps\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-note\u0022\u003E\uD83D\uDEA8 Security Note\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EPublic clients MUST use PKCE\u003C/li\u003E\n\u003Cli\u003EServer clients MAY allow direct login\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EDirect credential posting disabled by default and throws exception when you directly call login. You can enable it via options. You should only use it for debugging and development purposes.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Login.AllowCredentialPost = true;\n});\n---\n\n## \uD83C\uDFAF Summary\n\nAuthentication in UltimateAuth:\n\n- is flow-driven\n- adapts to client type\n- enforces security by design\n\n---\n\n\uD83D\uDC49 Always use \u0060FlowClient\u0060 for authentication operations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n", "Headings": [ { "Id": "overview", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json index 9d9d909b..13314c7a 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json @@ -1,7 +1,7 @@ { "Slug": "client/authorization", "Title": "Authorization", - "Html": "\n\u003Cp\u003EThis section explains how to manage roles, permissions, and access control using the UltimateAuth client.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EAuthorization in UltimateAuth is policy-driven and role-based.\u003C/p\u003E\n\u003Cp\u003EOn the client, you interact with:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EUAuthClient.Authorization\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-concepts\u0022\u003E\uD83D\uDD11 Core Concepts\u003C/h2\u003E\n\u003Cp\u003ERoles\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ENamed groups of permissions\u003C/li\u003E\n\u003Cli\u003EAssigned to users\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EPermissions\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EFine-grained access definitions\u003C/li\u003E\n\u003Cli\u003EExample: users.create.anonymous, users.delete.self, authorization.roles.admin\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EPolicies\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERuntime decision rules\u003C/li\u003E\n\u003Cli\u003EEnforced automatically on server\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022query-roles\u0022\u003E\uD83D\uDCCB Query Roles\u003C/h3\u003E\n\u003Cp\u003Evar result = await UAuthClient.Authorization.QueryRolesAsync(new RoleQuery\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-role\u0022\u003E\u2795 Create Role\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.CreateRoleAsync(new CreateRoleRequest\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rename-role\u0022\u003E\u270F\uFE0F Rename Role\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.RenameRoleAsync(new RenameRoleRequest\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022set-permissions\u0022\u003E\uD83E\uDDE9 Set Permissions\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.SetRolePermissionsAsync(new SetRolePermissionsRequest\n{\nRoleId = roleId,\nPermissions = new[]\n{\nPermission.From(\u0026quot;users.read\u0026quot;),\nPermission.From(\u0026quot;users.update\u0026quot;)\n}\n});\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Permissions support:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EExact match \u2192 users.create\u003C/li\u003E\n\u003Cli\u003EPrefix \u2192 users.*\u003C/li\u003E\n\u003Cli\u003EWildcard \u2192 *\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-role\u0022\u003E\u274C Delete Role\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.DeleteRoleAsync(new DeleteRoleRequest\n);\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Automatically removes role assignments from users\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-role-to-user\u0022\u003E\uD83D\uDC64 Assign Role to User\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.AssignRoleToUserAsync(new AssignRoleRequest\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-role\u0022\u003E\u2796 Remove Role\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.RemoveRoleFromUserAsync(new RemoveRoleRequest\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-user-roles\u0022\u003E\uD83D\uDCCB Get User Roles\u003C/h3\u003E\n\u003Cp\u003Evar roles = await UAuthClient.Authorization.GetUserRolesAsync(userKey);\n\uD83D\uDD0D Check Authorization\nvar result = await UAuthClient.Authorization.CheckAsync(new AuthorizationCheckRequest\n);\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Returns:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAllow / Deny\u003C/li\u003E\n\u003Cli\u003EReason (if denied)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-model\u0022\u003E\uD83E\uDDE0 Permission Model\u003C/h2\u003E\n\u003Cp\u003EPermissions are normalized and optimized:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EFull access \u2192 *\u003C/li\u003E\n\u003Cli\u003EGroup access \u2192 users.*\u003C/li\u003E\n\u003Cli\u003ESpecific \u2192 users.create\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Internally compiled for fast evaluation\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-notes\u0022\u003E\uD83D\uDD10 Security Notes\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthorization is enforced server-side\u003C/li\u003E\n\u003Cli\u003EClient only requests actions\u003C/li\u003E\n\u003Cli\u003EPolicies may override permissions\u003C/li\u003E\n\u003Cli\u003ECross-tenant access is denied by default\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003EAuthorization in UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ecombines roles \u002B permissions \u002B policies\u003C/li\u003E\n\u003Cli\u003Eis evaluated through a decision pipeline\u003C/li\u003E\n\u003Cli\u003Esupports fine-grained and scalable access control\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EThis section explains how to manage roles, permissions, and access control using the UltimateAuth client.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EAuthorization in UltimateAuth is policy-driven and role-based.\u003C/p\u003E\n\u003Cp\u003EOn the client, you interact with:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode\u003EUAuthClient.Authorization\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-concepts\u0022\u003E\uD83D\uDD11 Core Concepts\u003C/h2\u003E\n\u003Cp\u003ERoles\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ENamed groups of permissions\u003C/li\u003E\n\u003Cli\u003EAssigned to users\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EPermissions\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EFine-grained access definitions\u003C/li\u003E\n\u003Cli\u003EExample: users.create.anonymous, users.delete.self, authorization.roles.admin\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EPolicies\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERuntime decision rules\u003C/li\u003E\n\u003Cli\u003EEnforced automatically on server\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022query-roles\u0022\u003E\uD83D\uDCCB Query Roles\u003C/h3\u003E\n\u003Cp\u003Evar result = await UAuthClient.Authorization.QueryRolesAsync(new RoleQuery\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-role\u0022\u003E\u2795 Create Role\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.CreateRoleAsync(new CreateRoleRequest\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rename-role\u0022\u003E\u270F\uFE0F Rename Role\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.RenameRoleAsync(new RenameRoleRequest\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022set-permissions\u0022\u003E\uD83E\uDDE9 Set Permissions\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.SetRolePermissionsAsync(new SetRolePermissionsRequest\n{\nRoleId = roleId,\nPermissions = new[]\n{\nPermission.From(\u0026quot;users.read\u0026quot;),\nPermission.From(\u0026quot;users.update\u0026quot;)\n}\n});\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Permissions support:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EExact match \u2192 users.create\u003C/li\u003E\n\u003Cli\u003EPrefix \u2192 users.*\u003C/li\u003E\n\u003Cli\u003EWildcard \u2192 *\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-role\u0022\u003E\u274C Delete Role\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.DeleteRoleAsync(new DeleteRoleRequest\n);\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Automatically removes role assignments from users\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-role-to-user\u0022\u003E\uD83D\uDC64 Assign Role to User\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.AssignRoleToUserAsync(new AssignRoleRequest\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-role\u0022\u003E\u2796 Remove Role\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.RemoveRoleFromUserAsync(new RemoveRoleRequest\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-user-roles\u0022\u003E\uD83D\uDCCB Get User Roles\u003C/h3\u003E\n\u003Cp\u003Evar roles = await UAuthClient.Authorization.GetUserRolesAsync(userKey);\n\uD83D\uDD0D Check Authorization\nvar result = await UAuthClient.Authorization.CheckAsync(new AuthorizationCheckRequest\n);\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Returns:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAllow / Deny\u003C/li\u003E\n\u003Cli\u003EReason (if denied)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-model\u0022\u003E\uD83E\uDDE0 Permission Model\u003C/h2\u003E\n\u003Cp\u003EPermissions are normalized and optimized:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EFull access \u2192 *\u003C/li\u003E\n\u003Cli\u003EGroup access \u2192 users.*\u003C/li\u003E\n\u003Cli\u003ESpecific \u2192 users.create\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Internally compiled for fast evaluation\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-notes\u0022\u003E\uD83D\uDD10 Security Notes\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthorization is enforced server-side\u003C/li\u003E\n\u003Cli\u003EClient only requests actions\u003C/li\u003E\n\u003Cli\u003EPolicies may override permissions\u003C/li\u003E\n\u003Cli\u003ECross-tenant access is denied by default\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003EAuthorization in UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ecombines roles \u002B permissions \u002B policies\u003C/li\u003E\n\u003Cli\u003Eis evaluated through a decision pipeline\u003C/li\u003E\n\u003Cli\u003Esupports fine-grained and scalable access control\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "overview", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json index 429aa532..8dd6aac0 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json @@ -1,7 +1,7 @@ { "Slug": "client/credentials", "Title": "Credentials", - "Html": "\n\u003Cp\u003EThis section explains how to manage user credentials (such as passwords) using the UltimateAuth client.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003ECredential operations are handled via:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthClient.Credentials...\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Epassword management\u003C/li\u003E\n\u003Cli\u003Ecredential reset flows\u003C/li\u003E\n\u003Cli\u003Eadmin credential operations\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-password-self\u0022\u003E\uD83D\uDD10 Change Password (Self)\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Credentials.ChangeMyAsync(new ChangeCredentialRequest\n{\n CurrentSecret = \u0026quot;old-password\u0026quot;,\n NewSecret = \u0026quot;new-password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Requires current password\u003Cbr /\u003E\n\uD83D\uDC49 Triggers \u003Ccode\u003ECredentialsChangedSelf\u003C/code\u003E event\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022reset-password-self\u0022\u003E\uD83D\uDD01 Reset Password (Self)\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022begin-reset\u0022\u003EBegin Reset\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar begin = await UAuthClient.Credentials.BeginResetMyAsync(\n new BeginResetCredentialRequest\n {\n Identifier = \u0026quot;user@mail.com\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-reset\u0022\u003EComplete Reset\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Credentials.CompleteResetMyAsync(\n new CompleteResetCredentialRequest\n {\n Token = \u0026quot;reset-token\u0026quot;,\n NewSecret = \u0026quot;new-password\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Typically used in:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eforgot password flows\u003C/li\u003E\n\u003Cli\u003Eemail-based reset flows\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-credential-self\u0022\u003E\u2795 Add Credential (Self)\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Credentials.AddMyAsync(new AddCredentialRequest\n{\n Secret = \u0026quot;password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-credential-self\u0022\u003E\u274C Revoke Credential (Self)\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Credentials.RevokeMyAsync(new RevokeCredentialRequest\n{\n CredentialId = credentialId\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Useful for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eremoving login methods\u003C/li\u003E\n\u003Cli\u003Einvalidating compromised credentials\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-change-user-credential\u0022\u003E\uD83D\uDC51 Admin: Change User Credential\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Credentials.ChangeUserAsync(userKey, new ChangeCredentialRequest\n{\n NewSecret = \u0026quot;new-password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Does NOT require current password\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-add-credential\u0022\u003E\uD83D\uDC51 Admin: Add Credential\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Credentials.AddUserAsync(userKey, new AddCredentialRequest\n{\n Secret = \u0026quot;password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-revoke-credential\u0022\u003E\uD83D\uDC51 Admin: Revoke Credential\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Credentials.RevokeUserAsync(userKey, new RevokeCredentialRequest\n{\n CredentialId = credentialId\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-reset-credential\u0022\u003E\uD83D\uDC51 Admin: Reset Credential\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022begin\u0022\u003EBegin\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Credentials.BeginResetUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete\u0022\u003EComplete\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Credentials.CompleteResetUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-credential-admin\u0022\u003E\u274C Delete Credential (Admin)\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Credentials.DeleteUserAsync(userKey, new DeleteCredentialRequest\n{\n CredentialId = credentialId\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-model\u0022\u003E\uD83E\uDDE0 Credential Model\u003C/h2\u003E\n\u003Cp\u003ECredentials are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Euser-bound\u003C/li\u003E\n\u003Cli\u003Esecurity-version aware\u003C/li\u003E\n\u003Cli\u003Elifecycle-managed\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Changing credentials may:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Einvalidate sessions\u003C/li\u003E\n\u003Cli\u003Etrigger security updates\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-notes\u0022\u003E\uD83D\uDD10 Security Notes\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EPasswords are never sent back to client\u003C/li\u003E\n\u003Cli\u003EAll secrets are hashed server-side\u003C/li\u003E\n\u003Cli\u003EReset flows should be protected (email, OTP, etc.)\u003C/li\u003E\n\u003Cli\u003EAdmin operations are policy-protected\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003ECredential management in UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esupports self-service and admin flows\u003C/li\u003E\n\u003Cli\u003Eintegrates with security lifecycle\u003C/li\u003E\n\u003Cli\u003Eenables safe credential rotation\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EThis section explains how to manage user credentials (such as passwords) using the UltimateAuth client.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003ECredential operations are handled via:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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\u003EThis includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Epassword management\u003C/li\u003E\n\u003Cli\u003Ecredential reset flows\u003C/li\u003E\n\u003Cli\u003Eadmin credential operations\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-password-self\u0022\u003E\uD83D\uDD10 Change Password (Self)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Credentials.ChangeMyAsync(new ChangeCredentialRequest\n{\n CurrentSecret = \u0026quot;old-password\u0026quot;,\n NewSecret = \u0026quot;new-password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Requires current password\u003Cbr\u003E\n\uD83D\uDC49 Triggers \u003Ccode\u003ECredentialsChangedSelf\u003C/code\u003E event\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022reset-password-self\u0022\u003E\uD83D\uDD01 Reset Password (Self)\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022begin-reset\u0022\u003EBegin Reset\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 begin = await UAuthClient.Credentials.BeginResetMyAsync(\n new BeginResetCredentialRequest\n {\n Identifier = \u0026quot;user@mail.com\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-reset\u0022\u003EComplete Reset\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.Credentials.CompleteResetMyAsync(\n new CompleteResetCredentialRequest\n {\n Token = \u0026quot;reset-token\u0026quot;,\n NewSecret = \u0026quot;new-password\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Typically used in:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eforgot password flows\u003C/li\u003E\n\u003Cli\u003Eemail-based reset flows\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-credential-self\u0022\u003E\u2795 Add Credential (Self)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Credentials.AddMyAsync(new AddCredentialRequest\n{\n Secret = \u0026quot;password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-credential-self\u0022\u003E\u274C Revoke Credential (Self)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Credentials.RevokeMyAsync(new RevokeCredentialRequest\n{\n CredentialId = credentialId\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Useful for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eremoving login methods\u003C/li\u003E\n\u003Cli\u003Einvalidating compromised credentials\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-change-user-credential\u0022\u003E\uD83D\uDC51 Admin: Change User Credential\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Credentials.ChangeUserAsync(userKey, new ChangeCredentialRequest\n{\n NewSecret = \u0026quot;new-password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Does NOT require current password\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-add-credential\u0022\u003E\uD83D\uDC51 Admin: Add Credential\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Credentials.AddUserAsync(userKey, new AddCredentialRequest\n{\n Secret = \u0026quot;password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-revoke-credential\u0022\u003E\uD83D\uDC51 Admin: Revoke Credential\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Credentials.RevokeUserAsync(userKey, new RevokeCredentialRequest\n{\n CredentialId = credentialId\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-reset-credential\u0022\u003E\uD83D\uDC51 Admin: Reset Credential\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022begin\u0022\u003EBegin\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.Credentials.BeginResetUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete\u0022\u003EComplete\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.Credentials.CompleteResetUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-credential-admin\u0022\u003E\u274C Delete Credential (Admin)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Credentials.DeleteUserAsync(userKey, new DeleteCredentialRequest\n{\n CredentialId = credentialId\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-model\u0022\u003E\uD83E\uDDE0 Credential Model\u003C/h2\u003E\n\u003Cp\u003ECredentials are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Euser-bound\u003C/li\u003E\n\u003Cli\u003Esecurity-version aware\u003C/li\u003E\n\u003Cli\u003Elifecycle-managed\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Changing credentials may:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Einvalidate sessions\u003C/li\u003E\n\u003Cli\u003Etrigger security updates\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-notes\u0022\u003E\uD83D\uDD10 Security Notes\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EPasswords are never sent back to client\u003C/li\u003E\n\u003Cli\u003EAll secrets are hashed server-side\u003C/li\u003E\n\u003Cli\u003EReset flows should be protected (email, OTP, etc.)\u003C/li\u003E\n\u003Cli\u003EAdmin operations are policy-protected\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003ECredential management in UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esupports self-service and admin flows\u003C/li\u003E\n\u003Cli\u003Eintegrates with security lifecycle\u003C/li\u003E\n\u003Cli\u003Eenables safe credential rotation\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "overview", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json index 73b092d4..00de2972 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json @@ -1,7 +1,7 @@ { "Slug": "client/identifiers", "Title": "Identifiers", - "Html": "\n\u003Cp\u003EThis section explains how UltimateAuth manages user identifiers such as email, username, and phone.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth, identifiers are \u003Cstrong\u003Efirst-class entities\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThey are NOT just fields on the user.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 On the client, you interact with:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthClient.Identifiers...\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-an-identifier\u0022\u003E\uD83D\uDD11 What is an Identifier?\u003C/h2\u003E\n\u003Cp\u003EAn identifier represents a way to identify a user:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEmail\u003C/li\u003E\n\u003Cli\u003EUsername\u003C/li\u003E\n\u003Cli\u003EPhone number\u003C/li\u003E\n\u003Cli\u003ECustom identifiers\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Each identifier has:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EValue (e.g. user@ultimateauth.com)\u003C/li\u003E\n\u003Cli\u003EType (email, username, etc.)\u003C/li\u003E\n\u003Cli\u003EVerification state\u003C/li\u003E\n\u003Cli\u003EPrimary flag\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022primary-identifier\u0022\u003E\u2B50 Primary Identifier\u003C/h2\u003E\n\u003Cp\u003EA user can have multiple identifiers, but only one can be \u003Cstrong\u003Eprimary\u003C/strong\u003E. Setting an identifier to primary automatically unset the current primary identifier if exists.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Identifiers.SetMyPrimaryAsync(new SetPrimaryUserIdentifierRequest\n{\n Id = identifierId\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Primary identifier is typically:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUsed for display\u003C/li\u003E\n\u003Cli\u003EPreferred for communication\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022login-identifiers\u0022\u003E\uD83D\uDD10 Login Identifiers\u003C/h2\u003E\n\u003Cp\u003ENot all identifiers are used for login.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Cstrong\u003ELogin identifiers are a subset of identifiers\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This is configurable:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnable/disable per type\u003C/li\u003E\n\u003Cli\u003ECustom logic can be applied\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-my-identifiers\u0022\u003E\uD83D\uDCCB Get My Identifiers\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Identifiers.GetMyAsync();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-identifier\u0022\u003E\u2795 Add Identifier\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Identifiers.AddMyAsync(new AddUserIdentifierRequest\n{\n Identifier = \u0026quot;new@ultimateauth.com\u0026quot;,\n Type = UserIdentifierType.Email,\n IsPrimary = true\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-identifier\u0022\u003E\u270F\uFE0F Update Identifier\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUpdateUserIdentifierRequest updateRequest = new()\n{\n Id = item.Id,\n NewValue = item.Value\n};\n\nawait UAuthClient.Identifiers.UpdateMyAsync(updateRequest);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022verify-identifier\u0022\u003E\u2705 Verify Identifier\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Identifiers.VerifyMyAsync(new VerifyUserIdentifierRequest\n{\n Id = identifierId\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-identifier\u0022\u003E\u274C Delete Identifier\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Identifiers.DeleteMyAsync(new DeleteUserIdentifierRequest\n{\n Id = identifierId\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-operations\u0022\u003E\uD83D\uDC64 Admin Operations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-user-identifiers\u0022\u003EGet User Identifiers\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Identifiers.GetUserAsync(userKey);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-identifier-to-user\u0022\u003EAdd Identifier to User\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Identifiers.AddUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-identifier-1\u0022\u003EUpdate Identifier\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Identifiers.UpdateUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-identifier-1\u0022\u003EDelete Identifier\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Identifiers.DeleteUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022state-events\u0022\u003E\uD83D\uDD04 State Events\u003C/h2\u003E\n\u003Cp\u003EIdentifier changes trigger events:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EIdentifiersChanged\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Useful for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUI updates\u003C/li\u003E\n\u003Cli\u003ECache invalidation\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003E\uD83D\uDD10 Security Considerations\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EIdentifiers may require verification\u003C/li\u003E\n\u003Cli\u003ELogin identifiers can be restricted\u003C/li\u003E\n\u003Cli\u003EPrimary identifier can be controlled\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth identifiers:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eare independent entities\u003C/li\u003E\n\u003Cli\u003Esupport multiple types\u003C/li\u003E\n\u003Cli\u003Eseparate login vs non-login identifiers\u003C/li\u003E\n\u003Cli\u003Eare fully manageable via client\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EThis section explains how UltimateAuth manages user identifiers such as email, username, and phone.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth, identifiers are \u003Cstrong\u003Efirst-class entities\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThey are NOT just fields on the user.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 On the client, you interact 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\u003EUAuthClient.Identifiers...\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-an-identifier\u0022\u003E\uD83D\uDD11 What is an Identifier?\u003C/h2\u003E\n\u003Cp\u003EAn identifier represents a way to identify a user:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEmail\u003C/li\u003E\n\u003Cli\u003EUsername\u003C/li\u003E\n\u003Cli\u003EPhone number\u003C/li\u003E\n\u003Cli\u003ECustom identifiers\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Each identifier has:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EValue (e.g. user@ultimateauth.com)\u003C/li\u003E\n\u003Cli\u003EType (email, username, etc.)\u003C/li\u003E\n\u003Cli\u003EVerification state\u003C/li\u003E\n\u003Cli\u003EPrimary flag\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022primary-identifier\u0022\u003E\u2B50 Primary Identifier\u003C/h2\u003E\n\u003Cp\u003EA user can have multiple identifiers, but only one can be \u003Cstrong\u003Eprimary\u003C/strong\u003E. Setting an identifier to primary automatically unset the current primary identifier if exists.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Identifiers.SetMyPrimaryAsync(new SetPrimaryUserIdentifierRequest\n{\n Id = identifierId\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Primary identifier is typically:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUsed for display\u003C/li\u003E\n\u003Cli\u003EPreferred for communication\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022login-identifiers\u0022\u003E\uD83D\uDD10 Login Identifiers\u003C/h2\u003E\n\u003Cp\u003ENot all identifiers are used for login.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Cstrong\u003ELogin identifiers are a subset of identifiers\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This is configurable:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnable/disable per type\u003C/li\u003E\n\u003Cli\u003ECustom logic can be applied\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-my-identifiers\u0022\u003E\uD83D\uDCCB Get My Identifiers\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\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\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-identifier\u0022\u003E\u2795 Add Identifier\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Identifiers.AddMyAsync(new AddUserIdentifierRequest\n{\n Identifier = \u0026quot;new@ultimateauth.com\u0026quot;,\n Type = UserIdentifierType.Email,\n IsPrimary = true\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-identifier\u0022\u003E\u270F\uFE0F Update Identifier\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUpdateUserIdentifierRequest updateRequest = new()\n{\n Id = item.Id,\n NewValue = item.Value\n};\n\nawait UAuthClient.Identifiers.UpdateMyAsync(updateRequest);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022verify-identifier\u0022\u003E\u2705 Verify Identifier\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Identifiers.VerifyMyAsync(new VerifyUserIdentifierRequest\n{\n Id = identifierId\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-identifier\u0022\u003E\u274C Delete Identifier\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Identifiers.DeleteMyAsync(new DeleteUserIdentifierRequest\n{\n Id = identifierId\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-operations\u0022\u003E\uD83D\uDC64 Admin Operations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-user-identifiers\u0022\u003EGet User 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\u003Eawait UAuthClient.Identifiers.GetUserAsync(userKey);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-identifier-to-user\u0022\u003EAdd Identifier to 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\u003Eawait UAuthClient.Identifiers.AddUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-identifier-1\u0022\u003EUpdate 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\u003Eawait UAuthClient.Identifiers.UpdateUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-identifier-1\u0022\u003EDelete 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\u003Eawait UAuthClient.Identifiers.DeleteUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022state-events\u0022\u003E\uD83D\uDD04 State Events\u003C/h2\u003E\n\u003Cp\u003EIdentifier changes trigger events:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EIdentifiersChanged\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Useful for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUI updates\u003C/li\u003E\n\u003Cli\u003ECache invalidation\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003E\uD83D\uDD10 Security Considerations\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EIdentifiers may require verification\u003C/li\u003E\n\u003Cli\u003ELogin identifiers can be restricted\u003C/li\u003E\n\u003Cli\u003EPrimary identifier can be controlled\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth identifiers:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eare independent entities\u003C/li\u003E\n\u003Cli\u003Esupport multiple types\u003C/li\u003E\n\u003Cli\u003Eseparate login vs non-login identifiers\u003C/li\u003E\n\u003Cli\u003Eare fully manageable via client\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "overview", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/index.json index bcd0a44e..876b19e7 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/index.json @@ -1,7 +1,7 @@ { "Slug": "client/index", "Title": "Client \u0026 API", - "Html": "\n\u003Cp\u003EUltimateAuth Client is a \u003Cstrong\u003Ehigh-level SDK\u003C/strong\u003E designed to simplify authentication flows.\u003C/p\u003E\n\u003Cp\u003EIt is NOT just an HTTP wrapper.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-makes-the-client-different\u0022\u003E\uD83E\uDDE0 What Makes the Client Different?\u003C/h2\u003E\n\u003Cp\u003EThe client:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EHandles full authentication flows (login, PKCE, refresh)\u003C/li\u003E\n\u003Cli\u003EManages redirects automatically\u003C/li\u003E\n\u003Cli\u003EPublishes state events\u003C/li\u003E\n\u003Cli\u003EProvides structured results\u003C/li\u003E\n\u003Cli\u003EWorks across multiple client types (SPA, server, hybrid)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You SHOULD use the client instead of calling endpoints manually.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-architecture\u0022\u003E\uD83E\uDDF1 Client Architecture\u003C/h2\u003E\n\u003Cp\u003EThe client is split into multiple specialized services:\u003C/p\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EService\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\u003EFlowClient\u003C/td\u003E\n\u003Ctd\u003ELogin, logout, refresh, PKCE\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESessionClient\u003C/td\u003E\n\u003Ctd\u003ESession \u0026amp; device management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUserClient\u003C/td\u003E\n\u003Ctd\u003EUser profile \u0026amp; lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIdentifierClient\u003C/td\u003E\n\u003Ctd\u003EEmail / username / phone management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredentialClient\u003C/td\u003E\n\u003Ctd\u003EPassword management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthorizationClient\u003C/td\u003E\n\u003Ctd\u003ERoles \u0026amp; permissions\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-entry-point\u0022\u003E\uD83E\uDDE9 Client Entry Point\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth exposes a single entry point:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003EUAuthClient\u003C/code\u003E\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003E[Inject] IUAuthClient UAuthClient { get; set; } = null!;\n\nawait UAuthClient.Flows.LoginAsync(...);\nawait UAuthClient.Users.GetMeAsync();\nawait UAuthClient.Sessions.GetMyChainsAsync();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-concept-flow-based-design\u0022\u003E\uD83D\uDD11 Core Concept: Flow-Based Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is \u003Cstrong\u003Eflow-oriented\u003C/strong\u003E, not endpoint-oriented.\u003C/p\u003E\n\u003Cp\u003EInstead of calling endpoints:\u003C/p\u003E\n\u003Cp\u003E\u274C POST /auth/login\u003Cbr /\u003E\n\u2714 flowClient.LoginAsync()\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022example\u0022\u003E\u26A1 Example\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.LoginAsync(new LoginRequest\n{\n Identifier = \u0026quot;user@ultimateauth.com\u0026quot;,\n Secret = \u0026quot;password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 This automatically:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBuilds request payload\u003C/li\u003E\n\u003Cli\u003EHandles redirect\u003C/li\u003E\n\u003Cli\u003EIntegrates with configured endpoints\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022state-events\u0022\u003E\uD83D\uDD04 State Events\u003C/h2\u003E\n\u003Cp\u003EClient automatically publishes events:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESessionRevoked\u003C/li\u003E\n\u003Cli\u003EProfileChanged\u003C/li\u003E\n\u003Cli\u003EAuthorizationChanged\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-to-use-this-section\u0022\u003E\uD83E\uDDED How to Use This Section\u003C/h2\u003E\n\u003Cp\u003EFollow these guides:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eauthentication.md \u2192 login, refresh, logout\u003C/li\u003E\n\u003Cli\u003Esession-management.md \u2192 sessions \u0026amp; devices\u003C/li\u003E\n\u003Cli\u003Euser-management.md \u2192 user operations\u003C/li\u003E\n\u003Cli\u003Eidentifiers.md \u2192 login identifiers\u003C/li\u003E\n\u003Cli\u003Eauthorization.md \u2192 roles \u0026amp; permissions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth Client:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eabstracts complexity\u003C/li\u003E\n\u003Cli\u003Eenforces correct flows\u003C/li\u003E\n\u003Cli\u003Ereduces security mistakes\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Think of it as:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CAuthentication runtime for your frontend / app\u201D\u003C/strong\u003E\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth Client is a \u003Cstrong\u003Ehigh-level SDK\u003C/strong\u003E designed to simplify authentication flows.\u003C/p\u003E\n\u003Cp\u003EIt is NOT just an HTTP wrapper.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-makes-the-client-different\u0022\u003E\uD83E\uDDE0 What Makes the Client Different?\u003C/h2\u003E\n\u003Cp\u003EThe client:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EHandles full authentication flows (login, PKCE, refresh)\u003C/li\u003E\n\u003Cli\u003EManages redirects automatically\u003C/li\u003E\n\u003Cli\u003EPublishes state events\u003C/li\u003E\n\u003Cli\u003EProvides structured results\u003C/li\u003E\n\u003Cli\u003EWorks across multiple client types (SPA, server, hybrid)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You SHOULD use the client instead of calling endpoints manually.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-architecture\u0022\u003E\uD83E\uDDF1 Client Architecture\u003C/h2\u003E\n\u003Cp\u003EThe client is split into multiple specialized services:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EService\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\u003EFlowClient\u003C/td\u003E\n\u003Ctd\u003ELogin, logout, refresh, PKCE\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESessionClient\u003C/td\u003E\n\u003Ctd\u003ESession \u0026amp; device management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUserClient\u003C/td\u003E\n\u003Ctd\u003EUser profile \u0026amp; lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIdentifierClient\u003C/td\u003E\n\u003Ctd\u003EEmail / username / phone management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredentialClient\u003C/td\u003E\n\u003Ctd\u003EPassword management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthorizationClient\u003C/td\u003E\n\u003Ctd\u003ERoles \u0026amp; permissions\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-entry-point\u0022\u003E\uD83E\uDDE9 Client Entry Point\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth exposes a single entry point:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003EUAuthClient\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\u003E[Inject] IUAuthClient UAuthClient { get; set; } = null!;\n\nawait UAuthClient.Flows.LoginAsync(...);\nawait UAuthClient.Users.GetMeAsync();\nawait UAuthClient.Sessions.GetMyChainsAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-concept-flow-based-design\u0022\u003E\uD83D\uDD11 Core Concept: Flow-Based Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is \u003Cstrong\u003Eflow-oriented\u003C/strong\u003E, not endpoint-oriented.\u003C/p\u003E\n\u003Cp\u003EInstead of calling endpoints:\u003C/p\u003E\n\u003Cp\u003E\u274C POST /auth/login\u003Cbr\u003E\n\u2714 flowClient.LoginAsync()\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022example\u0022\u003E\u26A1 Example\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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(new LoginRequest\n{\n Identifier = \u0026quot;user@ultimateauth.com\u0026quot;,\n Secret = \u0026quot;password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This automatically:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBuilds request payload\u003C/li\u003E\n\u003Cli\u003EHandles redirect\u003C/li\u003E\n\u003Cli\u003EIntegrates with configured endpoints\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022state-events\u0022\u003E\uD83D\uDD04 State Events\u003C/h2\u003E\n\u003Cp\u003EClient automatically publishes events:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESessionRevoked\u003C/li\u003E\n\u003Cli\u003EProfileChanged\u003C/li\u003E\n\u003Cli\u003EAuthorizationChanged\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-to-use-this-section\u0022\u003E\uD83E\uDDED How to Use This Section\u003C/h2\u003E\n\u003Cp\u003EFollow these guides:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eauthentication.md \u2192 login, refresh, logout\u003C/li\u003E\n\u003Cli\u003Esession-management.md \u2192 sessions \u0026amp; devices\u003C/li\u003E\n\u003Cli\u003Euser-management.md \u2192 user operations\u003C/li\u003E\n\u003Cli\u003Eidentifiers.md \u2192 login identifiers\u003C/li\u003E\n\u003Cli\u003Eauthorization.md \u2192 roles \u0026amp; permissions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth Client:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eabstracts complexity\u003C/li\u003E\n\u003Cli\u003Eenforces correct flows\u003C/li\u003E\n\u003Cli\u003Ereduces security mistakes\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Think of it as:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CAuthentication runtime for your frontend / app\u201D\u003C/strong\u003E\u003C/p\u003E\n", "Headings": [ { "Id": "what-makes-the-client-different", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/session-management.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/session-management.json index b6189d48..1b24e140 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/session-management.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/session-management.json @@ -1,7 +1,7 @@ { "Slug": "client/session-management", "Title": "Session Management", - "Html": "\n\u003Cp\u003EThis section explains how to manage sessions and devices using the UltimateAuth client.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth, sessions are \u003Cstrong\u003Enot just tokens\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThey are structured as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoot \u2192 user-level security\u003C/li\u003E\n\u003Cli\u003EChain \u2192 device (browser / mobile / app)\u003C/li\u003E\n\u003Cli\u003ESession \u2192 individual authentication instance\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 On the client, you interact with:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003E[Inject] IUAuthClient UAuthClient { get; set; } = null!;\n\nawait UAuthClient.Sessions...\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-active-sessions-devices\u0022\u003E\uD83D\uDCCB Get Active Sessions (Devices)\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Sessions.GetMyChainsAsync();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Returns:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EActive devices\u003C/li\u003E\n\u003Cli\u003ESession chains\u003C/li\u003E\n\u003Cli\u003EMetadata (device, timestamps)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-session-detail\u0022\u003E\uD83D\uDD0D Get Session Detail\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar detail = await UAuthClient.Sessions.GetMyChainDetailAsync(chainId);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Use this to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInspect a specific device\u003C/li\u003E\n\u003Cli\u003EView session history\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-vs-revoke-important\u0022\u003E\uD83D\uDEAA Logout vs Revoke (Important)\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003ELogout\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.LogoutAsync();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnds \u003Cstrong\u003Ecurrent session\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003EUser can login again normally\u003C/li\u003E\n\u003Cli\u003EDoes NOT affect other devices\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-session-control\u0022\u003ERevoke (Session Control)\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Sessions.RevokeMyChainAsync(chainId);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalidates entire \u003Cstrong\u003Edevice chain\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003EAll sessions under that device are revoked\u003C/li\u003E\n\u003Cli\u003ECannot be restored\u003C/li\u003E\n\u003Cli\u003ENew login creates a new chain\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Key difference:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogout = end current session\u003C/li\u003E\n\u003Cli\u003ERevoke = destroy device identity\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor standard auth process, use \u003Ccode\u003EUAuthClient.Flows.LogoutMyDeviceAsync(chainId)\u003C/code\u003E instead of \u003Ccode\u003EUAuthClient.Sessions.RevokeMyChainAsync(chainId)\u003C/code\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-other-devices\u0022\u003E\uD83D\uDCF1 Revoke Other Devices\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Sessions.RevokeMyOtherChainsAsync();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 This:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EKeeps current device active\u003C/li\u003E\n\u003Cli\u003ELogs out all other devices\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-all-sessions\u0022\u003E\uD83D\uDCA5 Revoke All Sessions\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Sessions.RevokeAllMyChainsAsync();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 This:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogs out ALL devices (including current)\u003C/li\u003E\n\u003Cli\u003EForces full reauthentication everywhere\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-session-management\u0022\u003E\uD83D\uDC64 Admin Session Management\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-user-devices\u0022\u003EGet User Devices\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Sessions.GetUserChainsAsync(userKey);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-specific-session\u0022\u003ERevoke Specific Session\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Sessions.RevokeUserSessionAsync(userKey, sessionId);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-device-chain\u0022\u003ERevoke Device (Chain)\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Sessions.RevokeUserChainAsync(userKey, chainId);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-all-user-sessions\u0022\u003ERevoke All User Sessions\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Sessions.RevokeAllUserChainsAsync(userKey);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-model\u0022\u003E\uD83E\uDDE0 Device Model\u003C/h2\u003E\n\u003Cp\u003EEach chain represents a \u003Cstrong\u003Edevice identity\u003C/strong\u003E:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBrowser instance\u003C/li\u003E\n\u003Cli\u003EMobile device\u003C/li\u003E\n\u003Cli\u003EApplication instance\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Sessions are grouped under chains.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-implications\u0022\u003E\uD83D\uDD10 Security Implications\u003C/h2\u003E\n\u003Cp\u003ESession operations are security-critical:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERevoke is irreversible\u003C/li\u003E\n\u003Cli\u003EDevice isolation is enforced\u003C/li\u003E\n\u003Cli\u003ECross-device attacks are contained\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003ESession management in UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eis device-aware\u003C/li\u003E\n\u003Cli\u003Eseparates logout vs revoke\u003C/li\u003E\n\u003Cli\u003Egives full control over user sessions\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EThis section explains how to manage sessions and devices using the UltimateAuth client.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth, sessions are \u003Cstrong\u003Enot just tokens\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThey are structured as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoot \u2192 user-level security\u003C/li\u003E\n\u003Cli\u003EChain \u2192 device (browser / mobile / app)\u003C/li\u003E\n\u003Cli\u003ESession \u2192 individual authentication instance\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 On the client, you interact 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\u003E[Inject] IUAuthClient UAuthClient { get; set; } = null!;\n\nawait UAuthClient.Sessions...\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-active-sessions-devices\u0022\u003E\uD83D\uDCCB Get Active Sessions (Devices)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\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\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Returns:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EActive devices\u003C/li\u003E\n\u003Cli\u003ESession chains\u003C/li\u003E\n\u003Cli\u003EMetadata (device, timestamps)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-session-detail\u0022\u003E\uD83D\uDD0D Get Session Detail\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar detail = await UAuthClient.Sessions.GetMyChainDetailAsync(chainId);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Use this to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInspect a specific device\u003C/li\u003E\n\u003Cli\u003EView session history\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-vs-revoke-important\u0022\u003E\uD83D\uDEAA Logout vs Revoke (Important)\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003ELogout\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.LogoutAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnds \u003Cstrong\u003Ecurrent session\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003EUser can login again normally\u003C/li\u003E\n\u003Cli\u003EDoes NOT affect other devices\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-session-control\u0022\u003ERevoke (Session Control)\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.Sessions.RevokeMyChainAsync(chainId);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalidates entire \u003Cstrong\u003Edevice chain\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003EAll sessions under that device are revoked\u003C/li\u003E\n\u003Cli\u003ECannot be restored\u003C/li\u003E\n\u003Cli\u003ENew login creates a new chain\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Key difference:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogout = end current session\u003C/li\u003E\n\u003Cli\u003ERevoke = destroy device identity\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor standard auth process, use \u003Ccode\u003EUAuthClient.Flows.LogoutMyDeviceAsync(chainId)\u003C/code\u003E instead of \u003Ccode\u003EUAuthClient.Sessions.RevokeMyChainAsync(chainId)\u003C/code\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-other-devices\u0022\u003E\uD83D\uDCF1 Revoke Other Devices\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Sessions.RevokeMyOtherChainsAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EKeeps current device active\u003C/li\u003E\n\u003Cli\u003ELogs out all other devices\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-all-sessions\u0022\u003E\uD83D\uDCA5 Revoke All Sessions\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Sessions.RevokeAllMyChainsAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogs out ALL devices (including current)\u003C/li\u003E\n\u003Cli\u003EForces full reauthentication everywhere\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-session-management\u0022\u003E\uD83D\uDC64 Admin Session Management\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-user-devices\u0022\u003EGet User 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\u003Eawait UAuthClient.Sessions.GetUserChainsAsync(userKey);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-specific-session\u0022\u003ERevoke Specific Session\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.Sessions.RevokeUserSessionAsync(userKey, sessionId);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-device-chain\u0022\u003ERevoke 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\u003Eawait UAuthClient.Sessions.RevokeUserChainAsync(userKey, chainId);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-all-user-sessions\u0022\u003ERevoke All User Sessions\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.Sessions.RevokeAllUserChainsAsync(userKey);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-model\u0022\u003E\uD83E\uDDE0 Device Model\u003C/h2\u003E\n\u003Cp\u003EEach chain represents a \u003Cstrong\u003Edevice identity\u003C/strong\u003E:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBrowser instance\u003C/li\u003E\n\u003Cli\u003EMobile device\u003C/li\u003E\n\u003Cli\u003EApplication instance\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Sessions are grouped under chains.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-implications\u0022\u003E\uD83D\uDD10 Security Implications\u003C/h2\u003E\n\u003Cp\u003ESession operations are security-critical:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERevoke is irreversible\u003C/li\u003E\n\u003Cli\u003EDevice isolation is enforced\u003C/li\u003E\n\u003Cli\u003ECross-device attacks are contained\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003ESession management in UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eis device-aware\u003C/li\u003E\n\u003Cli\u003Eseparates logout vs revoke\u003C/li\u003E\n\u003Cli\u003Egives full control over user sessions\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "overview", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/user-management.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/user-management.json index 55d8be3e..b20bb2df 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/user-management.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/user-management.json @@ -1,7 +1,7 @@ { "Slug": "client/user-management", "Title": "User Management", - "Html": "\n\u003Cp\u003EThis section explains how to manage users using the UltimateAuth client.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EUser operations are handled via:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthClient.Users...\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EProfile management\u003C/li\u003E\n\u003Cli\u003EUser lifecycle\u003C/li\u003E\n\u003Cli\u003EAdmin operations\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-current-user\u0022\u003E\uD83D\uDE4B Get Current User\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar me = await UAuthClient.Users.GetMeAsync();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Returns:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUser profile\u003C/li\u003E\n\u003Cli\u003EStatus\u003C/li\u003E\n\u003Cli\u003EBasic identity data\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-profile\u0022\u003E\u270F\uFE0F Update Profile\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Users.UpdateMeAsync(new UpdateProfileRequest\n{\n DisplayName = \u0026quot;John Doe\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Triggers:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EProfile update\u003C/li\u003E\n\u003Cli\u003EState event (ProfileChanged)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-current-user\u0022\u003E\u274C Delete Current User\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Users.DeleteMeAsync();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 This:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDeletes user (based on configured mode)\u003C/li\u003E\n\u003Cli\u003EEnds session\u003C/li\u003E\n\u003Cli\u003ETriggers state update\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-query-users\u0022\u003E\uD83D\uDC51 Admin: Query Users\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Users.QueryAsync(new UserQuery\n{\n Search = \u0026quot;john\u0026quot;,\n PageNumber = 1,\n PageSize = 10\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Supports:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esearch\u003C/li\u003E\n\u003Cli\u003Epagination\u003C/li\u003E\n\u003Cli\u003Efiltering (status, etc.)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-user\u0022\u003E\u2795 Create User\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Users.CreateAsync(new CreateUserRequest\n{\n UserName = \u0026quot;john\u0026quot;,\n Email = \u0026quot;john@mail.com\u0026quot;,\n Password = \u0026quot;123456\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-create\u0022\u003E\uD83D\uDEE0 Admin Create\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Users.CreateAsAdminAsync(request);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-status\u0022\u003E\uD83D\uDD04 Change Status\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self\u0022\u003ESelf\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Users.ChangeMyStatusAsync(new ChangeUserStatusSelfRequest\n{\n Status = UserStatus.SelfSuspended\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin\u0022\u003EAdmin\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Users.ChangeUserStatusAsync(userKey, new ChangeUserStatusAdminRequest\n{\n Status = UserStatus.Suspended\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-user-admin\u0022\u003E\u274C Delete User (Admin)\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Users.DeleteUserAsync(userKey, new DeleteUserRequest\n{\n Mode = DeleteMode.Soft\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-user\u0022\u003E\uD83D\uDD0D Get User\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar user = await UAuthClient.Users.GetUserAsync(userKey);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-user-admin\u0022\u003E\u270F\uFE0F Update User (Admin)\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Users.UpdateUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifecycle-model\u0022\u003E\uD83E\uDDE0 Lifecycle Model\u003C/h2\u003E\n\u003Cp\u003EUsers have a lifecycle:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EActive\u003C/li\u003E\n\u003Cli\u003ESuspended\u003C/li\u003E\n\u003Cli\u003EDisabled\u003C/li\u003E\n\u003Cli\u003EDeleted (soft/hard)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Status impacts:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Elogin ability\u003C/li\u003E\n\u003Cli\u003Esession validity\u003C/li\u003E\n\u003Cli\u003Eauthorization\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-notes\u0022\u003E\uD83D\uDD10 Security Notes\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EStatus changes may invalidate sessions\u003C/li\u003E\n\u003Cli\u003EDelete may trigger cleanup across domains\u003C/li\u003E\n\u003Cli\u003EAdmin actions are policy-protected\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003EUser management in UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eis lifecycle-aware\u003C/li\u003E\n\u003Cli\u003Esupports self \u002B admin flows\u003C/li\u003E\n\u003Cli\u003Eintegrates with session \u0026amp; security model\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EThis section explains how to manage users using the UltimateAuth client.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EUser operations are handled via:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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\u003EThis includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EProfile management\u003C/li\u003E\n\u003Cli\u003EUser lifecycle\u003C/li\u003E\n\u003Cli\u003EAdmin operations\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-current-user\u0022\u003E\uD83D\uDE4B Get Current User\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar me = await UAuthClient.Users.GetMeAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Returns:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUser profile\u003C/li\u003E\n\u003Cli\u003EStatus\u003C/li\u003E\n\u003Cli\u003EBasic identity data\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-profile\u0022\u003E\u270F\uFE0F Update Profile\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.UpdateMeAsync(new UpdateProfileRequest\n{\n DisplayName = \u0026quot;John Doe\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Triggers:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EProfile update\u003C/li\u003E\n\u003Cli\u003EState event (ProfileChanged)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-current-user\u0022\u003E\u274C Delete Current User\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.DeleteMeAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDeletes user (based on configured mode)\u003C/li\u003E\n\u003Cli\u003EEnds session\u003C/li\u003E\n\u003Cli\u003ETriggers state update\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-query-users\u0022\u003E\uD83D\uDC51 Admin: Query Users\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\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(new UserQuery\n{\n Search = \u0026quot;john\u0026quot;,\n PageNumber = 1,\n PageSize = 10\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Supports:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esearch\u003C/li\u003E\n\u003Cli\u003Epagination\u003C/li\u003E\n\u003Cli\u003Efiltering (status, etc.)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-user\u0022\u003E\u2795 Create User\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.CreateAsync(new CreateUserRequest\n{\n UserName = \u0026quot;john\u0026quot;,\n Email = \u0026quot;john@mail.com\u0026quot;,\n Password = \u0026quot;123456\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-create\u0022\u003E\uD83D\uDEE0 Admin Create\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.CreateAsAdminAsync(request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-status\u0022\u003E\uD83D\uDD04 Change Status\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self\u0022\u003ESelf\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.Users.ChangeMyStatusAsync(new ChangeUserStatusSelfRequest\n{\n Status = UserStatus.SelfSuspended\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin\u0022\u003EAdmin\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.Users.ChangeUserStatusAsync(userKey, new ChangeUserStatusAdminRequest\n{\n Status = UserStatus.Suspended\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-user-admin\u0022\u003E\u274C Delete User (Admin)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.DeleteUserAsync(userKey, new DeleteUserRequest\n{\n Mode = DeleteMode.Soft\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-user\u0022\u003E\uD83D\uDD0D Get User\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar user = await UAuthClient.Users.GetUserAsync(userKey);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-user-admin\u0022\u003E\u270F\uFE0F Update User (Admin)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.UpdateUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifecycle-model\u0022\u003E\uD83E\uDDE0 Lifecycle Model\u003C/h2\u003E\n\u003Cp\u003EUsers have a lifecycle:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EActive\u003C/li\u003E\n\u003Cli\u003ESuspended\u003C/li\u003E\n\u003Cli\u003EDisabled\u003C/li\u003E\n\u003Cli\u003EDeleted (soft/hard)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Status impacts:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Elogin ability\u003C/li\u003E\n\u003Cli\u003Esession validity\u003C/li\u003E\n\u003Cli\u003Eauthorization\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-notes\u0022\u003E\uD83D\uDD10 Security Notes\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EStatus changes may invalidate sessions\u003C/li\u003E\n\u003Cli\u003EDelete may trigger cleanup across domains\u003C/li\u003E\n\u003Cli\u003EAdmin actions are policy-protected\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003EUser management in UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eis lifecycle-aware\u003C/li\u003E\n\u003Cli\u003Esupports self \u002B admin flows\u003C/li\u003E\n\u003Cli\u003Eintegrates with session \u0026amp; security model\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "overview", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/advanced-configuration.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/advanced-configuration.json index 051d3cbc..a098e38f 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/advanced-configuration.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/advanced-configuration.json @@ -1,7 +1,7 @@ { "Slug": "configuration/advanced-configuration", "Title": "Advanced Configuration", - "Html": "\n\u003Cp\u003EUltimateAuth is designed to be flexible \u2014 but not fragile.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You can customize almost every part of the system\u003Cbr /\u003E\n\uD83D\uDC49 Without breaking its security guarantees\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022philosophy\u0022\u003E\u26A0\uFE0F Philosophy\u003C/h2\u003E\n\u003Cp\u003ECustomization in UltimateAuth follows one rule:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You can extend behavior\u003Cbr /\u003E\n\uD83D\uDC49 You should not bypass security\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extension-points\u0022\u003E\uD83E\uDDE9 Extension Points\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth exposes multiple extension points:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EResolvers\u003C/li\u003E\n\u003Cli\u003EValidators\u003C/li\u003E\n\u003Cli\u003EAuthorities\u003C/li\u003E\n\u003Cli\u003EOrchestrators\u003C/li\u003E\n\u003Cli\u003EStores\u003C/li\u003E\n\u003Cli\u003EEvents\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You don\u2019t replace the system\u003Cbr /\u003E\n\uD83D\uDC49 You plug into it\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022replacing-services\u0022\u003E\uD83D\uDD0C Replacing Services\u003C/h2\u003E\n\u003Cp\u003EAll core services can be overridden using DI:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eservices.AddScoped\u0026lt;ISessionValidator, CustomSessionValidator\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 This allows deep customization\u003Cbr /\u003E\n\uD83D\uDC49 While preserving the pipeline\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorities-decisions\u0022\u003E\uD83E\uDDE0 Authorities \u0026amp; Decisions\u003C/h2\u003E\n\u003Cp\u003EAuthorities are responsible for decisions:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELoginAuthority\u003C/li\u003E\n\u003Cli\u003EAccessAuthority\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr /\u003E\n\u003Cp\u003EYou can override them:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eservices.AddScoped\u0026lt;ILoginAuthority, CustomLoginAuthority\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 This changes decision logic\u003Cbr /\u003E\n\uD83D\uDC49 Without touching flows\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022orchestrators\u0022\u003E\uD83D\uDD04 Orchestrators\u003C/h2\u003E\n\u003Cp\u003EOrchestrators coordinate execution:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EValidate\u003C/li\u003E\n\u003Cli\u003EAuthorize\u003C/li\u003E\n\u003Cli\u003EExecute command\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 They enforce invariants\u003Cbr /\u003E\n\uD83D\uDC49 Do not bypass them, unless you exact know what you are doing\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022store-customization\u0022\u003E\uD83D\uDDC4 Store Customization\u003C/h2\u003E\n\u003Cp\u003EYou can provide custom stores:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession store\u003C/li\u003E\n\u003Cli\u003ERefresh token store\u003C/li\u003E\n\u003Cli\u003EUser store\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Supports EF Core, in-memory, or custom implementations\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022events\u0022\u003E\uD83D\uDCE1 Events\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides event hooks:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogin\u003C/li\u003E\n\u003Cli\u003ELogout\u003C/li\u003E\n\u003Cli\u003ERefresh\u003C/li\u003E\n\u003Cli\u003ERevoke\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr /\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Events.OnUserLoggedIn = ctx =\u0026gt;\n{\n // custom logic\n return Task.CompletedTask;\n};\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Use events for side-effects\u003Cbr /\u003E\n\uD83D\uDC49 Not for core logic\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-configuration-overrides\u0022\u003E\u2699\uFE0F Mode Configuration Overrides\u003C/h2\u003E\n\u003Cp\u003EYou can customize behavior per mode:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.ModeConfigurations[UAuthMode.Hybrid] = options =\u0026gt;\n{\n options.Token.AccessTokenLifetime = TimeSpan.FromMinutes(5);\n};\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Runs after defaults\u003Cbr /\u003E\n\uD83D\uDC49 Allows fine-grained control\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-resolvers\u0022\u003E\uD83D\uDD10 Custom Resolvers\u003C/h2\u003E\n\u003Cp\u003EYou can override how data is resolved:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ETenant resolver\u003C/li\u003E\n\u003Cli\u003ESession resolver\u003C/li\u003E\n\u003Cli\u003EDevice resolver\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Enables full control over request interpretation\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022safety-boundaries\u0022\u003E\uD83D\uDEE1 Safety Boundaries\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth enforces:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvariants\u003C/li\u003E\n\u003Cli\u003EValidation\u003C/li\u003E\n\u003Cli\u003EFail-fast behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Unsafe overrides will fail early\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Extend the system\u003Cbr /\u003E\n\uD83D\uDC49 Don\u2019t fight the system\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EEverything is replaceable via DI\u003C/li\u003E\n\u003Cli\u003EAuthorities control decisions\u003C/li\u003E\n\u003Cli\u003EOrchestrators enforce flow\u003C/li\u003E\n\u003Cli\u003EEvents are for side-effects\u003C/li\u003E\n\u003Cli\u003ESecurity boundaries are protected\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EReturn to \u003Cstrong\u003EAuth Flows\u003C/strong\u003E or explore \u003Cstrong\u003EPlugin Domains\u003C/strong\u003E\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth is designed to be flexible \u2014 but not fragile.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You can customize almost every part of the system\u003Cbr\u003E\n\uD83D\uDC49 Without breaking its security guarantees\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022philosophy\u0022\u003E\u26A0\uFE0F Philosophy\u003C/h2\u003E\n\u003Cp\u003ECustomization in UltimateAuth follows one rule:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You can extend behavior\u003Cbr\u003E\n\uD83D\uDC49 You should not bypass security\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extension-points\u0022\u003E\uD83E\uDDE9 Extension Points\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth exposes multiple extension points:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EResolvers\u003C/li\u003E\n\u003Cli\u003EValidators\u003C/li\u003E\n\u003Cli\u003EAuthorities\u003C/li\u003E\n\u003Cli\u003EOrchestrators\u003C/li\u003E\n\u003Cli\u003EStores\u003C/li\u003E\n\u003Cli\u003EEvents\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You don\u2019t replace the system\u003Cbr\u003E\n\uD83D\uDC49 You plug into it\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022replacing-services\u0022\u003E\uD83D\uDD0C Replacing Services\u003C/h2\u003E\n\u003Cp\u003EAll core services can be overridden using DI:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddScoped\u0026lt;ISessionValidator, CustomSessionValidator\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This allows deep customization\u003Cbr\u003E\n\uD83D\uDC49 While preserving the pipeline\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorities-decisions\u0022\u003E\uD83E\uDDE0 Authorities \u0026amp; Decisions\u003C/h2\u003E\n\u003Cp\u003EAuthorities are responsible for decisions:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELoginAuthority\u003C/li\u003E\n\u003Cli\u003EAccessAuthority\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Cp\u003EYou can override 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\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddScoped\u0026lt;ILoginAuthority, CustomLoginAuthority\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This changes decision logic\u003Cbr\u003E\n\uD83D\uDC49 Without touching flows\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022orchestrators\u0022\u003E\uD83D\uDD04 Orchestrators\u003C/h2\u003E\n\u003Cp\u003EOrchestrators coordinate execution:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EValidate\u003C/li\u003E\n\u003Cli\u003EAuthorize\u003C/li\u003E\n\u003Cli\u003EExecute command\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 They enforce invariants\u003Cbr\u003E\n\uD83D\uDC49 Do not bypass them, unless you exact know what you are doing\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022store-customization\u0022\u003E\uD83D\uDDC4 Store Customization\u003C/h2\u003E\n\u003Cp\u003EYou can provide custom stores:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession store\u003C/li\u003E\n\u003Cli\u003ERefresh token store\u003C/li\u003E\n\u003Cli\u003EUser store\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Supports EF Core, in-memory, or custom implementations\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022events\u0022\u003E\uD83D\uDCE1 Events\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides event hooks:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogin\u003C/li\u003E\n\u003Cli\u003ELogout\u003C/li\u003E\n\u003Cli\u003ERefresh\u003C/li\u003E\n\u003Cli\u003ERevoke\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Events.OnUserLoggedIn = ctx =\u0026gt;\n{\n // custom logic\n return Task.CompletedTask;\n};\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Use events for side-effects\u003Cbr\u003E\n\uD83D\uDC49 Not for core logic\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-configuration-overrides\u0022\u003E\u2699\uFE0F Mode Configuration Overrides\u003C/h2\u003E\n\u003Cp\u003EYou can customize behavior per 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\u003Eo.ModeConfigurations[UAuthMode.Hybrid] = options =\u0026gt;\n{\n options.Token.AccessTokenLifetime = TimeSpan.FromMinutes(5);\n};\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Runs after defaults\u003Cbr\u003E\n\uD83D\uDC49 Allows fine-grained control\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-resolvers\u0022\u003E\uD83D\uDD10 Custom Resolvers\u003C/h2\u003E\n\u003Cp\u003EYou can override how data is resolved:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ETenant resolver\u003C/li\u003E\n\u003Cli\u003ESession resolver\u003C/li\u003E\n\u003Cli\u003EDevice resolver\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Enables full control over request interpretation\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022safety-boundaries\u0022\u003E\uD83D\uDEE1 Safety Boundaries\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth enforces:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvariants\u003C/li\u003E\n\u003Cli\u003EValidation\u003C/li\u003E\n\u003Cli\u003EFail-fast behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Unsafe overrides will fail early\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Extend the system\u003Cbr\u003E\n\uD83D\uDC49 Don\u2019t fight the system\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EEverything is replaceable via DI\u003C/li\u003E\n\u003Cli\u003EAuthorities control decisions\u003C/li\u003E\n\u003Cli\u003EOrchestrators enforce flow\u003C/li\u003E\n\u003Cli\u003EEvents are for side-effects\u003C/li\u003E\n\u003Cli\u003ESecurity boundaries are protected\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EReturn to \u003Cstrong\u003EAuth Flows\u003C/strong\u003E or explore \u003Cstrong\u003EPlugin Domains\u003C/strong\u003E\u003C/p\u003E\n", "Headings": [ { "Id": "philosophy", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/client-options.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/client-options.json index 90b12565..22e70422 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/client-options.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/client-options.json @@ -1,7 +1,7 @@ { "Slug": "configuration/client-options", "Title": "Client Options", - "Html": "\n\u003Cp\u003EClient Options define how UltimateAuth behaves on the \u003Cstrong\u003Eclient side\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 While Server controls the system,\u003Cbr /\u003E\n\uD83D\uDC49 Client controls how it is \u003Cstrong\u003Eused\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-are-client-options\u0022\u003E\uD83E\uDDE0 What Are Client Options?\u003C/h2\u003E\n\u003Cp\u003EClient options configure:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient profile (WASM, Server, MAUI, API)\u003C/li\u003E\n\u003Cli\u003EEndpoint communication\u003C/li\u003E\n\u003Cli\u003EPKCE behavior\u003C/li\u003E\n\u003Cli\u003EToken refresh\u003C/li\u003E\n\u003Cli\u003ERe-authentication behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic-usage\u0022\u003E\u2699\uFE0F Basic Usage\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.AutoDetectClientProfile = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-profile\u0022\u003E\uD83E\uDDE9 Client Profile\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth automatically detects client type by default:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBlazor WASM\u003C/li\u003E\n\u003Cli\u003EBlazor Server\u003C/li\u003E\n\u003Cli\u003EMAUI\u003C/li\u003E\n\u003Cli\u003EWebServer\u003C/li\u003E\n\u003Cli\u003EAPI\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EYou can override manually:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.ClientProfile = UAuthClientProfile.BlazorWasm;\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Manual override is useful for testing or special scenarios\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoints\u0022\u003E\uD83C\uDF10 Endpoints\u003C/h2\u003E\n\u003Cp\u003EDefines where requests are sent:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Endpoints.BasePath = \u0026quot;https://localhost:5001/auth\u0026quot;;\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Required for WASM / remote clients\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pkce-configuration\u0022\u003E\uD83D\uDD10 PKCE Configuration\u003C/h2\u003E\n\u003Cp\u003EUsed for browser-based login flows:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Pkce.ReturnUrl = \u0026quot;https://localhost:5002/home\u0026quot;;\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Required for WASM scenarios\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022auto-refresh\u0022\u003E\uD83D\uDD01 Auto Refresh\u003C/h2\u003E\n\u003Cp\u003EControls token/session refresh behavior:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.AutoRefresh.Interval = TimeSpan.FromMinutes(1);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Keeps authentication alive automatically\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022re-authentication\u0022\u003E\uD83D\uDD04 Re-authentication\u003C/h2\u003E\n\u003Cp\u003EControls behavior when session expires:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Reauth.Behavior = ReauthBehavior.RaiseEvent;\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Allows silent or interactive re-login\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Server decides\u003Cbr /\u003E\n\uD83D\uDC49 Client adapts\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient options control runtime behavior\u003C/li\u003E\n\u003Cli\u003EProfile detection is automatic\u003C/li\u003E\n\u003Cli\u003EPKCE is required for public clients\u003C/li\u003E\n\u003Cli\u003ERefresh and re-auth are configurable\u003C/li\u003E\n\u003Cli\u003EWorks together with Server options\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EConfiguration Sources\u003C/strong\u003E\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EClient Options define how UltimateAuth behaves on the \u003Cstrong\u003Eclient side\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 While Server controls the system,\u003Cbr\u003E\n\uD83D\uDC49 Client controls how it is \u003Cstrong\u003Eused\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-are-client-options\u0022\u003E\uD83E\uDDE0 What Are Client Options?\u003C/h2\u003E\n\u003Cp\u003EClient options configure:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient profile (WASM, Server, MAUI, API)\u003C/li\u003E\n\u003Cli\u003EEndpoint communication\u003C/li\u003E\n\u003Cli\u003EPKCE behavior\u003C/li\u003E\n\u003Cli\u003EToken refresh\u003C/li\u003E\n\u003Cli\u003ERe-authentication behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic-usage\u0022\u003E\u2699\uFE0F Basic Usage\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.AutoDetectClientProfile = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-profile\u0022\u003E\uD83E\uDDE9 Client Profile\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth automatically detects client type by default:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBlazor WASM\u003C/li\u003E\n\u003Cli\u003EBlazor Server\u003C/li\u003E\n\u003Cli\u003EMAUI\u003C/li\u003E\n\u003Cli\u003EWebServer\u003C/li\u003E\n\u003Cli\u003EAPI\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EYou can override manually:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.ClientProfile = UAuthClientProfile.BlazorWasm;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Manual override is useful for testing or special scenarios\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoints\u0022\u003E\uD83C\uDF10 Endpoints\u003C/h2\u003E\n\u003Cp\u003EDefines where requests are sent:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Endpoints.BasePath = \u0026quot;https://localhost:5001/auth\u0026quot;;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Required for WASM / remote clients\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pkce-configuration\u0022\u003E\uD83D\uDD10 PKCE Configuration\u003C/h2\u003E\n\u003Cp\u003EUsed for browser-based login flows:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Pkce.ReturnUrl = \u0026quot;https://localhost:5002/home\u0026quot;;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Required for WASM scenarios\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022auto-refresh\u0022\u003E\uD83D\uDD01 Auto Refresh\u003C/h2\u003E\n\u003Cp\u003EControls token/session refresh 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\u003Eo.AutoRefresh.Interval = TimeSpan.FromMinutes(1);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Keeps authentication alive automatically\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022re-authentication\u0022\u003E\uD83D\uDD04 Re-authentication\u003C/h2\u003E\n\u003Cp\u003EControls behavior when session expires:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Reauth.Behavior = ReauthBehavior.RaiseEvent;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Allows silent or interactive re-login\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Server decides\u003Cbr\u003E\n\uD83D\uDC49 Client adapts\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient options control runtime behavior\u003C/li\u003E\n\u003Cli\u003EProfile detection is automatic\u003C/li\u003E\n\u003Cli\u003EPKCE is required for public clients\u003C/li\u003E\n\u003Cli\u003ERefresh and re-auth are configurable\u003C/li\u003E\n\u003Cli\u003EWorks together with Server options\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EConfiguration Sources\u003C/strong\u003E\u003C/p\u003E\n", "Headings": [ { "Id": "what-are-client-options", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-overview.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-overview.json index 328f7601..2bd287de 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-overview.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-overview.json @@ -1,7 +1,7 @@ { "Slug": "configuration/configuration-overview", "Title": "Configuration Overview", - "Html": "\n\u003Cp\u003EUltimateAuth is not configured as a static system.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 It is configured as a \u003Cstrong\u003Eruntime-adaptive system\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-common-misunderstanding\u0022\u003E\u26A0\uFE0F A Common Misunderstanding\u003C/h2\u003E\n\u003Cp\u003EMany frameworks expect you to configure authentication once:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EChoose JWT or cookies\u003C/li\u003E\n\u003Cli\u003ESet token lifetimes\u003C/li\u003E\n\u003Cli\u003EConfigure behavior globally\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 And that configuration applies everywhere\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 UltimateAuth does NOT work like this\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022layered-configuration-model\u0022\u003E\uD83E\uDDE9 Layered Configuration Model\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates configuration into distinct layers:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-behavior-definition\u0022\u003E\uD83D\uDD39 Core (Behavior Definition)\u003C/h3\u003E\n\u003Cp\u003ECore defines \u003Cstrong\u003Ewhat authentication means\u003C/strong\u003E:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession lifecycle\u003C/li\u003E\n\u003Cli\u003EToken behavior\u003C/li\u003E\n\u003Cli\u003EPKCE rules\u003C/li\u003E\n\u003Cli\u003EMulti-tenant handling\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Core is the foundation\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 But you typically do NOT configure it directly\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-application-configuration\u0022\u003E\uD83D\uDD39 Server (Application Configuration)\u003C/h3\u003E\n\u003Cp\u003EServer is where you configure the system:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(o =\u0026gt;\n{\n o.Login.MaxFailedAttempts = 5;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis layer controls:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAllowed authentication modes\u003C/li\u003E\n\u003Cli\u003EEndpoint exposure\u003C/li\u003E\n\u003Cli\u003ECookie behavior\u003C/li\u003E\n\u003Cli\u003ESecurity policies\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This is your main configuration surface\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-runtime-behavior\u0022\u003E\uD83D\uDD39 Client (Runtime Behavior)\u003C/h3\u003E\n\u003Cp\u003EClient configuration controls:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient profile (WASM, Server, MAUI, API)\u003C/li\u003E\n\u003Cli\u003EPKCE behavior\u003C/li\u003E\n\u003Cli\u003EAuto-refresh\u003C/li\u003E\n\u003Cli\u003ERe-authentication\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Client influences how flows are executed\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-configuration-the-key-idea\u0022\u003E\u26A1 Runtime Configuration (The Key Idea)\u003C/h2\u003E\n\u003Cp\u003EHere is the most important concept:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 UltimateAuth does NOT use configuration as-is\u003C/p\u003E\n\u003Cp\u003EInstead, it computes \u003Cstrong\u003EEffective Configuration per request\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-it-works\u0022\u003E\uD83E\uDDE0 How It Works\u003C/h3\u003E\n\u003Cp\u003EAt runtime:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EClient profile is detected\u003C/li\u003E\n\u003Cli\u003EFlow type is determined (Login, Refresh, etc.)\u003C/li\u003E\n\u003Cli\u003EAuth mode is resolved\u003C/li\u003E\n\u003Cli\u003EDefaults are applied\u003C/li\u003E\n\u003Cli\u003EMode-specific overrides are applied\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003E\uD83D\uDC49 This produces:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EEffectiveOptions\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022from-static-to-dynamic\u0022\u003E\uD83D\uDD04 From Static to Dynamic\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode\u003EUAuthServerOptions (startup)\n \u2193\nMode Resolver\n \u2193\nApply Defaults\n \u2193\nMode Overrides\n \u2193\nEffectiveUAuthServerOptions (runtime)\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Every request can have a different effective configuration\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-matters\u0022\u003E\uD83C\uDFAF Why This Matters\u003C/h2\u003E\n\u003Cp\u003EThis allows UltimateAuth to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUse different auth modes per client\u003C/li\u003E\n\u003Cli\u003EAdapt behavior per flow\u003C/li\u003E\n\u003Cli\u003EEnforce security dynamically\u003C/li\u003E\n\u003Cli\u003EAvoid global misconfiguration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You don\u2019t configure \u201Cone system\u201D\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You configure a \u003Cstrong\u003Edecision engine\u003C/strong\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022safety-by-design\u0022\u003E\uD83D\uDEE1 Safety by Design\u003C/h2\u003E\n\u003Cp\u003EEven with dynamic behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalid combinations fail early\u003C/li\u003E\n\u003Cli\u003EDisallowed modes are rejected\u003C/li\u003E\n\u003Cli\u003ESecurity invariants are enforced\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Flexibility does not reduce safety\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-vs-effective\u0022\u003E\u2699\uFE0F Core vs Effective\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcept\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\u003ECore Options\u003C/td\u003E\n\u003Ctd\u003EBase behavior definitions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EServer Options\u003C/td\u003E\n\u003Ctd\u003EApplication-level configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EEffective Options\u003C/td\u003E\n\u003Ctd\u003ERuntime-resolved behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cp\u003E\uD83D\uDC49 Effective options are what actually run\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You don\u2019t configure authentication\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You configure how it is \u003Cstrong\u003Eresolved\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EControl runtime \u2192 Server Options\u003C/li\u003E\n\u003Cli\u003EConfigure clients \u2192 Client Options\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth is not configured as a static system.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 It is configured as a \u003Cstrong\u003Eruntime-adaptive system\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-common-misunderstanding\u0022\u003E\u26A0\uFE0F A Common Misunderstanding\u003C/h2\u003E\n\u003Cp\u003EMany frameworks expect you to configure authentication once:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EChoose JWT or cookies\u003C/li\u003E\n\u003Cli\u003ESet token lifetimes\u003C/li\u003E\n\u003Cli\u003EConfigure behavior globally\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 And that configuration applies everywhere\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 UltimateAuth does NOT work like this\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022layered-configuration-model\u0022\u003E\uD83E\uDDE9 Layered Configuration Model\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates configuration into distinct layers:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-behavior-definition\u0022\u003E\uD83D\uDD39 Core (Behavior Definition)\u003C/h3\u003E\n\u003Cp\u003ECore defines \u003Cstrong\u003Ewhat authentication means\u003C/strong\u003E:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession lifecycle\u003C/li\u003E\n\u003Cli\u003EToken behavior\u003C/li\u003E\n\u003Cli\u003EPKCE rules\u003C/li\u003E\n\u003Cli\u003EMulti-tenant handling\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Core is the foundation\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 But you typically do NOT configure it directly\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-application-configuration\u0022\u003E\uD83D\uDD39 Server (Application Configuration)\u003C/h3\u003E\n\u003Cp\u003EServer is where you configure the system:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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(o =\u0026gt;\n{\n o.Login.MaxFailedAttempts = 5;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis layer controls:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAllowed authentication modes\u003C/li\u003E\n\u003Cli\u003EEndpoint exposure\u003C/li\u003E\n\u003Cli\u003ECookie behavior\u003C/li\u003E\n\u003Cli\u003ESecurity policies\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This is your main configuration surface\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-runtime-behavior\u0022\u003E\uD83D\uDD39 Client (Runtime Behavior)\u003C/h3\u003E\n\u003Cp\u003EClient configuration controls:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient profile (WASM, Server, MAUI, API)\u003C/li\u003E\n\u003Cli\u003EPKCE behavior\u003C/li\u003E\n\u003Cli\u003EAuto-refresh\u003C/li\u003E\n\u003Cli\u003ERe-authentication\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Client influences how flows are executed\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-configuration-the-key-idea\u0022\u003E\u26A1 Runtime Configuration (The Key Idea)\u003C/h2\u003E\n\u003Cp\u003EHere is the most important concept:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 UltimateAuth does NOT use configuration as-is\u003C/p\u003E\n\u003Cp\u003EInstead, it computes \u003Cstrong\u003EEffective Configuration per request\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-it-works\u0022\u003E\uD83E\uDDE0 How It Works\u003C/h3\u003E\n\u003Cp\u003EAt runtime:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EClient profile is detected\u003C/li\u003E\n\u003Cli\u003EFlow type is determined (Login, Refresh, etc.)\u003C/li\u003E\n\u003Cli\u003EAuth mode is resolved\u003C/li\u003E\n\u003Cli\u003EDefaults are applied\u003C/li\u003E\n\u003Cli\u003EMode-specific overrides are applied\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003E\uD83D\uDC49 This produces:\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\u003EEffectiveOptions\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022from-static-to-dynamic\u0022\u003E\uD83D\uDD04 From Static to Dynamic\u003C/h2\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\u003EUAuthServerOptions (startup)\n \u2193\nMode Resolver\n \u2193\nApply Defaults\n \u2193\nMode Overrides\n \u2193\nEffectiveUAuthServerOptions (runtime)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Every request can have a different effective configuration\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-matters\u0022\u003E\uD83C\uDFAF Why This Matters\u003C/h2\u003E\n\u003Cp\u003EThis allows UltimateAuth to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUse different auth modes per client\u003C/li\u003E\n\u003Cli\u003EAdapt behavior per flow\u003C/li\u003E\n\u003Cli\u003EEnforce security dynamically\u003C/li\u003E\n\u003Cli\u003EAvoid global misconfiguration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You don\u2019t configure \u201Cone system\u201D\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You configure a \u003Cstrong\u003Edecision engine\u003C/strong\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022safety-by-design\u0022\u003E\uD83D\uDEE1 Safety by Design\u003C/h2\u003E\n\u003Cp\u003EEven with dynamic behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalid combinations fail early\u003C/li\u003E\n\u003Cli\u003EDisallowed modes are rejected\u003C/li\u003E\n\u003Cli\u003ESecurity invariants are enforced\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Flexibility does not reduce safety\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-vs-effective\u0022\u003E\u2699\uFE0F Core vs Effective\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\u003EMeaning\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECore Options\u003C/td\u003E\n\u003Ctd\u003EBase behavior definitions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EServer Options\u003C/td\u003E\n\u003Ctd\u003EApplication-level configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EEffective Options\u003C/td\u003E\n\u003Ctd\u003ERuntime-resolved behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Effective options are what actually run\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You don\u2019t configure authentication\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You configure how it is \u003Cstrong\u003Eresolved\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EControl runtime \u2192 Server Options\u003C/li\u003E\n\u003Cli\u003EConfigure clients \u2192 Client Options\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "a-common-misunderstanding", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-sources.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-sources.json index 1ebbf96d..1a195360 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-sources.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-sources.json @@ -1,7 +1,7 @@ { "Slug": "configuration/configuration-sources", "Title": "Configuration Sources", - "Html": "\n\u003Cp\u003EUltimateAuth supports multiple configuration sources.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 But more importantly, it defines a \u003Cstrong\u003Eclear and predictable precedence model\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022two-ways-to-configure\u0022\u003E\uD83E\uDDE0 Two Ways to Configure\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth can be configured using:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022code-program.cs\u0022\u003E\uD83D\uDD39 Code (Program.cs)\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(o =\u0026gt;\n{\n o.Login.MaxFailedAttempts = 5;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-files-appsettings.json\u0022\u003E\uD83D\uDD39 Configuration Files (appsettings.json)\u003C/h3\u003E\n\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 }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022precedence-rules\u0022\u003E\u2696\uFE0F Precedence Rules\u003C/h2\u003E\n\u003Cp\u003EThis is the most important rule:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Cstrong\u003EConfiguration files override code\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EExecution order:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EProgram.cs configuration\n \u2193\nappsettings.json binding\n \u2193\nFinal options\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 This means:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDefaults can be defined in code\u003C/li\u003E\n\u003Cli\u003EEnvironments can override them safely\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022environment-based-configuration\u0022\u003E\uD83C\uDF0D Environment-Based Configuration\u003C/h2\u003E\n\u003Cp\u003EASP.NET Core supports environment-specific configuration:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eappsettings.Development.json\u003C/li\u003E\n\u003Cli\u003Eappsettings.Staging.json\u003C/li\u003E\n\u003Cli\u003Eappsettings.Production.json\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EExample:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-json\u0022\u003E{\n \u0026quot;UltimateAuth\u0026quot;: {\n \u0026quot;Server\u0026quot;: {\n \u0026quot;Session\u0026quot;: {\n \u0026quot;IdleTimeout\u0026quot;: \u0026quot;7.00:00:00\u0026quot;\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 You can use different values per environment without changing code\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-strategy\u0022\u003E\uD83E\uDDE9 Recommended Strategy\u003C/h2\u003E\n\u003Cp\u003EFor real-world applications:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-program.cs-for\u0022\u003E\u2714 Use Program.cs for:\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EDefaults\u003C/li\u003E\n\u003Cli\u003EDevelopment setup\u003C/li\u003E\n\u003Cli\u003ELocal testing\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-appsettings-for\u0022\u003E\u2714 Use appsettings for:\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnvironment-specific overrides\u003C/li\u003E\n\u003Cli\u003EProduction tuning\u003C/li\u003E\n\u003Cli\u003EDeployment configuration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This keeps your system flexible and maintainable\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022safety-validation\u0022\u003E\uD83D\uDEE1 Safety \u0026amp; Validation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth validates configuration at startup:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalid combinations are rejected\u003C/li\u003E\n\u003Cli\u003EMissing required values fail fast\u003C/li\u003E\n\u003Cli\u003EUnsafe configurations are blocked\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You will not run with a broken configuration\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-pitfalls\u0022\u003E\u26A0\uFE0F Common Pitfalls\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-code-overrides-config\u0022\u003E\u274C Assuming code overrides config\u003C/h3\u003E\n\u003Cp\u003EIt does not.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 appsettings.json always wins\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hardcoding-production-values\u0022\u003E\u274C Hardcoding production values\u003C/h3\u003E\n\u003Cp\u003EAvoid:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Token.AccessTokenLifetime = TimeSpan.FromHours(1);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Use environment config instead for maximum flexibility\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022mixing-environments-unintentionally\u0022\u003E\u274C Mixing environments unintentionally\u003C/h3\u003E\n\u003Cp\u003EEnsure correct environment is set:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EASPNETCORE_ENVIRONMENT=Production\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Code defines defaults\u003Cbr /\u003E\n\uD83D\uDC49 Configuration defines reality\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EUltimateAuth supports code \u002B configuration\u003C/li\u003E\n\u003Cli\u003Eappsettings.json overrides Program.cs\u003C/li\u003E\n\u003Cli\u003EEnvironment-based configuration is first-class\u003C/li\u003E\n\u003Cli\u003EValidation prevents unsafe setups\u003C/li\u003E\n\u003Cli\u003EDesigned for real-world deployment\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EAdvanced Configuration\u003C/strong\u003E\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth supports multiple configuration sources.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 But more importantly, it defines a \u003Cstrong\u003Eclear and predictable precedence model\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022two-ways-to-configure\u0022\u003E\uD83E\uDDE0 Two Ways to Configure\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth can be configured using:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022code-program.cs\u0022\u003E\uD83D\uDD39 Code (Program.cs)\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(o =\u0026gt;\n{\n o.Login.MaxFailedAttempts = 5;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-files-appsettings.json\u0022\u003E\uD83D\uDD39 Configuration Files (appsettings.json)\u003C/h3\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 }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022precedence-rules\u0022\u003E\u2696\uFE0F Precedence Rules\u003C/h2\u003E\n\u003Cp\u003EThis is the most important rule:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Cstrong\u003EConfiguration files override code\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EExecution order:\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\u003EProgram.cs configuration\n \u2193\nappsettings.json binding\n \u2193\nFinal options\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This means:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDefaults can be defined in code\u003C/li\u003E\n\u003Cli\u003EEnvironments can override them safely\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022environment-based-configuration\u0022\u003E\uD83C\uDF0D Environment-Based Configuration\u003C/h2\u003E\n\u003Cp\u003EASP.NET Core supports environment-specific configuration:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eappsettings.Development.json\u003C/li\u003E\n\u003Cli\u003Eappsettings.Staging.json\u003C/li\u003E\n\u003Cli\u003Eappsettings.Production.json\u003C/li\u003E\n\u003C/ul\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\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;Session\u0026quot;: {\n \u0026quot;IdleTimeout\u0026quot;: \u0026quot;7.00:00:00\u0026quot;\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 You can use different values per environment without changing code\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-strategy\u0022\u003E\uD83E\uDDE9 Recommended Strategy\u003C/h2\u003E\n\u003Cp\u003EFor real-world applications:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-program.cs-for\u0022\u003E\u2714 Use Program.cs for:\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EDefaults\u003C/li\u003E\n\u003Cli\u003EDevelopment setup\u003C/li\u003E\n\u003Cli\u003ELocal testing\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-appsettings-for\u0022\u003E\u2714 Use appsettings for:\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnvironment-specific overrides\u003C/li\u003E\n\u003Cli\u003EProduction tuning\u003C/li\u003E\n\u003Cli\u003EDeployment configuration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This keeps your system flexible and maintainable\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022safety-validation\u0022\u003E\uD83D\uDEE1 Safety \u0026amp; Validation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth validates configuration at startup:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalid combinations are rejected\u003C/li\u003E\n\u003Cli\u003EMissing required values fail fast\u003C/li\u003E\n\u003Cli\u003EUnsafe configurations are blocked\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You will not run with a broken configuration\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-pitfalls\u0022\u003E\u26A0\uFE0F Common Pitfalls\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-code-overrides-config\u0022\u003E\u274C Assuming code overrides config\u003C/h3\u003E\n\u003Cp\u003EIt does not.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 appsettings.json always wins\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hardcoding-production-values\u0022\u003E\u274C Hardcoding production values\u003C/h3\u003E\n\u003Cp\u003EAvoid:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Token.AccessTokenLifetime = TimeSpan.FromHours(1);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Use environment config instead for maximum flexibility\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022mixing-environments-unintentionally\u0022\u003E\u274C Mixing environments unintentionally\u003C/h3\u003E\n\u003Cp\u003EEnsure correct environment is set:\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\u003EASPNETCORE_ENVIRONMENT=Production\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Code defines defaults\u003Cbr\u003E\n\uD83D\uDC49 Configuration defines reality\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EUltimateAuth supports code \u002B configuration\u003C/li\u003E\n\u003Cli\u003Eappsettings.json overrides Program.cs\u003C/li\u003E\n\u003Cli\u003EEnvironment-based configuration is first-class\u003C/li\u003E\n\u003Cli\u003EValidation prevents unsafe setups\u003C/li\u003E\n\u003Cli\u003EDesigned for real-world deployment\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EAdvanced Configuration\u003C/strong\u003E\u003C/p\u003E\n", "Headings": [ { "Id": "two-ways-to-configure", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json index fb345771..c21a436a 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json @@ -1,7 +1,7 @@ { "Slug": "configuration/index", "Title": "Configuration", - "Html": "\n\u003Cp\u003EUltimateAuth is designed to be flexible.\u003C/p\u003E\n\u003Cp\u003EBut flexibility without structure leads to chaos.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Configuration in UltimateAuth is structured, layered, and safe by default.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-you-configure\u0022\u003E\uD83E\uDDE0 What You Configure\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth, you don\u2019t just configure values.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You configure behavior.\u003C/p\u003E\n\u003Cp\u003EThis includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EHow sessions are created and managed\u003C/li\u003E\n\u003Cli\u003EHow tokens are issued and refreshed\u003C/li\u003E\n\u003Cli\u003EHow tenants are resolved\u003C/li\u003E\n\u003Cli\u003EHow clients interact with the system\u003C/li\u003E\n\u003Cli\u003EWhich features are enabled or restricted\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-layers\u0022\u003E\uD83E\uDDE9 Configuration Layers\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates configuration into three layers:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022core\u0022\u003E\uD83D\uDD39 Core\u003C/h3\u003E\n\u003Cp\u003EDefines authentication behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession lifecycle\u003C/li\u003E\n\u003Cli\u003EToken policies\u003C/li\u003E\n\u003Cli\u003EPKCE flows\u003C/li\u003E\n\u003Cli\u003EMulti-tenancy\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server\u0022\u003E\uD83D\uDD39 Server\u003C/h3\u003E\n\u003Cp\u003EDefines runtime behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAllowed authentication modes\u003C/li\u003E\n\u003Cli\u003EEndpoint exposure\u003C/li\u003E\n\u003Cli\u003ECookie and transport behavior\u003C/li\u003E\n\u003Cli\u003EHub deployment\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client\u0022\u003E\uD83D\uDD39 Client\u003C/h3\u003E\n\u003Cp\u003EDefines client-side behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient profile\u003C/li\u003E\n\u003Cli\u003EPKCE configuration\u003C/li\u003E\n\u003Cli\u003EAuto-refresh and re-authentication\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 These layers are independent but work together.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-sources\u0022\u003E\u2699\uFE0F Configuration Sources\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports two configuration styles:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022code-based-program.cs\u0022\u003ECode-based (Program.cs)\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(o =\u0026gt;\n{\n o.Login.MaxFailedAttempts = 5;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-based-appsettings.json\u0022\u003EConfiguration-based (appsettings.json)\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003E{\n \u0026quot;UltimateAuth\u0026quot;: {\n \u0026quot;Server\u0026quot;: {\n \u0026quot;Login\u0026quot;: {\n \u0026quot;MaxFailedAttempts\u0026quot;: 5\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 appsettings.json overrides Program.cs\u003C/p\u003E\n\u003Cp\u003EThis allows:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnvironment-based configuration\u003C/li\u003E\n\u003Cli\u003ECentralized management\u003C/li\u003E\n\u003Cli\u003EProduction-safe overrides\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022safety-by-design\u0022\u003E\uD83D\uDEE1 Safety by Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth does not allow unsafe configurations silently.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalid combinations fail at startup\u003C/li\u003E\n\u003Cli\u003EUnsupported modes are rejected\u003C/li\u003E\n\u003Cli\u003ESecurity invariants are enforced\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Flexibility is allowed\n\uD83D\uDC49 Unsafe behavior is not\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022whats-next\u0022\u003E\uD83C\uDFAF What\u2019s Next?\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EUnderstand configuration layers \u2192 Configuration Overview\u003C/li\u003E\n\u003Cli\u003ELearn Core behavior \u2192 Core Options\u003C/li\u003E\n\u003Cli\u003ECustomize server \u2192 Server Options\u003C/li\u003E\n\u003Cli\u003EControl clients \u2192 Client Options\u003C/li\u003E\n\u003Cli\u003EGo deeper \u2192 Advanced Configuration\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth is designed to be flexible.\u003C/p\u003E\n\u003Cp\u003EBut flexibility without structure leads to chaos.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Configuration in UltimateAuth is structured, layered, and safe by default.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-you-configure\u0022\u003E\uD83E\uDDE0 What You Configure\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth, you don\u2019t just configure values.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You configure behavior.\u003C/p\u003E\n\u003Cp\u003EThis includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EHow sessions are created and managed\u003C/li\u003E\n\u003Cli\u003EHow tokens are issued and refreshed\u003C/li\u003E\n\u003Cli\u003EHow tenants are resolved\u003C/li\u003E\n\u003Cli\u003EHow clients interact with the system\u003C/li\u003E\n\u003Cli\u003EWhich features are enabled or restricted\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-layers\u0022\u003E\uD83E\uDDE9 Configuration Layers\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates configuration into three layers:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022core\u0022\u003E\uD83D\uDD39 Core\u003C/h3\u003E\n\u003Cp\u003EDefines authentication behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession lifecycle\u003C/li\u003E\n\u003Cli\u003EToken policies\u003C/li\u003E\n\u003Cli\u003EPKCE flows\u003C/li\u003E\n\u003Cli\u003EMulti-tenancy\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server\u0022\u003E\uD83D\uDD39 Server\u003C/h3\u003E\n\u003Cp\u003EDefines runtime behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAllowed authentication modes\u003C/li\u003E\n\u003Cli\u003EEndpoint exposure\u003C/li\u003E\n\u003Cli\u003ECookie and transport behavior\u003C/li\u003E\n\u003Cli\u003EHub deployment\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client\u0022\u003E\uD83D\uDD39 Client\u003C/h3\u003E\n\u003Cp\u003EDefines client-side behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient profile\u003C/li\u003E\n\u003Cli\u003EPKCE configuration\u003C/li\u003E\n\u003Cli\u003EAuto-refresh and re-authentication\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 These layers are independent but work together.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-sources\u0022\u003E\u2699\uFE0F Configuration Sources\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports two configuration styles:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022code-based-program.cs\u0022\u003ECode-based (Program.cs)\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(o =\u0026gt;\n{\n o.Login.MaxFailedAttempts = 5;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-based-appsettings.json\u0022\u003EConfiguration-based (appsettings.json)\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\u003E{\n \u0026quot;UltimateAuth\u0026quot;: {\n \u0026quot;Server\u0026quot;: {\n \u0026quot;Login\u0026quot;: {\n \u0026quot;MaxFailedAttempts\u0026quot;: 5\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 appsettings.json overrides Program.cs\u003C/p\u003E\n\u003Cp\u003EThis allows:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnvironment-based configuration\u003C/li\u003E\n\u003Cli\u003ECentralized management\u003C/li\u003E\n\u003Cli\u003EProduction-safe overrides\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022safety-by-design\u0022\u003E\uD83D\uDEE1 Safety by Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth does not allow unsafe configurations silently.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalid combinations fail at startup\u003C/li\u003E\n\u003Cli\u003EUnsupported modes are rejected\u003C/li\u003E\n\u003Cli\u003ESecurity invariants are enforced\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Flexibility is allowed\n\uD83D\uDC49 Unsafe behavior is not\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022whats-next\u0022\u003E\uD83C\uDFAF What\u2019s Next?\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EUnderstand configuration layers \u2192 Configuration Overview\u003C/li\u003E\n\u003Cli\u003ELearn Core behavior \u2192 Core Options\u003C/li\u003E\n\u003Cli\u003ECustomize server \u2192 Server Options\u003C/li\u003E\n\u003Cli\u003EControl clients \u2192 Client Options\u003C/li\u003E\n\u003Cli\u003EGo deeper \u2192 Advanced Configuration\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "what-you-configure", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/server-options.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/server-options.json index 8804d48a..bccfc60a 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/server-options.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/server-options.json @@ -1,7 +1,7 @@ { "Slug": "configuration/server-options", "Title": "Server Options", - "Html": "\n\u003Cp\u003EUltimateAuth is configured primarily through \u003Cstrong\u003EServer Options\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This is the main entry point for configuring authentication behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-are-server-options\u0022\u003E\uD83E\uDDE0 What Are Server Options?\u003C/h2\u003E\n\u003Cp\u003EServer options define how UltimateAuth behaves \u003Cstrong\u003Einside your application\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThey control:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication behavior\u003C/li\u003E\n\u003Cli\u003ESecurity policies\u003C/li\u003E\n\u003Cli\u003EToken issuance\u003C/li\u003E\n\u003Cli\u003ESession lifecycle\u003C/li\u003E\n\u003Cli\u003EEndpoint exposure\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic-usage\u0022\u003E\u2699\uFE0F Basic Usage\u003C/h2\u003E\n\u003Cp\u003EYou configure server options in \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(o =\u0026gt;\n{\n o.Login.MaxFailedAttempts = 5;\n o.Session.IdleTimeout = TimeSpan.FromDays(7);\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Chr /\u003E\n\u003Cp\u003EYou can also use \u003Ccode\u003Eappsettings.json\u003C/code\u003E:\u003C/p\u003E\n\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;IdleTimeout\u0026quot;: \u0026quot;07.00.00.00\u0026quot;\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003Eappsettings.json\u003C/code\u003E overrides \u003Ccode\u003EProgram.cs\u003C/code\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-composition\u0022\u003E\uD83E\uDDE9 Core Composition\u003C/h2\u003E\n\u003Cp\u003EServer options include Core behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogin\u003C/li\u003E\n\u003Cli\u003ESession\u003C/li\u003E\n\u003Cli\u003EToken\u003C/li\u003E\n\u003Cli\u003EPKCE\u003C/li\u003E\n\u003Cli\u003EMulti-tenancy\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 These are defined in Core\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 But configured via Server\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-you-dont-configure-modes-directly\u0022\u003E\u26A0\uFE0F Important: You Don\u2019t Configure Modes Directly\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth does NOT expect you to select a single auth mode.\u003C/p\u003E\n\u003Cp\u003EInstead:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Mode is resolved at runtime\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022allowed-modes-guardrail\u0022\u003E\uD83D\uDEE1 Allowed Modes (Guardrail)\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.AllowedModes = new[]\n{\n UAuthMode.Hybrid,\n UAuthMode.PureOpaque\n};\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 This does NOT select a mode\u003Cbr /\u003E\n\uD83D\uDC49 It restricts which modes are allowed\u003C/p\u003E\n\u003Cp\u003EIf a resolved mode is not allowed:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Request fails early\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-behavior-effective-options\u0022\u003E\u26A1 Runtime Behavior (Effective Options)\u003C/h2\u003E\n\u003Cp\u003EServer options are not used directly.\u003C/p\u003E\n\u003Cp\u003EThey are transformed into:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003EEffectiveUAuthServerOptions\u003C/code\u003E\u003C/p\u003E\n\u003Cp\u003EThis happens per request:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMode is resolved\u003C/li\u003E\n\u003Cli\u003EDefaults are applied\u003C/li\u003E\n\u003Cli\u003EOverrides are applied\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 What actually runs is \u003Cstrong\u003EEffective Options\u003C/strong\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-based-defaults\u0022\u003E\uD83D\uDD04 Mode-Based Defaults\u003C/h2\u003E\n\u003Cp\u003EEach auth mode applies different defaults automatically:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPureOpaque \u2192 session-heavy\u003C/li\u003E\n\u003Cli\u003EHybrid \u2192 session \u002B token\u003C/li\u003E\n\u003Cli\u003EPureJwt \u2192 token-only\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You don\u2019t need to manually configure everything\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoint-control\u0022\u003E\uD83C\uDF9B Endpoint Control\u003C/h2\u003E\n\u003Cp\u003EYou can control which features are enabled:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Endpoints.Authentication = true;\no.Endpoints.Session = true;\no.Endpoints.Authorization = true;\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EYou can also disable specific actions:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Endpoints.DisabledActions.Add(\u0026quot;UAuthActions.Users.Create.Anonymous\u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Useful for API hardening\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022cookie-transport-behavior\u0022\u003E\uD83C\uDF6A Cookie \u0026amp; Transport Behavior\u003C/h2\u003E\n\u003Cp\u003EServer options define how credentials are transported:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECookies\u003C/li\u003E\n\u003Cli\u003EHeaders\u003C/li\u003E\n\u003Cli\u003ETokens\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Unsafe combinations are rejected at startup\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022hub-configuration\u0022\u003E\uD83C\uDF10 Hub Configuration\u003C/h2\u003E\n\u003Cp\u003EIf using UAuthHub:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.HubDeploymentMode = UAuthHubDeploymentMode.Integrated;\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Defines how auth server is deployed\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-resolution\u0022\u003E\uD83D\uDD01 Session Resolution\u003C/h2\u003E\n\u003Cp\u003EControls how session IDs are extracted:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECookie\u003C/li\u003E\n\u003Cli\u003EHeader\u003C/li\u003E\n\u003Cli\u003EBearer\u003C/li\u003E\n\u003Cli\u003EQuery\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Fully configurable\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Server options define \u003Cstrong\u003Ewhat is allowed\u003C/strong\u003E\u003Cbr /\u003E\n\uD83D\uDC49 Runtime determines \u003Cstrong\u003Ewhat is used\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EServer options are the main configuration entry\u003C/li\u003E\n\u003Cli\u003ECore behavior is configured via server\u003C/li\u003E\n\u003Cli\u003EModes are not selected manually\u003C/li\u003E\n\u003Cli\u003EEffective options are computed per request\u003C/li\u003E\n\u003Cli\u003ESecurity is enforced by design\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EClient Options\u003C/strong\u003E\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth is configured primarily through \u003Cstrong\u003EServer Options\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This is the main entry point for configuring authentication behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-are-server-options\u0022\u003E\uD83E\uDDE0 What Are Server Options?\u003C/h2\u003E\n\u003Cp\u003EServer options define how UltimateAuth behaves \u003Cstrong\u003Einside your application\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThey control:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication behavior\u003C/li\u003E\n\u003Cli\u003ESecurity policies\u003C/li\u003E\n\u003Cli\u003EToken issuance\u003C/li\u003E\n\u003Cli\u003ESession lifecycle\u003C/li\u003E\n\u003Cli\u003EEndpoint exposure\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic-usage\u0022\u003E\u2699\uFE0F Basic Usage\u003C/h2\u003E\n\u003Cp\u003EYou configure server options in \u003Ccode\u003EProgram.cs\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\u003Ebuilder.Services.AddUltimateAuthServer(o =\u0026gt;\n{\n o.Login.MaxFailedAttempts = 5;\n o.Session.IdleTimeout = TimeSpan.FromDays(7);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Chr\u003E\n\u003Cp\u003EYou can also use \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;IdleTimeout\u0026quot;: \u0026quot;07.00.00.00\u0026quot;\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003Eappsettings.json\u003C/code\u003E overrides \u003Ccode\u003EProgram.cs\u003C/code\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-composition\u0022\u003E\uD83E\uDDE9 Core Composition\u003C/h2\u003E\n\u003Cp\u003EServer options include Core behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogin\u003C/li\u003E\n\u003Cli\u003ESession\u003C/li\u003E\n\u003Cli\u003EToken\u003C/li\u003E\n\u003Cli\u003EPKCE\u003C/li\u003E\n\u003Cli\u003EMulti-tenancy\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 These are defined in Core\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 But configured via Server\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-you-dont-configure-modes-directly\u0022\u003E\u26A0\uFE0F Important: You Don\u2019t Configure Modes Directly\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth does NOT expect you to select a single auth mode.\u003C/p\u003E\n\u003Cp\u003EInstead:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Mode is resolved at runtime\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022allowed-modes-guardrail\u0022\u003E\uD83D\uDEE1 Allowed Modes (Guardrail)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.AllowedModes = new[]\n{\n UAuthMode.Hybrid,\n UAuthMode.PureOpaque\n};\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This does NOT select a mode\u003Cbr\u003E\n\uD83D\uDC49 It restricts which modes are allowed\u003C/p\u003E\n\u003Cp\u003EIf a resolved mode is not allowed:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Request fails early\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-behavior-effective-options\u0022\u003E\u26A1 Runtime Behavior (Effective Options)\u003C/h2\u003E\n\u003Cp\u003EServer options are not used directly.\u003C/p\u003E\n\u003Cp\u003EThey are transformed into:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003EEffectiveUAuthServerOptions\u003C/code\u003E\u003C/p\u003E\n\u003Cp\u003EThis happens per request:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMode is resolved\u003C/li\u003E\n\u003Cli\u003EDefaults are applied\u003C/li\u003E\n\u003Cli\u003EOverrides are applied\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 What actually runs is \u003Cstrong\u003EEffective Options\u003C/strong\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-based-defaults\u0022\u003E\uD83D\uDD04 Mode-Based Defaults\u003C/h2\u003E\n\u003Cp\u003EEach auth mode applies different defaults automatically:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPureOpaque \u2192 session-heavy\u003C/li\u003E\n\u003Cli\u003EHybrid \u2192 session \u002B token\u003C/li\u003E\n\u003Cli\u003EPureJwt \u2192 token-only\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You don\u2019t need to manually configure everything\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoint-control\u0022\u003E\uD83C\uDF9B Endpoint Control\u003C/h2\u003E\n\u003Cp\u003EYou can control which features are enabled:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Endpoints.Authentication = true;\no.Endpoints.Session = true;\no.Endpoints.Authorization = true;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EYou can also disable specific actions:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Endpoints.DisabledActions.Add(\u0026quot;UAuthActions.Users.Create.Anonymous\u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Useful for API hardening\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022cookie-transport-behavior\u0022\u003E\uD83C\uDF6A Cookie \u0026amp; Transport Behavior\u003C/h2\u003E\n\u003Cp\u003EServer options define how credentials are transported:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECookies\u003C/li\u003E\n\u003Cli\u003EHeaders\u003C/li\u003E\n\u003Cli\u003ETokens\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Unsafe combinations are rejected at startup\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022hub-configuration\u0022\u003E\uD83C\uDF10 Hub Configuration\u003C/h2\u003E\n\u003Cp\u003EIf using UAuthHub:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.HubDeploymentMode = UAuthHubDeploymentMode.Integrated;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Defines how auth server is deployed\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-resolution\u0022\u003E\uD83D\uDD01 Session Resolution\u003C/h2\u003E\n\u003Cp\u003EControls how session IDs are extracted:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECookie\u003C/li\u003E\n\u003Cli\u003EHeader\u003C/li\u003E\n\u003Cli\u003EBearer\u003C/li\u003E\n\u003Cli\u003EQuery\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Fully configurable\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Server options define \u003Cstrong\u003Ewhat is allowed\u003C/strong\u003E\u003Cbr\u003E\n\uD83D\uDC49 Runtime determines \u003Cstrong\u003Ewhat is used\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EServer options are the main configuration entry\u003C/li\u003E\n\u003Cli\u003ECore behavior is configured via server\u003C/li\u003E\n\u003Cli\u003EModes are not selected manually\u003C/li\u003E\n\u003Cli\u003EEffective options are computed per request\u003C/li\u003E\n\u003Cli\u003ESecurity is enforced by design\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EClient Options\u003C/strong\u003E\u003C/p\u003E\n", "Headings": [ { "Id": "what-are-server-options", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json index 1eac283f..c9ec03da 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json @@ -42,7 +42,7 @@ "GroupOrder": 2 }, { - "Title": "Flow Based Authentication", + "Title": "Flow-Based Authentication", "Slug": "fundamentals/flow-based-auth", "Order": 3, "Group": "fundamentals", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/auth-model.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/auth-model.json index 0e9e9033..2f1c136a 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/auth-model.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/auth-model.json @@ -1,91 +1,161 @@ { "Slug": "fundamentals/auth-model", "Title": "Authentication Model", - "Html": "\n\u003Cp\u003EUltimateAuth is built around a simple but powerful idea:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EAuthentication is not just a token.\u003Cbr /\u003E\nIt is a \u003Cstrong\u003Estructured, server-controlled session model\u003C/strong\u003E.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EAt the core of this model are three concepts:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ERoot\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EChain\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession\u003C/strong\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ETogether, they form what we call:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Cstrong\u003EAuthentication Lineage\u003C/strong\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-big-picture\u0022\u003E\uD83D\uDD11 The Big Picture\u003C/h2\u003E\n\u003Cp\u003EInstead of treating authentication as a single token or cookie, UltimateAuth models it as a hierarchy:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ERoot (user authority)\n\u251C\u2500\u2500 Chain (device context)\n\u2502 \u251C\u2500\u2500 Session (login instance)\n\u2502 \u251C\u2500\u2500 Session\n\u2502\n\u251C\u2500\u2500 Chain\n\u2502 \u251C\u2500\u2500 Session (login instance)\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EEach level has a distinct responsibility.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-the-authority\u0022\u003E\uD83E\uDDE9 Root \u2014 The Authority\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003ERoot\u003C/strong\u003E represents the authentication authority of a user within a tenant.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThere is \u003Cstrong\u003Eonly one active Root per user per tenant\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003EIt defines the \u003Cstrong\u003Eglobal security state\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003EIt controls all chains and sessions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-root-does\u0022\u003EWhat Root Does\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ETracks security version (security epoch)\u003C/li\u003E\n\u003Cli\u003EInvalidates all sessions when needed\u003C/li\u003E\n\u003Cli\u003EActs as the \u003Cstrong\u003Esource of truth\u003C/strong\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example\u0022\u003EExample\u003C/h3\u003E\n\u003Cp\u003EIf a user changes their password:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Root is updated\u003Cbr /\u003E\n\uD83D\uDC49 All existing sessions can become invalid\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-the-device-context\u0022\u003E\uD83D\uDD17 Chain \u2014 The Device Context\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EChain\u003C/strong\u003E represents a device or client context.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEach device typically has its own chain\u003C/li\u003E\n\u003Cli\u003EMultiple logins from the same device belong to the same chain\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Think of Chain as:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u201CWhere is the user logged in from?\u201D\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-chain-does\u0022\u003EWhat Chain Does\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cp\u003EGroups sessions by device\u003C/p\u003E\n\u003C/li\u003E\n\u003Cli\u003E\u003Cp\u003EEnables \u003Cstrong\u003Edevice-level control\u003C/strong\u003E\u003C/p\u003E\n\u003C/li\u003E\n\u003Cli\u003E\u003Cp\u003EAllows actions like:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogout from one device\u003C/li\u003E\n\u003Cli\u003ERevoke a specific device\u003C/li\u003E\n\u003C/ul\u003E\n\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-the-authentication-instance\u0022\u003E\uD83E\uDDFE Session \u2014 The Authentication Instance\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003ESession\u003C/strong\u003E is the actual authentication instance.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECreated when the user logs in\u003C/li\u003E\n\u003Cli\u003ERepresents a single authenticated state\u003C/li\u003E\n\u003Cli\u003ECarries a snapshot of the Root security version\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This is what gets validated on each request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-session-does\u0022\u003EWhat Session Does\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EProves the user is authenticated\u003C/li\u003E\n\u003Cli\u003ECan be refreshed, revoked, or expired\u003C/li\u003E\n\u003Cli\u003EIs tied to a specific chain\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-they-work-together\u0022\u003E\uD83D\uDD04 How They Work Together\u003C/h2\u003E\n\u003Cp\u003EWhen a user logs in:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003ERoot is resolved (or created)\u003C/li\u003E\n\u003Cli\u003EA Chain is identified (device context)\u003C/li\u003E\n\u003Cli\u003EA new Session is created\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003E\u003Ccode\u003ELogin \u2192 Root \u2192 Chain \u2192 Session\u003C/code\u003E\u003C/p\u003E\n\u003Cp\u003EOn each request:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003ESession is validated\u003C/li\u003E\n\u003Cli\u003EChain context is checked\u003C/li\u003E\n\u003Cli\u003ERoot security version is verified\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003E\uD83D\uDC49 If any level is invalid \u2192 authentication fails\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-model-matters\u0022\u003E\uD83D\uDEE1 Why This Model Matters\u003C/h2\u003E\n\u003Cp\u003ETraditional systems rely on:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECookies\u003C/li\u003E\n\u003Cli\u003EJWT tokens\u003C/li\u003E\n\u003Cli\u003EStateless validation\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese approaches have limitations:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ENo real session control\u003C/li\u003E\n\u003Cli\u003EWeak revocation\u003C/li\u003E\n\u003Cli\u003ENo device awareness\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr /\u003E\n\u003Cp\u003EUltimateAuth solves this by:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-controlled-authentication\u0022\u003E\u2714 Server-Controlled Authentication\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESessions are always validated server-side\u003C/li\u003E\n\u003Cli\u003ENo blind trust in tokens\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022instant-revocation\u0022\u003E\u2714 Instant Revocation\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ERevoke a session \u2192 immediate effect\u003C/li\u003E\n\u003Cli\u003ERevoke a chain \u2192 device logged out\u003C/li\u003E\n\u003Cli\u003ERevoke root \u2192 disable user (global logout)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-awareness\u0022\u003E\u2714 Device Awareness\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EEach device has its own chain\u003C/li\u003E\n\u003Cli\u003ESessions are bound to context\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022strong-security-model\u0022\u003E\u2714 Strong Security Model\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession carries Root security version\u003C/li\u003E\n\u003Cli\u003EOld sessions automatically become invalid\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember only one thing, remember this:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Cstrong\u003ERoot = authority\u003C/strong\u003E\u003Cbr /\u003E\n\uD83D\uDC49 \u003Cstrong\u003EChain = device\u003C/strong\u003E\u003Cbr /\u003E\n\uD83D\uDC49 \u003Cstrong\u003ESession = login\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication is not just a token\u003C/li\u003E\n\u003Cli\u003ESessions are first-class citizens\u003C/li\u003E\n\u003Cli\u003EThe server always remains in control\u003C/li\u003E\n\u003Cli\u003EDevice and security context are built-in\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003ENow that you understand the core model:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Continue to \u003Cstrong\u003EFlow-Based Authentication\u003C/strong\u003E\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth is built around a fundamental principle: \u003Cstrong\u003Eauthentication is a trusted process for establishing identity, not simply the presence of a token, cookie, or session identifier.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAn authentication credential provides evidence of identity. The server must evaluate that evidence according to the active authentication mode before treating a request as authenticated.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides a common authentication architecture that supports both server-side sessions and cryptographically verifiable tokens.\u003C/p\u003E\n\u003Cp\u003EThe authentication mode determines how identity is established, what state must be validated, and which lifecycle guarantees apply.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-and-identity\u0022\u003EAuthentication and identity\u003C/h2\u003E\n\u003Cp\u003EAuthentication answers a specific question:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EWho is making this request, and what evidence establishes that identity?\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAn authentication operation may involve credentials such as passwords, existing sessions, access tokens, refresh tokens, or other supported authentication proofs.\u003C/p\u003E\n\u003Cp\u003EThese credentials have different purposes.\u003C/p\u003E\n\u003Cp\u003EA password can establish a new authenticated context. An access token can represent an already established identity. A refresh token can continue an existing authentication context without repeating the original login.\u003C/p\u003E\n\u003Cp\u003ENone of these credentials should be trusted merely because a client presents them.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth evaluates authentication evidence through framework-controlled services and produces a trusted identity context only after the applicable validation succeeds.\u003C/p\u003E\n\u003Cp\u003EAuthorization is a separate responsibility. It determines what an authenticated actor is allowed to do.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-authority\u0022\u003EServer authority\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is server-authoritative.\u003C/p\u003E\n\u003Cp\u003EThis means the server defines the authentication rules and determines whether the presented evidence satisfies them.\u003C/p\u003E\n\u003Cp\u003EServer authority does not require every request to access a server-side session store.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EIn PureOpaque, the server validates the session represented by the client\u0027s opaque session credential.\u003C/li\u003E\n\u003Cli\u003EIn Hybrid, the server resolves an opaque access token and validates its associated session.\u003C/li\u003E\n\u003Cli\u003EIn SemiHybrid, normal requests authenticate through JWT validation, while server-side sessions govern authentication continuation and lifecycle operations.\u003C/li\u003E\n\u003Cli\u003EIn PureJwt, authentication can be established through JWT validation without requiring session persistence or lookup.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIn all cases, the effective authentication mode, validation requirements, and security policies remain under trusted server control.\u003C/p\u003E\n\u003Cp\u003EThe client cannot establish its own authenticated identity merely by supplying claims, identifiers, or an arbitrary authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-state\u0022\u003EAuthentication state\u003C/h2\u003E\n\u003Cp\u003EAuthentication state can be represented in different ways.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EServer-side authentication state\u003C/strong\u003E is maintained by the authentication system and evaluated when required. It enables centralized session management and immediate session-based revocation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EToken-based authentication state\u003C/strong\u003E can be represented by cryptographically verifiable credentials. A JWT can carry sufficient information to establish identity without a session lookup, provided its signature and applicable validation rules are satisfied.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ECombined authentication state\u003C/strong\u003E uses both approaches for different purposes. For example, an application can validate JWTs independently while retaining server-side state for refresh and session management.\u003C/p\u003E\n\u003Cp\u003EThese are different authentication strategies, not separate authentication architectures.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth preserves common trust boundaries while allowing the effective mode to determine how authentication state is represented and evaluated.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-lineage\u0022\u003EAuthentication lineage\u003C/h2\u003E\n\u003Cp\u003EFor session-backed authentication, UltimateAuth models authentication state through three related concepts:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ERoot:\u003C/strong\u003E User-level authentication authority within a tenant.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EChain:\u003C/strong\u003E A grouping of related authentication sessions, typically associated with a device or client context.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession:\u003C/strong\u003E An individual authenticated session.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ETogether, these form the authentication lineage.\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\u003ERoot\n |\n \u002B-- Chain A\n | |\n | \u002B-- Session 1\n | |\n | \u002B-- Session 2\n |\n \u002B-- Chain B\n |\n \u002B-- Session 3\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe lineage provides a structured way to manage authentication state at different scopes.\u003C/p\u003E\n\u003Cp\u003EIt allows the system to distinguish between terminating one session, terminating sessions associated with a particular chain, and applying a broader user-level security change.\u003C/p\u003E\n\u003Cp\u003EThe lineage is central to session-backed authentication. It must not be interpreted as a mandatory persistence requirement for every authentication mode.\u003C/p\u003E\n\u003Cp\u003EIn particular, PureJwt does not require Root, Chain, or Session records to authenticate requests.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-user-level-authentication-authority\u0022\u003ERoot: user-level authentication authority\u003C/h2\u003E\n\u003Cp\u003ERoot represents the user-level authority within a tenant\u0027s session-backed authentication lineage.\u003C/p\u003E\n\u003Cp\u003EIt provides a common security boundary for related chains and sessions.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured policies and supported operations, Root can participate in:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ETracking user-level authentication security state.\u003C/li\u003E\n\u003Cli\u003ECoordinating security changes affecting multiple sessions.\u003C/li\u003E\n\u003Cli\u003EApplying broader session invalidation.\u003C/li\u003E\n\u003Cli\u003EMaintaining a security version or epoch used by session-backed validation.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, a security-sensitive credential change may trigger a policy that invalidates existing sessions.\u003C/p\u003E\n\u003Cp\u003ERather than treating each session as unrelated, the lineage provides a common authority through which the affected authentication state can be managed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-versions\u0022\u003ESecurity versions\u003C/h3\u003E\n\u003Cp\u003EA security version, sometimes called a security epoch, can be used to determine whether an existing session still belongs to the current authentication security state.\u003C/p\u003E\n\u003Cp\u003EA session may retain the version associated with its creation. When the relevant authority changes, validation can reject sessions associated with an outdated version.\u003C/p\u003E\n\u003Cp\u003EThis mechanism is meaningful when authentication validation consults the relevant server-side state.\u003C/p\u003E\n\u003Cp\u003EIt does not automatically invalidate a previously issued JWT that is validated without a server-side lookup.\u003C/p\u003E\n\u003Cp\u003EThe exact invalidation behavior depends on the authentication mode and configured security policies.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-related-session-context\u0022\u003EChain: related session context\u003C/h2\u003E\n\u003Cp\u003EChain groups related authentication sessions under a common lifecycle context.\u003C/p\u003E\n\u003Cp\u003EA chain commonly represents a device or client context, but its purpose is broader than storing a device identifier.\u003C/p\u003E\n\u003Cp\u003EIt provides a boundary for managing related authentication activity.\u003C/p\u003E\n\u003Cp\u003EA chain can support:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EGrouping related sessions.\u003C/li\u003E\n\u003Cli\u003EDevice-oriented session management.\u003C/li\u003E\n\u003Cli\u003EChain-level expiration and invalidation.\u003C/li\u003E\n\u003Cli\u003ECoordinating refresh-token lifecycle where applicable.\u003C/li\u003E\n\u003Cli\u003EApplying security policies to related authentication sessions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, an application may allow a user to terminate authentication associated with a particular device without terminating every session belonging to the user.\u003C/p\u003E\n\u003Cp\u003EA chain provides a suitable scope for that operation.\u003C/p\u003E\n\u003Cp\u003EA chain is not itself proof of authentication. Its validity and relationship to the relevant session must be evaluated according to the active mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-an-authenticated-instance\u0022\u003ESession: an authenticated instance\u003C/h2\u003E\n\u003Cp\u003ESession represents an individual authenticated instance maintained by the server.\u003C/p\u003E\n\u003Cp\u003EIt can contain or reference the information required to manage that instance, such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe authenticated user and tenant.\u003C/li\u003E\n\u003Cli\u003EIts relationship to a chain.\u003C/li\u003E\n\u003Cli\u003ECreation and expiration information.\u003C/li\u003E\n\u003Cli\u003ERevocation or lifecycle status.\u003C/li\u003E\n\u003Cli\u003EApplicable security-version information.\u003C/li\u003E\n\u003Cli\u003ESession metadata required by supported policies.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe role of Session differs by authentication mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-authoritative-authentication\u0022\u003ESession-authoritative authentication\u003C/h3\u003E\n\u003Cp\u003EIn PureOpaque and Hybrid, session validity is part of the authentication decision.\u003C/p\u003E\n\u003Cp\u003EA request cannot be treated as authenticated merely because it presents a structurally valid credential. The associated session must also satisfy the applicable validation requirements.\u003C/p\u003E\n\u003Cp\u003EIn these modes, session invalidation affects subsequent authentication evaluations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-managed-authentication\u0022\u003ESession-managed authentication\u003C/h3\u003E\n\u003Cp\u003EIn SemiHybrid, sessions support lifecycle operations such as refresh, logout, device management, and session revocation.\u003C/p\u003E\n\u003Cp\u003EHowever, normal JWT authentication does not require session validation.\u003C/p\u003E\n\u003Cp\u003EConsequently, revoking a session can prevent future refresh operations without automatically invalidating an already issued JWT.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-independent-authentication\u0022\u003ESession-independent authentication\u003C/h3\u003E\n\u003Cp\u003EPureJwt does not require a Session record.\u003C/p\u003E\n\u003Cp\u003EAuthentication relies on cryptographic token validation and applicable claims and token-validation policies.\u003C/p\u003E\n\u003Cp\u003EThere is no mandatory session authority or session lookup in its authentication pipeline.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022establishing-a-new-authentication-context\u0022\u003EEstablishing a new authentication context\u003C/h2\u003E\n\u003Cp\u003EAuthentication begins when the system receives evidence sufficient to establish a new authenticated identity.\u003C/p\u003E\n\u003Cp\u003EA typical login operation follows these conceptual steps:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EResolve the effective authentication context and security policies.\u003C/li\u003E\n\u003Cli\u003EValidate the presented authentication proof.\u003C/li\u003E\n\u003Cli\u003EEstablish the authenticated identity.\u003C/li\u003E\n\u003Cli\u003ECreate the authentication state required by the selected mode.\u003C/li\u003E\n\u003Cli\u003EIssue the appropriate authentication credentials.\u003C/li\u003E\n\u003Cli\u003EDeliver those credentials through the configured transport mechanism.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe fourth and fifth steps are mode-dependent.\u003C/p\u003E\n\u003Cp\u003EFor session-backed modes, establishing authentication can involve creating or resolving the relevant Root, Chain, and Session state.\u003C/p\u003E\n\u003Cp\u003EFor PureJwt, issuing a JWT must not require creating a server-side session.\u003C/p\u003E\n\u003Cp\u003EThe shared authentication pipeline does not imply that every mode performs identical persistence operations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authenticating-subsequent-requests\u0022\u003EAuthenticating subsequent requests\u003C/h2\u003E\n\u003Cp\u003EAfter login, a client presents the credential appropriate for its authentication mode.\u003C/p\u003E\n\u003Cp\u003EThe request pipeline validates that credential and establishes the trusted 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\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 Request\n |\n v\nEffective Authentication Context\n |\n v\nCredential Resolution\n |\n v\nMode-Specific Authentication Validation\n |\n \u002B-- PureOpaque: Session validation\n |\n \u002B-- Hybrid: Opaque token \u002B session validation\n |\n \u002B-- SemiHybrid: JWT validation\n |\n \u002B-- PureJwt: JWT validation\n |\n v\nAuthenticated Identity\n |\n v\nAccessContext\n |\n v\nAuthorization\n |\n v\nProtected Operation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe authentication mode determines which validation steps are required.\u003C/p\u003E\n\u003Cp\u003EThe resulting identity must satisfy the same framework-level trust requirements regardless of the credential format.\u003C/p\u003E\n\u003Cp\u003EAn application must not construct a trusted identity directly from unvalidated client input.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-context-and-authorization-context\u0022\u003EAuthentication context and authorization context\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes the context used to execute an authentication flow from the context used to evaluate authorization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authflowcontext\u0022\u003EAuthFlowContext\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlowContext\u003C/code\u003E represents the effective authentication context for an operation.\u003C/p\u003E\n\u003Cp\u003EIt includes the relevant mode, client characteristics, tenant context, and other applicable runtime information.\u003C/p\u003E\n\u003Cp\u003EThe framework establishes this context through controlled boundaries.\u003C/p\u003E\n\u003Cp\u003EOnce established, the effective context remains stable throughout the operation.\u003C/p\u003E\n\u003Cp\u003EApplication code and individual domain services must not independently replace or reconstruct it to change the security rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022accesscontext\u0022\u003EAccessContext\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E represents the trusted actor and authorization context after authentication has been evaluated.\u003C/p\u003E\n\u003Cp\u003EIt provides the identity and contextual information needed for authorization and policy decisions.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E does not authenticate credentials.\u003C/p\u003E\n\u003Cp\u003EAuthentication must be resolved before a trusted authorization context is established.\u003C/p\u003E\n\u003Cp\u003EThis separation allows the authentication mechanism to vary without requiring a different authorization architecture for each mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-lifecycle\u0022\u003EAuthentication lifecycle\u003C/h2\u003E\n\u003Cp\u003EAuthentication does not end when credentials are issued.\u003C/p\u003E\n\u003Cp\u003EDepending on the mode, an authenticated context may be continued, renewed, expired, or revoked.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth distinguishes several lifecycle operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication\u0022\u003EAuthentication\u003C/h3\u003E\n\u003Cp\u003EEstablishes a new authenticated context after the required proof has been validated.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh\u0022\u003ERefresh\u003C/h3\u003E\n\u003Cp\u003EContinues an existing authentication context when its continuation requirements are satisfied.\u003C/p\u003E\n\u003Cp\u003ERefresh is mode-dependent.\u003C/p\u003E\n\u003Cp\u003EPureOpaque uses session continuation and touch semantics rather than refresh-token rotation.\u003C/p\u003E\n\u003Cp\u003EHybrid and SemiHybrid use refresh-token rotation together with the applicable session validation requirements.\u003C/p\u003E\n\u003Cp\u003EPureJwt may support session-independent token renewal when refresh tokens are enabled.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003ELogout\u003C/h3\u003E\n\u003Cp\u003EEnds or discards the relevant authentication context according to the mode.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, server-side session revocation can prevent subsequent authentication.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, removing credentials from the client does not by itself invalidate already issued JWTs.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation\u0022\u003ERevocation\u003C/h3\u003E\n\u003Cp\u003EInvalidates authentication state or credentials according to the guarantees supported by the selected mode.\u003C/p\u003E\n\u003Cp\u003ESession revocation and JWT revocation are not equivalent operations.\u003C/p\u003E\n\u003Cp\u003EA server-side session can be revoked immediately, but a JWT validated without session lookup can remain valid until expiration.\u003C/p\u003E\n\u003Cp\u003EApplications requiring stronger access-token invalidation guarantees must select a mode and validation strategy that explicitly provides them.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-security-boundaries\u0022\u003EAuthentication security boundaries\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s authentication architecture relies on several principles that apply across modes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentials-are-evidence-not-authority\u0022\u003ECredentials are evidence, not authority\u003C/h3\u003E\n\u003Cp\u003EThe presence of a token, cookie, or session identifier does not automatically establish identity.\u003C/p\u003E\n\u003Cp\u003EThe required validation must succeed first.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-modes-have-explicit-guarantees\u0022\u003EAuthentication modes have explicit guarantees\u003C/h3\u003E\n\u003Cp\u003EA mode must not silently weaken its validation or revocation guarantees because of client runtime, transport selection, or performance optimization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-authority-is-mode-dependent\u0022\u003ESession authority is mode-dependent\u003C/h3\u003E\n\u003Cp\u003EServer-side sessions are authoritative where the mode requires them.\u003C/p\u003E\n\u003Cp\u003EStateless authentication does not become session-backed merely because other parts of the framework support session management.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-context-is-not-inherently-trusted\u0022\u003EClient context is not inherently trusted\u003C/h3\u003E\n\u003Cp\u003EDevice identifiers, tenant identifiers, and client-provided runtime information must be handled according to the applicable trust boundaries.\u003C/p\u003E\n\u003Cp\u003EThey must not allow a client to bypass authentication requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-and-authorization-remain-separate\u0022\u003EAuthentication and authorization remain separate\u003C/h3\u003E\n\u003Cp\u003EA valid identity does not automatically imply permission to access a resource.\u003C/p\u003E\n\u003Cp\u003EAuthorization policies must still evaluate the authenticated actor\u0027s access rights.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-protection-remains-essential\u0022\u003ECredential protection remains essential\u003C/h3\u003E\n\u003Cp\u003EOpaque session credentials, opaque access tokens, JWTs, and refresh tokens are all sensitive.\u003C/p\u003E\n\u003Cp\u003ETheir safe use depends on appropriate transport, storage, expiration, and validation policies.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-the-model-relates-to-authentication-modes\u0022\u003EHow the model relates to authentication modes\u003C/h2\u003E\n\u003Cp\u003EThe authentication model establishes the shared principles and boundaries.\u003C/p\u003E\n\u003Cp\u003EAuthentication modes determine how those principles are executed.\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\u003EAuthentication authority\u003C/th\u003E\n\u003Cth\u003ERole of session state\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPureOpaque\u003C/td\u003E\n\u003Ctd\u003EServer-side session\u003C/td\u003E\n\u003Ctd\u003ERequired for authentication and lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EHybrid\u003C/td\u003E\n\u003Ctd\u003EServer-side session, accessed through an opaque token\u003C/td\u003E\n\u003Ctd\u003ERequired for authentication and lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESemiHybrid\u003C/td\u003E\n\u003Ctd\u003ECryptographically validated JWT for normal requests\u003C/td\u003E\n\u003Ctd\u003ERequired for session lifecycle, not default request validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPureJwt\u003C/td\u003E\n\u003Ctd\u003ECryptographically validated JWT\u003C/td\u003E\n\u003Ctd\u003ENot required\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis distinction is essential to understanding UltimateAuth.\u003C/p\u003E\n\u003Cp\u003ERoot, Chain, and Session provide structured lifecycle control where server-side authentication state is required.\u003C/p\u003E\n\u003Cp\u003EThey are not universal prerequisites for authenticating a request.\u003C/p\u003E\n\u003Cp\u003ELikewise, JWT authentication does not eliminate server authority. The server still defines which tokens are acceptable and which validation rules must be enforced.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth defines authentication as a controlled process that establishes trusted identity from validated evidence.\u003C/p\u003E\n\u003Cp\u003EIts architecture separates:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication proof and credential validation.\u003C/li\u003E\n\u003Cli\u003EAuthentication state and lifecycle management.\u003C/li\u003E\n\u003Cli\u003EAuthentication context and authorization context.\u003C/li\u003E\n\u003Cli\u003EShared security boundaries and mode-specific execution strategies.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ERoot, Chain, and Session form the authentication lineage used by session-backed modes.\u003C/p\u003E\n\u003Cp\u003EPureOpaque and Hybrid rely on that lineage as part of their authentication authority.\u003C/p\u003E\n\u003Cp\u003ESemiHybrid retains session lifecycle management while allowing JWT authentication without request-time session lookup.\u003C/p\u003E\n\u003Cp\u003EPureJwt does not require session persistence or validation.\u003C/p\u003E\n\u003Cp\u003EThese distinctions allow UltimateAuth to support different authentication strategies without changing the fundamental trust and authorization model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003ENext step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EFlow-Based Authentication\u003C/strong\u003E to understand how UltimateAuth establishes and executes authentication operations through its controlled flow pipeline.\u003C/p\u003E\n", "Headings": [ { - "Id": "the-big-picture", - "Text": "\uD83D\uDD11 The Big Picture", + "Id": "authentication-and-identity", + "Text": "Authentication and identity", "Level": 0 }, { - "Id": "root-the-authority", - "Text": "\uD83E\uDDE9 Root \u2014 The Authority", + "Id": "server-authority", + "Text": "Server authority", "Level": 0 }, { - "Id": "what-root-does", - "Text": "What Root Does", - "Level": 1 + "Id": "authentication-state", + "Text": "Authentication state", + "Level": 0 + }, + { + "Id": "authentication-lineage", + "Text": "Authentication lineage", + "Level": 0 }, { - "Id": "example", - "Text": "Example", + "Id": "root-user-level-authentication-authority", + "Text": "Root: user-level authentication authority", + "Level": 0 + }, + { + "Id": "security-versions", + "Text": "Security versions", "Level": 1 }, { - "Id": "chain-the-device-context", - "Text": "\uD83D\uDD17 Chain \u2014 The Device Context", + "Id": "chain-related-session-context", + "Text": "Chain: related session context", + "Level": 0 + }, + { + "Id": "session-an-authenticated-instance", + "Text": "Session: an authenticated instance", "Level": 0 }, { - "Id": "what-chain-does", - "Text": "What Chain Does", + "Id": "session-authoritative-authentication", + "Text": "Session-authoritative authentication", "Level": 1 }, { - "Id": "session-the-authentication-instance", - "Text": "\uD83E\uDDFE Session \u2014 The Authentication Instance", + "Id": "session-managed-authentication", + "Text": "Session-managed authentication", + "Level": 1 + }, + { + "Id": "session-independent-authentication", + "Text": "Session-independent authentication", + "Level": 1 + }, + { + "Id": "establishing-a-new-authentication-context", + "Text": "Establishing a new authentication context", + "Level": 0 + }, + { + "Id": "authenticating-subsequent-requests", + "Text": "Authenticating subsequent requests", + "Level": 0 + }, + { + "Id": "authentication-context-and-authorization-context", + "Text": "Authentication context and authorization context", "Level": 0 }, { - "Id": "what-session-does", - "Text": "What Session Does", + "Id": "authflowcontext", + "Text": "AuthFlowContext", + "Level": 1 + }, + { + "Id": "accesscontext", + "Text": "AccessContext", "Level": 1 }, { - "Id": "how-they-work-together", - "Text": "\uD83D\uDD04 How They Work Together", + "Id": "authentication-lifecycle", + "Text": "Authentication lifecycle", "Level": 0 }, { - "Id": "why-this-model-matters", - "Text": "\uD83D\uDEE1 Why This Model Matters", + "Id": "authentication", + "Text": "Authentication", + "Level": 1 + }, + { + "Id": "refresh", + "Text": "Refresh", + "Level": 1 + }, + { + "Id": "logout", + "Text": "Logout", + "Level": 1 + }, + { + "Id": "revocation", + "Text": "Revocation", + "Level": 1 + }, + { + "Id": "authentication-security-boundaries", + "Text": "Authentication security boundaries", "Level": 0 }, { - "Id": "server-controlled-authentication", - "Text": "\u2714 Server-Controlled Authentication", + "Id": "credentials-are-evidence-not-authority", + "Text": "Credentials are evidence, not authority", + "Level": 1 + }, + { + "Id": "authentication-modes-have-explicit-guarantees", + "Text": "Authentication modes have explicit guarantees", + "Level": 1 + }, + { + "Id": "session-authority-is-mode-dependent", + "Text": "Session authority is mode-dependent", "Level": 1 }, { - "Id": "instant-revocation", - "Text": "\u2714 Instant Revocation", + "Id": "client-context-is-not-inherently-trusted", + "Text": "Client context is not inherently trusted", "Level": 1 }, { - "Id": "device-awareness", - "Text": "\u2714 Device Awareness", + "Id": "authentication-and-authorization-remain-separate", + "Text": "Authentication and authorization remain separate", "Level": 1 }, { - "Id": "strong-security-model", - "Text": "\u2714 Strong Security Model", + "Id": "credential-protection-remains-essential", + "Text": "Credential protection remains essential", "Level": 1 }, { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", + "Id": "how-the-model-relates-to-authentication-modes", + "Text": "How the model relates to authentication modes", "Level": 0 }, { - "Id": "key-takeaways", - "Text": "\uD83D\uDCCC Key Takeaways", + "Id": "summary", + "Text": "Summary", "Level": 0 }, { "Id": "next-step", - "Text": "\u27A1\uFE0F Next Step", + "Text": "Next step", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/auth-modes.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/auth-modes.json index 6904fede..98396abd 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/auth-modes.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/auth-modes.json @@ -1,86 +1,186 @@ { "Slug": "fundamentals/auth-modes", "Title": "Authentication Modes", - "Html": "\u003Cblockquote\u003E\n\u003Cp\u003ENote: SemiHybrid and PureJwt modes will be available on future releases. For now you can safely use PureOpaque and Hybrid modes.\u003C/p\u003E\n\u003C/blockquote\u003E\n\n\u003Cp\u003EUltimateAuth supports multiple authentication modes.\u003C/p\u003E\n\u003Cp\u003EEach mode represents a different balance between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESecurity\u003C/li\u003E\n\u003Cli\u003EPerformance\u003C/li\u003E\n\u003Cli\u003EControl\u003C/li\u003E\n\u003Cli\u003EClient capabilities\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You don\u2019t always choose a single model.\u003Cbr /\u003E\nUltimateAuth can adapt based on context.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022available-modes\u0022\u003E\uD83E\uDDE9 Available Modes\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque\u0022\u003EPureOpaque\u003C/h3\u003E\n\u003Cp\u003EFully server-controlled session model.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003EHybrid\u003C/h3\u003E\n\u003Cp\u003ECombines session control with token-based access.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid\u0022\u003ESemiHybrid\u003C/h3\u003E\n\u003Cp\u003EJWT-based with server-side metadata awareness.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022purejwt\u0022\u003EPureJwt\u003C/h3\u003E\n\u003Cp\u003EFully stateless token-based authentication.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-comparison\u0022\u003E\u2696\uFE0F Mode Comparison\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EFeature\u003C/th\u003E\n\u003Cth\u003EPureOpaque\u003C/th\u003E\n\u003Cth\u003EHybrid\u003C/th\u003E\n\u003Cth\u003ESemiHybrid\u003C/th\u003E\n\u003Cth\u003EPureJwt\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESessionId\u003C/td\u003E\n\u003Ctd\u003ERequired\u003C/td\u003E\n\u003Ctd\u003ERequired\u003C/td\u003E\n\u003Ctd\u003EMetadata only\u003C/td\u003E\n\u003Ctd\u003ENone\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAccess Token\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2714\u003C/td\u003E\n\u003Ctd\u003E\u2714\u003C/td\u003E\n\u003Ctd\u003E\u2714\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERefresh Token\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2714\u003C/td\u003E\n\u003Ctd\u003E\u2714\u003C/td\u003E\n\u003Ctd\u003E\u2714\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERevocation\u003C/td\u003E\n\u003Ctd\u003EImmediate\u003C/td\u003E\n\u003Ctd\u003EImmediate\u003C/td\u003E\n\u003Ctd\u003EMetadata-based\u003C/td\u003E\n\u003Ctd\u003ENot immediate\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EStatefulness\u003C/td\u003E\n\u003Ctd\u003EFull\u003C/td\u003E\n\u003Ctd\u003EHybrid\u003C/td\u003E\n\u003Ctd\u003ESemi\u003C/td\u003E\n\u003Ctd\u003EStateless\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EServer Control\u003C/td\u003E\n\u003Ctd\u003EFull\u003C/td\u003E\n\u003Ctd\u003EHigh\u003C/td\u003E\n\u003Ctd\u003EMedium\u003C/td\u003E\n\u003Ctd\u003ELow\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPerformance*\u003C/td\u003E\n\u003Ctd\u003EMedium\u003C/td\u003E\n\u003Ctd\u003EMedium\u003C/td\u003E\n\u003Ctd\u003EHigh\u003C/td\u003E\n\u003Ctd\u003EHighest\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOffline Support\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003EPartial\u003C/td\u003E\n\u003Ctd\u003E\u2714\u003C/td\u003E\n\u003Ctd\u003E\u2714\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u26A1 \u003Cstrong\u003EPerformance Note\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAll modes in UltimateAuth are designed for production use and are highly optimized.\u003C/p\u003E\n\u003Cp\u003EThe differences here are about \u003Cstrong\u003Etrade-offs\u003C/strong\u003E, not absolute speed:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Even the most server-controlled mode is performant enough for real-world applications.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-to-think-about-auth-modes\u0022\u003E\uD83E\uDDE0 How to Think About Auth Modes\u003C/h2\u003E\n\u003Cp\u003EIt\u2019s important to understand that authentication modes in UltimateAuth are not rigid system-wide choices.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You are not expected to pick a single mode and use it everywhere.\u003C/p\u003E\n\u003Cp\u003EInstead:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDifferent clients can use different modes on a single UAuthHub\u003C/li\u003E\n\u003Cli\u003EThe mode can change \u003Cstrong\u003Eper request\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003EUltimateAuth selects the most appropriate mode based on \u003Cstrong\u003EClient Profile and runtime context\u003C/strong\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-driven-behavior\u0022\u003E\uD83D\uDD04 Runtime-Driven Behavior\u003C/h3\u003E\n\u003Cp\u003EIn a typical application:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBlazor Server \u2192 PureOpaque\u003C/li\u003E\n\u003Cli\u003EBlazor WASM \u2192 Hybrid\u003C/li\u003E\n\u003Cli\u003EAPI \u2192 PureJwt\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 All can coexist in the same system.\u003C/p\u003E\n\u003Cp\u003EYou don\u2019t split your architecture \u2014 UltimateAuth adapts automatically.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022you-can-override-everything\u0022\u003E\u2699\uFE0F You Can Override Everything\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth provides \u003Cstrong\u003Esafe defaults\u003C/strong\u003E, but nothing is locked.\u003C/p\u003E\n\u003Cp\u003EYou can:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EForce a specific auth mode\u003C/li\u003E\n\u003Cli\u003ECustomize behavior per client\u003C/li\u003E\n\u003Cli\u003EReplace default strategies\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 The system is designed to be flexible without sacrificing safety.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022safe-by-default\u0022\u003E\uD83D\uDEE1 Safe by Default\u003C/h3\u003E\n\u003Cp\u003EThe comparison table shows trade-offs \u2014 not risks.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAll modes are \u003Cstrong\u003Evalid and supported\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003EChoosing a different mode will not \u201Cbreak\u201D security\u003C/li\u003E\n\u003Cli\u003EIncompatible configurations will \u003Cstrong\u003Efail fast\u003C/strong\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You are always operating within a safe boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83D\uDCA1 Mental Model\u003C/h3\u003E\n\u003Cp\u003EThink of auth modes as:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EDifferent execution strategies \u2014 not different systems.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EUltimateAuth remains consistent.\u003Cbr /\u003E\nOnly the \u003Cstrong\u003Ebehavior adapts\u003C/strong\u003E.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque-1\u0022\u003E\uD83D\uDD10 PureOpaque\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EFully session-based\u003C/li\u003E\n\u003Cli\u003EEvery request validated on server\u003C/li\u003E\n\u003Cli\u003EMaximum security\u003C/li\u003E\n\u003Cli\u003ETouch semantics instead of refresh rotation\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Best for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBlazor Server\u003C/li\u003E\n\u003Cli\u003EInternal apps\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid-1\u0022\u003E\u26A1 Hybrid\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EAccess token as opaque session id\u003C/li\u003E\n\u003Cli\u003ERefresh token with rotate semantics\u003C/li\u003E\n\u003Cli\u003EServer control with API performance\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Best for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBlazor WASM\u003C/li\u003E\n\u003Cli\u003EWeb \u002B API systems\u003C/li\u003E\n\u003Cli\u003EFull-stack apps\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid-1\u0022\u003E\uD83D\uDE80 SemiHybrid\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EJWT-based access\u003C/li\u003E\n\u003Cli\u003EServer-side metadata control\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Best for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EHigh-performance APIs\u003C/li\u003E\n\u003Cli\u003EZero-trust systems\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022purejwt-1\u0022\u003E\uD83C\uDF10 PureJwt\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EFully stateless\u003C/li\u003E\n\u003Cli\u003ENo server-side session control\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Best for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EExternal APIs\u003C/li\u003E\n\u003Cli\u003EMicroservices\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022which-mode-should-you-use\u0022\u003E\uD83C\uDFAF Which Mode Should You Use?\u003C/h2\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EScenario\u003C/th\u003E\n\u003Cth\u003ERecommended Mode\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\u003EPureOpaque\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EWeb \u002B API\u003C/td\u003E\n\u003Ctd\u003EHybrid\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EHigh-scale API\u003C/td\u003E\n\u003Ctd\u003ESemiHybrid\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EExternal microservices\u003C/td\u003E\n\u003Ctd\u003EPureJwt\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth provides four authentication modes: \u003Cstrong\u003EPureOpaque\u003C/strong\u003E, \u003Cstrong\u003EHybrid\u003C/strong\u003E, \u003Cstrong\u003ESemiHybrid\u003C/strong\u003E, and \u003Cstrong\u003EPureJwt\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EEach mode defines how authentication credentials are issued, validated, renewed, and revoked. The modes share the same authentication and authorization architecture, but differ in their reliance on server-side sessions, credential formats, and lifecycle guarantees.\u003C/p\u003E\n\u003Cp\u003EThe choice of authentication mode affects more than performance. It determines where authentication state is maintained, whether a server-side session must be consulted during an authenticated request, and how quickly previously issued credentials can be invalidated.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003EAvailability\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EPureOpaque and Hybrid are supported in the initial release. SemiHybrid and PureJwt are defined by the UltimateAuth architecture but are not yet available as completed, production-supported modes. Their behavior is documented here to explain the intended authentication model.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022auth-credentials\u0022\u003EAuth credentials\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes three credential concepts.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESession credential\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA session credential identifies an authenticated session maintained by the server. In PureOpaque, it is the credential presented by the client to authenticate requests.\u003C/p\u003E\n\u003Cp\u003EA session credential may be represented by an opaque, randomly generated value. It does not need to expose the internal database identifier of the session.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAccess token\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAn access token is a credential used to authenticate requests to protected resources.\u003C/p\u003E\n\u003Cp\u003EDepending on the mode, it may be:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn opaque token resolved and validated by the server.\u003C/li\u003E\n\u003Cli\u003EA JWT validated cryptographically without a session lookup.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAn access token is not necessarily a session identifier. In Hybrid, the opaque access token is a separate credential associated with a server-side session.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERefresh token\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA refresh token allows an existing authentication context to continue by obtaining replacement credentials without requiring the user to authenticate again.\u003C/p\u003E\n\u003Cp\u003ERefresh tokens have their own validation, expiration, rotation, and replay-protection requirements.\u003C/p\u003E\n\u003Cp\u003ENot every authentication mode requires all three credential types.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-comparison\u0022\u003EMode comparison\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECharacteristic\u003C/th\u003E\n\u003Cth\u003EPureOpaque\u003C/th\u003E\n\u003Cth\u003EHybrid\u003C/th\u003E\n\u003Cth\u003ESemiHybrid\u003C/th\u003E\n\u003Cth\u003EPureJwt\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient authentication credential\u003C/td\u003E\n\u003Ctd\u003EOpaque session credential\u003C/td\u003E\n\u003Ctd\u003EOpaque access token\u003C/td\u003E\n\u003Ctd\u003EJWT access token\u003C/td\u003E\n\u003Ctd\u003EJWT access token\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESeparate access token\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAccess token format\u003C/td\u003E\n\u003Ctd\u003ENot applicable\u003C/td\u003E\n\u003Ctd\u003EOpaque\u003C/td\u003E\n\u003Ctd\u003EJWT\u003C/td\u003E\n\u003Ctd\u003EJWT\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERefresh token\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EOptional\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EServer-side session\u003C/td\u003E\n\u003Ctd\u003ERequired\u003C/td\u003E\n\u003Ctd\u003ERequired\u003C/td\u003E\n\u003Ctd\u003ERequired\u003C/td\u003E\n\u003Ctd\u003ENot required\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession lookup on authenticated requests\u003C/td\u003E\n\u003Ctd\u003ERequired\u003C/td\u003E\n\u003Ctd\u003ERequired\u003C/td\u003E\n\u003Ctd\u003ENot required by default\u003C/td\u003E\n\u003Ctd\u003ENot required\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession role\u003C/td\u003E\n\u003Ctd\u003EAuthentication authority\u003C/td\u003E\n\u003Ctd\u003EAuthentication authority\u003C/td\u003E\n\u003Ctd\u003ELifecycle management\u003C/td\u003E\n\u003Ctd\u003ENone\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAccess token validation\u003C/td\u003E\n\u003Ctd\u003ESession validation\u003C/td\u003E\n\u003Ctd\u003EToken and session validation\u003C/td\u003E\n\u003Ctd\u003ECryptographic JWT validation\u003C/td\u003E\n\u003Ctd\u003ECryptographic JWT validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession continuation\u003C/td\u003E\n\u003Ctd\u003ESession touch\u003C/td\u003E\n\u003Ctd\u003ERefresh token rotation and session policies\u003C/td\u003E\n\u003Ctd\u003ERefresh token rotation with session validation\u003C/td\u003E\n\u003Ctd\u003EIndependent token renewal, if enabled\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EImmediate session revocation\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EPrevents continuation; does not automatically invalidate issued JWTs\u003C/td\u003E\n\u003Ctd\u003ENot applicable\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EImmediate access-token revocation\u003C/td\u003E\n\u003Ctd\u003EThrough session invalidation\u003C/td\u003E\n\u003Ctd\u003EThrough session invalidation\u003C/td\u003E\n\u003Ctd\u003ERequires an additional revocation check\u003C/td\u003E\n\u003Ctd\u003ENot supported by the pure stateless model\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDevice/session management\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003ENo session-based device management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EServer-side state for authentication\u003C/td\u003E\n\u003Ctd\u003ERequired\u003C/td\u003E\n\u003Ctd\u003ERequired\u003C/td\u003E\n\u003Ctd\u003ERequired for lifecycle, not normal JWT validation\u003C/td\u003E\n\u003Ctd\u003ENot required\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMain advantage\u003C/td\u003E\n\u003Ctd\u003ESimple, centrally controlled sessions\u003C/td\u003E\n\u003Ctd\u003ESeparate token lifecycle with session authority\u003C/td\u003E\n\u003Ctd\u003EStateless access validation with session management\u003C/td\u003E\n\u003Ctd\u003EMinimal authentication-state dependencies\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMain trade-off\u003C/td\u003E\n\u003Ctd\u003ESession lookup on each authenticated request\u003C/td\u003E\n\u003Ctd\u003EMore credentials and lifecycle complexity\u003C/td\u003E\n\u003Ctd\u003EExisting JWTs can remain valid after session revocation\u003C/td\u003E\n\u003Ctd\u003ENo immediate server-side session revocation\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe comparison describes authentication behavior, not guaranteed throughput rankings. Actual performance depends on token validation costs, storage infrastructure, caching, network latency, request volume, and application architecture.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque\u0022\u003EPureOpaque\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022fully-session-based-authentication\u0022\u003EFully session-based authentication\u003C/h3\u003E\n\u003Cp\u003EPureOpaque is UltimateAuth\u0027s simplest stateful authentication mode.\u003C/p\u003E\n\u003Cp\u003EAuthentication is represented by a server-side session. The client receives an opaque session credential and presents it on subsequent authenticated requests.\u003C/p\u003E\n\u003Cp\u003EThe server resolves the credential, validates the session and its applicable security policies, and establishes the authenticated identity.\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\n |\n | Session Credential\n v\nSession Resolution\n |\n v\nSession Validation\n |\n v\nAuthenticated Identity\n |\n v\nAccessContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EPureOpaque does not issue a separate access token or refresh token.\u003C/p\u003E\n\u003Cp\u003EThe session credential itself is sufficient to identify the authentication context, provided the server determines that the session remains valid.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-continuation\u0022\u003ESession continuation\u003C/h3\u003E\n\u003Cp\u003EPureOpaque does not use refresh-token rotation.\u003C/p\u003E\n\u003Cp\u003EInstead, an existing session may continue through session touch operations, subject to the configured session policies.\u003C/p\u003E\n\u003Cp\u003EDepending on the configuration, these policies may include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession lifetime.\u003C/li\u003E\n\u003Cli\u003ESliding expiration.\u003C/li\u003E\n\u003Cli\u003EIdle timeout.\u003C/li\u003E\n\u003Cli\u003ETouch interval.\u003C/li\u003E\n\u003Cli\u003EAbsolute expiration limits.\u003C/li\u003E\n\u003Cli\u003ESession revocation.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA touch operation does not necessarily extend the session lifetime. It updates session state only when the applicable policy requires it.\u003C/p\u003E\n\u003Cp\u003EAn expired or revoked session cannot be restored merely by touching it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022advantages\u0022\u003EAdvantages\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EMinimal credential complexity\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThere is no separate access-token lifecycle, refresh-token storage, or refresh-token rotation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImmediate session revocation\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EWhen a session is revoked, subsequent authentication evaluations that validate that session reject the credential.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ECentralized authentication control\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThe server remains authoritative for session validity and can enforce current session policies.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ENatural integration with server-rendered applications\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EApplications already maintaining server-side authentication state can use a single session credential without introducing additional token-management mechanisms.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022limitations\u0022\u003ELimitations\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003ESession validation is required\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EEvery authenticated request must validate the server-side session. Depending on the deployment, this can involve a database or distributed cache lookup.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAvailability depends on session infrastructure\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA distributed deployment needs reliable access to shared session state or an equivalent session-validation mechanism.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe session credential is sensitive\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EPossession of a valid session credential may be sufficient to authenticate as the session owner. It must be protected against disclosure, replay, and session fixation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ELong-lived connections require special handling\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EIn environments such as Blazor Server, an established SignalR circuit may outlive the authentication state initially used to establish it. Appropriate revalidation and authorization checks remain necessary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-applications\u0022\u003ERecommended applications\u003C/h3\u003E\n\u003Cp\u003EPureOpaque is generally a strong default for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBlazor Server applications.\u003C/li\u003E\n\u003Cli\u003EASP.NET Core MVC and Razor Pages applications.\u003C/li\u003E\n\u003Cli\u003EInternal applications using centrally managed sessions.\u003C/li\u003E\n\u003Cli\u003EBrowser applications using secure, HttpOnly session cookies.\u003C/li\u003E\n\u003Cli\u003ESystems that prioritize simple session lifecycle management and immediate server-side revocation.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EPureOpaque is not restricted to server-to-server communication. It can also be used safely with browser and mobile clients when credentials are stored, transported, and validated appropriately.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003EHybrid\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022opaque-access-tokens-with-authoritative-sessions\u0022\u003EOpaque access tokens with authoritative sessions\u003C/h3\u003E\n\u003Cp\u003EHybrid combines separate access and refresh tokens with mandatory server-side session validation.\u003C/p\u003E\n\u003Cp\u003EUnlike PureOpaque, the client does not use the session credential as its primary authentication credential.\u003C/p\u003E\n\u003Cp\u003EInstead, the client receives an \u003Cstrong\u003Eopaque access token\u003C/strong\u003E associated with a server-side session.\u003C/p\u003E\n\u003Cp\u003EThe access token contains no readable JWT claims. The server resolves the token and validates the associated session before establishing the authenticated identity.\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\n |\n | Opaque Access Token\n v\nAccess Token Resolution\n |\n v\nAssociated Session\n |\n v\nSession Validation\n |\n v\nAuthenticated Identity\n |\n v\nAccessContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe opaque access token and internal session identifier represent different concepts.\u003C/p\u003E\n\u003Cp\u003EThe access token is a client-facing authentication credential with its own lifetime. The session represents the server-authoritative authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-and-session-lifecycle\u0022\u003ERefresh and session lifecycle\u003C/h3\u003E\n\u003Cp\u003EHybrid uses refresh tokens to continue authentication without requiring a new login.\u003C/p\u003E\n\u003Cp\u003EA successful refresh validates the relevant session and refresh-token state, applies the configured session policies, and issues replacement credentials.\u003C/p\u003E\n\u003Cp\u003EAccess-token lifetime and session lifetime can therefore be managed separately.\u003C/p\u003E\n\u003Cp\u003EFor example, an application may use a short-lived opaque access token while maintaining a longer-lived session, subject to refresh-token expiration and revocation rules.\u003C/p\u003E\n\u003Cp\u003EA valid refresh token must not be allowed to restore an expired or revoked session.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022advantages-1\u0022\u003EAdvantages\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003ESeparate credential lifetimes\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAccess tokens can expire independently of the underlying session.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImmediate session-based revocation\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EBecause session validity is enforced during authentication, revoking the session prevents associated access tokens from authenticating on subsequent validations.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOpaque access credentials\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThe access token does not disclose identity claims, roles, tenant information, or other application data through a readable token payload.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ECentralized security policies\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ESession status and related security conditions can be enforced as part of authentication.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESuitable for API-oriented clients\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EApplications can use access and refresh tokens while retaining server-side control over the authenticated session.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022limitations-1\u0022\u003ELimitations\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EMore complex lifecycle\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EHybrid requires access-token issuance, refresh-token management, rotation, replay protection, and session lifecycle coordination.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EServer-side validation remains mandatory\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EOpaque access tokens do not provide stateless JWT validation. The server must resolve the token and enforce the associated session state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAdditional infrastructure dependencies\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EToken resolution and session validation may require storage or cache access.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ECredential theft remains a risk\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EOpaque tokens conceal claims but remain bearer credentials. Their format does not prevent unauthorized use if they are stolen.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-applications-1\u0022\u003ERecommended applications\u003C/h3\u003E\n\u003Cp\u003EHybrid is generally appropriate for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBlazor WebAssembly applications communicating with an API.\u003C/li\u003E\n\u003Cli\u003EWeb applications with a separate backend API.\u003C/li\u003E\n\u003Cli\u003EMobile and desktop applications requiring refresh-token continuity.\u003C/li\u003E\n\u003Cli\u003EFull-stack applications needing centrally controlled authentication.\u003C/li\u003E\n\u003Cli\u003ESystems requiring separate access-token expiration and immediate session revocation.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EHybrid is particularly useful when the application benefits from access/refresh token semantics but does not want to sacrifice server-authoritative session validation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid\u0022\u003ESemiHybrid\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022stateless-jwt-validation-with-server-side-session-lifecycle\u0022\u003EStateless JWT validation with server-side session lifecycle\u003C/h3\u003E\n\u003Cp\u003ESemiHybrid combines JWT access tokens with server-side session management.\u003C/p\u003E\n\u003Cp\u003EIts defining characteristic is the separation between \u003Cstrong\u003Eaccess-token validation\u003C/strong\u003E and \u003Cstrong\u003Esession lifecycle operations\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EFor normal authenticated requests, the server validates the JWT cryptographically. A session lookup is not required by 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\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\n |\n | JWT Access Token\n v\nJWT Validation\n |\n v\nAuthenticated Identity\n |\n v\nAccessContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server-side session is not consulted during this normal authentication path.\u003C/p\u003E\n\u003Cp\u003EHowever, the session remains relevant when the authentication lifecycle must be managed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-session-metadata-is-used-for\u0022\u003EWhat session metadata is used for\u003C/h3\u003E\n\u003Cp\u003ESemiHybrid maintains server-side session state for operations such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERefresh-token validation and rotation.\u003C/li\u003E\n\u003Cli\u003ELogout and session termination.\u003C/li\u003E\n\u003Cli\u003ERevoking an individual session.\u003C/li\u003E\n\u003Cli\u003ERevoking all sessions belonging to a user.\u003C/li\u003E\n\u003Cli\u003EListing active sessions or devices.\u003C/li\u003E\n\u003Cli\u003EEnforcing session expiration and continuation policies.\u003C/li\u003E\n\u003Cli\u003ERecording session lifecycle information.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis state is a \u003Cstrong\u003Elifecycle control mechanism\u003C/strong\u003E, not a mandatory source of identity validation on every API request.\u003C/p\u003E\n\u003Cp\u003EFor example, when a user logs out, the server can revoke the session and prevent subsequent refresh operations from continuing it.\u003C/p\u003E\n\u003Cp\u003EHowever, a previously issued JWT may remain valid until its expiration because normal JWT validation does not consult that session.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-semantics\u0022\u003ERevocation semantics\u003C/h3\u003E\n\u003Cp\u003ESemiHybrid must distinguish two separate guarantees.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESession revocation\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ERevoking the session prevents authentication continuation through that session, including refresh operations that require it.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAccess-token revocation\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ERevoking the session does not automatically invalidate an already issued JWT.\u003C/p\u003E\n\u003Cp\u003EImmediate rejection of an issued JWT requires an additional mechanism, such as a revocation lookup against a distributed cache.\u003C/p\u003E\n\u003Cp\u003ESuch a mechanism can be supported as an explicit policy, but it introduces an additional request-time dependency and validation cost.\u003C/p\u003E\n\u003Cp\u003EWithout that policy, JWT expiration remains the boundary for an already issued access token.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022advantages-2\u0022\u003EAdvantages\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003EStateless normal request validation\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EProtected API requests can authenticate JWTs without consulting the session store.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ECentralized lifecycle management\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EApplications retain the ability to manage sessions, devices, refresh operations, and future authentication continuity.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EReduced request-time storage dependencies\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ESession infrastructure is not part of the default JWT validation path.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EConfigurable revocation trade-offs\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EApplications can choose whether to introduce additional request-time revocation enforcement when stronger invalidation guarantees are required.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022limitations-2\u0022\u003ELimitations\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003ESession revocation does not automatically revoke issued JWTs\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAn access token can remain valid after logout until its expiration unless an additional revocation mechanism is enforced.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ELifecycle state is still required\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ESemiHybrid is not a session-free mode. Refresh and session management depend on server-side state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EMore complex expiration semantics\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EJWT expiration, refresh-token expiration, session expiration, and session activity policies must be coordinated.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOptional revocation checks reduce statelessness\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EIf immediate access-token revocation is enabled, the request pipeline may need an additional cache or store lookup.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-applications-2\u0022\u003ERecommended applications\u003C/h3\u003E\n\u003Cp\u003ESemiHybrid is intended for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EHigh-throughput APIs that still require centralized session management.\u003C/li\u003E\n\u003Cli\u003EDistributed applications using short-lived JWT access tokens.\u003C/li\u003E\n\u003Cli\u003EApplications offering device and active-session management.\u003C/li\u003E\n\u003Cli\u003ESystems that need JWT validation performance without giving up refresh and session lifecycle control.\u003C/li\u003E\n\u003Cli\u003EArchitectures where access-token expiration is an acceptable default revocation boundary.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ESemiHybrid should not be described as providing immediate JWT revocation unless an additional enforcement mechanism is enabled.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022purejwt\u0022\u003EPureJwt\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-independent-jwt-authentication\u0022\u003ESession-independent JWT authentication\u003C/h3\u003E\n\u003Cp\u003EPureJwt is UltimateAuth\u0027s fully stateless JWT authentication mode.\u003C/p\u003E\n\u003Cp\u003EIt does not require server-side session creation, persistence, resolution, or validation.\u003C/p\u003E\n\u003Cp\u003EThe client receives a JWT access token. Protected resources validate the token using its signature and applicable validation rules, including issuer, audience, expiration, and other security-relevant claims.\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\n |\n | JWT Access Token\n v\nCryptographic JWT Validation\n |\n v\nAuthenticated Identity\n |\n v\nAccessContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ENo session lookup is involved.\u003C/p\u003E\n\u003Cp\u003EPureJwt must not require session infrastructure indirectly through token issuance, validation, or other authentication pipeline components.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-renewal\u0022\u003EToken renewal\u003C/h3\u003E\n\u003Cp\u003EPureJwt can operate without refresh tokens.\u003C/p\u003E\n\u003Cp\u003EWhen refresh tokens are enabled, their lifecycle must remain independent of server-side sessions.\u003C/p\u003E\n\u003Cp\u003ESecure refresh-token rotation and replay protection may require dedicated refresh-token state. This does not imply a dependency on the session model.\u003C/p\u003E\n\u003Cp\u003EThe distinction is important:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession independence\u003C/strong\u003E means that authentication does not require server-side sessions.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EStateless access validation\u003C/strong\u003E means that protected requests do not require server-side token or session lookups.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERefresh-token persistence\u003C/strong\u003E, when enabled, is a separate lifecycle concern.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAn application seeking a completely stateless credential lifecycle can disable refresh tokens and require reauthentication when access tokens expire.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-semantics-1\u0022\u003ERevocation semantics\u003C/h3\u003E\n\u003Cp\u003EPureJwt does not provide session-based immediate revocation.\u003C/p\u003E\n\u003Cp\u003EA correctly signed, unexpired JWT remains valid under the configured validation rules unless a relevant validation condition changes, such as the availability of its signing key.\u003C/p\u003E\n\u003Cp\u003EPureJwt does not maintain a mandatory blacklist or server-side session revocation mechanism.\u003C/p\u003E\n\u003Cp\u003EApplications requiring immediate per-session access revocation should consider Hybrid, or SemiHybrid with an explicit revocation enforcement strategy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022advantages-3\u0022\u003EAdvantages\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003ENo mandatory session infrastructure\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAuthentication does not depend on session stores or session-management services.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EIndependent resource validation\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EServices with the necessary verification keys and validation configuration can validate access tokens locally.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESuitable for distributed architectures\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EProtected services do not need to contact a central authentication session store for each request.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EReduced lifecycle complexity when refresh is disabled\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EApplications can avoid session management and refresh-token rotation entirely.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EPredictable validation behavior\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAuthentication is determined by the token and the applicable cryptographic and claim-validation rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022limitations-3\u0022\u003ELimitations\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003ENo immediate per-session revocation\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAn issued JWT can remain usable until expiration.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EToken lifetime becomes particularly important\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ELong-lived access tokens increase the period during which a stolen token may remain usable.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClaims can become stale\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EIdentity or authorization-related claims embedded in a JWT do not automatically change when server-side user data changes.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EJWT payloads are readable\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ESigned JWTs are not encrypted by default. Sensitive information should not be placed in their payloads.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EKey management is critical\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ESigning-key protection, key rotation, issuer and audience validation, and accepted algorithm configuration are essential.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-applications-3\u0022\u003ERecommended applications\u003C/h3\u003E\n\u003Cp\u003EPureJwt is intended for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EStateless resource APIs.\u003C/li\u003E\n\u003Cli\u003EDistributed services and microservices.\u003C/li\u003E\n\u003Cli\u003EExternal API integrations.\u003C/li\u003E\n\u003Cli\u003EWorkloads requiring independent JWT validation.\u003C/li\u003E\n\u003Cli\u003ESystems that do not require centralized session management.\u003C/li\u003E\n\u003Cli\u003EApplications prioritizing minimal authentication-state dependencies.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EPureJwt is not automatically the fastest mode in every deployment. Cryptographic validation has a cost, while well-cached stateful authentication can also be efficient. Its primary architectural advantage is eliminating mandatory session dependencies.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-a-mode\u0022\u003EChoosing a mode\u003C/h2\u003E\n\u003Cp\u003EThe most useful selection criterion is not the client technology alone, but the authentication guarantees the application requires.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EApplication or requirement\u003C/th\u003E\n\u003Cth\u003ERecommended mode\u003C/th\u003E\n\u003Cth\u003EReason\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\u003EPureOpaque\u003C/td\u003E\n\u003Ctd\u003EServer-controlled sessions without a separate access/refresh token lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EASP.NET Core MVC or Razor Pages\u003C/td\u003E\n\u003Ctd\u003EPureOpaque\u003C/td\u003E\n\u003Ctd\u003ESimple cookie-based session authentication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInternal administrative application\u003C/td\u003E\n\u003Ctd\u003EPureOpaque or Hybrid\u003C/td\u003E\n\u003Ctd\u003ECentralized session control and immediate revocation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor WebAssembly with a separate API\u003C/td\u003E\n\u003Ctd\u003EHybrid\u003C/td\u003E\n\u003Ctd\u003EAccess/refresh tokens with authoritative session validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMobile or desktop client\u003C/td\u003E\n\u003Ctd\u003EHybrid\u003C/td\u003E\n\u003Ctd\u003EIndependent token lifetimes with centralized session control\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EWeb application with separate frontend and API\u003C/td\u003E\n\u003Ctd\u003EHybrid\u003C/td\u003E\n\u003Ctd\u003EToken-based access without losing server-side revocation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EHigh-throughput API with session management\u003C/td\u003E\n\u003Ctd\u003ESemiHybrid\u003C/td\u003E\n\u003Ctd\u003EStateless JWT validation alongside managed session lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMulti-device application with session controls\u003C/td\u003E\n\u003Ctd\u003ESemiHybrid or Hybrid\u003C/td\u003E\n\u003Ctd\u003ECentralized device/session management; different access-validation costs\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDistributed resource APIs\u003C/td\u003E\n\u003Ctd\u003EPureJwt\u003C/td\u003E\n\u003Ctd\u003EIndependent JWT validation without mandatory session lookup\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EExternal service integration\u003C/td\u003E\n\u003Ctd\u003EPureJwt\u003C/td\u003E\n\u003Ctd\u003EStateless token validation across service boundaries\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EImmediate access revocation is essential\u003C/td\u003E\n\u003Ctd\u003EPureOpaque or Hybrid\u003C/td\u003E\n\u003Ctd\u003EEvery authentication evaluation enforces server-side session validity\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ENo session infrastructure is desired\u003C/td\u003E\n\u003Ctd\u003EPureJwt\u003C/td\u003E\n\u003Ctd\u003EAuthentication does not require session persistence\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese are recommendations, not restrictions.\u003C/p\u003E\n\u003Cp\u003EFor example, Blazor WebAssembly can use PureOpaque through a secure cookie-based backend-for-frontend architecture. Similarly, a server-rendered application can use JWT authentication when its broader system requirements justify it.\u003C/p\u003E\n\u003Cp\u003EThe runtime does not inherently determine the security model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-profiles-and-authentication-modes\u0022\u003EClient Profiles and authentication modes\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports different client environments through Client Profiles.\u003C/p\u003E\n\u003Cp\u003EA Client Profile can provide suitable defaults for a runtime, including credential delivery and authentication-mode selection.\u003C/p\u003E\n\u003Cp\u003EHowever, a Client Profile and an authentication mode represent different concerns.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient Profile\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EDescribes how the application interacts with the authentication system, including runtime characteristics and credential transport.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication Mode\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EDefines the authentication credential model, session dependencies, validation strategy, and lifecycle guarantees.\u003C/p\u003E\n\u003Cp\u003EA Blazor Server profile may default to PureOpaque, while a Blazor WebAssembly profile may default to Hybrid.\u003C/p\u003E\n\u003Cp\u003EThese defaults do not mean the modes are exclusive to those environments.\u003C/p\u003E\n\u003Cp\u003EApplications can override the effective mode through supported configuration and context-resolution mechanisms.\u003C/p\u003E\n\u003Cp\u003EWhen different clients share the same UAuthHub, the server must resolve the effective mode through trusted configuration and framework-controlled context. An untrusted client must not be allowed to select a weaker authentication policy arbitrarily.\u003C/p\u003E\n\u003Cp\u003EThe effective mode must remain stable throughout a single authentication operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003ESecurity considerations\u003C/h2\u003E\n\u003Cp\u003EAll four modes can support secure applications when implemented and configured correctly. None should be described as universally more secure than the others.\u003C/p\u003E\n\u003Cp\u003ETheir security guarantees differ.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-confidentiality\u0022\u003ECredential confidentiality\u003C/h3\u003E\n\u003Cp\u003EOpaque credentials do not expose readable claims, but possession of a valid bearer credential may still permit authentication.\u003C/p\u003E\n\u003Cp\u003EJWT signatures protect integrity and authenticity; they do not conceal the payload.\u003C/p\u003E\n\u003Cp\u003EAll credentials require appropriate transport and storage protections.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-guarantees\u0022\u003ERevocation guarantees\u003C/h3\u003E\n\u003Cp\u003EPureOpaque and Hybrid enforce server-side session validity during authenticated requests.\u003C/p\u003E\n\u003Cp\u003ESemiHybrid enforces session validity during lifecycle operations but does not require it during normal JWT validation.\u003C/p\u003E\n\u003Cp\u003EPureJwt does not depend on sessions and does not provide session-based revocation.\u003C/p\u003E\n\u003Cp\u003EThese differences must be considered when selecting token lifetimes and logout behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022browser-security\u0022\u003EBrowser security\u003C/h3\u003E\n\u003Cp\u003EWhen authentication credentials are delivered through cookies, appropriate \u003Ccode\u003EHttpOnly\u003C/code\u003E, \u003Ccode\u003ESecure\u003C/code\u003E, and \u003Ccode\u003ESameSite\u003C/code\u003E settings should be applied.\u003C/p\u003E\n\u003Cp\u003ECookie-based authentication also requires appropriate CSRF protections.\u003C/p\u003E\n\u003Cp\u003EWhen tokens are accessible to browser application code, XSS and token-exfiltration risks require particular attention.\u003C/p\u003E\n\u003Cp\u003EThe authentication mode does not eliminate these browser security requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-remains-separate\u0022\u003EAuthorization remains separate\u003C/h3\u003E\n\u003Cp\u003EAuthentication establishes the identity of the current actor.\u003C/p\u003E\n\u003Cp\u003EAuthorization determines what that actor is permitted to do.\u003C/p\u003E\n\u003Cp\u003EAll modes must produce trusted authentication state before authorization policies are evaluated.\u003C/p\u003E\n\u003Cp\u003EChoosing a stateless mode does not remove the need for authorization checks or appropriate handling of stale authorization claims.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s four authentication modes represent four distinct strategies:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EPureOpaque\u003C/strong\u003E uses a server-side session as the authentication authority and avoids a separate access/refresh token lifecycle.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EHybrid\u003C/strong\u003E uses separate opaque access and refresh tokens while requiring authoritative session validation for authenticated requests.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESemiHybrid\u003C/strong\u003E uses JWT access tokens for stateless request authentication while retaining server-side sessions for refresh, logout, revocation, and device management.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EPureJwt\u003C/strong\u003E uses JWT access tokens without requiring server-side session infrastructure.\u003C/p\u003E\n\u003Cp\u003EThe correct mode is the one whose validation, lifecycle, revocation, and operational guarantees match the application\u0027s requirements.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth preserves a consistent authentication and authorization architecture while allowing these underlying strategies to differ.\u003C/p\u003E\n", "Headings": [ { - "Id": "available-modes", - "Text": "\uD83E\uDDE9 Available Modes", + "Id": "auth-credentials", + "Text": "Auth credentials", + "Level": 0 + }, + { + "Id": "mode-comparison", + "Text": "Mode comparison", "Level": 0 }, { "Id": "pureopaque", "Text": "PureOpaque", + "Level": 0 + }, + { + "Id": "fully-session-based-authentication", + "Text": "Fully session-based authentication", + "Level": 1 + }, + { + "Id": "session-continuation", + "Text": "Session continuation", + "Level": 1 + }, + { + "Id": "advantages", + "Text": "Advantages", + "Level": 1 + }, + { + "Id": "limitations", + "Text": "Limitations", + "Level": 1 + }, + { + "Id": "recommended-applications", + "Text": "Recommended applications", "Level": 1 }, { "Id": "hybrid", "Text": "Hybrid", + "Level": 0 + }, + { + "Id": "opaque-access-tokens-with-authoritative-sessions", + "Text": "Opaque access tokens with authoritative sessions", + "Level": 1 + }, + { + "Id": "refresh-and-session-lifecycle", + "Text": "Refresh and session lifecycle", + "Level": 1 + }, + { + "Id": "advantages-1", + "Text": "Advantages", + "Level": 1 + }, + { + "Id": "limitations-1", + "Text": "Limitations", + "Level": 1 + }, + { + "Id": "recommended-applications-1", + "Text": "Recommended applications", "Level": 1 }, { "Id": "semihybrid", "Text": "SemiHybrid", + "Level": 0 + }, + { + "Id": "stateless-jwt-validation-with-server-side-session-lifecycle", + "Text": "Stateless JWT validation with server-side session lifecycle", "Level": 1 }, { - "Id": "purejwt", - "Text": "PureJwt", + "Id": "what-session-metadata-is-used-for", + "Text": "What session metadata is used for", "Level": 1 }, { - "Id": "mode-comparison", - "Text": "\u2696\uFE0F Mode Comparison", - "Level": 0 + "Id": "revocation-semantics", + "Text": "Revocation semantics", + "Level": 1 + }, + { + "Id": "advantages-2", + "Text": "Advantages", + "Level": 1 }, { - "Id": "how-to-think-about-auth-modes", - "Text": "\uD83E\uDDE0 How to Think About Auth Modes", + "Id": "limitations-2", + "Text": "Limitations", + "Level": 1 + }, + { + "Id": "recommended-applications-2", + "Text": "Recommended applications", + "Level": 1 + }, + { + "Id": "purejwt", + "Text": "PureJwt", "Level": 0 }, { - "Id": "runtime-driven-behavior", - "Text": "\uD83D\uDD04 Runtime-Driven Behavior", + "Id": "session-independent-jwt-authentication", + "Text": "Session-independent JWT authentication", "Level": 1 }, { - "Id": "you-can-override-everything", - "Text": "\u2699\uFE0F You Can Override Everything", + "Id": "token-renewal", + "Text": "Token renewal", "Level": 1 }, { - "Id": "safe-by-default", - "Text": "\uD83D\uDEE1 Safe by Default", + "Id": "revocation-semantics-1", + "Text": "Revocation semantics", "Level": 1 }, { - "Id": "mental-model", - "Text": "\uD83D\uDCA1 Mental Model", + "Id": "advantages-3", + "Text": "Advantages", "Level": 1 }, { - "Id": "pureopaque-1", - "Text": "\uD83D\uDD10 PureOpaque", - "Level": 0 + "Id": "limitations-3", + "Text": "Limitations", + "Level": 1 + }, + { + "Id": "recommended-applications-3", + "Text": "Recommended applications", + "Level": 1 }, { - "Id": "hybrid-1", - "Text": "\u26A1 Hybrid", + "Id": "choosing-a-mode", + "Text": "Choosing a mode", "Level": 0 }, { - "Id": "semihybrid-1", - "Text": "\uD83D\uDE80 SemiHybrid", + "Id": "client-profiles-and-authentication-modes", + "Text": "Client Profiles and authentication modes", "Level": 0 }, { - "Id": "purejwt-1", - "Text": "\uD83C\uDF10 PureJwt", + "Id": "security-considerations", + "Text": "Security considerations", "Level": 0 }, { - "Id": "which-mode-should-you-use", - "Text": "\uD83C\uDFAF Which Mode Should You Use?", + "Id": "credential-confidentiality", + "Text": "Credential confidentiality", + "Level": 1 + }, + { + "Id": "revocation-guarantees", + "Text": "Revocation guarantees", + "Level": 1 + }, + { + "Id": "browser-security", + "Text": "Browser security", + "Level": 1 + }, + { + "Id": "authorization-remains-separate", + "Text": "Authorization remains separate", + "Level": 1 + }, + { + "Id": "summary", + "Text": "Summary", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/client-profiles.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/client-profiles.json index 101ce23b..3a37f6a1 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/client-profiles.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/client-profiles.json @@ -1,76 +1,151 @@ { "Slug": "fundamentals/client-profiles", "Title": "Client Profiles", - "Html": "\n\u003Cp\u003EUltimateAuth adapts its authentication behavior based on the client.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This is powered by \u003Cstrong\u003EClient Profiles\u003C/strong\u003E.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-a-client-profile\u0022\u003E\uD83D\uDD11 What is a Client Profile?\u003C/h2\u003E\n\u003Cp\u003EA Client Profile defines how authentication behaves for a specific client type.\u003C/p\u003E\n\u003Cp\u003EIt affects:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication mode\u003C/li\u003E\n\u003Cli\u003EFlow behavior\u003C/li\u003E\n\u003Cli\u003EToken usage\u003C/li\u003E\n\u003Cli\u003ESecurity constraints\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUnlike traditional systems:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You don\u2019t configure authentication globally\u003Cbr /\u003E\n\uD83D\uDC49 You let the system adapt per client\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-key-idea\u0022\u003E\uD83E\uDDE0 The Key Idea\u003C/h2\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EAuthentication behavior is not static\u003Cbr /\u003E\nIt is determined \u003Cstrong\u003Eper client and per request\u003C/strong\u003E\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-detection\u0022\u003E\uD83D\uDD04 Runtime Detection\u003C/h2\u003E\n\u003Cp\u003EBy default, UltimateAuth automatically detects the client profile.\u003C/p\u003E\n\u003Cp\u003EThis is done using runtime signals such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELoaded assemblies\u003C/li\u003E\n\u003Cli\u003EHosting environment\u003C/li\u003E\n\u003Cli\u003ERegistered services\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-detection-logic\u0022\u003EExample Detection Logic\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EMAUI assemblies \u2192 \u003Ccode\u003EMaui\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EWebAssembly runtime \u2192 \u003Ccode\u003EBlazorWasm\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EServer components \u2192 \u003Ccode\u003EBlazorServer\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EUAuthHub marker \u2192 \u003Ccode\u003EUAuthHub\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EFallback \u2192 \u003Ccode\u003EWebServer\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Detection happens inside the client at startup.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-server-propagation\u0022\u003E\uD83D\uDCE1 Client \u2192 Server Propagation\u003C/h2\u003E\n\u003Cp\u003EThe detected client profile is sent to the server on each request.\u003C/p\u003E\n\u003Cp\u003EThis can happen via:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequest headers\u003C/li\u003E\n\u003Cli\u003EForm fields (for flow-based operations)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EClient \u2192 (ClientProfile) \u2192 Server\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EOn the server:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe profile is read from the request\u003C/li\u003E\n\u003Cli\u003EIf not provided \u2192 NotSpecified\u003C/li\u003E\n\u003Cli\u003EServer applies defaults or resolves behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-vs-manual-configuration\u0022\u003E\u2699\uFE0F Automatic vs Manual Configuration\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-default\u0022\u003EAutomatic (Default)\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EIt means:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAutoDetectClientProfile = true\u003C/li\u003E\n\u003Cli\u003EProfile is resolved automatically\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022manual-override\u0022\u003EManual Override\u003C/h3\u003E\n\u003Cp\u003EYou can explicitly set the client profile:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.ClientProfile = UAuthClientProfile.Maui;\n o.AutoDetectClientProfile = false; // optional\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 This is useful when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERunning in custom hosting environments\u003C/li\u003E\n\u003Cli\u003EDetection is ambiguous\u003C/li\u003E\n\u003Cli\u003EYou want full control\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-in-profiles\u0022\u003E\uD83E\uDDE9 Built-in Profiles\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth includes predefined profiles:\u003C/p\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProfile\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\u003EBlazorServer\u003C/td\u003E\n\u003Ctd\u003EServer-rendered apps\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazorWasm\u003C/td\u003E\n\u003Ctd\u003EBrowser-based WASM apps\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMaui\u003C/td\u003E\n\u003Ctd\u003ENative mobile apps\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EWebServer\u003C/td\u003E\n\u003Ctd\u003EMVC / Razor / generic server\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EApi\u003C/td\u003E\n\u003Ctd\u003EAPI-only backend\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUAuthHub\u003C/td\u003E\n\u003Ctd\u003ECentral auth server\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022safe-defaults\u0022\u003E\uD83D\uDEE1 Safe Defaults\u003C/h3\u003E\n\u003Cp\u003EIf no profile is specified (and auto detection is false):\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient \u2192 NotSpecified \u2192 Server resolves safely\u003C/li\u003E\n\u003Cli\u003EDefaults are applied\u003C/li\u003E\n\u003Cli\u003EUnsafe combinations are prevented\u003C/li\u003E\n\u003Cli\u003ESystem remains consistent\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-matters\u0022\u003E\uD83D\uDD10 Why This Matters\u003C/h3\u003E\n\u003Cp\u003EClient Profiles enable:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMulti-client systems (Web \u002B Mobile \u002B API)\u003C/li\u003E\n\u003Cli\u003ERuntime adaptation\u003C/li\u003E\n\u003Cli\u003ESafe defaults without manual configuration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EWithout Client Profiles You would need:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESeparate auth setups per client\u003C/li\u003E\n\u003Cli\u003EComplex branching logic\u003C/li\u003E\n\u003Cli\u003EManual security handling\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Client defines behavior\n\uD83D\uDC49 Server enforces rules\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient Profiles are automatically detected\u003C/li\u003E\n\u003Cli\u003EThey are sent to the server on each request\u003C/li\u003E\n\u003Cli\u003EBehavior adapts per request\u003C/li\u003E\n\u003Cli\u003EYou can override everything when needed\u003C/li\u003E\n\u003Cli\u003ESafe defaults are always applied\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003ENow that you understand runtime behavior:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Continue to Runtime Architecture\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth is designed to support different application environments through a unified authentication architecture.\u003C/p\u003E\n\u003Cp\u003EA Blazor Server application, a browser-based WebAssembly application, a native mobile client, and a server-side application do not interact with authentication credentials in exactly the same way.\u003C/p\u003E\n\u003Cp\u003EThey may differ in how credentials are stored, how requests are sent, whether browser cookies are available, and how authentication results are delivered.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient Profiles allow UltimateAuth to account for these runtime differences without requiring separate authentication architectures for each client type.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA Client Profile describes the environment in which a client operates. It helps the framework resolve suitable authentication behavior and defaults, while the server remains responsible for enforcing the effective security policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-a-client-profile\u0022\u003EWhat is a Client Profile?\u003C/h2\u003E\n\u003Cp\u003EA Client Profile identifies a supported client environment and its relevant runtime characteristics.\u003C/p\u003E\n\u003Cp\u003EIt can influence how UltimateAuth resolves:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDefault authentication mode.\u003C/li\u003E\n\u003Cli\u003ECredential delivery and transport.\u003C/li\u003E\n\u003Cli\u003EClient-specific authentication flow behavior.\u003C/li\u003E\n\u003Cli\u003EBrowser and non-browser integration requirements.\u003C/li\u003E\n\u003Cli\u003EApplicable server configuration and compatibility constraints.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA profile does not authenticate the client or grant permissions.\u003C/p\u003E\n\u003Cp\u003EFor example, identifying a request as originating from a Blazor WebAssembly application does not establish the identity of its user.\u003C/p\u003E\n\u003Cp\u003ELikewise, a client declaring itself to be a server-side application must not automatically receive privileges associated with a trusted server.\u003C/p\u003E\n\u003Cp\u003EThe distinction is fundamental:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient Profile describes the environment. Authentication Mode defines the authentication strategy. Server policy determines what is permitted.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-client-profiles-exist\u0022\u003EWhy Client Profiles exist\u003C/h2\u003E\n\u003Cp\u003EConsider an application that includes a browser frontend, a native mobile application, and a server-rendered administrative interface.\u003C/p\u003E\n\u003Cp\u003EAll three clients may authenticate against the same UAuthHub.\u003C/p\u003E\n\u003Cp\u003EHowever, their runtime characteristics differ.\u003C/p\u003E\n\u003Cp\u003EA browser application can use cookies managed by the browser. A native application may need to manage credentials through platform-specific secure storage. A server-rendered application may be able to maintain authentication through a server-controlled session without introducing a separate refresh-token lifecycle.\u003C/p\u003E\n\u003Cp\u003EWithout a client abstraction, these differences tend to spread throughout application code.\u003C/p\u003E\n\u003Cp\u003EDevelopers may need separate configuration paths, credential-handling logic, and authentication integration code for every runtime.\u003C/p\u003E\n\u003Cp\u003EClient Profiles provide a consistent way to describe those differences.\u003C/p\u003E\n\u003Cp\u003EThey allow UltimateAuth to select appropriate integration behavior while preserving the same underlying authentication and authorization architecture.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-in-client-profiles\u0022\u003EBuilt-in Client Profiles\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth defines profiles for its supported client environments.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProfile\u003C/th\u003E\n\u003Cth\u003EEnvironment\u003C/th\u003E\n\u003Cth\u003ETypical characteristics\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBlazor Server applications\u003C/td\u003E\n\u003Ctd\u003EServer-side execution, browser connection, server-managed authentication context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorWasm\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBlazor WebAssembly applications\u003C/td\u003E\n\u003Ctd\u003EBrowser execution, HTTP-based API communication, browser credential constraints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMaui\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E.NET MAUI applications\u003C/td\u003E\n\u003Ctd\u003ENative execution, platform-specific credential storage and HTTP communication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EWebServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EASP.NET Core MVC, Razor Pages, and other server-hosted web applications\u003C/td\u003E\n\u003Ctd\u003EServer-side execution with browser-oriented authentication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EApi\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAPI-oriented integrations\u003C/td\u003E\n\u003Ctd\u003EHTTP request authentication without assuming a browser UI\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthHub\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUltimateAuth authentication host\u003C/td\u003E\n\u003Ctd\u003EAuthentication-host-specific runtime integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENotSpecified\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo explicit profile resolved\u003C/td\u003E\n\u003Ctd\u003ERequires the applicable server-side fallback or resolution policy\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese profiles describe runtime environments rather than independent authentication systems.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EBlazorWasm\u003C/code\u003E does not inherently mean JWT authentication, and \u003Ccode\u003EBlazorServer\u003C/code\u003E does not inherently prohibit access tokens.\u003C/p\u003E\n\u003Cp\u003EA profile may provide a recommended default, but the authentication mode remains a separate decision.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022api-profile-versus-resource-api\u0022\u003EAPI profile versus resource API\u003C/h3\u003E\n\u003Cp\u003EAn API-oriented client and an API acting as a protected resource are not necessarily the same thing.\u003C/p\u003E\n\u003Cp\u003EA protected resource API may receive requests from browser applications, mobile applications, or other services.\u003C/p\u003E\n\u003Cp\u003EThe calling client\u0027s profile should not be confused with the hosting environment of the resource API.\u003C/p\u003E\n\u003Cp\u003EResource authentication must follow the applicable server-side configuration and token-validation requirements, regardless of what an untrusted caller claims its runtime to be.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-profile-detection\u0022\u003EAutomatic profile detection\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports automatic Client Profile detection in compatible client integrations.\u003C/p\u003E\n\u003Cp\u003EDetection is intended to reduce configuration requirements for common application environments.\u003C/p\u003E\n\u003Cp\u003EDepending on the client implementation, the runtime can provide information such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe current execution environment.\u003C/li\u003E\n\u003Cli\u003EAvailable framework capabilities.\u003C/li\u003E\n\u003Cli\u003EHosting-specific registrations.\u003C/li\u003E\n\u003Cli\u003ERuntime and platform characteristics.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, a Blazor WebAssembly client can identify that it is executing in a browser environment, while a native MAUI client can identify its native application context.\u003C/p\u003E\n\u003Cp\u003EThis resolution occurs within the supported client integration.\u003C/p\u003E\n\u003Cp\u003EIt should not be confused with a server independently discovering the true identity or trust level of a remote application.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022detection-is-a-convenience-not-a-security-boundary\u0022\u003EDetection is a convenience, not a security boundary\u003C/h3\u003E\n\u003Cp\u003ERuntime detection helps choose appropriate defaults.\u003C/p\u003E\n\u003Cp\u003EIt is not an authentication proof.\u003C/p\u003E\n\u003Cp\u003EA malicious HTTP client can imitate headers, request parameters, and other client-provided metadata.\u003C/p\u003E\n\u003Cp\u003EConsequently, the server must not grant additional authentication privileges solely because a request claims a particular Client Profile.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for validating the profile against its own configuration and security requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-to-server-propagation\u0022\u003EClient-to-server propagation\u003C/h2\u003E\n\u003Cp\u003EFor supported operations, the client can communicate its resolved profile to the authentication server.\u003C/p\u003E\n\u003Cp\u003EThe transport may depend on the integration and operation, including request metadata such as headers or flow-specific form fields.\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 Runtime\n |\n v\nClient Profile Detection\n |\n v\nProfile Metadata\n |\n v\nUAuthHub\n |\n v\nServer-Side Profile Resolution\n |\n v\nEffective Authentication Context\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe important distinction is between a \u003Cstrong\u003Edeclared profile\u003C/strong\u003E and an \u003Cstrong\u003Eeffective profile\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EA declared profile is information supplied by the client.\u003C/p\u003E\n\u003Cp\u003EAn effective profile is the result accepted or resolved by the server under its configured rules.\u003C/p\u003E\n\u003Cp\u003EThe server must not treat arbitrary client-provided profile values as authority to weaken authentication requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022missing-profile-information\u0022\u003EMissing profile information\u003C/h3\u003E\n\u003Cp\u003ESome requests may not provide an explicit Client Profile.\u003C/p\u003E\n\u003Cp\u003EIn those cases, UltimateAuth can represent the unresolved client profile as \u003Ccode\u003ENotSpecified\u003C/code\u003E and apply the relevant server-side resolution or fallback behavior.\u003C/p\u003E\n\u003Cp\u003EA missing profile must not automatically imply a privileged or less restrictive environment.\u003C/p\u003E\n\u003Cp\u003EIf the server cannot establish a permitted configuration for the operation, it should reject the operation rather than inventing an unsafe authentication context.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-and-explicit-configuration\u0022\u003EAutomatic and explicit configuration\u003C/h2\u003E\n\u003Cp\u003EClient Profile resolution can be automatic or explicitly configured, depending on the supported client integration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-configuration\u0022\u003EAutomatic configuration\u003C/h3\u003E\n\u003Cp\u003EFor supported Blazor client registrations, automatic detection can reduce the need to specify the runtime manually.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client integration can resolve the appropriate profile according to its runtime detection configuration.\u003C/p\u003E\n\u003Cp\u003EThis is generally the preferred approach when the application uses a standard, supported hosting environment.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022explicit-configuration\u0022\u003EExplicit configuration\u003C/h3\u003E\n\u003Cp\u003EApplications can also configure the profile directly when their integration supports 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\u003Ebuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.ClientProfile = UAuthClientProfile.BlazorWasm;\n options.AutoDetectClientProfile = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EExplicit configuration may be useful when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn application uses a custom hosting environment.\u003C/li\u003E\n\u003Cli\u003ERuntime detection is ambiguous.\u003C/li\u003E\n\u003Cli\u003EA shared integration requires a specific client profile.\u003C/li\u003E\n\u003Cli\u003EThe application needs predictable profile selection across environments.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EExplicitly configuring a client profile does not override the server\u0027s authentication policies.\u003C/p\u003E\n\u003Cp\u003EIt only determines what profile the client integration declares or uses locally.\u003C/p\u003E\n\u003Cp\u003EThe server must still resolve and enforce the effective authentication context.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-profiles-and-authentication-modes\u0022\u003EClient Profiles and Authentication Modes\u003C/h2\u003E\n\u003Cp\u003EClient Profiles and Authentication Modes are related but independent concepts.\u003C/p\u003E\n\u003Cp\u003EA Client Profile describes the execution environment and its integration requirements.\u003C/p\u003E\n\u003Cp\u003EAn Authentication Mode defines how authentication credentials are issued, validated, continued, and revoked.\u003C/p\u003E\n\u003Cp\u003EFor example, a Blazor Server application commonly benefits from PureOpaque because it can use server-controlled sessions without managing separate access and refresh tokens.\u003C/p\u003E\n\u003Cp\u003EA Blazor WebAssembly application communicating with a separate API may benefit from Hybrid because it can use access and refresh tokens while retaining server-authoritative session validation.\u003C/p\u003E\n\u003Cp\u003EThese are architectural recommendations, not inherent limitations of the runtimes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-defaults\u0022\u003ERecommended defaults\u003C/h3\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 mode\u003C/th\u003E\n\u003Cth\u003EReason\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\u003EPureOpaque\u003C/td\u003E\n\u003Ctd\u003ESimple session-based authentication without separate token rotation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor WebAssembly with API\u003C/td\u003E\n\u003Ctd\u003EHybrid\u003C/td\u003E\n\u003Ctd\u003EAccess/refresh credentials with authoritative session control\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E.NET MAUI\u003C/td\u003E\n\u003Ctd\u003EHybrid\u003C/td\u003E\n\u003Ctd\u003EToken lifecycle suitable for native clients\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMVC / Razor Pages\u003C/td\u003E\n\u003Ctd\u003EPureOpaque\u003C/td\u003E\n\u003Ctd\u003ENatural integration with secure browser cookies and server-side sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAPI-oriented service client\u003C/td\u003E\n\u003Ctd\u003EPureJwt, when stateless authentication is appropriate\u003C/td\u003E\n\u003Ctd\u003EIndependent JWT validation without mandatory session infrastructure\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUAuthHub\u003C/td\u003E\n\u003Ctd\u003EDepends on its configured role and operation\u003C/td\u003E\n\u003Ctd\u003EHosting the authentication system is not itself an authentication mode\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese recommendations describe suitable architectural choices.\u003C/p\u003E\n\u003Cp\u003EThey are not a guarantee that every listed mode is the current implementation default for that profile.\u003C/p\u003E\n\u003Cp\u003EIn particular, the \u003Ccode\u003EUAuthHub\u003C/code\u003E profile should not be interpreted as an authentication mode. UAuthHub is the authentication host, and its supported operations may serve clients using different modes.\u003C/p\u003E\n\u003Cp\u003EFor the full comparison of validation and lifecycle guarantees, see \u003Ca href=\u0022./authentication-modes.md\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-resolution-and-request-context\u0022\u003EProfile resolution and request context\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth can serve clients with different profiles through the same authentication infrastructure.\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\u003EBlazor Server Client\n |\n | Profile: BlazorServer\n |\n \u002B--------------------\u002B\n |\nBlazor WASM Client |\n | |\n | Profile: BlazorWasm |\n | v\n \u002B---------------\u0026gt; UAuthHub\n |\nMAUI Client |\n | |\n | Profile: Maui |\n | |\n \u002B--------------------\u002B\n |\n v\n AuthFlowContext\n |\n v\n Effective Mode\n Effective Policy\n Response Behavior\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server resolves the effective authentication context for each applicable operation.\u003C/p\u003E\n\u003Cp\u003EThis can include the Client Profile, authentication mode, tenant, security options, and credential delivery requirements.\u003C/p\u003E\n\u003Cp\u003EDifferent clients can therefore operate under different authentication strategies without requiring separate authentication architectures.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022per-request-resolution-does-not-mean-arbitrary-mode-switching\u0022\u003EPer-request resolution does not mean arbitrary mode switching\u003C/h3\u003E\n\u003Cp\u003EThe effective profile and mode may differ between requests from different clients.\u003C/p\u003E\n\u003Cp\u003EHowever, they must remain consistent throughout an individual authentication operation.\u003C/p\u003E\n\u003Cp\u003EA request must not begin under one authentication policy and complete under another because different components interpreted its profile differently.\u003C/p\u003E\n\u003Cp\u003ELikewise, a client must not be allowed to change its declared profile to bypass validation requirements associated with an existing credential or session.\u003C/p\u003E\n\u003Cp\u003EAuthentication continuation must preserve the relevant security relationships established by the original authentication context.\u003C/p\u003E\n\u003Cp\u003EThe framework-controlled \u003Ccode\u003EAuthFlowContext\u003C/code\u003E provides a consistent boundary for these decisions.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022./flow-based-authentication.md\u0022\u003EFlow-Based Authentication\u003C/a\u003E for the conceptual model and \u003Ca href=\u0022../auth-flows/\u0022\u003EAuth Flows\u003C/a\u003E for the technical request pipeline.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-delivery-and-runtime-capabilities\u0022\u003ECredential delivery and runtime capabilities\u003C/h2\u003E\n\u003Cp\u003EDifferent clients have different capabilities for receiving and storing authentication credentials.\u003C/p\u003E\n\u003Cp\u003EClient Profiles help the framework account for those differences.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022browser-environments\u0022\u003EBrowser environments\u003C/h3\u003E\n\u003Cp\u003EBrowser applications can use cookies for authentication.\u003C/p\u003E\n\u003Cp\u003EWhen cookies carry sensitive authentication credentials, appropriate \u003Ccode\u003EHttpOnly\u003C/code\u003E, \u003Ccode\u003ESecure\u003C/code\u003E, and \u003Ccode\u003ESameSite\u003C/code\u003E policies should be applied.\u003C/p\u003E\n\u003Cp\u003ECookie-based authentication also requires appropriate CSRF protection.\u003C/p\u003E\n\u003Cp\u003EIn browser environments, a credential stored in an HttpOnly cookie is not directly readable by JavaScript or WebAssembly application code, although the browser can attach it to applicable requests.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022native-applications\u0022\u003ENative applications\u003C/h3\u003E\n\u003Cp\u003ENative applications generally communicate through HTTP clients and can use platform-specific secure credential storage.\u003C/p\u003E\n\u003Cp\u003ETheir credential transport and storage requirements differ from those of a browser.\u003C/p\u003E\n\u003Cp\u003EA native Client Profile can help select appropriate integration behavior, but secure storage remains an application and platform responsibility.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-hosted-applications\u0022\u003EServer-hosted applications\u003C/h3\u003E\n\u003Cp\u003EServer-hosted applications can manage authentication state without exposing all authentication implementation details to browser application code.\u003C/p\u003E\n\u003Cp\u003EThis often makes session-oriented authentication attractive.\u003C/p\u003E\n\u003Cp\u003EHowever, server execution alone does not guarantee security. Cookie protection, session validation, authorization, and long-lived connection behavior must still be handled correctly.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-apis\u0022\u003EResource APIs\u003C/h3\u003E\n\u003Cp\u003EResource APIs validate the credentials presented by callers.\u003C/p\u003E\n\u003Cp\u003ETheir authentication requirements must be enforced by trusted server configuration.\u003C/p\u003E\n\u003Cp\u003EA caller\u0027s declared Client Profile must not determine whether token signatures, session validity, tenant boundaries, or authorization requirements can be bypassed.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-and-trust-boundaries\u0022\u003ESecurity and trust boundaries\u003C/h2\u003E\n\u003Cp\u003EClient Profiles must preserve a clear separation between runtime adaptation and security authority.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-declarations-are-not-trusted-identity\u0022\u003EProfile declarations are not trusted identity\u003C/h3\u003E\n\u003Cp\u003EA Client Profile does not prove that the caller is a particular application, device, or user.\u003C/p\u003E\n\u003Cp\u003EAdditional authentication mechanisms are required when an application needs to establish client identity.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-selection-is-server-controlled\u0022\u003EMode selection is server-controlled\u003C/h3\u003E\n\u003Cp\u003EA client may provide information relevant to mode resolution, but the server determines which authentication modes are permitted.\u003C/p\u003E\n\u003Cp\u003EA profile must not provide a path to downgrade authentication requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-validity-is-independent-of-profile-labels\u0022\u003ECredential validity is independent of profile labels\u003C/h3\u003E\n\u003Cp\u003EA valid-looking credential must still satisfy the validation requirements of its authentication mode.\u003C/p\u003E\n\u003Cp\u003EChanging a profile label must not turn an invalid credential into a valid one.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022transport-configuration-is-not-authorization\u0022\u003ETransport configuration is not authorization\u003C/h3\u003E\n\u003Cp\u003ESelecting cookies, headers, or another supported delivery mechanism does not grant access to protected resources.\u003C/p\u003E\n\u003Cp\u003EAuthentication and authorization decisions remain separate from credential transport.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022defaults-are-not-universal-guarantees\u0022\u003EDefaults are not universal guarantees\u003C/h3\u003E\n\u003Cp\u003EA secure default depends on the application\u0027s deployment, runtime, authentication mode, and security requirements.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth can provide suitable defaults, but developers remain responsible for selecting and configuring the policies appropriate to their applications.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-environments-and-extensibility\u0022\u003ECustom environments and extensibility\u003C/h2\u003E\n\u003Cp\u003ENot every application fits a predefined runtime category.\u003C/p\u003E\n\u003Cp\u003EA custom host, embedded client, hybrid application, or specialized integration may need explicit profile configuration or additional integration behavior.\u003C/p\u003E\n\u003Cp\u003EThe purpose of Client Profiles is to keep these differences at a defined architectural boundary.\u003C/p\u003E\n\u003Cp\u003ENew integrations should reuse the existing authentication model and flow infrastructure wherever possible, rather than introducing independent authentication implementations.\u003C/p\u003E\n\u003Cp\u003ECustom profile resolution and integration behavior must continue to respect server-controlled mode selection and authentication security policies.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EClient Profiles allow UltimateAuth to adapt authentication integration to different application environments.\u003C/p\u003E\n\u003Cp\u003EThey provide a consistent way to describe runtime characteristics and influence suitable defaults without turning client-provided metadata into a security authority.\u003C/p\u003E\n\u003Cp\u003EThe fundamental distinctions are:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient Profile\u003C/strong\u003E describes the runtime environment and integration requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication Mode\u003C/strong\u003E defines credential validation, session dependencies, and authentication lifecycle guarantees.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthFlowContext\u003C/strong\u003E establishes the effective security context for an authentication operation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EServer policy\u003C/strong\u003E determines which behavior is permitted.\u003C/p\u003E\n\u003Cp\u003ETogether, these concepts allow different clients to share the same UltimateAuth infrastructure while preserving explicit authentication boundaries.\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./runtime-architecture.md\u0022\u003ERuntime Architecture\u003C/a\u003E to understand how UltimateAuth\u0027s client, server, and hosting components work together across different application environments.\u003C/p\u003E\n", "Headings": [ { "Id": "what-is-a-client-profile", - "Text": "\uD83D\uDD11 What is a Client Profile?", + "Text": "What is a Client Profile?", "Level": 0 }, { - "Id": "the-key-idea", - "Text": "\uD83E\uDDE0 The Key Idea", + "Id": "why-client-profiles-exist", + "Text": "Why Client Profiles exist", "Level": 0 }, { - "Id": "runtime-detection", - "Text": "\uD83D\uDD04 Runtime Detection", + "Id": "built-in-client-profiles", + "Text": "Built-in Client Profiles", "Level": 0 }, { - "Id": "example-detection-logic", - "Text": "Example Detection Logic", + "Id": "api-profile-versus-resource-api", + "Text": "API profile versus resource API", "Level": 1 }, { - "Id": "client-server-propagation", - "Text": "\uD83D\uDCE1 Client \u2192 Server Propagation", + "Id": "automatic-profile-detection", + "Text": "Automatic profile detection", "Level": 0 }, { - "Id": "automatic-vs-manual-configuration", - "Text": "\u2699\uFE0F Automatic vs Manual Configuration", + "Id": "detection-is-a-convenience-not-a-security-boundary", + "Text": "Detection is a convenience, not a security boundary", + "Level": 1 + }, + { + "Id": "client-to-server-propagation", + "Text": "Client-to-server propagation", + "Level": 0 + }, + { + "Id": "missing-profile-information", + "Text": "Missing profile information", + "Level": 1 + }, + { + "Id": "automatic-and-explicit-configuration", + "Text": "Automatic and explicit configuration", "Level": 0 }, { - "Id": "automatic-default", - "Text": "Automatic (Default)", + "Id": "automatic-configuration", + "Text": "Automatic configuration", "Level": 1 }, { - "Id": "manual-override", - "Text": "Manual Override", + "Id": "explicit-configuration", + "Text": "Explicit configuration", "Level": 1 }, { - "Id": "built-in-profiles", - "Text": "\uD83E\uDDE9 Built-in Profiles", + "Id": "client-profiles-and-authentication-modes", + "Text": "Client Profiles and Authentication Modes", "Level": 0 }, { - "Id": "safe-defaults", - "Text": "\uD83D\uDEE1 Safe Defaults", + "Id": "recommended-defaults", + "Text": "Recommended defaults", + "Level": 1 + }, + { + "Id": "profile-resolution-and-request-context", + "Text": "Profile resolution and request context", + "Level": 0 + }, + { + "Id": "per-request-resolution-does-not-mean-arbitrary-mode-switching", + "Text": "Per-request resolution does not mean arbitrary mode switching", + "Level": 1 + }, + { + "Id": "credential-delivery-and-runtime-capabilities", + "Text": "Credential delivery and runtime capabilities", + "Level": 0 + }, + { + "Id": "browser-environments", + "Text": "Browser environments", + "Level": 1 + }, + { + "Id": "native-applications", + "Text": "Native applications", + "Level": 1 + }, + { + "Id": "server-hosted-applications", + "Text": "Server-hosted applications", + "Level": 1 + }, + { + "Id": "resource-apis", + "Text": "Resource APIs", + "Level": 1 + }, + { + "Id": "security-and-trust-boundaries", + "Text": "Security and trust boundaries", + "Level": 0 + }, + { + "Id": "profile-declarations-are-not-trusted-identity", + "Text": "Profile declarations are not trusted identity", + "Level": 1 + }, + { + "Id": "mode-selection-is-server-controlled", + "Text": "Mode selection is server-controlled", + "Level": 1 + }, + { + "Id": "credential-validity-is-independent-of-profile-labels", + "Text": "Credential validity is independent of profile labels", + "Level": 1 + }, + { + "Id": "transport-configuration-is-not-authorization", + "Text": "Transport configuration is not authorization", "Level": 1 }, { - "Id": "why-this-matters", - "Text": "\uD83D\uDD10 Why This Matters", + "Id": "defaults-are-not-universal-guarantees", + "Text": "Defaults are not universal guarantees", "Level": 1 }, { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", + "Id": "custom-environments-and-extensibility", + "Text": "Custom environments and extensibility", "Level": 0 }, { - "Id": "key-takeaways", - "Text": "\uD83D\uDCCC Key Takeaways", + "Id": "summary", + "Text": "Summary", "Level": 0 }, { "Id": "next-step", - "Text": "\u27A1\uFE0F Next Step", + "Text": "Next step", "Level": 0 } ] 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 1d44015a..bd7474e3 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,96 +1,91 @@ { "Slug": "fundamentals/flow-based-auth", - "Title": "Flow Based Authentication", - "Html": "\n\u003Cp\u003EUltimateAuth is not cookie-based or token-based.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 It is \u003Cstrong\u003Eflow-based\u003C/strong\u003E.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-does-flow-based-mean\u0022\u003E\uD83D\uDD11 What Does \u201CFlow-Based\u201D Mean?\u003C/h2\u003E\n\u003Cp\u003EIn traditional systems, authentication is treated as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA cookie\u003C/li\u003E\n\u003Cli\u003EA JWT token\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EOnce issued, the system simply checks:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u201CIs this token valid?\u201D\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cbr\u003E\n\u003Cp\u003EUltimateAuth takes a different approach:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Authentication is a \u003Cstrong\u003Eseries of controlled flows\u003C/strong\u003E, not a static artifact.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-as-flows\u0022\u003E\uD83E\uDDED Authentication as Flows\u003C/h2\u003E\n\u003Cp\u003EEvery authentication operation is an explicit flow:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ELogin\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ELogout\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EValidate\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERefresh\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERe-authentication\u003C/strong\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EEach flow:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EIs initiated intentionally\u003C/li\u003E\n\u003Cli\u003EIs processed on the server\u003C/li\u003E\n\u003Cli\u003EProduces a controlled result\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-login-flow\u0022\u003E\uD83D\uDD01 Example: Login Flow\u003C/h2\u003E\n\u003Cp\u003EInstead of:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u201CGenerate a token and store it\u201D\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EUltimateAuth does:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ELogin Request\n\u2192 Validate credentials\n\u2192 Resolve Root\n\u2192 Resolve or create Chain\n\u2192 Create Session\n\u2192 Issue authentication grant\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Login is not a single step \u2014 it is a \u003Cstrong\u003Emanaged process\u003C/strong\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-refresh-flow\u0022\u003E\uD83D\uDD04 Example: Refresh Flow\u003C/h2\u003E\n\u003Cp\u003ETraditional systems:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003ERefresh = issue new token\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EUltimateAuth:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ERefresh Request\n\u2192 Validate session\n\u2192 Check security constraints\n\u2192 Apply policies (if any)\n\u2192 Optionally rotate tokens\n\u2192 Update session state (if needed)\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 The server decides what actually happens\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-validate-flow\u0022\u003E\uD83D\uDD0D Example: Validate Flow\u003C/h2\u003E\n\u003Cp\u003EOn each request:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EIncoming Request\n\u2192 Extract session/token\n\u2192 Validate session\n\u2192 Check chain (device context)\n\u2192 Verify root security version\n\u2192 Build auth state\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Validation is not just \u201Ctoken valid?\u201D\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-token-based-thinking-falls-short\u0022\u003E\u26A0\uFE0F Why Token-Based Thinking Falls Short\u003C/h2\u003E\n\u003Cp\u003EToken-based systems assume:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe token contains truth\u003C/li\u003E\n\u003Cli\u003EThe server trusts the token\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis leads to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWeak revocation\u003C/li\u003E\n\u003Cli\u003ENo device awareness\u003C/li\u003E\n\u003Cli\u003ELimited control\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022ultimateauth-approach\u0022\u003E\u2705 UltimateAuth Approach\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth treats tokens as:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Cstrong\u003Etransport artifacts\u003C/strong\u003E, not sources of truth\u003C/p\u003E\n\u003Cp\u003EThe real authority is:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoot\u003C/li\u003E\n\u003Cli\u003EChain\u003C/li\u003E\n\u003Cli\u003ESession\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-idea\u0022\u003E\uD83E\uDDE0 Key Idea\u003C/h2\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003ETokens carry data\u003Cbr /\u003E\nFlows enforce rules\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-controlled-by-design\u0022\u003E\uD83D\uDD10 Server-Controlled by Design\u003C/h2\u003E\n\u003Cp\u003EAll flows are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EExecuted on the server\u003C/li\u003E\n\u003Cli\u003EEvaluated against policies\u003C/li\u003E\n\u003Cli\u003ESubject to security constraints\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 The client does not control authentication state\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-examples-in-code\u0022\u003E\u2699\uFE0F Flow Examples in Code\u003C/h2\u003E\n\u003Cp\u003EUsing \u003Ccode\u003EIUAuthClient\u003C/code\u003E:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eawait UAuthClient.Flows.LoginAsync(request);\nawait UAuthClient.Flows.RefreshAsync();\nawait UAuthClient.Flows.LogoutAsync();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Each method represents a server-driven flow\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-this-changes-development\u0022\u003E\uD83E\uDDE9 How This Changes Development\u003C/h2\u003E\n\u003Cp\u003EInstead of thinking:\u003C/p\u003E\n\u003Cp\u003E\u274C \u201CI need to manage tokens\u201D\u003C/p\u003E\n\u003Cp\u003EYou think:\u003C/p\u003E\n\u003Cp\u003E\u2705 \u201CI need to trigger flows\u201D\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022benefits-of-flow-based-authentication\u0022\u003E\uD83D\uDCCC Benefits of Flow-Based Authentication\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022predictable-behavior\u0022\u003E\u2714 Predictable Behavior\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EEvery action is explicit and controlled.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022better-security\u0022\u003E\u2714 Better Security\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ENo blind token trust\u003C/li\u003E\n\u003Cli\u003EServer-side validation\u003C/li\u003E\n\u003Cli\u003EPolicy-driven decisions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022extensibility\u0022\u003E\u2714 Extensibility\u003C/h3\u003E\n\u003Cp\u003EFlows can be extended with:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMFA\u003C/li\u003E\n\u003Cli\u003ERisk-based checks\u003C/li\u003E\n\u003Cli\u003ECustom policies\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022consistent-across-clients\u0022\u003E\u2714 Consistent Across Clients\u003C/h3\u003E\n\u003Cp\u003ESame flows work for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBlazor Server\u003C/li\u003E\n\u003Cli\u003EWASM (PKCE)\u003C/li\u003E\n\u003Cli\u003EAPIs\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Authentication is not a token \u2014 it is a process\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003ENow that you understand flows:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Continue to Auth Modes\u003C/p\u003E\n", + "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../auth-flows/\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./authentication-model.md\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./authentication-modes.md\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./client-profiles.md\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./authentication-model.md\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./authentication-modes.md\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": "what-does-flow-based-mean", - "Text": "\uD83D\uDD11 What Does \u201CFlow-Based\u201D Mean?", + "Id": "why-authentication-flows-matter", + "Text": "Why authentication flows matter", "Level": 0 }, { - "Id": "authentication-as-flows", - "Text": "\uD83E\uDDED Authentication as Flows", + "Id": "what-is-an-authentication-flow", + "Text": "What is an authentication flow?", "Level": 0 }, { - "Id": "example-login-flow", - "Text": "\uD83D\uDD01 Example: Login Flow", - "Level": 0 + "Id": "login", + "Text": "Login", + "Level": 1 }, { - "Id": "example-refresh-flow", - "Text": "\uD83D\uDD04 Example: Refresh Flow", - "Level": 0 + "Id": "refresh", + "Text": "Refresh", + "Level": 1 }, { - "Id": "example-validate-flow", - "Text": "\uD83D\uDD0D Example: Validate Flow", - "Level": 0 + "Id": "logout", + "Text": "Logout", + "Level": 1 }, { - "Id": "why-token-based-thinking-falls-short", - "Text": "\u26A0\uFE0F Why Token-Based Thinking Falls Short", + "Id": "the-flow-execution-model", + "Text": "The flow execution model", "Level": 0 }, { - "Id": "ultimateauth-approach", - "Text": "\u2705 UltimateAuth Approach", + "Id": "authflowcontext", + "Text": "AuthFlowContext", "Level": 0 }, { - "Id": "key-idea", - "Text": "\uD83E\uDDE0 Key Idea", - "Level": 0 + "Id": "context-is-not-authorization", + "Text": "Context is not authorization", + "Level": 1 }, { - "Id": "server-controlled-by-design", - "Text": "\uD83D\uDD10 Server-Controlled by Design", + "Id": "flows-and-authentication-modes", + "Text": "Flows and authentication modes", "Level": 0 }, { - "Id": "flow-examples-in-code", - "Text": "\u2699\uFE0F Flow Examples in Code", + "Id": "server-controlled-decisions", + "Text": "Server-controlled decisions", "Level": 0 }, { - "Id": "how-this-changes-development", - "Text": "\uD83E\uDDE9 How This Changes Development", + "Id": "separation-of-responsibilities", + "Text": "Separation of responsibilities", "Level": 0 }, { - "Id": "benefits-of-flow-based-authentication", - "Text": "\uD83D\uDCCC Benefits of Flow-Based Authentication", + "Id": "flows-across-client-runtimes", + "Text": "Flows across client runtimes", "Level": 0 }, { - "Id": "predictable-behavior", - "Text": "\u2714 Predictable Behavior", - "Level": 1 - }, - { - "Id": "better-security", - "Text": "\u2714 Better Security", - "Level": 1 + "Id": "predictable-outcomes", + "Text": "Predictable outcomes", + "Level": 0 }, { - "Id": "extensibility", - "Text": "\u2714 Extensibility", - "Level": 1 + "Id": "extensibility-and-security-boundaries", + "Text": "Extensibility and security boundaries", + "Level": 0 }, { - "Id": "consistent-across-clients", - "Text": "\u2714 Consistent Across Clients", - "Level": 1 + "Id": "flow-based-does-not-mean-session-based", + "Text": "Flow-based does not mean session-based", + "Level": 0 }, { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", + "Id": "summary", + "Text": "Summary", "Level": 0 }, { "Id": "next-step", - "Text": "\u27A1\uFE0F Next Step", + "Text": "Next step", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/request-lifecycle.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/request-lifecycle.json index 23ab07ac..19f894a6 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/request-lifecycle.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/request-lifecycle.json @@ -1,96 +1,111 @@ { "Slug": "fundamentals/request-lifecycle", "Title": "Request Lifecycle", - "Html": "\n\u003Cp\u003EThis section explains what happens when a request enters UltimateAuth.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 From the moment an HTTP request arrives\u003Cbr /\u003E\n\uD83D\uDC49 Until authentication state is established or a flow is executed\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022two-types-of-requests\u0022\u003E\uD83E\uDDE0 Two Types of Requests\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth processes requests in two different ways:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022passive-requests\u0022\u003E1. Passive Requests\u003C/h3\u003E\n\u003Cp\u003ERegular application requests (page load, API call)\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022active-flow-requests\u0022\u003E2. Active Flow Requests\u003C/h3\u003E\n\u003Cp\u003EAuthentication flows (login, refresh, logout)\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Both share the same foundation, but diverge at the flow level.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022middleware-pipeline\u0022\u003E\uD83E\uDDE9 Middleware Pipeline\u003C/h2\u003E\n\u003Cp\u003EEvery request passes through the UltimateAuth middleware pipeline:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ETenant \u2192 Session Resolution \u2192 (Validation) \u2192 User Resolution\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022tenant-resolution\u0022\u003E\uD83C\uDFE2 Tenant Resolution\u003C/h3\u003E\n\u003Cp\u003EThe system determines the tenant:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMulti-tenant \u2192 resolved via \u003Ccode\u003EITenantResolver\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003ESingle-tenant \u2192 default context applied\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 If tenant cannot be resolved \u2192 request fails early\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-resolution\u0022\u003E\uD83D\uDD10 Session Resolution\u003C/h3\u003E\n\u003Cp\u003EThe system attempts to extract a session:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EFrom headers, cookies, or tokens\u003C/li\u003E\n\u003Cli\u003EConverted into a \u003Ccode\u003ESessionContext\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cpre\u003E\u003Ccode\u003ESessionId \u2192 SessionContext\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 If no session is found \u2192 request becomes anonymous\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-validation-resource-apis\u0022\u003E\u2714 Session Validation (Resource APIs)\u003C/h3\u003E\n\u003Cp\u003EFor API scenarios:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession is validated immediately\u003C/li\u003E\n\u003Cli\u003EDevice context is considered\u003C/li\u003E\n\u003Cli\u003EA validation result is attached to the request\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This enables stateless or semi-stateful validation\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022user-resolution\u0022\u003E\uD83D\uDC64 User Resolution\u003C/h3\u003E\n\u003Cp\u003EThe system resolves the current user:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBased on validated session\u003C/li\u003E\n\u003Cli\u003EUsing \u003Ccode\u003EIUserAccessor\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This produces the final user context\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022passive-request-flow\u0022\u003E\uD83D\uDD04 Passive Request Flow\u003C/h2\u003E\n\u003Cp\u003EFor normal requests:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ERequest \n\u2192 Middleware pipeline \n\u2192 Session resolved \n\u2192 User resolved \n\u2192 Application executes \n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 No flow execution happens\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022active-flow-requests-1\u0022\u003E\uD83D\uDD10 Active Flow Requests\u003C/h2\u003E\n\u003Cp\u003EFor auth endpoints (login, refresh, etc.):\u003C/p\u003E\n\u003Cp\u003EThe lifecycle continues beyond middleware.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-1-flow-detection\u0022\u003EStep 1: Flow Detection\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode\u003EEndpoint \u2192 FlowType\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-2-context-creation\u0022\u003EStep 2: Context Creation\u003C/h3\u003E\n\u003Cp\u003EAn \u003Ccode\u003EAuthFlowContext\u003C/code\u003E is created.\u003C/p\u003E\n\u003Cp\u003EIt includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient profile\u003C/li\u003E\n\u003Cli\u003EEffective mode\u003C/li\u003E\n\u003Cli\u003ETenant\u003C/li\u003E\n\u003Cli\u003EDevice\u003C/li\u003E\n\u003Cli\u003ESession\u003C/li\u003E\n\u003Cli\u003EResponse configuration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This defines the execution environment\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-3-flow-execution\u0022\u003EStep 3: Flow Execution\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode\u003EAuthFlowContext \u2192 Flow Service \u2192 Orchestrator \u2192 Authority\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-4-state-mutation\u0022\u003EStep 4: State Mutation\u003C/h3\u003E\n\u003Cp\u003EDepending on the flow:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession may be created, updated, or revoked\u003C/li\u003E\n\u003Cli\u003ETokens may be issued\u003C/li\u003E\n\u003Cli\u003ESecurity state may change\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-5-response-generation\u0022\u003EStep 5: Response Generation\u003C/h3\u003E\n\u003Cp\u003EThe system writes the response:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESessionId\u003C/li\u003E\n\u003Cli\u003EAccess token\u003C/li\u003E\n\u003Cli\u003ERefresh token\u003C/li\u003E\n\u003Cli\u003ERedirect (if needed)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-login-request\u0022\u003E\uD83D\uDD01 Example: Login Request\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode\u003EHTTP Request \n\u2192 Tenant resolved \n\u2192 Session resolved (anonymous) \n\u2192 Flow detected (Login) \n\u2192 AuthFlowContext created \n\u2192 Credentials validated \n\u2192 Session created \n\u2192 Tokens issued \n\u2192 Response returned \n\u003C/code\u003E\u003C/pre\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-execution-boundary\u0022\u003E\uD83D\uDD10 Flow Execution Boundary\u003C/h2\u003E\n\u003Cp\u003EAuthentication flows are only executed for endpoints explicitly marked with flow metadata.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERegular requests do not create an AuthFlowContext\u003C/li\u003E\n\u003Cli\u003EAuthFlowContext is only created during flow execution\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This ensures that authentication logic does not interfere with normal application behavior.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003E\uD83D\uDC49 Middleware prepares the request\u003Cbr /\u003E\n\uD83D\uDC49 Flows change the state\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth distinguishes between establishing authentication, validating an existing identity, and authorizing access to protected operations.\u003C/p\u003E\n\u003Cp\u003EThese responsibilities are related, but they do not necessarily execute through the same pipeline.\u003C/p\u003E\n\u003Cp\u003EA login request may establish a new authentication context. A protected API request may only need to validate an existing credential. A server-side application operation may execute without making an additional HTTP request.\u003C/p\u003E\n\u003Cp\u003EUnderstanding these differences is essential when integrating UltimateAuth into real applications.\u003C/p\u003E\n\u003Cp\u003EThis page focuses on \u003Cstrong\u003Ehow authentication is evaluated during application requests and where the security boundaries are enforced\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EFor authentication flow orchestration, see \u003Ca href=\u0022/docs/auth-flows/index\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022two-request-categories\u0022\u003ETwo request categories\u003C/h2\u003E\n\u003Cp\u003EAt a conceptual level, requests interacting with UltimateAuth fall into two categories.\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\u003Cth\u003ETypical examples\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthentication operations\u003C/td\u003E\n\u003Ctd\u003EEstablish, continue, or terminate authentication\u003C/td\u003E\n\u003Ctd\u003ELogin, refresh, logout\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EProtected resource operations\u003C/td\u003E\n\u003Ctd\u003EAccess application functionality using an existing identity\u003C/td\u003E\n\u003Ctd\u003EAPI calls, protected pages, application commands\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe distinction matters because an authentication operation may change authentication state, while a protected resource operation generally consumes an existing authentication context.\u003C/p\u003E\n\u003Cp\u003EBoth require appropriate security checks, but they do not need to execute identical flows.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-operations\u0022\u003EAuthentication operations\u003C/h2\u003E\n\u003Cp\u003EAuthentication operations are explicitly initiated through supported authentication endpoints or application integrations.\u003C/p\u003E\n\u003Cp\u003EExamples include login, refresh, and logout.\u003C/p\u003E\n\u003Cp\u003EFor HTTP authentication endpoints, UltimateAuth uses its flow infrastructure to resolve the effective authentication context and execute the requested operation.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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 Request\n |\n v\nAuthentication Flow Boundary\n |\n v\nEffective AuthFlowContext\n |\n v\nOperation-Specific Validation\n |\n v\nAuthentication Operation\n |\n v\nAuthentication Result\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe operation may create or update sessions, issue credentials, rotate refresh tokens, revoke authentication state, or produce a result without persistent changes.\u003C/p\u003E\n\u003Cp\u003EThese details depend on the flow and authentication mode.\u003C/p\u003E\n\u003Cp\u003EThe internal execution pipeline is described in \u003Ca href=\u0022/docs/auth-flows/index\u0022\u003EAuth Flows\u003C/a\u003E, while individual operations are documented in their respective flow guides.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022protected-resource-operations\u0022\u003EProtected resource operations\u003C/h2\u003E\n\u003Cp\u003EMost application requests do not initiate a login, refresh, or logout flow.\u003C/p\u003E\n\u003Cp\u003EInstead, they attempt to access a protected resource using an existing authentication credential or established server-side authentication context.\u003C/p\u003E\n\u003Cp\u003EA typical protected HTTP request follows this conceptual lifecycle:\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 Request\n |\n v\nResolve Applicable Authentication Scheme\n |\n v\nExtract Authentication Credential\n |\n v\nValidate Credential and Required State\n |\n v\nEstablish Trusted Identity\n |\n v\nEvaluate Authorization\n |\n \u002B---- Denied\n |\n v\nExecute Protected Operation\n |\n v\nReturn Response\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe exact framework components and ordering depend on the application integration.\u003C/p\u003E\n\u003Cp\u003EHowever, the security distinction remains the same:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication establishes identity. Authorization determines whether that identity may perform the requested operation.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA successfully authenticated request is not automatically authorized.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-validation\u0022\u003ECredential validation\u003C/h2\u003E\n\u003Cp\u003EThe most important mode-dependent difference in the request lifecycle is how authentication credentials are validated.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports different authentication strategies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque\u0022\u003EPureOpaque\u003C/h3\u003E\n\u003Cp\u003EThe client presents an opaque session credential.\u003C/p\u003E\n\u003Cp\u003EThe server resolves the associated session and evaluates the applicable session, chain, and security requirements.\u003C/p\u003E\n\u003Cp\u003EThe session remains authoritative for authentication.\u003C/p\u003E\n\u003Cp\u003EA revoked or expired session must not continue to authenticate subsequent requests.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003EHybrid\u003C/h3\u003E\n\u003Cp\u003EThe client presents an opaque access token.\u003C/p\u003E\n\u003Cp\u003EThe server resolves the token and validates its relationship to the authoritative server-side session.\u003C/p\u003E\n\u003Cp\u003EThe access token and session are separate concepts.\u003C/p\u003E\n\u003Cp\u003EAuthentication requires both a valid access credential and the applicable session validation requirements to succeed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid\u0022\u003ESemiHybrid\u003C/h3\u003E\n\u003Cp\u003EThe client presents a JWT access token.\u003C/p\u003E\n\u003Cp\u003ENormal resource authentication validates the JWT cryptographically and applies the relevant token-validation rules.\u003C/p\u003E\n\u003Cp\u003EA server-side session remains relevant for lifecycle operations such as refresh and session management, but normal JWT validation does not require a session lookup.\u003C/p\u003E\n\u003Cp\u003ERevoking a session therefore does not automatically invalidate an already issued JWT.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022purejwt\u0022\u003EPureJwt\u003C/h3\u003E\n\u003Cp\u003EThe client presents a JWT access token.\u003C/p\u003E\n\u003Cp\u003EThe resource validates the token cryptographically and applies the configured issuer, audience, lifetime, and other relevant validation requirements.\u003C/p\u003E\n\u003Cp\u003EAuthentication does not require a server-side session.\u003C/p\u003E\n\u003Cp\u003EPreviously issued JWTs may remain valid until expiration unless an additional invalidation mechanism is explicitly configured.\u003C/p\u003E\n\u003Cp\u003EFor the complete guarantees and tradeoffs, 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=\u0022when-identity-becomes-trusted\u0022\u003EWhen identity becomes trusted\u003C/h2\u003E\n\u003Cp\u003EAn incoming request may contain user identifiers, claims, tokens, cookies, device information, or tenant-related metadata.\u003C/p\u003E\n\u003Cp\u003EThese values must not automatically be treated as a trusted identity.\u003C/p\u003E\n\u003Cp\u003EThe relevant authentication mechanism must validate the presented evidence before the application relies on it.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, this includes the required server-side session checks.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, it includes the applicable cryptographic and token-validation checks.\u003C/p\u003E\n\u003Cp\u003EOnly after successful authentication should the application treat the resulting identity as authenticated.\u003C/p\u003E\n\u003Cp\u003EA failed authentication attempt must not leave behind an identity that appears successfully authenticated to downstream application code.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022anonymous-requests\u0022\u003EAnonymous requests\u003C/h3\u003E\n\u003Cp\u003ENot every application request requires authentication.\u003C/p\u003E\n\u003Cp\u003EPublic endpoints may allow anonymous access.\u003C/p\u003E\n\u003Cp\u003EA request without valid authentication credentials can remain anonymous when the endpoint permits it.\u003C/p\u003E\n\u003Cp\u003EFor protected endpoints, missing or invalid authentication must prevent access according to the applicable authentication and authorization configuration.\u003C/p\u003E\n\u003Cp\u003EAn anonymous request is not necessarily an error. It becomes an access problem when the requested operation requires an authenticated identity.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-context-and-accesscontext\u0022\u003EAuthentication context and AccessContext\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates authentication-flow execution from authorization context.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlowContext\u003C/code\u003E is associated with authentication operations and describes the effective environment in which those operations execute.\u003C/p\u003E\n\u003Cp\u003EIt includes the applicable mode, client profile, tenant, and other flow-related information.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E represents the trusted actor and contextual information used for access and authorization decisions.\u003C/p\u003E\n\u003Cp\u003EThese contexts serve different purposes.\u003C/p\u003E\n\u003Cp\u003EAn application request does not need to execute a login flow merely to establish an identity from an already valid authentication credential.\u003C/p\u003E\n\u003Cp\u003ELikewise, the existence of an \u003Ccode\u003EAuthFlowContext\u003C/code\u003E does not mean the operation has already been authorized.\u003C/p\u003E\n\u003Cp\u003EThe authentication model and these trust boundaries are introduced 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=\u0022authorization-boundary\u0022\u003EAuthorization boundary\u003C/h2\u003E\n\u003Cp\u003EAfter authentication establishes the caller\u0027s identity, the application must determine whether the requested operation is permitted.\u003C/p\u003E\n\u003Cp\u003EAuthorization may depend on:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe authenticated actor.\u003C/li\u003E\n\u003Cli\u003EThe requested resource or operation.\u003C/li\u003E\n\u003Cli\u003ETenant boundaries.\u003C/li\u003E\n\u003Cli\u003ERoles and permissions.\u003C/li\u003E\n\u003Cli\u003EApplicable application policies.\u003C/li\u003E\n\u003Cli\u003EResource ownership or other domain-specific requirements.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth integrates with the relevant server-side authorization mechanisms, but authentication and authorization remain separate responsibilities.\u003C/p\u003E\n\u003Cp\u003EFor ASP.NET Core applications, endpoint and policy-based authorization can protect HTTP resources.\u003C/p\u003E\n\u003Cp\u003EHowever, an authorization attribute applied to an HTTP endpoint does not automatically protect arbitrary direct calls to the underlying application service.\u003C/p\u003E\n\u003Cp\u003ESecurity requirements must be enforced at the boundary through which the operation is actually accessible.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-requests-and-in-process-operations\u0022\u003EHTTP requests and in-process operations\u003C/h2\u003E\n\u003Cp\u003ENot every protected application operation crosses an HTTP boundary.\u003C/p\u003E\n\u003Cp\u003EThis distinction is especially important for server-hosted applications.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-resource-requests\u0022\u003EHTTP resource requests\u003C/h3\u003E\n\u003Cp\u003EA remote client sends an HTTP request to a protected application or resource API.\u003C/p\u003E\n\u003Cp\u003EThe receiving application must authenticate the request and enforce authorization before performing the protected operation.\u003C/p\u003E\n\u003Cp\u003EClient-side authentication state alone is insufficient.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022in-process-application-operations\u0022\u003EIn-process application operations\u003C/h3\u003E\n\u003Cp\u003EA server-hosted application may invoke application services directly without making an HTTP request.\u003C/p\u003E\n\u003Cp\u003EFor example, a Blazor Server component may call a registered service within the same process.\u003C/p\u003E\n\u003Cp\u003EIn this case, the application must not assume that HTTP middleware or endpoint authorization filters execute automatically.\u003C/p\u003E\n\u003Cp\u003EThe service invocation must operate with an appropriately established identity and enforce the relevant authorization requirements.\u003C/p\u003E\n\u003Cp\u003EThis is an architectural distinction, not a separate authentication mode.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/runtime-architecture\u0022\u003ERuntime Architecture\u003C/a\u003E for integrated and distributed deployment models.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022long-lived-connections\u0022\u003ELong-lived connections\u003C/h2\u003E\n\u003Cp\u003ESome application environments maintain connections or execution contexts that outlive individual HTTP requests.\u003C/p\u003E\n\u003Cp\u003EBlazor Server is an important example.\u003C/p\u003E\n\u003Cp\u003EAn interactive circuit may remain active after its initial authentication state was established.\u003C/p\u003E\n\u003Cp\u003EDuring that time, the underlying authentication state may change.\u003C/p\u003E\n\u003Cp\u003EA session may expire, be revoked, or become invalid because of a security-sensitive operation.\u003C/p\u003E\n\u003Cp\u003EConsequently, applications must account for authentication-state revalidation and authorization during long-lived interactions.\u003C/p\u003E\n\u003Cp\u003EAn initially authenticated connection must not be treated as permanent proof that every later operation remains authorized.\u003C/p\u003E\n\u003Cp\u003EThe appropriate mechanism depends on the runtime integration and authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-failures-and-authorization-failures\u0022\u003EAuthentication failures and authorization failures\u003C/h2\u003E\n\u003Cp\u003EAuthentication and authorization failures represent different conditions.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECondition\u003C/th\u003E\n\u003Cth\u003EMeaning\u003C/th\u003E\n\u003Cth\u003ETypical HTTP outcome\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMissing required authentication\u003C/td\u003E\n\u003Ctd\u003ENo acceptable authenticated identity was established\u003C/td\u003E\n\u003Ctd\u003E401 Unauthorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInvalid or expired credential\u003C/td\u003E\n\u003Ctd\u003EPresented evidence failed authentication\u003C/td\u003E\n\u003Ctd\u003E401 Unauthorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthenticated but insufficient permission\u003C/td\u003E\n\u003Ctd\u003EIdentity is valid, but access is denied\u003C/td\u003E\n\u003Ctd\u003E403 Forbidden\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPublic endpoint accessed anonymously\u003C/td\u003E\n\u003Ctd\u003EAuthentication is not required\u003C/td\u003E\n\u003Ctd\u003ERequest proceeds\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese are typical HTTP semantics, not guarantees that every integration returns identical status codes.\u003C/p\u003E\n\u003Cp\u003EBrowser applications may use redirects or other configured challenge behavior.\u003C/p\u003E\n\u003Cp\u003EApplication services operating outside HTTP may express failures through typed results or exceptions rather than HTTP responses.\u003C/p\u003E\n\u003Cp\u003EThe essential distinction is that authentication failure and authorization denial must not be confused.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022request-isolation\u0022\u003ERequest isolation\u003C/h2\u003E\n\u003Cp\u003EAuthentication state must be associated with the correct execution context.\u003C/p\u003E\n\u003Cp\u003EA request must not inherit another request\u0027s authenticated identity, tenant context, or authorization state.\u003C/p\u003E\n\u003Cp\u003EThis matters in multi-tenant systems, applications with concurrent requests, and server-side environments where services may have different lifetimes.\u003C/p\u003E\n\u003Cp\u003ERequest-specific context must be resolved and propagated through the appropriate framework boundaries.\u003C/p\u003E\n\u003Cp\u003ELong-lived or singleton services must not accidentally retain request-specific authentication information.\u003C/p\u003E\n\u003Cp\u003EThe same principle applies to direct application service calls and background operations.\u003C/p\u003E\n\u003Cp\u003EA background operation without an authenticated HTTP request must establish its execution and authorization context through an explicitly supported mechanism rather than assuming a user identity exists.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-the-request-lifecycle-does-not-guarantee\u0022\u003EWhat the request lifecycle does not guarantee\u003C/h2\u003E\n\u003Cp\u003EThe request lifecycle should not be interpreted as a promise that every UltimateAuth operation:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECreates an \u003Ccode\u003EAuthFlowContext\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EResolves a server-side session.\u003C/li\u003E\n\u003Cli\u003EQueries authentication persistence.\u003C/li\u003E\n\u003Cli\u003ERotates or issues tokens.\u003C/li\u003E\n\u003Cli\u003EExecutes the same orchestrator or authority method.\u003C/li\u003E\n\u003Cli\u003EPasses through an HTTP endpoint.\u003C/li\u003E\n\u003Cli\u003EUses identical authorization mechanisms.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThose requirements depend on the operation, authentication mode, and runtime integration.\u003C/p\u003E\n\u003Cp\u003EThe shared guarantee is architectural: authentication evidence must be validated according to the applicable security model, and protected operations must enforce their authorization requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between authentication operations and requests that consume an existing authenticated identity.\u003C/p\u003E\n\u003Cp\u003EAuthentication operations establish, continue, or terminate authentication through controlled flows.\u003C/p\u003E\n\u003Cp\u003EProtected resource operations validate existing authentication evidence, establish trusted identity, and enforce authorization before allowing access.\u003C/p\u003E\n\u003Cp\u003EThe validation strategy depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EThe execution boundary depends on the runtime and deployment architecture.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA request is not trusted because it contains a credential. It becomes authenticated only when the required validation succeeds, and it becomes authorized only when the applicable access rules permit the operation.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003ENext step\u003C/h2\u003E\n\u003Cp\u003EYou have completed the Fundamentals section.\u003C/p\u003E\n\u003Cp\u003EContinue to \u003Ca href=\u0022/docs/auth-flows/index\u0022\u003EAuth Flows\u003C/a\u003E to explore the technical authentication pipeline and the individual login, refresh, and logout operations.\u003C/p\u003E\n", "Headings": [ { - "Id": "two-types-of-requests", - "Text": "\uD83E\uDDE0 Two Types of Requests", + "Id": "two-request-categories", + "Text": "Two request categories", "Level": 0 }, { - "Id": "passive-requests", - "Text": "1. Passive Requests", - "Level": 1 + "Id": "authentication-operations", + "Text": "Authentication operations", + "Level": 0 }, { - "Id": "active-flow-requests", - "Text": "2. Active Flow Requests", - "Level": 1 + "Id": "protected-resource-operations", + "Text": "Protected resource operations", + "Level": 0 }, { - "Id": "middleware-pipeline", - "Text": "\uD83E\uDDE9 Middleware Pipeline", + "Id": "credential-validation", + "Text": "Credential validation", "Level": 0 }, { - "Id": "tenant-resolution", - "Text": "\uD83C\uDFE2 Tenant Resolution", + "Id": "pureopaque", + "Text": "PureOpaque", "Level": 1 }, { - "Id": "session-resolution", - "Text": "\uD83D\uDD10 Session Resolution", + "Id": "hybrid", + "Text": "Hybrid", "Level": 1 }, { - "Id": "session-validation-resource-apis", - "Text": "\u2714 Session Validation (Resource APIs)", + "Id": "semihybrid", + "Text": "SemiHybrid", "Level": 1 }, { - "Id": "user-resolution", - "Text": "\uD83D\uDC64 User Resolution", + "Id": "purejwt", + "Text": "PureJwt", "Level": 1 }, { - "Id": "passive-request-flow", - "Text": "\uD83D\uDD04 Passive Request Flow", + "Id": "when-identity-becomes-trusted", + "Text": "When identity becomes trusted", "Level": 0 }, { - "Id": "active-flow-requests-1", - "Text": "\uD83D\uDD10 Active Flow Requests", + "Id": "anonymous-requests", + "Text": "Anonymous requests", + "Level": 1 + }, + { + "Id": "authentication-context-and-accesscontext", + "Text": "Authentication context and AccessContext", "Level": 0 }, { - "Id": "step-1-flow-detection", - "Text": "Step 1: Flow Detection", - "Level": 1 + "Id": "authorization-boundary", + "Text": "Authorization boundary", + "Level": 0 }, { - "Id": "step-2-context-creation", - "Text": "Step 2: Context Creation", - "Level": 1 + "Id": "http-requests-and-in-process-operations", + "Text": "HTTP requests and in-process operations", + "Level": 0 }, { - "Id": "step-3-flow-execution", - "Text": "Step 3: Flow Execution", + "Id": "http-resource-requests", + "Text": "HTTP resource requests", "Level": 1 }, { - "Id": "step-4-state-mutation", - "Text": "Step 4: State Mutation", + "Id": "in-process-application-operations", + "Text": "In-process application operations", "Level": 1 }, { - "Id": "step-5-response-generation", - "Text": "Step 5: Response Generation", - "Level": 1 + "Id": "long-lived-connections", + "Text": "Long-lived connections", + "Level": 0 + }, + { + "Id": "authentication-failures-and-authorization-failures", + "Text": "Authentication failures and authorization failures", + "Level": 0 + }, + { + "Id": "request-isolation", + "Text": "Request isolation", + "Level": 0 }, { - "Id": "example-login-request", - "Text": "\uD83D\uDD01 Example: Login Request", + "Id": "what-the-request-lifecycle-does-not-guarantee", + "Text": "What the request lifecycle does not guarantee", "Level": 0 }, { - "Id": "flow-execution-boundary", - "Text": "\uD83D\uDD10 Flow Execution Boundary", + "Id": "summary", + "Text": "Summary", "Level": 0 }, { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", + "Id": "next-step", + "Text": "Next step", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/runtime-architecture.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/runtime-architecture.json index fd508bf5..d371d924 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/runtime-architecture.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/runtime-architecture.json @@ -1,71 +1,166 @@ { "Slug": "fundamentals/runtime-architecture", "Title": "Runtime Architecture", - "Html": "\n\u003Cp\u003EUltimateAuth processes authentication through a structured execution pipeline.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 It is not just middleware-based authentication\u003Cbr /\u003E\n\uD83D\uDC49 It is a \u003Cstrong\u003Eflow-driven execution system\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-big-picture\u0022\u003E\uD83E\uDDE0 The Big Picture\u003C/h2\u003E\n\u003Cp\u003EWhen a request reaches an auth endpoint:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ERequest\n \u2192 Endpoint Filter\n \u2192 AuthFlowContext\n \u2192 Endpoint Handler\n \u2192 Flow Service\n \u2192 Orchestrator\n \u2192 Authority\n \u2192 Stores / Issuers\n \u2192 Response\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EEach step has a clearly defined responsibility.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022request-entry-point\u0022\u003E\uD83D\uDD04 Request Entry Point\u003C/h2\u003E\n\u003Cp\u003EAuthentication begins at the endpoint level.\u003C/p\u003E\n\u003Cp\u003EAn endpoint filter detects the flow:\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EEndpoint \u2192 FlowType (Login, Refresh, Logout\u2026)\u003C/code\u003E\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 The system knows which flow is being executed before any logic runs.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authflowcontext-the-core-state\u0022\u003E\uD83E\uDDFE AuthFlowContext \u2014 The Core State\u003C/h2\u003E\n\u003Cp\u003EBefore any operation starts, UltimateAuth creates an AuthFlowContext.\u003C/p\u003E\n\u003Cp\u003EThis is the central object that carries:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient profile\u003C/li\u003E\n\u003Cli\u003EEffective authentication mode\u003C/li\u003E\n\u003Cli\u003ETenant information\u003C/li\u003E\n\u003Cli\u003EDevice context\u003C/li\u003E\n\u003Cli\u003ESession state\u003C/li\u003E\n\u003Cli\u003EResponse configuration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This context defines the entire execution environment\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-service-entry-layer\u0022\u003E\u2699\uFE0F Flow Service \u2014 Entry Layer\u003C/h2\u003E\n\u003Cp\u003EAfter the context is created, the request is passed to the Flow Service.\u003C/p\u003E\n\u003Cp\u003EThe Flow Service:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EActs as the entry point for all flows\u003C/li\u003E\n\u003Cli\u003ENormalizes execution\u003C/li\u003E\n\u003Cli\u003EDelegates work to orchestrators\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 It does not implement business logic directly\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022orchestrator-flow-coordinator\u0022\u003E\uD83E\uDDED Orchestrator \u2014 Flow Coordinator\u003C/h2\u003E\n\u003Cp\u003EThe Orchestrator manages the execution of a flow.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECoordinates multiple steps\u003C/li\u003E\n\u003Cli\u003EEnsures correct execution order\u003C/li\u003E\n\u003Cli\u003EDelegates decisions to Authority\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Think of it as the flow engine\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authority-decision-layer\u0022\u003E\uD83D\uDD10 Authority \u2014 Decision Layer\u003C/h2\u003E\n\u003Cp\u003EThe Authority is the most critical component.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EValidates authentication state\u003C/li\u003E\n\u003Cli\u003EApplies security rules\u003C/li\u003E\n\u003Cli\u003EApproves or rejects operations\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 No sensitive operation bypasses Authority\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022services-stores\u0022\u003E\u2699\uFE0F Services \u0026amp; Stores\u003C/h2\u003E\n\u003Cp\u003EOnce decisions are made:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EServices handle domain logic\u003C/li\u003E\n\u003Cli\u003EStores handle persistence\u003C/li\u003E\n\u003Cli\u003EIssuers generate tokens or session artifacts\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 These layers do not make security decisions\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022end-to-end-example-login\u0022\u003E\uD83D\uDD01 End-to-End Example (Login)\u003C/h2\u003E\n\u003Cp\u003ELogin Request\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003E \u2192 Endpoint Filter (Login Flow)\n \u2192 AuthFlowContext created\n \u2192 Flow Service\n \u2192 Orchestrator\n \u2192 Authority validates credentials\n \u2192 Session created (Store)\n \u2192 Tokens issued (Issuer)\n \u2192 Response generated\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-architecture-matters\u0022\u003E\uD83E\uDDE0 Why This Architecture Matters\u003C/h2\u003E\n\u003Cp\u003E\u2714 Centralized Decision Making\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthority is always in control\u003C/li\u003E\n\u003Cli\u003ENo scattered validation logic\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\u2714 Predictable Execution\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEvery flow follows the same pipeline\u003C/li\u003E\n\u003Cli\u003ENo hidden behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\u2714 Extensibility\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EReplace stores\u003C/li\u003E\n\u003Cli\u003EExtend flows\u003C/li\u003E\n\u003Cli\u003ECustomize orchestration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\u2714 Security by Design\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ENo bypass of Authority\u003C/li\u003E\n\u003Cli\u003EContext-driven validation\u003C/li\u003E\n\u003Cli\u003EFlow-aware execution\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022relation-to-other-concepts\u0022\u003E\uD83D\uDD17 Relation to Other Concepts\u003C/h2\u003E\n\u003Cp\u003EThis architecture connects all previous concepts:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuth Model (Root / Chain / Session) \u2192 validated in Authority\u003C/li\u003E\n\u003Cli\u003EAuth Flows \u2192 executed by Orchestrator\u003C/li\u003E\n\u003Cli\u003EAuth Modes \u2192 applied via EffectiveMode\u003C/li\u003E\n\u003Cli\u003EClient Profiles \u2192 influence behavior at runtime\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Flow defines what happens\n\uD83D\uDC49 Context defines how it happens\n\uD83D\uDC49 Authority decides if it happens\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cp\u003EAuthentication is executed as a pipeline\nAuthFlowContext carries execution state\nOrchestrator coordinates flows\nAuthority enforces security\nServices and Stores execute operations\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003ENow that you understand the execution model:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Continue to Request Lifecycle\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth is designed to provide a unified authentication architecture across different .NET application environments.\u003C/p\u003E\n\u003Cp\u003EA Blazor Server application, a standalone Blazor WebAssembly application, a native MAUI application, and a resource API may have different execution models, but they should not require entirely separate authentication systems.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth addresses this through a separation between \u003Cstrong\u003Eauthentication infrastructure, client integration, hosting, and protected resources\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThis separation allows authentication behavior to remain consistent while adapting to the capabilities and responsibilities of each runtime.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-overview\u0022\u003EArchitectural overview\u003C/h2\u003E\n\u003Cp\u003EAt a high level, UltimateAuth consists of several cooperating layers:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ECore:\u003C/strong\u003E Shared authentication contracts, models, and foundational abstractions.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EServer:\u003C/strong\u003E Authentication operations, security decisions, credential management, and persistence coordination.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClient:\u003C/strong\u003E Client-side integration, authentication requests, and credential lifecycle handling.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUAuthHub:\u003C/strong\u003E The application hosting UltimateAuth\u0027s authentication endpoints and services.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EResource applications:\u003C/strong\u003E Applications and APIs that consume authenticated identity and enforce authorization.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese are architectural responsibilities, not necessarily separate processes or deployments.\u003C/p\u003E\n\u003Cp\u003EA single application may host multiple responsibilities. Alternatively, they can be distributed across independently deployed services.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Application Clients\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502 \u2502\n Blazor WASM MAUI Other Clients\n \u2502 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u2502 HTTP\n \u25BC\n UAuthHub\n \u2502\n UltimateAuth Server\n \u2502\n Authentication Services\n \u2502\n Stores / Providers\n \u2502\n \u25BC\n Persistent State\n\n\n Protected Resources\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502 \u2502\n Resource API Web App Other Services\n \u2502 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n Authentication Validation\n \u2502\n Authorization\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis diagram illustrates the separation of responsibilities.\u003C/p\u003E\n\u003Cp\u003EIt does not imply that every authenticated resource request must pass through UAuthHub.\u003C/p\u003E\n\u003Cp\u003EWhether a protected resource needs to consult server-side authentication infrastructure depends on the effective authentication mode and its validation requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-shared-authentication-foundations\u0022\u003ECore: shared authentication foundations\u003C/h2\u003E\n\u003Cp\u003EThe Core layer defines the common concepts used throughout UltimateAuth.\u003C/p\u003E\n\u003Cp\u003EIts responsibility is to provide stable contracts and abstractions without depending on a particular client runtime or application host.\u003C/p\u003E\n\u003Cp\u003EThese foundations include concepts such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication modes.\u003C/li\u003E\n\u003Cli\u003EAuthentication and security contexts.\u003C/li\u003E\n\u003Cli\u003EAuthentication results and contracts.\u003C/li\u003E\n\u003Cli\u003EDomain models and shared abstractions.\u003C/li\u003E\n\u003Cli\u003ESecurity-related options and definitions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe Core layer provides the common vocabulary used by client integrations, server services, and other framework components.\u003C/p\u003E\n\u003Cp\u003EIt should not contain assumptions that authentication always occurs through an HTTP endpoint, a browser cookie, or a particular hosting model.\u003C/p\u003E\n\u003Cp\u003EThis separation is important because UltimateAuth supports both stateful and stateless authentication strategies.\u003C/p\u003E\n\u003Cp\u003EA shared authentication contract must not implicitly require session persistence when the selected mode does not use sessions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-authentication-execution-and-lifecycle\u0022\u003EServer: authentication execution and lifecycle\u003C/h2\u003E\n\u003Cp\u003EThe Server layer implements the server-side authentication capabilities.\u003C/p\u003E\n\u003Cp\u003EIt coordinates the operations required to establish, continue, validate, and terminate authentication according to the effective mode and security policies.\u003C/p\u003E\n\u003Cp\u003EIts responsibilities include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication flow execution.\u003C/li\u003E\n\u003Cli\u003ECredential validation and management.\u003C/li\u003E\n\u003Cli\u003ESession and token lifecycle operations.\u003C/li\u003E\n\u003Cli\u003EAuthentication security decisions.\u003C/li\u003E\n\u003Cli\u003ECoordination with persistence providers.\u003C/li\u003E\n\u003Cli\u003ECredential issuance and validation.\u003C/li\u003E\n\u003Cli\u003EIntegration with server-side authorization infrastructure.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe Server layer contains multiple cooperating services rather than one universal authentication engine responsible for every decision.\u003C/p\u003E\n\u003Cp\u003EFor example, session lifecycle operations, credential verification, and authorization may follow different execution paths while sharing common security contracts.\u003C/p\u003E\n\u003Cp\u003EThe architecture separates orchestration from the underlying services that perform domain-specific work.\u003C/p\u003E\n\u003Cp\u003EFor the detailed execution pipeline, see \u003Ca href=\u0022../auth-flows/\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-runtime-integration\u0022\u003EClient: runtime integration\u003C/h2\u003E\n\u003Cp\u003EThe Client layer provides the integration between an application and UltimateAuth\u0027s authentication services.\u003C/p\u003E\n\u003Cp\u003EIts purpose is to reduce the amount of authentication infrastructure that application developers need to implement themselves.\u003C/p\u003E\n\u003Cp\u003EDepending on the supported runtime and configuration, client integration may handle:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInitiating authentication flows.\u003C/li\u003E\n\u003Cli\u003ECommunicating with UAuthHub.\u003C/li\u003E\n\u003Cli\u003EProcessing authentication outcomes.\u003C/li\u003E\n\u003Cli\u003EManaging supported credential delivery mechanisms.\u003C/li\u003E\n\u003Cli\u003ECoordinating authentication state with the application.\u003C/li\u003E\n\u003Cli\u003EIntegrating authenticated requests with protected resources.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe client SDK does not replace server-side authentication validation.\u003C/p\u003E\n\u003Cp\u003EIt provides an application-facing interface to the authentication infrastructure.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-integration-is-runtime-aware\u0022\u003EClient integration is runtime-aware\u003C/h3\u003E\n\u003Cp\u003EDifferent client runtimes have different capabilities.\u003C/p\u003E\n\u003Cp\u003EA browser-based WebAssembly application executes within browser security restrictions.\u003C/p\u003E\n\u003Cp\u003EA native MAUI application has access to platform-specific capabilities.\u003C/p\u003E\n\u003Cp\u003EA Blazor Server application executes application logic on the server while maintaining an interactive connection with the browser.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth accounts for these differences through runtime-specific integrations and Client Profiles.\u003C/p\u003E\n\u003Cp\u003EThe authentication model remains shared even when the implementation of credential transport or application state differs.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022./client-profiles.md\u0022\u003EClient Profiles\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022uauthhub-the-authentication-host\u0022\u003EUAuthHub: the authentication host\u003C/h2\u003E\n\u003Cp\u003EUAuthHub is the application hosting UltimateAuth\u0027s server-side authentication infrastructure.\u003C/p\u003E\n\u003Cp\u003EIt provides the environment in which authentication endpoints and the required server services are available.\u003C/p\u003E\n\u003Cp\u003EDepending on the deployment, UAuthHub can be responsible for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAccepting authentication requests.\u003C/li\u003E\n\u003Cli\u003EResolving the applicable tenant and authentication context.\u003C/li\u003E\n\u003Cli\u003EExecuting authentication flows.\u003C/li\u003E\n\u003Cli\u003EManaging credentials and sessions.\u003C/li\u003E\n\u003Cli\u003EIssuing authentication credentials.\u003C/li\u003E\n\u003Cli\u003EApplying configured authentication policies.\u003C/li\u003E\n\u003Cli\u003EExposing the supported authentication endpoints.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUAuthHub is a hosting role, not an authentication mode.\u003C/p\u003E\n\u003Cp\u003EA single UAuthHub can support multiple client environments and authentication strategies, subject to its configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022centralized-hosting\u0022\u003ECentralized hosting\u003C/h3\u003E\n\u003Cp\u003EIn a centralized deployment, several applications can use the same UAuthHub.\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\u003EBlazor WASM Application \u2500\u2500\u2510\n \u2502\nMAUI Application \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u25BA UAuthHub\n \u2502 \u2502\nWeb Application \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n \u25BC\n Authentication Services\n \u2502\n \u25BC\n Authentication Storage\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows applications to share authentication infrastructure without requiring each application to implement its own credential management and authentication lifecycle.\u003C/p\u003E\n\u003Cp\u003EHowever, centralized authentication hosting does not mean that all protected resource requests must be routed through UAuthHub.\u003C/p\u003E\n\u003Cp\u003EThe resource authentication strategy remains mode-dependent.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-applications-and-apis\u0022\u003EResource applications and APIs\u003C/h2\u003E\n\u003Cp\u003EA resource application is an application or service that exposes functionality requiring authentication and authorization.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA protected Web API.\u003C/li\u003E\n\u003Cli\u003EAn administrative web application.\u003C/li\u003E\n\u003Cli\u003EA business application backend.\u003C/li\u003E\n\u003Cli\u003EAn independently deployed service.\u003C/li\u003E\n\u003Cli\u003EA server-rendered application with protected operations.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA resource application must establish the caller\u0027s authenticated identity before allowing access to protected functionality.\u003C/p\u003E\n\u003Cp\u003EIt must then enforce the relevant authorization policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-validation-depends-on-the-mode\u0022\u003EAuthentication validation depends on the mode\u003C/h3\u003E\n\u003Cp\u003EIn PureOpaque and Hybrid, authentication requires server-side session authority.\u003C/p\u003E\n\u003Cp\u003EThe resource authentication path must therefore have access to the infrastructure necessary to enforce the relevant session validation requirements.\u003C/p\u003E\n\u003Cp\u003EIn SemiHybrid and PureJwt, normal access-token authentication uses JWT validation without a mandatory session lookup.\u003C/p\u003E\n\u003Cp\u003EThis can allow a resource API to validate access tokens locally when it has the required verification keys and token-validation configuration.\u003C/p\u003E\n\u003Cp\u003EThe authentication mode therefore affects the resource application\u0027s infrastructure dependencies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-remains-local-to-the-protected-operation\u0022\u003EAuthorization remains local to the protected operation\u003C/h3\u003E\n\u003Cp\u003EAuthentication establishes who the caller is.\u003C/p\u003E\n\u003Cp\u003EAuthorization determines whether that caller may perform the requested operation.\u003C/p\u003E\n\u003Cp\u003EA valid authentication credential does not automatically grant permission to every resource.\u003C/p\u003E\n\u003Cp\u003EResource applications must continue to enforce their authorization requirements through the applicable framework and application policies.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s authentication integration should preserve these boundaries rather than replacing resource authorization with client-side assumptions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022deployment-topology\u0022\u003EDeployment topology\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates architectural responsibilities from physical deployment boundaries.\u003C/p\u003E\n\u003Cp\u003EThis means the same authentication architecture can support different deployment topologies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022integrated-deployment\u0022\u003EIntegrated deployment\u003C/h3\u003E\n\u003Cp\u003EAn integrated deployment hosts authentication infrastructure and application functionality within the same server application.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\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\u003EASP.NET Core Application\n \u2502\n \u251C\u2500\u2500 UltimateAuth Server\n \u2502\n \u251C\u2500\u2500 Authentication Services\n \u2502\n \u251C\u2500\u2500 Application Services\n \u2502\n \u2514\u2500\u2500 Protected Endpoints\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn this model, authentication services and protected application functionality may share the same dependency injection container and process.\u003C/p\u003E\n\u003Cp\u003EWhere supported, internal operations can interact with server-side services directly without requiring an HTTP call to the same application.\u003C/p\u003E\n\u003Cp\u003EThis can simplify deployment and avoid unnecessary network communication.\u003C/p\u003E\n\u003Cp\u003EHowever, direct service access must still respect the required authentication and authorization boundaries.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022distributed-deployment\u0022\u003EDistributed deployment\u003C/h3\u003E\n\u003Cp\u003EA distributed deployment separates the authentication host from one or more resource applications.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E UAuthHub\n \u2502\n \u2502 Authentication\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502\n Web Application Mobile Client\n \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u25BC\n Resource API\n \u2502\n \u25BC\n Application Services\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn this model, clients communicate with UAuthHub for the applicable authentication operations and access resource APIs using the supported credentials.\u003C/p\u003E\n\u003Cp\u003EResource APIs validate those credentials according to the configured authentication mode.\u003C/p\u003E\n\u003Cp\u003EThis architecture supports independently deployed applications while maintaining shared authentication policies and identity infrastructure.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-a-topology\u0022\u003EChoosing a topology\u003C/h3\u003E\n\u003Cp\u003EIntegrated and distributed deployments are not different authentication modes.\u003C/p\u003E\n\u003Cp\u003EThey describe where application components execute and how they communicate.\u003C/p\u003E\n\u003Cp\u003EThe appropriate topology depends on:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EApplication boundaries.\u003C/li\u003E\n\u003Cli\u003EDeployment and operational requirements.\u003C/li\u003E\n\u003Cli\u003ENetwork and infrastructure constraints.\u003C/li\u003E\n\u003Cli\u003EAuthentication mode dependencies.\u003C/li\u003E\n\u003Cli\u003EService ownership and scaling requirements.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA distributed deployment is not inherently more secure or more scalable than an integrated deployment.\u003C/p\u003E\n\u003Cp\u003EBoth require correct authentication validation, authorization enforcement, and infrastructure configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-architecture\u0022\u003EBlazor Server architecture\u003C/h2\u003E\n\u003Cp\u003EBlazor Server has a distinct execution model.\u003C/p\u003E\n\u003Cp\u003EThe browser maintains an interactive connection to an application whose component logic executes on the server.\u003C/p\u003E\n\u003Cp\u003EThis makes it possible to keep many application operations and authentication-related services within the server environment.\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\u003EBrowser\n \u2502\n \u2502 Interactive Connection\n \u25BC\nBlazor Server Application\n \u2502\n \u251C\u2500\u2500 UI Components\n \u2502\n \u251C\u2500\u2500 Application Services\n \u2502\n \u251C\u2500\u2500 UltimateAuth Integration\n \u2502\n \u2514\u2500\u2500 Protected Operations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EPureOpaque is a natural default for this environment because it provides server-controlled authentication without requiring a separate access/refresh token lifecycle.\u003C/p\u003E\n\u003Cp\u003EHowever, the hosting model does not require PureOpaque exclusively.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022direct-service-access\u0022\u003EDirect service access\u003C/h3\u003E\n\u003Cp\u003EWhen authentication infrastructure and application services are hosted in the same process, server-side code may be able to use registered services directly.\u003C/p\u003E\n\u003Cp\u003EThis can avoid unnecessary HTTP requests between components of the same application.\u003C/p\u003E\n\u003Cp\u003EThe distinction is between an application service call and a remote API request.\u003C/p\u003E\n\u003Cp\u003EA direct service call must not bypass security checks merely because it avoids HTTP.\u003C/p\u003E\n\u003Cp\u003EAuthorization requirements must still be enforced at the appropriate trusted boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022long-lived-interactive-connections\u0022\u003ELong-lived interactive connections\u003C/h3\u003E\n\u003Cp\u003EBlazor Server introduces an additional consideration: authentication state may change while an interactive circuit remains active.\u003C/p\u003E\n\u003Cp\u003EA session can expire or be revoked after the circuit was established.\u003C/p\u003E\n\u003Cp\u003EApplications must therefore account for authentication-state revalidation and authorization of sensitive operations.\u003C/p\u003E\n\u003Cp\u003EAn authenticated circuit must not be treated as permanently authorized solely because its initial connection succeeded.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-webassembly-architecture\u0022\u003EBlazor WebAssembly architecture\u003C/h2\u003E\n\u003Cp\u003EBlazor WebAssembly executes application code in the browser.\u003C/p\u003E\n\u003Cp\u003EUnlike Blazor Server, it cannot directly access private server-side authentication services.\u003C/p\u003E\n\u003Cp\u003EAuthentication and protected application operations therefore cross an HTTP boundary.\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\u003EBrowser\n \u2502\n \u2514\u2500\u2500 Blazor WebAssembly\n \u2502\n \u251C\u2500\u2500 UltimateAuth Client\n \u2502 \u2502\n \u2502 \u25BC\n \u2502 UAuthHub\n \u2502\n \u2514\u2500\u2500 Application API Client\n \u2502\n \u25BC\n Resource API\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EHybrid is a suitable authentication strategy for applications that need separate access and refresh tokens while retaining server-authoritative session validation.\u003C/p\u003E\n\u003Cp\u003EHowever, a browser application can also use other modes when the corresponding transport and security architecture supports them.\u003C/p\u003E\n\u003Cp\u003EFor example, a backend-for-frontend architecture may use secure session cookies rather than exposing bearer tokens to browser application code.\u003C/p\u003E\n\u003Cp\u003EThe runtime determines which integration techniques are available; the authentication mode determines the security and lifecycle model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022native-application-architecture\u0022\u003ENative application architecture\u003C/h2\u003E\n\u003Cp\u003ENative applications, including .NET MAUI applications, execute outside the browser environment.\u003C/p\u003E\n\u003Cp\u003EThey generally communicate with authentication hosts and resource APIs through HTTP.\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\u003EMAUI Application\n \u2502\n \u251C\u2500\u2500 UltimateAuth Client\n \u2502 \u2502\n \u2502 \u25BC\n \u2502 UAuthHub\n \u2502\n \u251C\u2500\u2500 Secure Credential Storage\n \u2502\n \u2514\u2500\u2500 Resource API Client\n \u2502\n \u25BC\n Resource API\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ENative applications can use platform-specific secure storage for sensitive credentials.\u003C/p\u003E\n\u003Cp\u003ETheir credential delivery and persistence requirements differ from browser applications.\u003C/p\u003E\n\u003Cp\u003EHybrid can provide a suitable model for applications requiring refresh-token continuity and centralized session control.\u003C/p\u003E\n\u003Cp\u003EThe choice remains configurable according to the supported authentication modes and application requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022shared-components-across-runtimes\u0022\u003EShared components across runtimes\u003C/h2\u003E\n\u003Cp\u003EA unified authentication architecture should minimize unnecessary duplication between client environments.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth separates shared authentication behavior from runtime-specific integration.\u003C/p\u003E\n\u003Cp\u003EFor example, Blazor Server and Blazor WebAssembly applications may share:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication-related UI components.\u003C/li\u003E\n\u003Cli\u003ELogin and logout interaction patterns.\u003C/li\u003E\n\u003Cli\u003ERequest and response contracts.\u003C/li\u003E\n\u003Cli\u003EApplication-facing client abstractions.\u003C/li\u003E\n\u003Cli\u003EAuthentication-state presentation logic.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAt the same time, they may require different implementations for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredential storage.\u003C/li\u003E\n\u003Cli\u003EHTTP communication.\u003C/li\u003E\n\u003Cli\u003EBrowser interaction.\u003C/li\u003E\n\u003Cli\u003EServer-side service access.\u003C/li\u003E\n\u003Cli\u003EAuthentication-state synchronization.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe goal is not to force every runtime to execute identical code.\u003C/p\u003E\n\u003Cp\u003EThe goal is to share behavior and contracts where appropriate while preserving each runtime\u0027s security and execution requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-boundaries-and-direct-execution\u0022\u003EHTTP boundaries and direct execution\u003C/h2\u003E\n\u003Cp\u003EAn important architectural distinction is whether an operation crosses an HTTP boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remote-execution\u0022\u003ERemote execution\u003C/h3\u003E\n\u003Cp\u003EA remote client communicates with an authentication host or resource API through HTTP.\u003C/p\u003E\n\u003Cp\u003EThe server must independently validate the request, credentials, and authorization requirements.\u003C/p\u003E\n\u003Cp\u003EClient-side checks are not sufficient.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022in-process-execution\u0022\u003EIn-process execution\u003C/h3\u003E\n\u003Cp\u003EAn application may host UltimateAuth services and protected application services within the same process.\u003C/p\u003E\n\u003Cp\u003EIn that case, some operations may use direct service calls instead of HTTP.\u003C/p\u003E\n\u003Cp\u003EThis can reduce serialization and network overhead.\u003C/p\u003E\n\u003Cp\u003EHowever, HTTP middleware and endpoint authorization attributes do not automatically execute during arbitrary direct service calls.\u003C/p\u003E\n\u003Cp\u003EThe application must ensure that equivalent security requirements are enforced through the appropriate service or authorization boundary.\u003C/p\u003E\n\u003Cp\u003EDirect execution is an optimization and integration choice, not permission to bypass authentication or authorization.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022infrastructure-and-persistence\u0022\u003EInfrastructure and persistence\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses abstractions to separate authentication behavior from infrastructure implementations.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured mode and services, the system may require storage for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUser and credential information.\u003C/li\u003E\n\u003Cli\u003EAuthentication security state.\u003C/li\u003E\n\u003Cli\u003ESession lineage.\u003C/li\u003E\n\u003Cli\u003ERefresh-token lifecycle.\u003C/li\u003E\n\u003Cli\u003EDevice-associated authentication state.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese dependencies vary by authentication mode.\u003C/p\u003E\n\u003Cp\u003EFor example, PureOpaque and Hybrid require session infrastructure for authentication.\u003C/p\u003E\n\u003Cp\u003ESemiHybrid requires session infrastructure for lifecycle management but not for normal JWT validation.\u003C/p\u003E\n\u003Cp\u003EPureJwt does not require server-side session infrastructure. Optional refresh-token functionality may introduce independent renewal-state requirements.\u003C/p\u003E\n\u003Cp\u003EThe architecture must preserve these distinctions.\u003C/p\u003E\n\u003Cp\u003EInfrastructure abstractions allow the authentication services to remain independent of a particular persistence implementation, while the selected mode determines which capabilities are required.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-boundaries\u0022\u003EArchitectural boundaries\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s runtime architecture depends on several boundaries remaining explicit.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-and-server\u0022\u003EClient and server\u003C/h3\u003E\n\u003Cp\u003EClients initiate operations and present credentials.\u003C/p\u003E\n\u003Cp\u003EServers establish trusted authentication state and enforce security requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-and-authorization\u0022\u003EAuthentication and authorization\u003C/h3\u003E\n\u003Cp\u003EAuthentication determines identity.\u003C/p\u003E\n\u003Cp\u003EAuthorization determines whether an operation is permitted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hosting-and-authentication-mode\u0022\u003EHosting and authentication mode\u003C/h3\u003E\n\u003Cp\u003EUAuthHub and resource applications describe hosting responsibilities.\u003C/p\u003E\n\u003Cp\u003EPureOpaque, Hybrid, SemiHybrid, and PureJwt describe authentication strategies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022shared-contracts-and-runtime-integration\u0022\u003EShared contracts and runtime integration\u003C/h3\u003E\n\u003Cp\u003EShared contracts define consistent behavior.\u003C/p\u003E\n\u003Cp\u003ERuntime integrations adapt that behavior to browser, native, and server-side environments.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022orchestration-and-infrastructure\u0022\u003EOrchestration and infrastructure\u003C/h3\u003E\n\u003Cp\u003EOrchestration coordinates authentication operations.\u003C/p\u003E\n\u003Cp\u003EInfrastructure services provide persistence, token operations, and other required capabilities.\u003C/p\u003E\n\u003Cp\u003EThese boundaries allow UltimateAuth to evolve without requiring every client integration or authentication mode to duplicate the complete authentication implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s runtime architecture separates authentication responsibilities from application hosting and client execution environments.\u003C/p\u003E\n\u003Cp\u003EIts main architectural components are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ECore\u003C/strong\u003E, which defines shared contracts and authentication foundations.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EServer\u003C/strong\u003E, which implements authentication operations and lifecycle management.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClient\u003C/strong\u003E, which provides application-facing authentication integration.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUAuthHub\u003C/strong\u003E, which hosts the authentication infrastructure.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EResource applications\u003C/strong\u003E, which authenticate callers and enforce authorization.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese components can operate within an integrated application or across distributed services.\u003C/p\u003E\n\u003Cp\u003EDifferent client runtimes can share authentication contracts and behavior while using integration mechanisms appropriate to their environments.\u003C/p\u003E\n\u003Cp\u003EAuthentication modes determine the required validation and state dependencies.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe architecture is unified because its security contracts and responsibilities remain consistent\u2014not because every runtime, mode, or deployment follows an identical execution path.\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./request-lifecycle.md\u0022\u003ERequest Lifecycle\u003C/a\u003E to understand how an incoming request moves through authentication validation, identity establishment, and authorization before reaching a protected operation.\u003C/p\u003E\n", "Headings": [ { - "Id": "the-big-picture", - "Text": "\uD83E\uDDE0 The Big Picture", + "Id": "architectural-overview", + "Text": "Architectural overview", "Level": 0 }, { - "Id": "request-entry-point", - "Text": "\uD83D\uDD04 Request Entry Point", + "Id": "core-shared-authentication-foundations", + "Text": "Core: shared authentication foundations", "Level": 0 }, { - "Id": "authflowcontext-the-core-state", - "Text": "\uD83E\uDDFE AuthFlowContext \u2014 The Core State", + "Id": "server-authentication-execution-and-lifecycle", + "Text": "Server: authentication execution and lifecycle", "Level": 0 }, { - "Id": "flow-service-entry-layer", - "Text": "\u2699\uFE0F Flow Service \u2014 Entry Layer", + "Id": "client-runtime-integration", + "Text": "Client: runtime integration", "Level": 0 }, { - "Id": "orchestrator-flow-coordinator", - "Text": "\uD83E\uDDED Orchestrator \u2014 Flow Coordinator", + "Id": "client-integration-is-runtime-aware", + "Text": "Client integration is runtime-aware", + "Level": 1 + }, + { + "Id": "uauthhub-the-authentication-host", + "Text": "UAuthHub: the authentication host", "Level": 0 }, { - "Id": "authority-decision-layer", - "Text": "\uD83D\uDD10 Authority \u2014 Decision Layer", + "Id": "centralized-hosting", + "Text": "Centralized hosting", + "Level": 1 + }, + { + "Id": "resource-applications-and-apis", + "Text": "Resource applications and APIs", "Level": 0 }, { - "Id": "services-stores", - "Text": "\u2699\uFE0F Services \u0026 Stores", + "Id": "authentication-validation-depends-on-the-mode", + "Text": "Authentication validation depends on the mode", + "Level": 1 + }, + { + "Id": "authorization-remains-local-to-the-protected-operation", + "Text": "Authorization remains local to the protected operation", + "Level": 1 + }, + { + "Id": "deployment-topology", + "Text": "Deployment topology", "Level": 0 }, { - "Id": "end-to-end-example-login", - "Text": "\uD83D\uDD01 End-to-End Example (Login)", + "Id": "integrated-deployment", + "Text": "Integrated deployment", + "Level": 1 + }, + { + "Id": "distributed-deployment", + "Text": "Distributed deployment", + "Level": 1 + }, + { + "Id": "choosing-a-topology", + "Text": "Choosing a topology", + "Level": 1 + }, + { + "Id": "blazor-server-architecture", + "Text": "Blazor Server architecture", + "Level": 0 + }, + { + "Id": "direct-service-access", + "Text": "Direct service access", + "Level": 1 + }, + { + "Id": "long-lived-interactive-connections", + "Text": "Long-lived interactive connections", + "Level": 1 + }, + { + "Id": "blazor-webassembly-architecture", + "Text": "Blazor WebAssembly architecture", "Level": 0 }, { - "Id": "why-this-architecture-matters", - "Text": "\uD83E\uDDE0 Why This Architecture Matters", + "Id": "native-application-architecture", + "Text": "Native application architecture", "Level": 0 }, { - "Id": "relation-to-other-concepts", - "Text": "\uD83D\uDD17 Relation to Other Concepts", + "Id": "shared-components-across-runtimes", + "Text": "Shared components across runtimes", "Level": 0 }, { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", + "Id": "http-boundaries-and-direct-execution", + "Text": "HTTP boundaries and direct execution", "Level": 0 }, { - "Id": "key-takeaways", - "Text": "\uD83D\uDCCC Key Takeaways", + "Id": "remote-execution", + "Text": "Remote execution", + "Level": 1 + }, + { + "Id": "in-process-execution", + "Text": "In-process execution", + "Level": 1 + }, + { + "Id": "infrastructure-and-persistence", + "Text": "Infrastructure and persistence", + "Level": 0 + }, + { + "Id": "architectural-boundaries", + "Text": "Architectural boundaries", + "Level": 0 + }, + { + "Id": "client-and-server", + "Text": "Client and server", + "Level": 1 + }, + { + "Id": "authentication-and-authorization", + "Text": "Authentication and authorization", + "Level": 1 + }, + { + "Id": "hosting-and-authentication-mode", + "Text": "Hosting and authentication mode", + "Level": 1 + }, + { + "Id": "shared-contracts-and-runtime-integration", + "Text": "Shared contracts and runtime integration", + "Level": 1 + }, + { + "Id": "orchestration-and-infrastructure", + "Text": "Orchestration and infrastructure", + "Level": 1 + }, + { + "Id": "summary", + "Text": "Summary", "Level": 0 }, { "Id": "next-step", - "Text": "\u27A1\uFE0F Next Step", + "Text": "Next step", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/ecosystem.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/ecosystem.json index 63f4e9f8..5d58ecc3 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/ecosystem.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/ecosystem.json @@ -1,7 +1,7 @@ { "Slug": "getting-started/ecosystem", "Title": "UltimateAuth Ecosystem", - "Html": "\n\u003Cp\u003EUltimateAuth is designed as a modular authentication and identity platform for .NET.\u003C/p\u003E\n\u003Cp\u003EThe platform has three primary foundations:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E UltimateAuth\n |\n Core\n |\n \u002B------------\u002B------------\u002B\n | |\n Server Client\n | |\n Server composition Client integration\n through bundles for the application\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\u003Cstrong\u003ECore\u003C/strong\u003E defines the common language and foundational abstractions of the platform.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EServer\u003C/strong\u003E hosts and executes authentication and identity capabilities.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient\u003C/strong\u003E provides the application-facing model used by applications to interact with those capabilities.\u003C/p\u003E\n\u003Cp\u003EAn application does not necessarily need both Server and Client. Depending on its role, it may use:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EServer only\u003C/li\u003E\n\u003Cli\u003EClient only\u003C/li\u003E\n\u003Cli\u003EServer and Client together\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis separation is intentional. UltimateAuth is designed so that application topology does not dictate a monolithic package structure.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022platform-at-a-glance\u0022\u003EPlatform at a Glance\u003C/h2\u003E\n\u003Cp\u003EA useful way to think about UltimateAuth is:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EUltimateAuth\n|\n\u002B-- Core\n|\n\u002B-- Server\n| |\n| \u002B-- Essential server capabilities\n| | \u002B-- Authentication\n| | \u002B-- Sessions\n| | \u002B-- Tokens\n| | \u002B-- Policies\n| |\n| \u002B-- Plugin Domains\n| | \u002B-- Users\n| | \u002B-- Credentials\n| | \u002B-- Authorization\n| |\n| \u002B-- Implementations\n| \u002B-- Reference\n| \u002B-- InMemory\n| \u002B-- Entity Framework Core\n|\n\u002B-- Client\n |\n \u002B-- Client.Blazor\n \u002B-- Client.AspNetCore compatibility\n \u002B-- Future client integrations\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThe important distinction is that these packages are not intended to make installation complicated.\u003C/p\u003E\n\u003Cp\u003EThe internal architecture is modular, while the installation experience is deliberately composed around \u003Cstrong\u003Eserver bundles\u003C/strong\u003E and \u003Cstrong\u003Eclient-specific packages\u003C/strong\u003E.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core\u0022\u003ECore\u003C/h2\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.core\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Core\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003ECore is the foundation of the entire UltimateAuth platform.\u003C/p\u003E\n\u003Cp\u003EIt contains the shared primitives, domain concepts, contracts, abstractions, value objects, configuration foundations, and common infrastructure required by the rest of UltimateAuth.\u003C/p\u003E\n\u003Cp\u003EConcepts such as UltimateAuth\u0027s identity and session model originate from this layer.\u003C/p\u003E\n\u003Cp\u003ECore is intentionally independent from a particular application type or persistence technology.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Core\n |\n \u002B--------\u002B--------\u002B\n | |\n Server Client\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EServer and Client share the same platform vocabulary because both are built on Core.\u003C/p\u003E\n\u003Cp\u003EMost application developers do not need to install Core directly. It is normally brought into the application through the appropriate Server bundle or Client integration package.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server\u0022\u003EServer\u003C/h2\u003E\n\u003Cp\u003EThe Server side is the authority that executes UltimateAuth authentication and identity behavior.\u003C/p\u003E\n\u003Cp\u003EIt is responsible for the server-side runtime, including authentication flows, security decisions, orchestration, endpoints, middleware integration, and the coordination of server capabilities.\u003C/p\u003E\n\u003Cp\u003EAt source level, \u003Ccode\u003ECodeBeam.UltimateAuth.Server\u003C/code\u003E is the central server project and depends on major server domains such as:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ECodeBeam.UltimateAuth.Server\n|\n\u002B-- Core\n\u002B-- Authorization\n\u002B-- Credentials\n\u002B-- Users\n\u002B-- Policies\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EOther essential server capabilities, including authentication, sessions, and tokens, are kept in focused projects rather than being merged into one oversized server assembly.\u003C/p\u003E\n\u003Cp\u003EThis gives UltimateAuth a modular internal architecture without forcing that complexity onto application developers.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-i-install-codebeam.ultimateauth.server-directly\u0022\u003EDo I install \u003Ccode\u003ECodeBeam.UltimateAuth.Server\u003C/code\u003E directly?\u003C/h3\u003E\n\u003Cp\u003EFor normal application setup, \u003Cstrong\u003Eno\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe recommended server installation unit is a \u003Cstrong\u003EBundle\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EA bundle represents a complete server composition for a particular infrastructure strategy.\u003C/p\u003E\n\u003Cp\u003EInstead of manually selecting all server packages and implementations, choose the bundle that matches your application.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Your Server\n |\n Bundle\n |\n \u002B---------------\u002B---------------\u002B\n | | |\n Server Domains Infrastructure\n | | |\n Runtime Reference Persistence\n behavior providers\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis is an important part of the UltimateAuth design:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EThe framework remains modular internally, while common server configurations remain simple to install.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-bundles\u0022\u003EServer Bundles\u003C/h2\u003E\n\u003Cp\u003EBundles are server-side composition packages.\u003C/p\u003E\n\u003Cp\u003EThey collect the UltimateAuth server capabilities and the implementations required for a particular configuration into a single installation unit.\u003C/p\u003E\n\u003Cp\u003EYou normally choose \u003Cstrong\u003Eone server bundle\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reference-bundle\u0022\u003EReference Bundle\u003C/h3\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.reference.bundle\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Reference.Bundle\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003EThe Reference Bundle provides the recommended UltimateAuth server composition without selecting a persistence implementation for your application.\u003C/p\u003E\n\u003Cp\u003EUse it when you want UltimateAuth\u0027s reference behavior but provide your own persistence infrastructure.\u003C/p\u003E\n\u003Cp\u003EConceptually:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EReference.Bundle\n|\n\u002B-- Server\n\u002B-- UltimateAuth reference implementations\n\u002B-- Required server capabilities\n|\n\u002B-- Persistence: supplied by your application\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis is the lowest-level recommended server composition for applications that want to own their storage implementation.\u003C/p\u003E\n\u003Cp\u003EIt is also an important extensibility point: UltimateAuth provides a recommended architecture without requiring applications to use one particular persistence technology.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inmemory-bundle\u0022\u003EInMemory Bundle\u003C/h3\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.inmemory.bundle\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.InMemory.Bundle\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003EThe InMemory Bundle provides a complete server setup backed by UltimateAuth\u0027s in-memory implementations.\u003C/p\u003E\n\u003Cp\u003EIt is primarily intended for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Edevelopment\u003C/li\u003E\n\u003Cli\u003EQuick Start\u003C/li\u003E\n\u003Cli\u003Esamples\u003C/li\u003E\n\u003Cli\u003Eautomated tests\u003C/li\u003E\n\u003Cli\u003Eevaluation\u003C/li\u003E\n\u003Cli\u003Eprototypes\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EInMemory.Bundle\n|\n\u002B-- Server\n\u002B-- Reference implementations\n\u002B-- InMemory persistence implementations\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis is the easiest way to start an UltimateAuth server.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022entity-framework-core-bundle\u0022\u003EEntity Framework Core Bundle\u003C/h3\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.entityframeworkcore.bundle\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003EThe Entity Framework Core Bundle provides the complete server composition using UltimateAuth\u0027s EF Core persistence implementations.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EEntityFrameworkCore.Bundle\n|\n\u002B-- Server\n\u002B-- Reference implementations\n\u002B-- EF Core persistence implementations\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EFor applications that use EF Core for persistent UltimateAuth data, this is normally the server package to install.\u003C/p\u003E\n\u003Cp\u003EThe developer does not need to manually assemble the individual Users, Credentials, Authorization, Sessions, Tokens, and other persistence packages.\u003C/p\u003E\n\u003Cp\u003EThe bundle performs that composition.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-bundles-matter\u0022\u003EWhy Bundles Matter\u003C/h3\u003E\n\u003Cp\u003EA highly modular framework can easily make the developer experience worse if every application has to understand and install its internal package graph.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth deliberately separates \u003Cstrong\u003Earchitecture modularity\u003C/strong\u003E from \u003Cstrong\u003Einstallation complexity\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EInternally:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EServer\n \u002B Users\n \u002B Credentials\n \u002B Authorization\n \u002B Authentication\n \u002B Sessions\n \u002B Tokens\n \u002B Policies\n \u002B persistence implementations\n \u002B reference implementations\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EFrom the application:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis gives UltimateAuth two useful properties at the same time:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003Eindividual capabilities remain independently replaceable and evolvable;\u003C/li\u003E\n\u003Cli\u003Ethe common path remains simple.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EYou should not need to understand the complete internal dependency graph before building your first application.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client\u0022\u003EClient\u003C/h2\u003E\n\u003Cp\u003EThe Client side has a different packaging model.\u003C/p\u003E\n\u003Cp\u003EThere are \u003Cstrong\u003Eno Client bundles\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EInstead, applications install the client package designed for their application model.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Client Foundation\n |\n \u002B-------------\u002B-------------\u002B\n | | |\n Blazor MAUI Future\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EFor example, a Blazor application installs:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EA future MAUI integration can follow the same model without changing the underlying UltimateAuth Client architecture.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022codebeam.ultimateauth.client\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client\u003C/code\u003E\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client\u003C/code\u003E is the shared client foundation.\u003C/p\u003E\n\u003Cp\u003EIt contains common client contracts, abstractions, flow APIs, state concepts, and runtime-independent client behavior.\u003C/p\u003E\n\u003Cp\u003EIt should be thought of as the foundation on which concrete UltimateAuth client integrations are built.\u003C/p\u003E\n\u003Cp\u003EMost applications should therefore install their platform-specific client package rather than installing \u003Ccode\u003ECodeBeam.UltimateAuth.Client\u003C/code\u003E directly.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022codebeam.ultimateauth.client.blazor\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.Blazor\u003C/code\u003E\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.Blazor\u003C/code\u003E is the concrete UltimateAuth client integration for Blazor.\u003C/p\u003E\n\u003Cp\u003EIt builds on the Client foundation and adds the Blazor-specific runtime and UI integration required by Blazor applications.\u003C/p\u003E\n\u003Cp\u003EThis includes capabilities such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthApp\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthStateView\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003Eauthentication state integration\u003C/li\u003E\n\u003Cli\u003EBlazor authorization integration\u003C/li\u003E\n\u003Cli\u003Erouting integration\u003C/li\u003E\n\u003Cli\u003Eauthentication flow components\u003C/li\u003E\n\u003Cli\u003EJavaScript transport integration\u003C/li\u003E\n\u003Cli\u003Eclient lifecycle coordination\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EConceptually:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ECore\n |\nClient\n |\nClient.Blazor\n |\nYour Blazor Application\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis model keeps the shared Client architecture independent from Blazor while still giving Blazor developers one natural package to install.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-compatibility-packages\u0022\u003EClient Compatibility Packages\u003C/h3\u003E\n\u003Cp\u003ENot every client-side package represents a complete client implementation.\u003C/p\u003E\n\u003Cp\u003ESome packages exist to bridge UltimateAuth with a host framework.\u003C/p\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.client.aspnetcore\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.AspNetCore\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.AspNetCore\u003C/code\u003E is a lightweight ASP.NET Core compatibility layer for UltimateAuth client applications.\u003C/p\u003E\n\u003Cp\u003EIt allows client-oriented applications to integrate with ASP.NET Core authentication and authorization infrastructure without introducing the full UltimateAuth Server runtime.\u003C/p\u003E\n\u003Cp\u003EThis is particularly useful for hosting models such as Blazor Web App with Interactive WebAssembly.\u003C/p\u003E\n\u003Cp\u003EIt can enable compatibility with ASP.NET Core features such as \u003Ccode\u003E[Authorize]\u003C/code\u003E, but it does \u003Cstrong\u003Enot\u003C/strong\u003E implement authentication itself and does not turn the application into an UltimateAuth Server.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022application-composition\u0022\u003EApplication Composition\u003C/h2\u003E\n\u003Cp\u003EBecause Server and Client are independent platform sides, different applications can compose UltimateAuth differently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-only\u0022\u003EServer Only\u003C/h3\u003E\n\u003Cp\u003ETypical examples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ededicated authentication servers\u003C/li\u003E\n\u003Cli\u003Ebackend services hosting UltimateAuth\u003C/li\u003E\n\u003Cli\u003Eserver-side identity infrastructure\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EApplication\n|\n\u002B-- Server Bundle\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-only\u0022\u003EClient Only\u003C/h3\u003E\n\u003Cp\u003ETypical examples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Estandalone Blazor WebAssembly\u003C/li\u003E\n\u003Cli\u003Efuture MAUI applications\u003C/li\u003E\n\u003Cli\u003Eapplications using a remote UAuthHub\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EApplication\n|\n\u002B-- Client.Blazor\n |\n \u002B---- remote UltimateAuth Server / UAuthHub\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-client\u0022\u003EServer \u002B Client\u003C/h3\u003E\n\u003Cp\u003ESome applications host UltimateAuth and consume its client API in the same application.\u003C/p\u003E\n\u003Cp\u003EBlazor Server is a common example.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EBlazor Server Application\n|\n\u002B-- Server Bundle\n|\n\u002B-- Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThe two sides remain architecturally distinct even when deployed in the same process.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022plugin-domains\u0022\u003EPlugin Domains\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth extends its identity platform through \u003Cstrong\u003EPlugin Domains\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe current primary Plugin Domains are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUsers\u003C/li\u003E\n\u003Cli\u003ECredentials\u003C/li\u003E\n\u003Cli\u003EAuthorization\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA Plugin Domain is more than a single assembly. It is a capability area with contracts, runtime behavior, recommended implementations, and infrastructure implementations.\u003C/p\u003E\n\u003Cp\u003EThis structure allows a domain to remain replaceable without requiring applications to redesign the rest of UltimateAuth.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022anatomy-of-a-plugin-domain\u0022\u003EAnatomy of a Plugin Domain\u003C/h3\u003E\n\u003Cp\u003EA typical Plugin Domain follows this model:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EPlugin Domain\n|\n\u002B-- Contracts\n|\n\u002B-- Domain / Runtime\n|\n\u002B-- Reference\n|\n\u002B-- Persistence\n |\n \u002B-- InMemory\n \u002B-- EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EEach layer has a distinct purpose.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022contracts\u0022\u003EContracts\u003C/h3\u003E\n\u003Cp\u003EExample:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ECodeBeam.UltimateAuth.Users.Contracts\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EContracts define the public boundary of the domain. Server and Client can communicate through same contracts without requiring one to depend on the other.\u003C/p\u003E\n\u003Cp\u003EThey are intentionally kept close to Core and represent the smallest dependency surface for applications or extensions that only need to interact with the domain contract.\u003C/p\u003E\n\u003Cp\u003EDepending on the domain, contracts can contain concepts such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Erequests\u003C/li\u003E\n\u003Cli\u003Eresults\u003C/li\u003E\n\u003Cli\u003Epublic models\u003C/li\u003E\n\u003Cli\u003Eabstractions\u003C/li\u003E\n\u003Cli\u003Edomain-facing contracts\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis allows another implementation to integrate with UltimateAuth without depending on the entire built-in implementation.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022domain-runtime\u0022\u003EDomain / Runtime\u003C/h3\u003E\n\u003Cp\u003EExample:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ECodeBeam.UltimateAuth.Users\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThe main domain project provides the runtime and application behavior of that Plugin Domain.\u003C/p\u003E\n\u003Cp\u003EIt operates against abstractions rather than requiring one persistence implementation.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reference-implementations\u0022\u003EReference Implementations\u003C/h3\u003E\n\u003Cp\u003EExample:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ECodeBeam.UltimateAuth.Users.Reference\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EReference packages contain the implementation UltimateAuth recommends as the standard baseline for the domain.\u003C/p\u003E\n\u003Cp\u003EThe distinction between \u003Cstrong\u003Econtract\u003C/strong\u003E and \u003Cstrong\u003Ereference implementation\u003C/strong\u003E is intentional.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth does not leave extensibility points empty and require every developer to design critical identity behavior from scratch.\u003C/p\u003E\n\u003Cp\u003EInstead, the platform provides:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EContract\n |\n \u002B---- UltimateAuth Reference Implementation\n |\n \u002B---- Your Custom Implementation\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EFor the common path, use the UltimateAuth reference implementation.\u003C/p\u003E\n\u003Cp\u003EFor specialized requirements, replace the appropriate implementation while preserving the surrounding platform contract and security boundaries.\u003C/p\u003E\n\u003Cp\u003EThis pattern gives advanced applications extensibility without making simple applications incomplete.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022persistence-implementations\u0022\u003EPersistence Implementations\u003C/h3\u003E\n\u003Cp\u003EPlugin Domains can provide persistence-specific packages independently.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EUsers\n|\n\u002B-- Users.Contracts\n\u002B-- Users\n\u002B-- Users.Reference\n\u002B-- Users.InMemory\n\u002B-- Users.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThe same pattern applies to other Plugin Domains where appropriate.\u003C/p\u003E\n\u003Cp\u003EA developer using a server bundle normally does not need to install these packages individually. The selected bundle composes the appropriate persistence strategy.\u003C/p\u003E\n\u003Cp\u003ETheir separation exists primarily to preserve architectural modularity and replaceability.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022current-plugin-domains\u0022\u003ECurrent Plugin Domains\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022users\u0022\u003EUsers\u003C/h3\u003E\n\u003Cp\u003EThe Users domain owns user lifecycle and user-management capabilities.\u003C/p\u003E\n\u003Cp\u003EIts package family includes:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ECodeBeam.UltimateAuth.Users.Contracts\nCodeBeam.UltimateAuth.Users\nCodeBeam.UltimateAuth.Users.Reference\nCodeBeam.UltimateAuth.Users.InMemory\nCodeBeam.UltimateAuth.Users.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentials\u0022\u003ECredentials\u003C/h3\u003E\n\u003Cp\u003EThe Credentials domain owns credential-related capabilities and credential management.\u003C/p\u003E\n\u003Cp\u003EIts package family includes:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ECodeBeam.UltimateAuth.Credentials.Contracts\nCodeBeam.UltimateAuth.Credentials\nCodeBeam.UltimateAuth.Credentials.Reference\nCodeBeam.UltimateAuth.Credentials.InMemory\nCodeBeam.UltimateAuth.Credentials.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization\u0022\u003EAuthorization\u003C/h3\u003E\n\u003Cp\u003EThe Authorization domain provides UltimateAuth authorization capabilities.\u003C/p\u003E\n\u003Cp\u003EIts package family includes:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ECodeBeam.UltimateAuth.Authorization.Contracts\nCodeBeam.UltimateAuth.Authorization\nCodeBeam.UltimateAuth.Authorization.Reference\nCodeBeam.UltimateAuth.Authorization.InMemory\nCodeBeam.UltimateAuth.Authorization.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThe same architectural pattern makes these capabilities independently evolvable while keeping their public boundaries explicit.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022essential-server-capabilities\u0022\u003EEssential Server Capabilities\u003C/h2\u003E\n\u003Cp\u003EAuthentication, Sessions, and Tokens are slightly different from Plugin Domains.\u003C/p\u003E\n\u003Cp\u003EThey are fundamental parts of an UltimateAuth Server and are not treated as optional identity plugins in the same sense as Users, Credentials, or Authorization.\u003C/p\u003E\n\u003Cp\u003EHowever, they are still separated into focused projects.\u003C/p\u003E\n\u003Cp\u003EConceptually:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E UltimateAuth Server\n |\n \u002B--------------------\u002B--------------------\u002B\n | | |\n Authentication Sessions Tokens\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EWhy separate them if the Server needs them?\u003C/p\u003E\n\u003Cp\u003EBecause \u003Cstrong\u003Erequired does not have to mean monolithic\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EKeeping these capabilities isolated:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eprevents the central Server project from becoming an oversized implementation assembly;\u003C/li\u003E\n\u003Cli\u003Ekeeps responsibilities and dependency boundaries explicit;\u003C/li\u003E\n\u003Cli\u003Eallows implementations to evolve independently;\u003C/li\u003E\n\u003Cli\u003Emakes persistence providers independently composable;\u003C/li\u003E\n\u003Cli\u003Ekeeps replacement and testing boundaries smaller.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThey can be thought of as \u003Cstrong\u003Emodular server subsystems\u003C/strong\u003E rather than full Plugin Domains.\u003C/p\u003E\n\u003Cp\u003EFor example, persistence implementations can still exist independently:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EAuthentication\n\u002B-- Authentication.InMemory\n\u002B-- Authentication.EntityFrameworkCore\n\nSessions\n\u002B-- Sessions.InMemory\n\u002B-- Sessions.EntityFrameworkCore\n\nTokens\n\u002B-- Tokens.InMemory\n\u002B-- Tokens.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThe selected Server bundle composes the required implementations for the application.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies-and-security\u0022\u003EPolicies and Security\u003C/h2\u003E\n\u003Cp\u003ESome capabilities are deliberately isolated even though they support multiple areas of the platform.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies\u0022\u003EPolicies\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Policies\u003C/code\u003E contains policy infrastructure used to keep security and application decisions explicit and extensible.\u003C/p\u003E\n\u003Cp\u003EPolicies are part of the server architecture rather than application-specific transport logic.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-implementations\u0022\u003ESecurity Implementations\u003C/h3\u003E\n\u003Cp\u003ESecurity algorithms can also live behind focused implementations.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ECodeBeam.UltimateAuth.Security.Argon2\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EKeeping security implementations separate avoids coupling the entire platform to one concrete algorithm or provider.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022modularity-without-an-incomplete-framework\u0022\u003EModularity Without an Incomplete Framework\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s modularity has an important design goal:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EExtensibility should not require developers to build the missing half of the framework themselves.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EEvery major extensibility boundary should have a practical UltimateAuth-provided path.\u003C/p\u003E\n\u003Cp\u003EThe common model is:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E UltimateAuth Contract\n |\n \u002B-----------\u002B-----------\u002B\n | |\n Reference Path Custom Path\n | |\n Works out of the box Replace when needed\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis means the platform can provide strong defaults and reference implementations while still allowing advanced applications to replace individual capabilities.\u003C/p\u003E\n\u003Cp\u003EA customization should be local to the capability being replaced rather than requiring the authentication architecture to be rewritten.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022replaceability-by-design\u0022\u003EReplaceability by Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is intentionally built from small architectural boundaries.\u003C/p\u003E\n\u003Cp\u003EThe goal is not modularity for its own sake.\u003C/p\u003E\n\u003Cp\u003EThe goal is to reduce the cost of change.\u003C/p\u003E\n\u003Cp\u003EAn application may start with:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EEntityFrameworkCore.Bundle\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003Ewhile still having clearly separated domains and infrastructure underneath.\u003C/p\u003E\n\u003Cp\u003EIf a specialized application later needs a custom implementation for one capability, the architecture already has a boundary for it.\u003C/p\u003E\n\u003Cp\u003EThis provides a different model from choosing between two extremes:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ESimple but monolithic\n OR\nFlexible but difficult to configure\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EUltimateAuth aims for:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Simple installation\n \u002B\n Modular internals\n \u002B\n Reference implementations\n \u002B\n Explicit contracts\n =\n Low-cost customization\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003ESecurity-sensitive extension points still remain subject to UltimateAuth\u0027s security invariants and authority boundaries. Replaceability is intended to enable integration and specialization, not to bypass required security decisions.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-packages\u0022\u003EChoosing Packages\u003C/h2\u003E\n\u003Cp\u003EFor most developers, package selection should be simple.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-with-inmemory-persistence\u0022\u003EBlazor Server with InMemory persistence\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.InMemory.Bundle\ndotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThe application both hosts UltimateAuth and consumes the Blazor client integration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-with-entity-framework-core\u0022\u003EBlazor Server with Entity Framework Core\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\ndotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-persistence\u0022\u003ECustom persistence\u003C/h3\u003E\n\u003Cp\u003EStart from:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Reference.Bundle\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003Eand provide the required persistence implementations for your architecture.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022standalone-blazor-webassembly\u0022\u003EStandalone Blazor WebAssembly\u003C/h3\u003E\n\u003Cp\u003EInstall the client integration:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThe authentication authority lives in a remote UltimateAuth Server or UAuthHub.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-rule-of-thumb\u0022\u003EThe Rule of Thumb\u003C/h2\u003E\n\u003Cp\u003EYou generally do not need to assemble UltimateAuth package-by-package.\u003C/p\u003E\n\u003Cp\u003EFor the \u003Cstrong\u003EServer\u003C/strong\u003E:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EChoose the Bundle that matches your infrastructure.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EFor the \u003Cstrong\u003EClient\u003C/strong\u003E:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EChoose the Client package that matches your application platform.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ESERVER\n\u0026quot;What infrastructure do I use?\u0026quot;\n |\n \u002B-- Reference.Bundle\n \u002B-- InMemory.Bundle\n \u002B-- EntityFrameworkCore.Bundle\n\n\nCLIENT\n\u0026quot;What kind of application am I building?\u0026quot;\n |\n \u002B-- Client.Blazor\n \u002B-- Client.Maui (future)\n \u002B-- ...\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThe detailed package graph exists so that UltimateAuth remains extensible.\u003C/p\u003E\n\u003Cp\u003EThe bundles and client integrations exist so that you do not have to manage that graph for ordinary applications.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch1 id=\u0022next\u0022\u003ENext\u003C/h1\u003E\n\u003Cp\u003EYou now have the mental model needed to understand the rest of the UltimateAuth documentation:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ECore\n |\n \u002B-- Server --\u0026gt; choose a Bundle\n |\n \u002B-- Client --\u0026gt; choose your platform integration\n\nServer\n |\n \u002B-- Essential server subsystems\n |\n \u002B-- Plugin Domains\n |\n \u002B-- Contracts\n \u002B-- Runtime\n \u002B-- Reference implementations\n \u002B-- Persistence implementations\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EContinue with the \u003Ca href=\u0022./quickstart.md\u0022\u003EQuick Start\u003C/a\u003E to build your first UltimateAuth application.\u003C/p\u003E\n\u003Cp\u003EFor persistent storage, UAuthHub, standalone WebAssembly, Resource API, and other deployment models, continue with the \u003Ca href=\u0022./real-world-setup.md\u0022\u003EReal-World Setup\u003C/a\u003E.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth is designed as a modular authentication and identity platform for .NET.\u003C/p\u003E\n\u003Cp\u003EThe platform has three primary foundations:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E UltimateAuth\n |\n Core\n |\n \u002B------------\u002B------------\u002B\n | |\n Server Client\n | |\n Server composition Client integration\n through bundles for the application\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003ECore\u003C/strong\u003E defines the common language and foundational abstractions of the platform.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EServer\u003C/strong\u003E hosts and executes authentication and identity capabilities.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient\u003C/strong\u003E provides the application-facing model used by applications to interact with those capabilities.\u003C/p\u003E\n\u003Cp\u003EAn application does not necessarily need both Server and Client. Depending on its role, it may use:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EServer only\u003C/li\u003E\n\u003Cli\u003EClient only\u003C/li\u003E\n\u003Cli\u003EServer and Client together\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis separation is intentional. UltimateAuth is designed so that application topology does not dictate a monolithic package structure.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022platform-at-a-glance\u0022\u003EPlatform at a Glance\u003C/h2\u003E\n\u003Cp\u003EA useful way to think about UltimateAuth 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\n|\n\u002B-- Core\n|\n\u002B-- Server\n| |\n| \u002B-- Essential server capabilities\n| | \u002B-- Authentication\n| | \u002B-- Sessions\n| | \u002B-- Tokens\n| | \u002B-- Policies\n| |\n| \u002B-- Plugin Domains\n| | \u002B-- Users\n| | \u002B-- Credentials\n| | \u002B-- Authorization\n| |\n| \u002B-- Implementations\n| \u002B-- Reference\n| \u002B-- InMemory\n| \u002B-- Entity Framework Core\n|\n\u002B-- Client\n |\n \u002B-- Client.Blazor\n \u002B-- Client.AspNetCore compatibility\n \u002B-- Future client integrations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe important distinction is that these packages are not intended to make installation complicated.\u003C/p\u003E\n\u003Cp\u003EThe internal architecture is modular, while the installation experience is deliberately composed around \u003Cstrong\u003Eserver bundles\u003C/strong\u003E and \u003Cstrong\u003Eclient-specific packages\u003C/strong\u003E.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core\u0022\u003ECore\u003C/h2\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.core\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Core\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003ECore is the foundation of the entire UltimateAuth platform.\u003C/p\u003E\n\u003Cp\u003EIt contains the shared primitives, domain concepts, contracts, abstractions, value objects, configuration foundations, and common infrastructure required by the rest of UltimateAuth.\u003C/p\u003E\n\u003Cp\u003EConcepts such as UltimateAuth\u0027s identity and session model originate from this layer.\u003C/p\u003E\n\u003Cp\u003ECore is intentionally independent from a particular application type or persistence technology.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Core\n |\n \u002B--------\u002B--------\u002B\n | |\n Server Client\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EServer and Client share the same platform vocabulary because both are built on Core.\u003C/p\u003E\n\u003Cp\u003EMost application developers do not need to install Core directly. It is normally brought into the application through the appropriate Server bundle or Client integration package.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server\u0022\u003EServer\u003C/h2\u003E\n\u003Cp\u003EThe Server side is the authority that executes UltimateAuth authentication and identity behavior.\u003C/p\u003E\n\u003Cp\u003EIt is responsible for the server-side runtime, including authentication flows, security decisions, orchestration, endpoints, middleware integration, and the coordination of server capabilities.\u003C/p\u003E\n\u003Cp\u003EAt source level, \u003Ccode\u003ECodeBeam.UltimateAuth.Server\u003C/code\u003E is the central server project and depends on major server domains such 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\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\u003ECodeBeam.UltimateAuth.Server\n|\n\u002B-- Core\n\u002B-- Authorization\n\u002B-- Credentials\n\u002B-- Users\n\u002B-- Policies\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOther essential server capabilities, including authentication, sessions, and tokens, are kept in focused projects rather than being merged into one oversized server assembly.\u003C/p\u003E\n\u003Cp\u003EThis gives UltimateAuth a modular internal architecture without forcing that complexity onto application developers.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-i-install-codebeam.ultimateauth.server-directly\u0022\u003EDo I install \u003Ccode\u003ECodeBeam.UltimateAuth.Server\u003C/code\u003E directly?\u003C/h3\u003E\n\u003Cp\u003EFor normal application setup, \u003Cstrong\u003Eno\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe recommended server installation unit is a \u003Cstrong\u003EBundle\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EA bundle represents a complete server composition for a particular infrastructure strategy.\u003C/p\u003E\n\u003Cp\u003EInstead of manually selecting all server packages and implementations, choose the bundle that matches your application.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Your Server\n |\n Bundle\n |\n \u002B---------------\u002B---------------\u002B\n | | |\n Server Domains Infrastructure\n | | |\n Runtime Reference Persistence\n behavior providers\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is an important part of the UltimateAuth design:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EThe framework remains modular internally, while common server configurations remain simple to install.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-bundles\u0022\u003EServer Bundles\u003C/h2\u003E\n\u003Cp\u003EBundles are server-side composition packages.\u003C/p\u003E\n\u003Cp\u003EThey collect the UltimateAuth server capabilities and the implementations required for a particular configuration into a single installation unit.\u003C/p\u003E\n\u003Cp\u003EYou normally choose \u003Cstrong\u003Eone server bundle\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reference-bundle\u0022\u003EReference Bundle\u003C/h3\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.reference.bundle\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Reference.Bundle\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003EThe Reference Bundle provides the recommended UltimateAuth server composition without selecting a persistence implementation for your application.\u003C/p\u003E\n\u003Cp\u003EUse it when you want UltimateAuth\u0027s reference behavior but provide your own persistence infrastructure.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EReference.Bundle\n|\n\u002B-- Server\n\u002B-- UltimateAuth reference implementations\n\u002B-- Required server capabilities\n|\n\u002B-- Persistence: supplied by your application\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is the lowest-level recommended server composition for applications that want to own their storage implementation.\u003C/p\u003E\n\u003Cp\u003EIt is also an important extensibility point: UltimateAuth provides a recommended architecture without requiring applications to use one particular persistence technology.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inmemory-bundle\u0022\u003EInMemory Bundle\u003C/h3\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.inmemory.bundle\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.InMemory.Bundle\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003EThe InMemory Bundle provides a complete server setup backed by UltimateAuth\u0027s in-memory implementations.\u003C/p\u003E\n\u003Cp\u003EIt is primarily intended for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Edevelopment\u003C/li\u003E\n\u003Cli\u003EQuick Start\u003C/li\u003E\n\u003Cli\u003Esamples\u003C/li\u003E\n\u003Cli\u003Eautomated tests\u003C/li\u003E\n\u003Cli\u003Eevaluation\u003C/li\u003E\n\u003Cli\u003Eprototypes\u003C/li\u003E\n\u003C/ul\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\u003EInMemory.Bundle\n|\n\u002B-- Server\n\u002B-- Reference implementations\n\u002B-- InMemory persistence implementations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is the easiest way to start an UltimateAuth server.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022entity-framework-core-bundle\u0022\u003EEntity Framework Core Bundle\u003C/h3\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.entityframeworkcore.bundle\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003EThe Entity Framework Core Bundle provides the complete server composition using UltimateAuth\u0027s EF Core persistence implementations.\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\u003EEntityFrameworkCore.Bundle\n|\n\u002B-- Server\n\u002B-- Reference implementations\n\u002B-- EF Core persistence implementations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor applications that use EF Core for persistent UltimateAuth data, this is normally the server package to install.\u003C/p\u003E\n\u003Cp\u003EThe developer does not need to manually assemble the individual Users, Credentials, Authorization, Sessions, Tokens, and other persistence packages.\u003C/p\u003E\n\u003Cp\u003EThe bundle performs that composition.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-bundles-matter\u0022\u003EWhy Bundles Matter\u003C/h3\u003E\n\u003Cp\u003EA highly modular framework can easily make the developer experience worse if every application has to understand and install its internal package graph.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth deliberately separates \u003Cstrong\u003Earchitecture modularity\u003C/strong\u003E from \u003Cstrong\u003Einstallation complexity\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EInternally:\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\u003EServer\n \u002B Users\n \u002B Credentials\n \u002B Authorization\n \u002B Authentication\n \u002B Sessions\n \u002B Tokens\n \u002B Policies\n \u002B persistence implementations\n \u002B reference implementations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFrom the application:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis gives UltimateAuth two useful properties at the same time:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003Eindividual capabilities remain independently replaceable and evolvable;\u003C/li\u003E\n\u003Cli\u003Ethe common path remains simple.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EYou should not need to understand the complete internal dependency graph before building your first application.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client\u0022\u003EClient\u003C/h2\u003E\n\u003Cp\u003EThe Client side has a different packaging model.\u003C/p\u003E\n\u003Cp\u003EThere are \u003Cstrong\u003Eno Client bundles\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EInstead, applications install the client package designed for their application 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\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Client Foundation\n |\n \u002B-------------\u002B-------------\u002B\n | | |\n Blazor MAUI Future\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor example, a Blazor application installs:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA future MAUI integration can follow the same model without changing the underlying UltimateAuth Client architecture.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022codebeam.ultimateauth.client\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client\u003C/code\u003E\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client\u003C/code\u003E is the shared client foundation.\u003C/p\u003E\n\u003Cp\u003EIt contains common client contracts, abstractions, flow APIs, state concepts, and runtime-independent client behavior.\u003C/p\u003E\n\u003Cp\u003EIt should be thought of as the foundation on which concrete UltimateAuth client integrations are built.\u003C/p\u003E\n\u003Cp\u003EMost applications should therefore install their platform-specific client package rather than installing \u003Ccode\u003ECodeBeam.UltimateAuth.Client\u003C/code\u003E directly.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022codebeam.ultimateauth.client.blazor\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.Blazor\u003C/code\u003E\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.Blazor\u003C/code\u003E is the concrete UltimateAuth client integration for Blazor.\u003C/p\u003E\n\u003Cp\u003EIt builds on the Client foundation and adds the Blazor-specific runtime and UI integration required by Blazor applications.\u003C/p\u003E\n\u003Cp\u003EThis includes capabilities such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthApp\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthStateView\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003Eauthentication state integration\u003C/li\u003E\n\u003Cli\u003EBlazor authorization integration\u003C/li\u003E\n\u003Cli\u003Erouting integration\u003C/li\u003E\n\u003Cli\u003Eauthentication flow components\u003C/li\u003E\n\u003Cli\u003EJavaScript transport integration\u003C/li\u003E\n\u003Cli\u003Eclient lifecycle coordination\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EConceptually:\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\u003ECore\n |\nClient\n |\nClient.Blazor\n |\nYour Blazor Application\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis model keeps the shared Client architecture independent from Blazor while still giving Blazor developers one natural package to install.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-compatibility-packages\u0022\u003EClient Compatibility Packages\u003C/h3\u003E\n\u003Cp\u003ENot every client-side package represents a complete client implementation.\u003C/p\u003E\n\u003Cp\u003ESome packages exist to bridge UltimateAuth with a host framework.\u003C/p\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.client.aspnetcore\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.AspNetCore\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.AspNetCore\u003C/code\u003E is a lightweight ASP.NET Core compatibility layer for UltimateAuth client applications.\u003C/p\u003E\n\u003Cp\u003EIt allows client-oriented applications to integrate with ASP.NET Core authentication and authorization infrastructure without introducing the full UltimateAuth Server runtime.\u003C/p\u003E\n\u003Cp\u003EThis is particularly useful for hosting models such as Blazor Web App with Interactive WebAssembly.\u003C/p\u003E\n\u003Cp\u003EIt can enable compatibility with ASP.NET Core features such as \u003Ccode\u003E[Authorize]\u003C/code\u003E, but it does \u003Cstrong\u003Enot\u003C/strong\u003E implement authentication itself and does not turn the application into an UltimateAuth Server.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022application-composition\u0022\u003EApplication Composition\u003C/h2\u003E\n\u003Cp\u003EBecause Server and Client are independent platform sides, different applications can compose UltimateAuth differently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-only\u0022\u003EServer Only\u003C/h3\u003E\n\u003Cp\u003ETypical examples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ededicated authentication servers\u003C/li\u003E\n\u003Cli\u003Ebackend services hosting UltimateAuth\u003C/li\u003E\n\u003Cli\u003Eserver-side identity infrastructure\u003C/li\u003E\n\u003C/ul\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\u003EApplication\n|\n\u002B-- Server Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-only\u0022\u003EClient Only\u003C/h3\u003E\n\u003Cp\u003ETypical examples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Estandalone Blazor WebAssembly\u003C/li\u003E\n\u003Cli\u003Efuture MAUI applications\u003C/li\u003E\n\u003Cli\u003Eapplications using a remote UAuthHub\u003C/li\u003E\n\u003C/ul\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\u003EApplication\n|\n\u002B-- Client.Blazor\n |\n \u002B---- remote UltimateAuth Server / UAuthHub\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-client\u0022\u003EServer \u002B Client\u003C/h3\u003E\n\u003Cp\u003ESome applications host UltimateAuth and consume its client API in the same application.\u003C/p\u003E\n\u003Cp\u003EBlazor Server is a common 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\u003EBlazor Server Application\n|\n\u002B-- Server Bundle\n|\n\u002B-- Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe two sides remain architecturally distinct even when deployed in the same process.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022plugin-domains\u0022\u003EPlugin Domains\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth extends its identity platform through \u003Cstrong\u003EPlugin Domains\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe current primary Plugin Domains are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUsers\u003C/li\u003E\n\u003Cli\u003ECredentials\u003C/li\u003E\n\u003Cli\u003EAuthorization\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA Plugin Domain is more than a single assembly. It is a capability area with contracts, runtime behavior, recommended implementations, and infrastructure implementations.\u003C/p\u003E\n\u003Cp\u003EThis structure allows a domain to remain replaceable without requiring applications to redesign the rest of UltimateAuth.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022anatomy-of-a-plugin-domain\u0022\u003EAnatomy of a Plugin Domain\u003C/h3\u003E\n\u003Cp\u003EA typical Plugin Domain follows this 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\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\u003EPlugin Domain\n|\n\u002B-- Contracts\n|\n\u002B-- Domain / Runtime\n|\n\u002B-- Reference\n|\n\u002B-- Persistence\n |\n \u002B-- InMemory\n \u002B-- EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach layer has a distinct purpose.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022contracts\u0022\u003EContracts\u003C/h3\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\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\u003ECodeBeam.UltimateAuth.Users.Contracts\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EContracts define the public boundary of the domain. Server and Client can communicate through same contracts without requiring one to depend on the other.\u003C/p\u003E\n\u003Cp\u003EThey are intentionally kept close to Core and represent the smallest dependency surface for applications or extensions that only need to interact with the domain contract.\u003C/p\u003E\n\u003Cp\u003EDepending on the domain, contracts can contain concepts such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Erequests\u003C/li\u003E\n\u003Cli\u003Eresults\u003C/li\u003E\n\u003Cli\u003Epublic models\u003C/li\u003E\n\u003Cli\u003Eabstractions\u003C/li\u003E\n\u003Cli\u003Edomain-facing contracts\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis allows another implementation to integrate with UltimateAuth without depending on the entire built-in implementation.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022domain-runtime\u0022\u003EDomain / Runtime\u003C/h3\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\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\u003ECodeBeam.UltimateAuth.Users\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe main domain project provides the runtime and application behavior of that Plugin Domain.\u003C/p\u003E\n\u003Cp\u003EIt operates against abstractions rather than requiring one persistence implementation.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reference-implementations\u0022\u003EReference Implementations\u003C/h3\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\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\u003ECodeBeam.UltimateAuth.Users.Reference\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EReference packages contain the implementation UltimateAuth recommends as the standard baseline for the domain.\u003C/p\u003E\n\u003Cp\u003EThe distinction between \u003Cstrong\u003Econtract\u003C/strong\u003E and \u003Cstrong\u003Ereference implementation\u003C/strong\u003E is intentional.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth does not leave extensibility points empty and require every developer to design critical identity behavior from scratch.\u003C/p\u003E\n\u003Cp\u003EInstead, the platform provides:\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\u003EContract\n |\n \u002B---- UltimateAuth Reference Implementation\n |\n \u002B---- Your Custom Implementation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor the common path, use the UltimateAuth reference implementation.\u003C/p\u003E\n\u003Cp\u003EFor specialized requirements, replace the appropriate implementation while preserving the surrounding platform contract and security boundaries.\u003C/p\u003E\n\u003Cp\u003EThis pattern gives advanced applications extensibility without making simple applications incomplete.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022persistence-implementations\u0022\u003EPersistence Implementations\u003C/h3\u003E\n\u003Cp\u003EPlugin Domains can provide persistence-specific packages independently.\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|\n\u002B-- Users.Contracts\n\u002B-- Users\n\u002B-- Users.Reference\n\u002B-- Users.InMemory\n\u002B-- Users.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same pattern applies to other Plugin Domains where appropriate.\u003C/p\u003E\n\u003Cp\u003EA developer using a server bundle normally does not need to install these packages individually. The selected bundle composes the appropriate persistence strategy.\u003C/p\u003E\n\u003Cp\u003ETheir separation exists primarily to preserve architectural modularity and replaceability.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022current-plugin-domains\u0022\u003ECurrent Plugin Domains\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022users\u0022\u003EUsers\u003C/h3\u003E\n\u003Cp\u003EThe Users domain owns user lifecycle and user-management capabilities.\u003C/p\u003E\n\u003Cp\u003EIts package family 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\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\u003ECodeBeam.UltimateAuth.Users.Contracts\nCodeBeam.UltimateAuth.Users\nCodeBeam.UltimateAuth.Users.Reference\nCodeBeam.UltimateAuth.Users.InMemory\nCodeBeam.UltimateAuth.Users.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentials\u0022\u003ECredentials\u003C/h3\u003E\n\u003Cp\u003EThe Credentials domain owns credential-related capabilities and credential management.\u003C/p\u003E\n\u003Cp\u003EIts package family 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\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\u003ECodeBeam.UltimateAuth.Credentials.Contracts\nCodeBeam.UltimateAuth.Credentials\nCodeBeam.UltimateAuth.Credentials.Reference\nCodeBeam.UltimateAuth.Credentials.InMemory\nCodeBeam.UltimateAuth.Credentials.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization\u0022\u003EAuthorization\u003C/h3\u003E\n\u003Cp\u003EThe Authorization domain provides UltimateAuth authorization capabilities.\u003C/p\u003E\n\u003Cp\u003EIts package family 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\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\u003ECodeBeam.UltimateAuth.Authorization.Contracts\nCodeBeam.UltimateAuth.Authorization\nCodeBeam.UltimateAuth.Authorization.Reference\nCodeBeam.UltimateAuth.Authorization.InMemory\nCodeBeam.UltimateAuth.Authorization.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same architectural pattern makes these capabilities independently evolvable while keeping their public boundaries explicit.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022essential-server-capabilities\u0022\u003EEssential Server Capabilities\u003C/h2\u003E\n\u003Cp\u003EAuthentication, Sessions, and Tokens are slightly different from Plugin Domains.\u003C/p\u003E\n\u003Cp\u003EThey are fundamental parts of an UltimateAuth Server and are not treated as optional identity plugins in the same sense as Users, Credentials, or Authorization.\u003C/p\u003E\n\u003Cp\u003EHowever, they are still separated into focused projects.\u003C/p\u003E\n\u003Cp\u003EConceptually:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E UltimateAuth Server\n |\n \u002B--------------------\u002B--------------------\u002B\n | | |\n Authentication Sessions Tokens\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhy separate them if the Server needs them?\u003C/p\u003E\n\u003Cp\u003EBecause \u003Cstrong\u003Erequired does not have to mean monolithic\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EKeeping these capabilities isolated:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eprevents the central Server project from becoming an oversized implementation assembly;\u003C/li\u003E\n\u003Cli\u003Ekeeps responsibilities and dependency boundaries explicit;\u003C/li\u003E\n\u003Cli\u003Eallows implementations to evolve independently;\u003C/li\u003E\n\u003Cli\u003Emakes persistence providers independently composable;\u003C/li\u003E\n\u003Cli\u003Ekeeps replacement and testing boundaries smaller.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThey can be thought of as \u003Cstrong\u003Emodular server subsystems\u003C/strong\u003E rather than full Plugin Domains.\u003C/p\u003E\n\u003Cp\u003EFor example, persistence implementations can still exist independently:\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\n\u002B-- Authentication.InMemory\n\u002B-- Authentication.EntityFrameworkCore\n\nSessions\n\u002B-- Sessions.InMemory\n\u002B-- Sessions.EntityFrameworkCore\n\nTokens\n\u002B-- Tokens.InMemory\n\u002B-- Tokens.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe selected Server bundle composes the required implementations for the application.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies-and-security\u0022\u003EPolicies and Security\u003C/h2\u003E\n\u003Cp\u003ESome capabilities are deliberately isolated even though they support multiple areas of the platform.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies\u0022\u003EPolicies\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Policies\u003C/code\u003E contains policy infrastructure used to keep security and application decisions explicit and extensible.\u003C/p\u003E\n\u003Cp\u003EPolicies are part of the server architecture rather than application-specific transport logic.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-implementations\u0022\u003ESecurity Implementations\u003C/h3\u003E\n\u003Cp\u003ESecurity algorithms can also live behind focused implementations.\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\u003ECodeBeam.UltimateAuth.Security.Argon2\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EKeeping security implementations separate avoids coupling the entire platform to one concrete algorithm or provider.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022modularity-without-an-incomplete-framework\u0022\u003EModularity Without an Incomplete Framework\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s modularity has an important design goal:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EExtensibility should not require developers to build the missing half of the framework themselves.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EEvery major extensibility boundary should have a practical UltimateAuth-provided path.\u003C/p\u003E\n\u003Cp\u003EThe common model 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\u003E UltimateAuth Contract\n |\n \u002B-----------\u002B-----------\u002B\n | |\n Reference Path Custom Path\n | |\n Works out of the box Replace when needed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis means the platform can provide strong defaults and reference implementations while still allowing advanced applications to replace individual capabilities.\u003C/p\u003E\n\u003Cp\u003EA customization should be local to the capability being replaced rather than requiring the authentication architecture to be rewritten.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022replaceability-by-design\u0022\u003EReplaceability by Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is intentionally built from small architectural boundaries.\u003C/p\u003E\n\u003Cp\u003EThe goal is not modularity for its own sake.\u003C/p\u003E\n\u003Cp\u003EThe goal is to reduce the cost of change.\u003C/p\u003E\n\u003Cp\u003EAn application may start with:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EEntityFrameworkCore.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Ewhile still having clearly separated domains and infrastructure underneath.\u003C/p\u003E\n\u003Cp\u003EIf a specialized application later needs a custom implementation for one capability, the architecture already has a boundary for it.\u003C/p\u003E\n\u003Cp\u003EThis provides a different model from choosing between two extremes:\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\u003ESimple but monolithic\n OR\nFlexible but difficult to configure\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUltimateAuth aims for:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Simple installation\n \u002B\n Modular internals\n \u002B\n Reference implementations\n \u002B\n Explicit contracts\n =\n Low-cost customization\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ESecurity-sensitive extension points still remain subject to UltimateAuth\u0027s security invariants and authority boundaries. Replaceability is intended to enable integration and specialization, not to bypass required security decisions.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-packages\u0022\u003EChoosing Packages\u003C/h2\u003E\n\u003Cp\u003EFor most developers, package selection should be simple.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-with-inmemory-persistence\u0022\u003EBlazor Server with InMemory persistence\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.InMemory.Bundle\ndotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe application both hosts UltimateAuth and consumes the Blazor client integration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-with-entity-framework-core\u0022\u003EBlazor Server with Entity Framework Core\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\ndotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-persistence\u0022\u003ECustom persistence\u003C/h3\u003E\n\u003Cp\u003EStart 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\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Reference.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eand provide the required persistence implementations for your architecture.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022standalone-blazor-webassembly\u0022\u003EStandalone Blazor WebAssembly\u003C/h3\u003E\n\u003Cp\u003EInstall the client integration:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe authentication authority lives in a remote UltimateAuth Server or UAuthHub.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-rule-of-thumb\u0022\u003EThe Rule of Thumb\u003C/h2\u003E\n\u003Cp\u003EYou generally do not need to assemble UltimateAuth package-by-package.\u003C/p\u003E\n\u003Cp\u003EFor the \u003Cstrong\u003EServer\u003C/strong\u003E:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EChoose the Bundle that matches your infrastructure.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EFor the \u003Cstrong\u003EClient\u003C/strong\u003E:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EChoose the Client package that matches your application platform.\u003C/p\u003E\n\u003C/blockquote\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\u003ESERVER\n\u0026quot;What infrastructure do I use?\u0026quot;\n |\n \u002B-- Reference.Bundle\n \u002B-- InMemory.Bundle\n \u002B-- EntityFrameworkCore.Bundle\n\n\nCLIENT\n\u0026quot;What kind of application am I building?\u0026quot;\n |\n \u002B-- Client.Blazor\n \u002B-- Client.Maui (future)\n \u002B-- ...\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe detailed package graph exists so that UltimateAuth remains extensible.\u003C/p\u003E\n\u003Cp\u003EThe bundles and client integrations exist so that you do not have to manage that graph for ordinary applications.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch1 id=\u0022next\u0022\u003ENext\u003C/h1\u003E\n\u003Cp\u003EYou now have the mental model needed to understand the rest of the UltimateAuth documentation:\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\u003ECore\n |\n \u002B-- Server --\u0026gt; choose a Bundle\n |\n \u002B-- Client --\u0026gt; choose your platform integration\n\nServer\n |\n \u002B-- Essential server subsystems\n |\n \u002B-- Plugin Domains\n |\n \u002B-- Contracts\n \u002B-- Runtime\n \u002B-- Reference implementations\n \u002B-- Persistence implementations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EContinue with the \u003Ca href=\u0022./quickstart.md\u0022\u003EQuick Start\u003C/a\u003E to build your first UltimateAuth application.\u003C/p\u003E\n\u003Cp\u003EFor persistent storage, UAuthHub, standalone WebAssembly, Resource API, and other deployment models, continue with the \u003Ca href=\u0022./real-world-setup.md\u0022\u003EReal-World Setup\u003C/a\u003E.\u003C/p\u003E\n", "Headings": [ { "Id": "platform-at-a-glance", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json index 09f5f9c3..d70edb96 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json @@ -1,7 +1,7 @@ { "Slug": "getting-started/index", "Title": "Getting Started", - "Html": "\n\u003Cp\u003EWelcome to \u003Cstrong\u003EUltimateAuth\u003C/strong\u003E, a unified authentication and authorization framework for modern .NET applications.\u003C/p\u003E\n\u003Cp\u003EAuthentication should not become a different system every time your application gains a new client, tenant, device, or security requirement. UltimateAuth brings these concerns into \u003Cstrong\u003Eone coherent model\u003C/strong\u003E\u2014without pretending that every application needs the same authentication strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOne framework. Multiple authentication models. A consistent security lifecycle.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-ultimateauth\u0022\u003EWhat is UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is a modular, flow-driven framework for building and managing authentication in .NET. It connects concepts that are often implemented separately:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ESessions, cookies and tokens\u003C/strong\u003E \u2014 stateful session management alongside access and refresh token workflows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthentication and authorization\u003C/strong\u003E \u2014 identity establishment and access decisions within a consistent architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUsers and credentials\u003C/strong\u003E \u2014 identity, identifiers, credentials, and their security state.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevices and session continuity\u003C/strong\u003E \u2014 device-aware session chains, refresh, logout, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClients and delivery mechanisms\u003C/strong\u003E \u2014 browser, API, Blazor, and mobile scenarios with client-aware behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ETenants and isolation\u003C/strong\u003E \u2014 tenant-aware request context and authentication boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESecurity policies and execution flows\u003C/strong\u003E \u2014 explicit decisions, validation, and controlled state transitions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe goal is not to hide these concepts. It is to make them \u003Cstrong\u003Ework together predictably\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022unified-does-not-mean-one-size-fits-all\u0022\u003EUnified Does Not Mean One-Size-Fits-All\u003C/h2\u003E\n\u003Cp\u003EA server-rendered web application, a browser-based SPA, a mobile app, and a backend API do not share the same trust boundaries or credential transport requirements.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides a common foundation while allowing behavior to be shaped by the \u003Cstrong\u003Eeffective authentication mode\u003C/strong\u003E, \u003Cstrong\u003Eclient profile\u003C/strong\u003E, \u003Cstrong\u003Etenant\u003C/strong\u003E, and \u003Cstrong\u003Erequest context\u003C/strong\u003E.\u003C/p\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcern\u003C/th\u003E\n\u003Cth\u003EWhat UltimateAuth brings together\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\u003EAuthentication models\u003C/td\u003E\n\u003Ctd\u003ESession-oriented and token-oriented workflows\u003C/td\u003E\n\u003Ctd\u003EDifferent clients can participate in one security architecture\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession lifecycle\u003C/td\u003E\n\u003Ctd\u003ERoot \u2192 Chain \u2192 Session\u003C/td\u003E\n\u003Ctd\u003EManage authentication at identity, device, and session scope\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient environments\u003C/td\u003E\n\u003Ctd\u003EASP.NET Core, Blazor, APIs, and .NET mobile clients\u003C/td\u003E\n\u003Ctd\u003EAvoid rebuilding the core authentication model for each frontend\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMulti-tenancy\u003C/td\u003E\n\u003Ctd\u003ETenant resolution and tenant-bound context\u003C/td\u003E\n\u003Ctd\u003EKeep authentication operations within the intended tenant boundary\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredentials and identities\u003C/td\u003E\n\u003Ctd\u003EUser identifiers, credential providers, and security state\u003C/td\u003E\n\u003Ctd\u003ESeparate \u003Cem\u003Ewho the user is\u003C/em\u003E from \u003Cem\u003Ehow they prove it\u003C/em\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESecurity enforcement\u003C/td\u003E\n\u003Ctd\u003EContext, validation, authority, and orchestration\u003C/td\u003E\n\u003Ctd\u003EMake decisions explicit rather than scattering checks across endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential delivery\u003C/td\u003E\n\u003Ctd\u003EMode- and client-aware response policies\u003C/td\u003E\n\u003Ctd\u003EHandle cookies, tokens, and redirects according to the scenario\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession termination\u003C/td\u003E\n\u003Ctd\u003ELogout, targeted revocation, and device management\u003C/td\u003E\n\u003Ctd\u003ERespond proportionately to ordinary sign-out and security incidents\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cp\u003E\u003Cstrong\u003EUnified\u003C/strong\u003E is therefore about a shared security model\u2014not forcing all applications to use identical cookies, tokens, or endpoints.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-is-more-than-issuing-a-token\u0022\u003EAuthentication Is More Than Issuing a Token\u003C/h2\u003E\n\u003Cp\u003EIn many applications, authentication is described as a short sequence: validate credentials, generate a token, return it to the client.\u003C/p\u003E\n\u003Cp\u003EThat is only part of the story.\u003C/p\u003E\n\u003Cp\u003EA production authentication system must also answer questions such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhich tenant does this request belong to?\u003C/li\u003E\n\u003Cli\u003EWhich user and credential are being evaluated?\u003C/li\u003E\n\u003Cli\u003EIs an existing session still valid?\u003C/li\u003E\n\u003Cli\u003EIs the request coming from a known device?\u003C/li\u003E\n\u003Cli\u003ECan access be renewed, restricted, or terminated?\u003C/li\u003E\n\u003Cli\u003EWhat should happen when a credential changes or a device is no longer trusted?\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth treats authentication as a \u003Cstrong\u003Econtrolled lifecycle\u003C/strong\u003E. Login, refresh, and logout are operations within that lifecycle\u2014not unrelated endpoints that happen to exchange tokens.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessions-are-first-class\u0022\u003ESessions Are First-Class\u003C/h2\u003E\n\u003Cp\u003EWhere session-backed modes apply, UltimateAuth models authentication through a hierarchy:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EUser\n \u2514\u2500\u2500 Root Global session-security boundary\n \u2514\u2500\u2500 Chain Device-associated lifecycle\n \u2514\u2500\u2500 Session Individual authentication instance\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis structure gives the system a vocabulary for different security actions. A session can be invalidated, a device-associated chain can be terminated, and a broader security boundary can be revoked.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA token is not the entire authentication model.\u003C/strong\u003E It is a credential used within that model, subject to the applicable mode and validation rules.\u003C/p\u003E\n\u003Cp\u003ENot every authentication mode has identical persistence or session behavior. The session hierarchy describes the stateful lifecycle where that model is in use.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-driven-by-design\u0022\u003EFlow-Driven by Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth organizes authentication operations around explicit flows. A flow brings together the request context, operation-specific validation, security decisions, permitted state transitions, and response behavior.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ERequest\n \u2192 Tenant, session, and user resolution\n \u2192 AuthFlowContext\n \u2192 Flow-specific validation\n \u2192 Security decision and orchestration\n \u2192 State transition (when permitted)\n \u2192 Result and client-appropriate response\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis is not a claim that pipelines or orchestration are new inventions. The value is in applying them \u003Cstrong\u003Econsistently across authentication operations\u003C/strong\u003E, so the rules are easier to reason about, test, and extend.\u003C/p\u003E\n\u003Cp\u003EFor the implementation model, see \u003Ca href=\u0022../auth-flows/auth-flows.md\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022tenant-aware-from-the-request-boundary\u0022\u003ETenant-Aware from the Request Boundary\u003C/h2\u003E\n\u003Cp\u003EMulti-tenancy is not simply a \u003Ccode\u003ETenantId\u003C/code\u003E added to a user table. Authentication must understand \u003Cem\u003Ewhich tenant\u0027s security context\u003C/em\u003E a request is operating within.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth resolves tenant context as part of the ASP.NET Core request pipeline and carries it into the authentication flow. The flow context then combines that boundary with client settings, device information, and applicable session validation.\u003C/p\u003E\n\u003Cp\u003EThis helps teams design multi-tenant applications without treating tenant isolation as an afterthought. \u003Cstrong\u003ETenant resolution and configuration still need to be correctly integrated by the host application.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-for-different-clients-not-just-different-screens\u0022\u003EBuilt for Different Clients, Not Just Different Screens\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is designed for applications that may span several environments:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EASP.NET Core web applications\u003C/strong\u003E with server-managed authentication concerns.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EBlazor Server and Blazor WebAssembly\u003C/strong\u003E with different hosting and transport constraints.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E.NET MAUI\u003C/strong\u003E and other native clients with device-oriented authentication needs.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EHTTP APIs\u003C/strong\u003E serving browsers, applications, and other services.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EClient profiles and effective server options help adapt authentication behavior to the requesting client. Response policies determine how applicable credentials are delivered and whether a redirect belongs in the interaction.\u003C/p\u003E\n\u003Cp\u003EThe result is a common backend security vocabulary across multiple application types\u2014not a promise that every client uses the same transport or requires zero configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-is-a-lifecycle-too\u0022\u003ESecurity Is a Lifecycle, Too\u003C/h2\u003E\n\u003Cp\u003ESecurity is not finished when login succeeds. UltimateAuth\u0027s architecture accounts for what happens \u003Cem\u003Eafter\u003C/em\u003E access is established:\u003C/p\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ESituation\u003C/th\u003E\n\u003Cth\u003ESecurity question\u003C/th\u003E\n\u003Cth\u003EUltimateAuth concept\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs in\u003C/td\u003E\n\u003Ctd\u003EShould this attempt establish authentication?\u003C/td\u003E\n\u003Ctd\u003ELogin flow and security decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA session needs renewal\u003C/td\u003E\n\u003Ctd\u003ECan continuity be preserved safely?\u003C/td\u003E\n\u003Ctd\u003ERefresh flow and session lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs out\u003C/td\u003E\n\u003Ctd\u003EWhich authentication scope should end?\u003C/td\u003E\n\u003Ctd\u003ELogout\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA device is lost\u003C/td\u003E\n\u003Ctd\u003EHow can that device\u0027s access be terminated?\u003C/td\u003E\n\u003Ctd\u003EChain-level management and revocation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA credential is compromised\u003C/td\u003E\n\u003Ctd\u003EHow should existing authentication be affected?\u003C/td\u003E\n\u003Ctd\u003ECredential security state and revocation policies\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAn administrator manages access\u003C/td\u003E\n\u003Ctd\u003EIs the operation permitted for this actor and target?\u003C/td\u003E\n\u003Ctd\u003EAuthorization and access context\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cp\u003EA key distinction is \u003Cstrong\u003Elogout versus revoke\u003C/strong\u003E. Ordinary logout ends authentication according to its scope; revocation is an irreversible invalidation of the targeted security state. Signing in again may establish \u003Cem\u003Enew\u003C/em\u003E authentication, but it does not restore what was revoked.\u003C/p\u003E\n\u003Cp\u003EExplore the details in \u003Ca href=\u0022../auth-flows/login-flow.md\u0022\u003ELogin Flow\u003C/a\u003E, \u003Ca href=\u0022../auth-flows/refresh-flow.md\u0022\u003ERefresh Flow\u003C/a\u003E, and \u003Ca href=\u0022../auth-flows/logout-flow.md\u0022\u003ELogout Flow\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022modular-and-extensible\u0022\u003EModular and Extensible\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is intended to serve both straightforward applications and systems with more demanding security requirements. Its architecture separates responsibilities such as core contracts, server execution, client integration, user management, credentials, and authorization.\u003C/p\u003E\n\u003Cp\u003EThis separation makes it possible to understand and evolve individual concerns without reducing authentication to a collection of disconnected custom handlers.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EExtensible does not mean every possible flow is already implemented.\u003C/strong\u003E Available modes, integrations, and capabilities should be evaluated against the version you are using; the documentation for each feature describes its current behavior and limitations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-should-you-choose-ultimateauth\u0022\u003EWhen Should You Choose UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is especially relevant when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need quickly-installed, consistent, secure and easy-to-use auth in your .NET application.\u003C/li\u003E\n\u003Cli\u003EYour .NET application has\u2014or will have\u2014\u003Cstrong\u003Emore than one client type\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003EYou need \u003Cstrong\u003Edevice-aware session management\u003C/strong\u003E, not only token generation.\u003C/li\u003E\n\u003Cli\u003EYou are designing a \u003Cstrong\u003Emulti-tenant\u003C/strong\u003E system with clear authentication boundaries.\u003C/li\u003E\n\u003Cli\u003EYou want \u003Cstrong\u003Eauthentication and authorization\u003C/strong\u003E to follow a consistent architectural model.\u003C/li\u003E\n\u003Cli\u003EYou need control over \u003Cstrong\u003Erefresh, logout, revocation, and credential lifecycle\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003EYou value a \u003Cstrong\u003Emodular, self-hosted, open-source\u003C/strong\u003E foundation you can understand and extend.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAlso for a small application with minimal authentication needs, UltimateAuth brings the simplest possible solution.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022open-source-first\u0022\u003EOpen Source First\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is built with a simple commitment: \u003Cstrong\u003Eself-hosted authentication should remain accessible\u003C/strong\u003E. The framework is open source, and self-hosting is intended to remain free. A hosted offering may provide an operational alternative, not a separate tier of authentication features.\u003C/p\u003E\n\u003Cp\u003EThe ambition is to build a framework that developers can adopt, inspect, contribute to, and trust\u2014not merely a service they must depend on.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022where-to-go-next\u0022\u003EWhere to Go Next\u003C/h2\u003E\n\u003Cp\u003EYou do not need to understand every internal component before getting started.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EContinue to Quick Start\u003C/strong\u003E to set up your first UltimateAuth application. Then explore \u003Ca href=\u0022../auth-flows/auth-flows.md\u0022\u003EAuth Flows\u003C/a\u003E to understand how the pieces work together.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe idea to remember:\u003C/strong\u003E UltimateAuth unifies authentication concepts without erasing their differences. One framework, a consistent lifecycle, and room for the security model your application actually needs.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EWelcome to \u003Cstrong\u003EUltimateAuth\u003C/strong\u003E, a unified authentication and authorization framework for modern .NET applications.\u003C/p\u003E\n\u003Cp\u003EAuthentication should not become a different system every time your application gains a new client, tenant, device, or security requirement. UltimateAuth brings these concerns into \u003Cstrong\u003Eone coherent model\u003C/strong\u003E\u2014without pretending that every application needs the same authentication strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOne framework. Multiple authentication models. A consistent security lifecycle.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-ultimateauth\u0022\u003EWhat is UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is a modular, flow-driven framework for building and managing authentication in .NET. It connects concepts that are often implemented separately:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ESessions, cookies and tokens\u003C/strong\u003E \u2014 stateful session management alongside access and refresh token workflows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthentication and authorization\u003C/strong\u003E \u2014 identity establishment and access decisions within a consistent architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUsers and credentials\u003C/strong\u003E \u2014 identity, identifiers, credentials, and their security state.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevices and session continuity\u003C/strong\u003E \u2014 device-aware session chains, refresh, logout, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClients and delivery mechanisms\u003C/strong\u003E \u2014 browser, API, Blazor, and mobile scenarios with client-aware behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ETenants and isolation\u003C/strong\u003E \u2014 tenant-aware request context and authentication boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESecurity policies and execution flows\u003C/strong\u003E \u2014 explicit decisions, validation, and controlled state transitions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe goal is not to hide these concepts. It is to make them \u003Cstrong\u003Ework together predictably\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022unified-does-not-mean-one-size-fits-all\u0022\u003EUnified Does Not Mean One-Size-Fits-All\u003C/h2\u003E\n\u003Cp\u003EA server-rendered web application, a browser-based SPA, a mobile app, and a backend API do not share the same trust boundaries or credential transport requirements.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides a common foundation while allowing behavior to be shaped by the \u003Cstrong\u003Eeffective authentication mode\u003C/strong\u003E, \u003Cstrong\u003Eclient profile\u003C/strong\u003E, \u003Cstrong\u003Etenant\u003C/strong\u003E, and \u003Cstrong\u003Erequest context\u003C/strong\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcern\u003C/th\u003E\n\u003Cth\u003EWhat UltimateAuth brings together\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\u003EAuthentication models\u003C/td\u003E\n\u003Ctd\u003ESession-oriented and token-oriented workflows\u003C/td\u003E\n\u003Ctd\u003EDifferent clients can participate in one security architecture\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession lifecycle\u003C/td\u003E\n\u003Ctd\u003ERoot \u2192 Chain \u2192 Session\u003C/td\u003E\n\u003Ctd\u003EManage authentication at identity, device, and session scope\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient environments\u003C/td\u003E\n\u003Ctd\u003EASP.NET Core, Blazor, APIs, and .NET mobile clients\u003C/td\u003E\n\u003Ctd\u003EAvoid rebuilding the core authentication model for each frontend\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMulti-tenancy\u003C/td\u003E\n\u003Ctd\u003ETenant resolution and tenant-bound context\u003C/td\u003E\n\u003Ctd\u003EKeep authentication operations within the intended tenant boundary\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredentials and identities\u003C/td\u003E\n\u003Ctd\u003EUser identifiers, credential providers, and security state\u003C/td\u003E\n\u003Ctd\u003ESeparate \u003Cem\u003Ewho the user is\u003C/em\u003E from \u003Cem\u003Ehow they prove it\u003C/em\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESecurity enforcement\u003C/td\u003E\n\u003Ctd\u003EContext, validation, authority, and orchestration\u003C/td\u003E\n\u003Ctd\u003EMake decisions explicit rather than scattering checks across endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential delivery\u003C/td\u003E\n\u003Ctd\u003EMode- and client-aware response policies\u003C/td\u003E\n\u003Ctd\u003EHandle cookies, tokens, and redirects according to the scenario\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession termination\u003C/td\u003E\n\u003Ctd\u003ELogout, targeted revocation, and device management\u003C/td\u003E\n\u003Ctd\u003ERespond proportionately to ordinary sign-out and security incidents\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EUnified\u003C/strong\u003E is therefore about a shared security model\u2014not forcing all applications to use identical cookies, tokens, or endpoints.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-is-more-than-issuing-a-token\u0022\u003EAuthentication Is More Than Issuing a Token\u003C/h2\u003E\n\u003Cp\u003EIn many applications, authentication is described as a short sequence: validate credentials, generate a token, return it to the client.\u003C/p\u003E\n\u003Cp\u003EThat is only part of the story.\u003C/p\u003E\n\u003Cp\u003EA production authentication system must also answer questions such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhich tenant does this request belong to?\u003C/li\u003E\n\u003Cli\u003EWhich user and credential are being evaluated?\u003C/li\u003E\n\u003Cli\u003EIs an existing session still valid?\u003C/li\u003E\n\u003Cli\u003EIs the request coming from a known device?\u003C/li\u003E\n\u003Cli\u003ECan access be renewed, restricted, or terminated?\u003C/li\u003E\n\u003Cli\u003EWhat should happen when a credential changes or a device is no longer trusted?\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth treats authentication as a \u003Cstrong\u003Econtrolled lifecycle\u003C/strong\u003E. Login, refresh, and logout are operations within that lifecycle\u2014not unrelated endpoints that happen to exchange tokens.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessions-are-first-class\u0022\u003ESessions Are First-Class\u003C/h2\u003E\n\u003Cp\u003EWhere session-backed modes apply, UltimateAuth models authentication through a hierarchy:\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 Global session-security boundary\n \u2514\u2500\u2500 Chain Device-associated lifecycle\n \u2514\u2500\u2500 Session Individual authentication instance\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis structure gives the system a vocabulary for different security actions. A session can be invalidated, a device-associated chain can be terminated, and a broader security boundary can be revoked.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA token is not the entire authentication model.\u003C/strong\u003E It is a credential used within that model, subject to the applicable mode and validation rules.\u003C/p\u003E\n\u003Cp\u003ENot every authentication mode has identical persistence or session behavior. The session hierarchy describes the stateful lifecycle where that model is in use.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-driven-by-design\u0022\u003EFlow-Driven by Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth organizes authentication operations around explicit flows. A flow brings together the request context, operation-specific validation, security decisions, permitted state transitions, and response 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\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\u003ERequest\n \u2192 Tenant, session, and user resolution\n \u2192 AuthFlowContext\n \u2192 Flow-specific validation\n \u2192 Security decision and orchestration\n \u2192 State transition (when permitted)\n \u2192 Result and client-appropriate response\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is not a claim that pipelines or orchestration are new inventions. The value is in applying them \u003Cstrong\u003Econsistently across authentication operations\u003C/strong\u003E, so the rules are easier to reason about, test, and extend.\u003C/p\u003E\n\u003Cp\u003EFor the implementation model, see \u003Ca href=\u0022../auth-flows/auth-flows.md\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022tenant-aware-from-the-request-boundary\u0022\u003ETenant-Aware from the Request Boundary\u003C/h2\u003E\n\u003Cp\u003EMulti-tenancy is not simply a \u003Ccode\u003ETenantId\u003C/code\u003E added to a user table. Authentication must understand \u003Cem\u003Ewhich tenant\u0027s security context\u003C/em\u003E a request is operating within.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth resolves tenant context as part of the ASP.NET Core request pipeline and carries it into the authentication flow. The flow context then combines that boundary with client settings, device information, and applicable session validation.\u003C/p\u003E\n\u003Cp\u003EThis helps teams design multi-tenant applications without treating tenant isolation as an afterthought. \u003Cstrong\u003ETenant resolution and configuration still need to be correctly integrated by the host application.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-for-different-clients-not-just-different-screens\u0022\u003EBuilt for Different Clients, Not Just Different Screens\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is designed for applications that may span several environments:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EASP.NET Core web applications\u003C/strong\u003E with server-managed authentication concerns.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EBlazor Server and Blazor WebAssembly\u003C/strong\u003E with different hosting and transport constraints.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E.NET MAUI\u003C/strong\u003E and other native clients with device-oriented authentication needs.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EHTTP APIs\u003C/strong\u003E serving browsers, applications, and other services.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EClient profiles and effective server options help adapt authentication behavior to the requesting client. Response policies determine how applicable credentials are delivered and whether a redirect belongs in the interaction.\u003C/p\u003E\n\u003Cp\u003EThe result is a common backend security vocabulary across multiple application types\u2014not a promise that every client uses the same transport or requires zero configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-is-a-lifecycle-too\u0022\u003ESecurity Is a Lifecycle, Too\u003C/h2\u003E\n\u003Cp\u003ESecurity is not finished when login succeeds. UltimateAuth\u0027s architecture accounts for what happens \u003Cem\u003Eafter\u003C/em\u003E access is established:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ESituation\u003C/th\u003E\n\u003Cth\u003ESecurity question\u003C/th\u003E\n\u003Cth\u003EUltimateAuth concept\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs in\u003C/td\u003E\n\u003Ctd\u003EShould this attempt establish authentication?\u003C/td\u003E\n\u003Ctd\u003ELogin flow and security decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA session needs renewal\u003C/td\u003E\n\u003Ctd\u003ECan continuity be preserved safely?\u003C/td\u003E\n\u003Ctd\u003ERefresh flow and session lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs out\u003C/td\u003E\n\u003Ctd\u003EWhich authentication scope should end?\u003C/td\u003E\n\u003Ctd\u003ELogout\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA device is lost\u003C/td\u003E\n\u003Ctd\u003EHow can that device\u0027s access be terminated?\u003C/td\u003E\n\u003Ctd\u003EChain-level management and revocation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA credential is compromised\u003C/td\u003E\n\u003Ctd\u003EHow should existing authentication be affected?\u003C/td\u003E\n\u003Ctd\u003ECredential security state and revocation policies\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAn administrator manages access\u003C/td\u003E\n\u003Ctd\u003EIs the operation permitted for this actor and target?\u003C/td\u003E\n\u003Ctd\u003EAuthorization and access context\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA key distinction is \u003Cstrong\u003Elogout versus revoke\u003C/strong\u003E. Ordinary logout ends authentication according to its scope; revocation is an irreversible invalidation of the targeted security state. Signing in again may establish \u003Cem\u003Enew\u003C/em\u003E authentication, but it does not restore what was revoked.\u003C/p\u003E\n\u003Cp\u003EExplore the details in \u003Ca href=\u0022../auth-flows/login-flow.md\u0022\u003ELogin Flow\u003C/a\u003E, \u003Ca href=\u0022../auth-flows/refresh-flow.md\u0022\u003ERefresh Flow\u003C/a\u003E, and \u003Ca href=\u0022../auth-flows/logout-flow.md\u0022\u003ELogout Flow\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022modular-and-extensible\u0022\u003EModular and Extensible\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is intended to serve both straightforward applications and systems with more demanding security requirements. Its architecture separates responsibilities such as core contracts, server execution, client integration, user management, credentials, and authorization.\u003C/p\u003E\n\u003Cp\u003EThis separation makes it possible to understand and evolve individual concerns without reducing authentication to a collection of disconnected custom handlers.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EExtensible does not mean every possible flow is already implemented.\u003C/strong\u003E Available modes, integrations, and capabilities should be evaluated against the version you are using; the documentation for each feature describes its current behavior and limitations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-should-you-choose-ultimateauth\u0022\u003EWhen Should You Choose UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is especially relevant when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need quickly-installed, consistent, secure and easy-to-use auth in your .NET application.\u003C/li\u003E\n\u003Cli\u003EYour .NET application has\u2014or will have\u2014\u003Cstrong\u003Emore than one client type\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003EYou need \u003Cstrong\u003Edevice-aware session management\u003C/strong\u003E, not only token generation.\u003C/li\u003E\n\u003Cli\u003EYou are designing a \u003Cstrong\u003Emulti-tenant\u003C/strong\u003E system with clear authentication boundaries.\u003C/li\u003E\n\u003Cli\u003EYou want \u003Cstrong\u003Eauthentication and authorization\u003C/strong\u003E to follow a consistent architectural model.\u003C/li\u003E\n\u003Cli\u003EYou need control over \u003Cstrong\u003Erefresh, logout, revocation, and credential lifecycle\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003EYou value a \u003Cstrong\u003Emodular, self-hosted, open-source\u003C/strong\u003E foundation you can understand and extend.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAlso for a small application with minimal authentication needs, UltimateAuth brings the simplest possible solution.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022open-source-first\u0022\u003EOpen Source First\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is built with a simple commitment: \u003Cstrong\u003Eself-hosted authentication should remain accessible\u003C/strong\u003E. The framework is open source, and self-hosting is intended to remain free. A hosted offering may provide an operational alternative, not a separate tier of authentication features.\u003C/p\u003E\n\u003Cp\u003EThe ambition is to build a framework that developers can adopt, inspect, contribute to, and trust\u2014not merely a service they must depend on.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022where-to-go-next\u0022\u003EWhere to Go Next\u003C/h2\u003E\n\u003Cp\u003EYou do not need to understand every internal component before getting started.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EContinue to Quick Start\u003C/strong\u003E to set up your first UltimateAuth application. Then explore \u003Ca href=\u0022../auth-flows/auth-flows.md\u0022\u003EAuth Flows\u003C/a\u003E to understand how the pieces work together.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe idea to remember:\u003C/strong\u003E UltimateAuth unifies authentication concepts without erasing their differences. One framework, a consistent lifecycle, and room for the security model your application actually needs.\u003C/p\u003E\n", "Headings": [ { "Id": "what-is-ultimateauth", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json index c2877c46..1f0ba736 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json @@ -1,7 +1,7 @@ { "Slug": "getting-started/quickstart", "Title": "QuickStart", - "Html": "\n\u003Cp\u003EIn this guide, you will set up UltimateAuth in a few minutes and perform your \u003Cstrong\u003Efirst login\u003C/strong\u003E.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-project\u0022\u003E1. Create a Project\u003C/h2\u003E\n\u003Cp\u003EStart by creating a new Blazor app:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet new blazorserver -n UltimateAuthDemo\ncd UltimateAuthDemo\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-packages\u0022\u003E2. Install Packages\u003C/h2\u003E\n\u003Cp\u003EInstall the required UltimateAuth packages:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.InMemory.Bundle\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services\u0022\u003E3. Configure Services\u003C/h2\u003E\n\u003Cp\u003EUpdate your Program.cs:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services\n .AddUltimateAuthServer()\n .AddUltimateAuthInMemory();\n\nbuilder.Services\n .AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-middleware\u0022\u003E4. Configure Middleware\u003C/h2\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\u003C/code\u003E\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.UseUltimateAuthWithAspNetCore();\napp.MapUltimateAuthEndpoints();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022enable-blazor-integration\u0022\u003E5. Enable Blazor Integration\u003C/h2\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\u003C/code\u003E\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveServerRenderMode() // or webassembly (depends on your application type)\n .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient());\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-uauth-script\u0022\u003E6. Add UAuth Script\u003C/h2\u003E\n\u003Cp\u003EAdd this to \u003Ccode\u003EApp.razor\u003C/code\u003E or \u003Ccode\u003Eindex.html\u003C/code\u003E:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003E\u0026lt;script src=\u0026quot;_content/CodeBeam.UltimateAuth.Client.Blazor/uauth.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-application-lifecycle\u0022\u003E7. Configure Application Lifecycle\u003C/h2\u003E\n\u003Cp\u003EReplace \u003Ccode\u003ERoutes.razor\u003C/code\u003E with this code:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003E\u0026lt;UAuthApp UseBuiltInRouter=\u0026quot;true\u0026quot; AppAssembly=\u0026quot;typeof(Program).Assembly\u0026quot; DefaultLayout=\u0026quot;typeof(Layout.MainLayout)\u0026quot; /\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-setup-optional\u0022\u003E8. Recommended Setup (Optional)\u003C/h2\u003E\n\u003Cp\u003EAdd these for better experience:\u003C/p\u003E\n\u003Cp\u003EFor login page (Use this only once in your application)\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003E@attribute [UAuthLoginPage]\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EFor protected pages\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003E@attribute [UAuthAuthorize]\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EFor any page that you use UltimateAuth features like AuthState etc.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003E@inherits UAuthPageBase\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022seed-data-for-quickstart-optional\u0022\u003E9. Seed Data For QuickStart (Optional)\u003C/h2\u003E\n\u003Cp\u003EThis code creates admin and user users with same password and admin role.\u003C/p\u003E\n\u003Cp\u003EFor in memory\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthSampleSeed();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EFor entity framework core, use scoped services:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddScopedUltimateAuthSampleSeed();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EIn pipeline configuration\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eif (app.Environment.IsDevelopment())\n{\n await app.SeedUltimateAuthAsync();\n}\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022perform-your-first-login\u0022\u003E10. Perform Your First Login\u003C/h2\u003E\n\u003Cp\u003EExample using IUAuthClient:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003E[Inject] IUAuthClient UAuthClient { get; set; } = null!;\n\nprivate async Task Login()\n{\n await UAuthClient.Flows.LoginAsync(new LoginRequest\n {\n Identifier = \u0026quot;admin\u0026quot;,\n Secret = \u0026quot;admin\u0026quot;\n });\n}\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022thats-it\u0022\u003E\uD83C\uDF89 That\u2019s It\u003C/h2\u003E\n\u003Cp\u003EYou now have a working authentication system with:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession-based authentication\u003C/li\u003E\n\u003Cli\u003EAutomatic client detection\u003C/li\u003E\n\u003Cli\u003EBuilt-in login flow\u003C/li\u003E\n\u003Cli\u003ESecure session handling\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-just-happened\u0022\u003EWhat Just Happened?\u003C/h2\u003E\n\u003Cp\u003EWhen you logged in:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA session (with root and chain) was created on the server,\u003C/li\u003E\n\u003Cli\u003EYour client received an authentication grant (cookie or token),\u003C/li\u003E\n\u003Cli\u003EUltimateAuth established your auth state automatically.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You didn\u2019t manage cookies, tokens, or redirects manually.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cp\u003EDiscover the setup for real world applications with entity framework core.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EIn this guide, you will set up UltimateAuth in a few minutes and perform your \u003Cstrong\u003Efirst login\u003C/strong\u003E.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-project\u0022\u003E1. Create a Project\u003C/h2\u003E\n\u003Cp\u003EStart by creating a new Blazor app:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet new blazorserver -n UltimateAuthDemo\ncd UltimateAuthDemo\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-packages\u0022\u003E2. Install Packages\u003C/h2\u003E\n\u003Cp\u003EInstall the required UltimateAuth packages:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.InMemory.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services\u0022\u003E3. Configure Services\u003C/h2\u003E\n\u003Cp\u003EUpdate your Program.cs:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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\n .AddUltimateAuthServer()\n .AddUltimateAuthInMemory();\n\nbuilder.Services\n .AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-middleware\u0022\u003E4. Configure Middleware\u003C/h2\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\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\u003Eapp.UseUltimateAuthWithAspNetCore();\napp.MapUltimateAuthEndpoints();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022enable-blazor-integration\u0022\u003E5. Enable Blazor Integration\u003C/h2\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\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\u003Eapp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveServerRenderMode() // or webassembly (depends on your application type)\n .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient());\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-uauth-script\u0022\u003E6. Add UAuth Script\u003C/h2\u003E\n\u003Cp\u003EAdd this to \u003Ccode\u003EApp.razor\u003C/code\u003E or \u003Ccode\u003Eindex.html\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\u003E\u0026lt;script src=\u0026quot;_content/CodeBeam.UltimateAuth.Client.Blazor/uauth.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-application-lifecycle\u0022\u003E7. Configure Application Lifecycle\u003C/h2\u003E\n\u003Cp\u003EReplace \u003Ccode\u003ERoutes.razor\u003C/code\u003E with this code:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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\u0026lt;UAuthApp UseBuiltInRouter=\u0026quot;true\u0026quot; AppAssembly=\u0026quot;typeof(Program).Assembly\u0026quot; DefaultLayout=\u0026quot;typeof(Layout.MainLayout)\u0026quot; /\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-setup-optional\u0022\u003E8. Recommended Setup (Optional)\u003C/h2\u003E\n\u003Cp\u003EAdd these for better experience:\u003C/p\u003E\n\u003Cp\u003EFor login page (Use this only once in your application)\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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@attribute [UAuthLoginPage]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor protected pages\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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@attribute [UAuthAuthorize]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor any page that you use UltimateAuth features like AuthState etc.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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@inherits UAuthPageBase\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022seed-data-for-quickstart-optional\u0022\u003E9. Seed Data For QuickStart (Optional)\u003C/h2\u003E\n\u003Cp\u003EThis code creates admin and user users with same password and admin role.\u003C/p\u003E\n\u003Cp\u003EFor in memory\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthSampleSeed();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor entity framework core, use scoped services:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddScopedUltimateAuthSampleSeed();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn pipeline 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\u003Eif (app.Environment.IsDevelopment())\n{\n await app.SeedUltimateAuthAsync();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022perform-your-first-login\u0022\u003E10. Perform Your First Login\u003C/h2\u003E\n\u003Cp\u003EExample using IUAuthClient:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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[Inject] IUAuthClient UAuthClient { get; set; } = null!;\n\nprivate async Task Login()\n{\n await UAuthClient.Flows.LoginAsync(new LoginRequest\n {\n Identifier = \u0026quot;admin\u0026quot;,\n Secret = \u0026quot;admin\u0026quot;\n });\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022thats-it\u0022\u003E\uD83C\uDF89 That\u2019s It\u003C/h2\u003E\n\u003Cp\u003EYou now have a working authentication system with:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession-based authentication\u003C/li\u003E\n\u003Cli\u003EAutomatic client detection\u003C/li\u003E\n\u003Cli\u003EBuilt-in login flow\u003C/li\u003E\n\u003Cli\u003ESecure session handling\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-just-happened\u0022\u003EWhat Just Happened?\u003C/h2\u003E\n\u003Cp\u003EWhen you logged in:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA session (with root and chain) was created on the server,\u003C/li\u003E\n\u003Cli\u003EYour client received an authentication grant (cookie or token),\u003C/li\u003E\n\u003Cli\u003EUltimateAuth established your auth state automatically.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You didn\u2019t manage cookies, tokens, or redirects manually.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cp\u003EDiscover the setup for real world applications with entity framework core.\u003C/p\u003E\n", "Headings": [ { "Id": "create-a-project", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json index f2964dbf..338049d9 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json @@ -1,7 +1,7 @@ { "Slug": "getting-started/real-world-setup", "Title": "Real World Setup", - "Html": "\n\u003Cp\u003EThe Quick Start uses an in-memory setup for simplicity.\nIn real-world applications, you should replace it with a persistent configuration as shown below.\u003C/p\u003E\n\u003Cp\u003EIn real applications, you will typically configure:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA persistent database\u003C/li\u003E\n\u003Cli\u003EAn appropriate client profile\u003C/li\u003E\n\u003Cli\u003EA suitable authentication mode\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis guide shows how to set up UltimateAuth for real-world scenarios.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-entity-framework-core\u0022\u003E\uD83D\uDDC4\uFE0F Using Entity Framework Core\u003C/h2\u003E\n\u003Cp\u003EFor production, you should use a persistent store. (In-memory provider is volatile and automatically resets on each restart.)\u003C/p\u003E\n\u003Cp\u003EIn this setup, you no longer need the \u003Ccode\u003ECodeBeam.UltimateAuth.InMemory.Bundle\u003C/code\u003E package.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-packages\u0022\u003EInstall Packages\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services\u0022\u003EConfigure Services\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services\n .AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(\u0026quot;Data Source=uauth.db\u0026quot;);\n // or UseSqlServer(...) / UseNpgsql(...) / UseMySql(...) etc.\n });\n\nbuilder.Services\n .AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022database-migrations\u0022\u003EDatabase Migrations\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth integrates with Entity Framework Core, but database migrations belong to your application.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth does not automatically create or apply migrations on your behalf. This keeps your database schema lifecycle under your control and allows migrations to follow the same deployment and review process as the rest of your application.\u003C/p\u003E\n\u003Cp\u003EAfter configuring the Entity Framework Core provider, create the initial migration and update the database using either the .NET CLI or Visual Studio Package Manager Console.\u003C/p\u003E\n\u003Ch4 id=\u0022option-a.net-cli\u0022\u003EOption A \u2014 .NET CLI\u003C/h4\u003E\n\u003Cp\u003EIf you use the .NET CLI:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet ef migrations add InitUAuth --context UAuthDbContext\ndotnet ef database update --context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EIf the dotnet ef command is not available, install the EF Core CLI tool:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet tool install --global dotnet-ef\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EYour project also needs the Entity Framework Core design package:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package Microsoft.EntityFrameworkCore.Design\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch4 id=\u0022option-b-visual-studio-package-manager-console\u0022\u003EOption B \u2014 Visual Studio Package Manager Console\u003C/h4\u003E\n\u003Cp\u003EIf you use Visual Studio, you can perform the same operation from Tools \u2192 NuGet Package Manager \u2192 Package Manager Console:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003EAdd-Migration InitUAuth -Context UAuthDbContext\nUpdate-Database -Context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EFor Package Manager Console tooling, make sure the required EF Core tooling package is available:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003EInstall-Package Microsoft.EntityFrameworkCore.Tools\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022who-owns-the-migrations\u0022\u003EWho Owns the Migrations?\u003C/h3\u003E\n\u003Cp\u003EYour application does.\u003C/p\u003E\n\u003Cp\u003EThis is intentional. UltimateAuth provides the authentication and identity model through its Entity Framework Core integration, while your application remains responsible for managing the resulting database schema.\u003C/p\u003E\n\u003Cp\u003EThis means you can:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ereview migrations before applying them,\u003C/li\u003E\n\u003Cli\u003Einclude UltimateAuth schema changes in your normal deployment process,\u003C/li\u003E\n\u003Cli\u003Econtrol when database changes are applied,\u003C/li\u003E\n\u003Cli\u003Emaintain migration history alongside your application,\u003C/li\u003E\n\u003Cli\u003Euse the database provider and deployment strategy appropriate for your environment.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EWhen upgrading UltimateAuth, review the release notes for persistence-related schema changes and create a new migration when required.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003ETip:\u003C/strong\u003E Treat UltimateAuth model changes like any other Entity Framework Core model change: upgrade the package, create a migration, review the generated migration, and apply it through your normal deployment process.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services-with-options\u0022\u003EConfigure Services With Options\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides rich options for server and client service registration.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(o =\u0026gt; {\n o.Diagnostics.EnableRefreshDetails = true;\n o.Login.MaxFailedAttempts = 4;\n o.Identifiers.AllowMultipleUsernames = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-standalone-wasm-setup\u0022\u003EBlazor Standalone WASM Setup\u003C/h2\u003E\n\u003Cp\u003EBlazor WASM applications run entirely on the client and cannot securely handle credentials.\nFor this reason, UltimateAuth uses a dedicated Auth server called \u003Cstrong\u003EUAuthHub\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EWASM \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6110/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6130/home\u0026quot;; // Your (WASM) application domain \u002B return path\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EUAuthHub \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthInMemory()\n .AddUAuthHub(o =\u0026gt; o.AllowedClientOrigins.Add(\u0026quot;https://localhost:6130\u0026quot;)); // WASM application\u0027s URL\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EUAuthHub Pipeline Configuration\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.MapUltimateAuthEndpoints();\napp.MapUAuthHub();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-web-app-setup\u0022\u003EBlazor Web App Setup\u003C/h2\u003E\n\u003Cp\u003EA blazor web app contains two projects that includes host and client. You need to arrange them both.\u003C/p\u003E\n\u003Cp\u003EIn the host project:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;; // Current application domain \u002B path\n});\n\n// In pipeline configuration\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveWebAssemblyRenderMode()\n .AddAdditionalAssemblies(UAuthAssemblies.BlazorClient().First());\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EIn the client project:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;; // Current application domain \u002B path\n});\n\nbuilder.Services.AddScoped(sp =\u0026gt; new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });\n\n// Optional if you use external API calls in your client project.\nbuilder.Services.AddHttpClient(\u0026quot;resourceApi\u0026quot;, client =\u0026gt;\n{\n client.BaseAddress = new Uri(\u0026quot;https://localhost:6122\u0026quot;);\n});\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EIf you want to use embedded UAuthHub in host project, you can register server services as shown in quickstart.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u2139\uFE0F UltimateAuth automatically selects the appropriate authentication mode (PureOpaque, Hybrid, etc.) based on the client type.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resourceapi-setup\u0022\u003EResourceApi Setup\u003C/h2\u003E\n\u003Cp\u003EYou may want to secure your custom API with UltimateAuth. UltimateAuth provides a lightweight option for this case. (ResourceApi doesn\u0027t have to be a blazor application, it can be any server-side project like MVC.)\u003C/p\u003E\n\u003Cp\u003EResourceApi\u0027s \u003Ccode\u003EProgram.cs\u003C/code\u003E\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthResourceApi(o =\u0026gt;\n {\n o.UAuthHubBaseUrl = \u0026quot;https://localhost:6110\u0026quot;;\n o.AllowedClientOrigins.Add(\u0026quot;https://localhost:6130\u0026quot;);\n });\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EConfigure pipeline:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.UseUltimateAuthResourceApiWithAspNetCore();\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003ENotes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EResourceApi should connect with an UAuthHub, not a pure-server. Make sure \u003Ccode\u003E.AddUAuthHub()\u003C/code\u003E after calling \u003Ccode\u003Ebuilder.Services.AddUltimateAuthServer()\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EUltimateAuth automatically configures CORS based on the provided origins.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUse ResourceApi when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou have a separate backend API\u003C/li\u003E\n\u003Cli\u003EYou want to validate sessions or tokens externally\u003C/li\u003E\n\u003Cli\u003EYour API is not hosting UltimateAuth directly\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-to-think-about-setup\u0022\u003E\uD83E\uDDE0 How to Think About Setup\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EServer\u003C/strong\u003E manages authentication flows and sessions\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EClient\u003C/strong\u003E interacts through flows (not tokens directly)\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EStorage layer\u003C/strong\u003E (InMemory / EF Core) defines persistence\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EApplication type\u003C/strong\u003E determines runtime behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You configure the system once, and UltimateAuth adapts automatically.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EThe Quick Start uses an in-memory setup for simplicity.\nIn real-world applications, you should replace it with a persistent configuration as shown below.\u003C/p\u003E\n\u003Cp\u003EIn real applications, you will typically configure:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA persistent database\u003C/li\u003E\n\u003Cli\u003EAn appropriate client profile\u003C/li\u003E\n\u003Cli\u003EA suitable authentication mode\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis guide shows how to set up UltimateAuth for real-world scenarios.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-entity-framework-core\u0022\u003E\uD83D\uDDC4\uFE0F Using Entity Framework Core\u003C/h2\u003E\n\u003Cp\u003EFor production, you should use a persistent store. (In-memory provider is volatile and automatically resets on each restart.)\u003C/p\u003E\n\u003Cp\u003EIn this setup, you no longer need the \u003Ccode\u003ECodeBeam.UltimateAuth.InMemory.Bundle\u003C/code\u003E package.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-packages\u0022\u003EInstall Packages\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services\u0022\u003EConfigure Services\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\n .AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(\u0026quot;Data Source=uauth.db\u0026quot;);\n // or UseSqlServer(...) / UseNpgsql(...) / UseMySql(...) etc.\n });\n\nbuilder.Services\n .AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022database-migrations\u0022\u003EDatabase Migrations\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth integrates with Entity Framework Core, but database migrations belong to your application.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth does not automatically create or apply migrations on your behalf. This keeps your database schema lifecycle under your control and allows migrations to follow the same deployment and review process as the rest of your application.\u003C/p\u003E\n\u003Cp\u003EAfter configuring the Entity Framework Core provider, create the initial migration and update the database using either the .NET CLI or Visual Studio Package Manager Console.\u003C/p\u003E\n\u003Ch4 id=\u0022option-a.net-cli\u0022\u003EOption A \u2014 .NET CLI\u003C/h4\u003E\n\u003Cp\u003EIf you use the .NET CLI:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet ef migrations add InitUAuth --context UAuthDbContext\ndotnet ef database update --context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIf the dotnet ef command is not available, install the EF Core CLI tool:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet tool install --global dotnet-ef\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EYour project also needs the Entity Framework Core design package:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package Microsoft.EntityFrameworkCore.Design\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch4 id=\u0022option-b-visual-studio-package-manager-console\u0022\u003EOption B \u2014 Visual Studio Package Manager Console\u003C/h4\u003E\n\u003Cp\u003EIf you use Visual Studio, you can perform the same operation from Tools \u2192 NuGet Package Manager \u2192 Package Manager Console:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003EAdd-Migration InitUAuth -Context UAuthDbContext\nUpdate-Database -Context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor Package Manager Console tooling, make sure the required EF Core tooling package is available:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003EInstall-Package Microsoft.EntityFrameworkCore.Tools\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022who-owns-the-migrations\u0022\u003EWho Owns the Migrations?\u003C/h3\u003E\n\u003Cp\u003EYour application does.\u003C/p\u003E\n\u003Cp\u003EThis is intentional. UltimateAuth provides the authentication and identity model through its Entity Framework Core integration, while your application remains responsible for managing the resulting database schema.\u003C/p\u003E\n\u003Cp\u003EThis means you can:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ereview migrations before applying them,\u003C/li\u003E\n\u003Cli\u003Einclude UltimateAuth schema changes in your normal deployment process,\u003C/li\u003E\n\u003Cli\u003Econtrol when database changes are applied,\u003C/li\u003E\n\u003Cli\u003Emaintain migration history alongside your application,\u003C/li\u003E\n\u003Cli\u003Euse the database provider and deployment strategy appropriate for your environment.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EWhen upgrading UltimateAuth, review the release notes for persistence-related schema changes and create a new migration when required.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003ETip:\u003C/strong\u003E Treat UltimateAuth model changes like any other Entity Framework Core model change: upgrade the package, create a migration, review the generated migration, and apply it through your normal deployment process.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services-with-options\u0022\u003EConfigure Services With Options\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides rich options for server and client service registration.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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(o =\u0026gt; {\n o.Diagnostics.EnableRefreshDetails = true;\n o.Login.MaxFailedAttempts = 4;\n o.Identifiers.AllowMultipleUsernames = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-standalone-wasm-setup\u0022\u003EBlazor Standalone WASM Setup\u003C/h2\u003E\n\u003Cp\u003EBlazor WASM applications run entirely on the client and cannot securely handle credentials.\nFor this reason, UltimateAuth uses a dedicated Auth server called \u003Cstrong\u003EUAuthHub\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EWASM \u003Ccode\u003EProgram.cs\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\u003Ebuilder.Services.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6110/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6130/home\u0026quot;; // Your (WASM) application domain \u002B return path\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUAuthHub \u003Ccode\u003EProgram.cs\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\u003Ebuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthInMemory()\n .AddUAuthHub(o =\u0026gt; o.AllowedClientOrigins.Add(\u0026quot;https://localhost:6130\u0026quot;)); // WASM application\u0027s URL\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUAuthHub Pipeline 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\u003Eapp.MapUltimateAuthEndpoints();\napp.MapUAuthHub();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-web-app-setup\u0022\u003EBlazor Web App Setup\u003C/h2\u003E\n\u003Cp\u003EA blazor web app contains two projects that includes host and client. You need to arrange them both.\u003C/p\u003E\n\u003Cp\u003EIn the host project:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;; // Current application domain \u002B path\n});\n\n// In pipeline configuration\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveWebAssemblyRenderMode()\n .AddAdditionalAssemblies(UAuthAssemblies.BlazorClient().First());\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn the client project:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;; // Current application domain \u002B path\n});\n\nbuilder.Services.AddScoped(sp =\u0026gt; new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });\n\n// Optional if you use external API calls in your client project.\nbuilder.Services.AddHttpClient(\u0026quot;resourceApi\u0026quot;, client =\u0026gt;\n{\n client.BaseAddress = new Uri(\u0026quot;https://localhost:6122\u0026quot;);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EIf you want to use embedded UAuthHub in host project, you can register server services as shown in quickstart.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u2139\uFE0F UltimateAuth automatically selects the appropriate authentication mode (PureOpaque, Hybrid, etc.) based on the client type.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resourceapi-setup\u0022\u003EResourceApi Setup\u003C/h2\u003E\n\u003Cp\u003EYou may want to secure your custom API with UltimateAuth. UltimateAuth provides a lightweight option for this case. (ResourceApi doesn\u0027t have to be a blazor application, it can be any server-side project like MVC.)\u003C/p\u003E\n\u003Cp\u003EResourceApi\u0027s \u003Ccode\u003EProgram.cs\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\u003Ebuilder.Services.AddUltimateAuthResourceApi(o =\u0026gt;\n {\n o.UAuthHubBaseUrl = \u0026quot;https://localhost:6110\u0026quot;;\n o.AllowedClientOrigins.Add(\u0026quot;https://localhost:6130\u0026quot;);\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EConfigure pipeline:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.UseUltimateAuthResourceApiWithAspNetCore();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ENotes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EResourceApi should connect with an UAuthHub, not a pure-server. Make sure \u003Ccode\u003E.AddUAuthHub()\u003C/code\u003E after calling \u003Ccode\u003Ebuilder.Services.AddUltimateAuthServer()\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EUltimateAuth automatically configures CORS based on the provided origins.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUse ResourceApi when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou have a separate backend API\u003C/li\u003E\n\u003Cli\u003EYou want to validate sessions or tokens externally\u003C/li\u003E\n\u003Cli\u003EYour API is not hosting UltimateAuth directly\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-to-think-about-setup\u0022\u003E\uD83E\uDDE0 How to Think About Setup\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EServer\u003C/strong\u003E manages authentication flows and sessions\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EClient\u003C/strong\u003E interacts through flows (not tokens directly)\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EStorage layer\u003C/strong\u003E (InMemory / EF Core) defines persistence\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EApplication type\u003C/strong\u003E determines runtime behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You configure the system once, and UltimateAuth adapts automatically.\u003C/p\u003E\n", "Headings": [ { "Id": "using-entity-framework-core", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization-domain.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization-domain.json index d69338a7..df32dffb 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization-domain.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization-domain.json @@ -1,7 +1,7 @@ { "Slug": "plugin-domains/authorization-domain", "Title": "Authorization", - "Html": "\n\u003Cp\u003EUltimateAuth provides a flexible and extensible authorization system based on:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoles\u003C/li\u003E\n\u003Cli\u003EPermissions\u003C/li\u003E\n\u003Cli\u003EPolicies\u003C/li\u003E\n\u003Cli\u003EAccess orchestration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-concepts\u0022\u003E\uD83E\uDDE9 Core Concepts\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permissions\u0022\u003E\uD83D\uDD11 Permissions\u003C/h3\u003E\n\u003Cp\u003EIn UltimateAuth, permissions are not just arbitrary strings.\u003C/p\u003E\n\u003Cp\u003EThey follow a \u003Cstrong\u003Estructured action model\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch4 id=\u0022permission-structure\u0022\u003E\uD83E\uDDE9 Permission Structure\u003C/h4\u003E\n\u003Cp\u003EPermissions are built using a consistent format:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003Eresource.operation.scope\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003Eor\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003Eresource.subresource.operation.scope\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch4 id=\u0022examples\u0022\u003E\u2705 Examples\u003C/h4\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.create.admin\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.profile.update.self\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Esessions.revokechain.admin\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Ecredentials.change.self\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This structure is not accidental \u2014\u003Cbr /\u003E\nit is \u003Cstrong\u003Edesigned for consistency, readability, and policy evaluation\u003C/strong\u003E.\u003C/p\u003E\n\u003Chr /\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-in-action-catalog\u0022\u003E\u2699\uFE0F Built-in Action Catalog\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth provides a predefined action catalog.\u003C/p\u003E\n\u003Cp\u003EExamples:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003Eflows.logout.self\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Esessions.listchains.admin\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.delete.self\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Ecredentials.revoke.admin\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eauthorization.roles.assign.admin\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This ensures:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ENo magic strings\u003C/li\u003E\n\u003Cli\u003EDiscoverable permissions\u003C/li\u003E\n\u003Cli\u003EConsistent naming across the system\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022scope-semantics\u0022\u003E\uD83E\uDDE0 Scope Semantics\u003C/h3\u003E\n\u003Cp\u003EThe last part of the permission defines \u003Cstrong\u003Escope\u003C/strong\u003E:\u003C/p\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EScope\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\u003Eself\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser acts on own resources\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eadmin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser acts on other users\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eanonymous\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo authentication required\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cbr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022wildcards-grouping\u0022\u003E\uD83C\uDF32 Wildcards \u0026amp; Grouping\u003C/h3\u003E\n\u003Cp\u003EPermissions support hierarchical matching:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.*\u003C/code\u003E \u2192 all user actions\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.profile.*\u003C/code\u003E \u2192 all profile operations\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003E*\u003C/code\u003E \u2192 full access\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022normalization\u0022\u003E\u26A1 Normalization\u003C/h3\u003E\n\u003Cp\u003EPermissions are automatically normalized:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EFull coverage \u2192 replaced with \u003Ccode\u003E*\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EFull group \u2192 replaced with \u003Ccode\u003Eprefix.*\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022role\u0022\u003ERole\u003C/h3\u003E\n\u003Cp\u003EA role is a collection of permissions.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoles are tenant-scoped\u003C/li\u003E\n\u003Cli\u003ERoles can be dynamically updated\u003C/li\u003E\n\u003Cli\u003EPermissions are normalized internally\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userrole\u0022\u003EUserRole\u003C/h3\u003E\n\u003Cp\u003EUsers are assigned roles:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMany-to-many relationship\u003C/li\u003E\n\u003Cli\u003EAssignment is timestamped\u003C/li\u003E\n\u003Cli\u003ERole resolution is runtime-based\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-resolution\u0022\u003E\uD83D\uDD04 Permission Resolution\u003C/h2\u003E\n\u003Cp\u003EPermissions are evaluated using:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EExact match\u003C/li\u003E\n\u003Cli\u003EPrefix match\u003C/li\u003E\n\u003Cli\u003EWildcard match\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ECompiledPermissionSet optimizes runtime checks.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022claims-integration\u0022\u003E\uD83E\uDDE0 Claims Integration\u003C/h2\u003E\n\u003Cp\u003EAuthorization integrates with authentication via claims:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoles \u2192 \u003Ccode\u003EClaimTypes.Role\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EPermissions \u2192 \u003Ccode\u003Epermission\u003C/code\u003E claim\u003C/li\u003E\n\u003Cli\u003ETenant \u2192 \u003Ccode\u003Etenant\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis allows:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EToken-based authorization\u003C/li\u003E\n\u003Cli\u003EStateless permission checks (for JWT modes)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-flow\u0022\u003E\u2699\uFE0F Authorization Flow\u003C/h2\u003E\n\u003Cp\u003EAuthorization is executed through:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 AccessOrchestrator\u003C/p\u003E\n\u003Cp\u003ESteps:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EBuild AccessContext\u003C/li\u003E\n\u003Cli\u003EExecute policies\u003C/li\u003E\n\u003Cli\u003EAllow or deny operation\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies\u0022\u003E\uD83D\uDEE1 Policies\u003C/h2\u003E\n\u003Cp\u003EPolicies are the core of authorization logic.\u003C/p\u003E\n\u003Cp\u003EDefault policies include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequireAuthenticated\u003C/li\u003E\n\u003Cli\u003EDenyCrossTenant\u003C/li\u003E\n\u003Cli\u003ERequireActiveUser\u003C/li\u003E\n\u003Cli\u003ERequireSelf\u003C/li\u003E\n\u003Cli\u003ERequireSystem\u003C/li\u003E\n\u003Cli\u003EMustHavePermission\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022plugin-integration\u0022\u003E\uD83D\uDD0C Plugin Integration\u003C/h2\u003E\n\u003Cp\u003EAuthorization is a plugin domain.\u003C/p\u003E\n\u003Cp\u003EIt:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDoes NOT depend on other domains\u003C/li\u003E\n\u003Cli\u003EUses contracts only\u003C/li\u003E\n\u003Cli\u003EIntegrates via policies and claims\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83C\uDFAF Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthorization is policy-driven\u003C/li\u003E\n\u003Cli\u003ERoles are permission containers\u003C/li\u003E\n\u003Cli\u003EPermissions support wildcard \u0026amp; prefix\u003C/li\u003E\n\u003Cli\u003EPolicies enforce rules\u003C/li\u003E\n\u003Cli\u003EFully extensible and replaceable\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth provides a flexible and extensible authorization system based on:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoles\u003C/li\u003E\n\u003Cli\u003EPermissions\u003C/li\u003E\n\u003Cli\u003EPolicies\u003C/li\u003E\n\u003Cli\u003EAccess orchestration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-concepts\u0022\u003E\uD83E\uDDE9 Core Concepts\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permissions\u0022\u003E\uD83D\uDD11 Permissions\u003C/h3\u003E\n\u003Cp\u003EIn UltimateAuth, permissions are not just arbitrary strings.\u003C/p\u003E\n\u003Cp\u003EThey follow a \u003Cstrong\u003Estructured action model\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch4 id=\u0022permission-structure\u0022\u003E\uD83E\uDDE9 Permission Structure\u003C/h4\u003E\n\u003Cp\u003EPermissions are built using a consistent format:\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\u003Eresource.operation.scope\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eor\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\u003Eresource.subresource.operation.scope\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch4 id=\u0022examples\u0022\u003E\u2705 Examples\u003C/h4\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.create.admin\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.profile.update.self\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Esessions.revokechain.admin\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Ecredentials.change.self\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This structure is not accidental \u2014\u003Cbr\u003E\nit is \u003Cstrong\u003Edesigned for consistency, readability, and policy evaluation\u003C/strong\u003E.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-in-action-catalog\u0022\u003E\u2699\uFE0F Built-in Action Catalog\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth provides a predefined action catalog.\u003C/p\u003E\n\u003Cp\u003EExamples:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003Eflows.logout.self\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Esessions.listchains.admin\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.delete.self\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Ecredentials.revoke.admin\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eauthorization.roles.assign.admin\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This ensures:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ENo magic strings\u003C/li\u003E\n\u003Cli\u003EDiscoverable permissions\u003C/li\u003E\n\u003Cli\u003EConsistent naming across the system\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022scope-semantics\u0022\u003E\uD83E\uDDE0 Scope Semantics\u003C/h3\u003E\n\u003Cp\u003EThe last part of the permission defines \u003Cstrong\u003Escope\u003C/strong\u003E:\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\u003EMeaning\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eself\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser acts on own resources\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eadmin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser acts on other users\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eanonymous\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo authentication required\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022wildcards-grouping\u0022\u003E\uD83C\uDF32 Wildcards \u0026amp; Grouping\u003C/h3\u003E\n\u003Cp\u003EPermissions support hierarchical matching:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.*\u003C/code\u003E \u2192 all user actions\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.profile.*\u003C/code\u003E \u2192 all profile operations\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003E*\u003C/code\u003E \u2192 full access\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022normalization\u0022\u003E\u26A1 Normalization\u003C/h3\u003E\n\u003Cp\u003EPermissions are automatically normalized:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EFull coverage \u2192 replaced with \u003Ccode\u003E*\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EFull group \u2192 replaced with \u003Ccode\u003Eprefix.*\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022role\u0022\u003ERole\u003C/h3\u003E\n\u003Cp\u003EA role is a collection of permissions.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoles are tenant-scoped\u003C/li\u003E\n\u003Cli\u003ERoles can be dynamically updated\u003C/li\u003E\n\u003Cli\u003EPermissions are normalized internally\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userrole\u0022\u003EUserRole\u003C/h3\u003E\n\u003Cp\u003EUsers are assigned roles:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMany-to-many relationship\u003C/li\u003E\n\u003Cli\u003EAssignment is timestamped\u003C/li\u003E\n\u003Cli\u003ERole resolution is runtime-based\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-resolution\u0022\u003E\uD83D\uDD04 Permission Resolution\u003C/h2\u003E\n\u003Cp\u003EPermissions are evaluated using:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EExact match\u003C/li\u003E\n\u003Cli\u003EPrefix match\u003C/li\u003E\n\u003Cli\u003EWildcard match\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ECompiledPermissionSet optimizes runtime checks.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022claims-integration\u0022\u003E\uD83E\uDDE0 Claims Integration\u003C/h2\u003E\n\u003Cp\u003EAuthorization integrates with authentication via claims:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoles \u2192 \u003Ccode\u003EClaimTypes.Role\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EPermissions \u2192 \u003Ccode\u003Epermission\u003C/code\u003E claim\u003C/li\u003E\n\u003Cli\u003ETenant \u2192 \u003Ccode\u003Etenant\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis allows:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EToken-based authorization\u003C/li\u003E\n\u003Cli\u003EStateless permission checks (for JWT modes)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-flow\u0022\u003E\u2699\uFE0F Authorization Flow\u003C/h2\u003E\n\u003Cp\u003EAuthorization is executed through:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 AccessOrchestrator\u003C/p\u003E\n\u003Cp\u003ESteps:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EBuild AccessContext\u003C/li\u003E\n\u003Cli\u003EExecute policies\u003C/li\u003E\n\u003Cli\u003EAllow or deny operation\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies\u0022\u003E\uD83D\uDEE1 Policies\u003C/h2\u003E\n\u003Cp\u003EPolicies are the core of authorization logic.\u003C/p\u003E\n\u003Cp\u003EDefault policies include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequireAuthenticated\u003C/li\u003E\n\u003Cli\u003EDenyCrossTenant\u003C/li\u003E\n\u003Cli\u003ERequireActiveUser\u003C/li\u003E\n\u003Cli\u003ERequireSelf\u003C/li\u003E\n\u003Cli\u003ERequireSystem\u003C/li\u003E\n\u003Cli\u003EMustHavePermission\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022plugin-integration\u0022\u003E\uD83D\uDD0C Plugin Integration\u003C/h2\u003E\n\u003Cp\u003EAuthorization is a plugin domain.\u003C/p\u003E\n\u003Cp\u003EIt:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDoes NOT depend on other domains\u003C/li\u003E\n\u003Cli\u003EUses contracts only\u003C/li\u003E\n\u003Cli\u003EIntegrates via policies and claims\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83C\uDFAF Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthorization is policy-driven\u003C/li\u003E\n\u003Cli\u003ERoles are permission containers\u003C/li\u003E\n\u003Cli\u003EPermissions support wildcard \u0026amp; prefix\u003C/li\u003E\n\u003Cli\u003EPolicies enforce rules\u003C/li\u003E\n\u003Cli\u003EFully extensible and replaceable\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "core-concepts", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/access-token-behavior.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/access-token-behavior.json index d90f47f3..cc83e67b 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/access-token-behavior.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/access-token-behavior.json @@ -1,7 +1,7 @@ { "Slug": "security/access-token-behavior", "Title": "Access Token Behavior", - "Html": "\n\u003Cp\u003EAccess tokens in UltimateAuth are intentionally \u003Cstrong\u003Eshort-lived and mode-aware\u003C/strong\u003E.\nThey are not the primary source of truth for authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-principle\u0022\u003E\uD83E\uDDE0 Core Principle\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Access tokens are \u003Cstrong\u003Etransport tokens\u003C/strong\u003E\n\uD83D\uDC49 Sessions are the \u003Cstrong\u003Esource of truth\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-types\u0022\u003E\uD83D\uDD11 Token Types\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports two access token types:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022opaque-tokens\u0022\u003E\uD83D\uDD39 Opaque Tokens\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ERandom, non-readable values\u003C/li\u003E\n\u003Cli\u003EStored and validated server-side\u003C/li\u003E\n\u003Cli\u003ETypically used with session-based auth\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022jwt-tokens\u0022\u003E\uD83D\uDD39 JWT Tokens\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESelf-contained tokens\u003C/li\u003E\n\u003Cli\u003EContain claims (user, tenant, session, etc.)\u003C/li\u003E\n\u003Cli\u003ESigned and verifiable without storage\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-dependent-behavior\u0022\u003E\u2699\uFE0F Mode-Dependent Behavior\u003C/h2\u003E\n\u003Cp\u003EAccess token behavior depends on auth mode:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque\u0022\u003E\uD83D\uDFE2 PureOpaque\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ENo JWT issued\u003C/li\u003E\n\u003Cli\u003ESession cookie is the primary mechanism\u003C/li\u003E\n\u003Cli\u003EAccess token may not exist externally\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Validation = session validation\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003E\uD83D\uDFE1 Hybrid\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EOpaque \u002B JWT together\u003C/li\u003E\n\u003Cli\u003ESession still authoritative\u003C/li\u003E\n\u003Cli\u003EJWT used for API access\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Validation = session \u002B token\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid\u0022\u003E\uD83D\uDFE0 SemiHybrid\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EJWT is primary access token\u003C/li\u003E\n\u003Cli\u003ESession still exists\u003C/li\u003E\n\u003Cli\u003ERefresh rotation enabled\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Balanced approach\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022purejwt\u0022\u003E\uD83D\uDD35 PureJwt\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EOnly JWT \u002B refresh tokens\u003C/li\u003E\n\u003Cli\u003ENo session state required\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Stateless mode\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifetime-strategy\u0022\u003E\u23F1 Lifetime Strategy\u003C/h2\u003E\n\u003Cp\u003EAccess tokens are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eshort-lived\u003C/li\u003E\n\u003Cli\u003Ereplaceable\u003C/li\u003E\n\u003Cli\u003Enot trusted long-term\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ETypical lifetime:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E5\u201315 minutes\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-interaction\u0022\u003E\uD83D\uDD04 Refresh Interaction\u003C/h2\u003E\n\u003Cp\u003EAccess tokens are never extended directly.\u003C/p\u003E\n\u003Cp\u003EInstead:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EAccess Token \u2192 expires \u2192 Refresh \u2192 new Access Token\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 This ensures:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eforward-only security\u003C/li\u003E\n\u003Cli\u003Eno silent extension\u003C/li\u003E\n\u003Cli\u003Ereplay window minimization\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022claims-model\u0022\u003E\uD83E\uDDFE Claims Model\u003C/h2\u003E\n\u003Cp\u003EJWT tokens may include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esub (user id)\u003C/li\u003E\n\u003Cli\u003Etenant\u003C/li\u003E\n\u003Cli\u003Esid (session id)\u003C/li\u003E\n\u003Cli\u003Ejti (token id)\u003C/li\u003E\n\u003Cli\u003Ecustom claims\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Claims are generated at issuance time\n\uD83D\uDC49 Not dynamically updated afterward\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-implication\u0022\u003E\u26A0\uFE0F Important Implication\u003C/h2\u003E\n\u003Cp\u003EIf something changes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Euser roles\u003C/li\u003E\n\u003Cli\u003Epermissions\u003C/li\u003E\n\u003Cli\u003Esecurity state\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 existing JWTs are NOT updated\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This is why:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Etokens are short-lived\u003C/li\u003E\n\u003Cli\u003Erefresh is required\u003C/li\u003E\n\u003Cli\u003Esession validation may still apply\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-boundaries\u0022\u003E\uD83D\uDEE1 Security Boundaries\u003C/h2\u003E\n\u003Cp\u003EAccess tokens are:\u003C/p\u003E\n\u003Cp\u003E\u274C not revocable individually (JWT)\n\u274C not long-term identity\u003C/p\u003E\n\u003Cp\u003E\u2714 tied to session or refresh flow\n\u2714 bounded by expiration\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-matters\u0022\u003E\uD83D\uDD25 Why This Matters\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth avoids a common mistake:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 treating JWT as the system of record\u003C/p\u003E\n\u003Cp\u003EInstead:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 JWT is a \u003Cstrong\u003Esnapshot\u003C/strong\u003E, not truth\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022design-tradeoff\u0022\u003E\u26A0\uFE0F Design Tradeoff\u003C/h2\u003E\n\u003Cp\u003EShort-lived tokens mean:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Emore refresh calls\u003C/li\u003E\n\u003Cli\u003Eslightly more backend interaction\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 But this enables:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esafer rotation\u003C/li\u003E\n\u003Cli\u003Ebetter revocation\u003C/li\u003E\n\u003Cli\u003Ereduced attack window\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Access tokens are \u003Cstrong\u003Etemporary access grants\u003C/strong\u003E\n\uD83D\uDC49 Not persistent identity\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EPolicy Pipeline Deep Dive\u003C/strong\u003E to understand how access decisions are enforced.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EAccess tokens in UltimateAuth are intentionally \u003Cstrong\u003Eshort-lived and mode-aware\u003C/strong\u003E.\nThey are not the primary source of truth for authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-principle\u0022\u003E\uD83E\uDDE0 Core Principle\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Access tokens are \u003Cstrong\u003Etransport tokens\u003C/strong\u003E\n\uD83D\uDC49 Sessions are the \u003Cstrong\u003Esource of truth\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-types\u0022\u003E\uD83D\uDD11 Token Types\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports two access token types:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022opaque-tokens\u0022\u003E\uD83D\uDD39 Opaque Tokens\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ERandom, non-readable values\u003C/li\u003E\n\u003Cli\u003EStored and validated server-side\u003C/li\u003E\n\u003Cli\u003ETypically used with session-based auth\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022jwt-tokens\u0022\u003E\uD83D\uDD39 JWT Tokens\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESelf-contained tokens\u003C/li\u003E\n\u003Cli\u003EContain claims (user, tenant, session, etc.)\u003C/li\u003E\n\u003Cli\u003ESigned and verifiable without storage\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-dependent-behavior\u0022\u003E\u2699\uFE0F Mode-Dependent Behavior\u003C/h2\u003E\n\u003Cp\u003EAccess token behavior depends on auth mode:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque\u0022\u003E\uD83D\uDFE2 PureOpaque\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ENo JWT issued\u003C/li\u003E\n\u003Cli\u003ESession cookie is the primary mechanism\u003C/li\u003E\n\u003Cli\u003EAccess token may not exist externally\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Validation = session validation\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003E\uD83D\uDFE1 Hybrid\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EOpaque \u002B JWT together\u003C/li\u003E\n\u003Cli\u003ESession still authoritative\u003C/li\u003E\n\u003Cli\u003EJWT used for API access\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Validation = session \u002B token\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid\u0022\u003E\uD83D\uDFE0 SemiHybrid\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EJWT is primary access token\u003C/li\u003E\n\u003Cli\u003ESession still exists\u003C/li\u003E\n\u003Cli\u003ERefresh rotation enabled\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Balanced approach\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022purejwt\u0022\u003E\uD83D\uDD35 PureJwt\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EOnly JWT \u002B refresh tokens\u003C/li\u003E\n\u003Cli\u003ENo session state required\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Stateless mode\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifetime-strategy\u0022\u003E\u23F1 Lifetime Strategy\u003C/h2\u003E\n\u003Cp\u003EAccess tokens are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eshort-lived\u003C/li\u003E\n\u003Cli\u003Ereplaceable\u003C/li\u003E\n\u003Cli\u003Enot trusted long-term\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ETypical lifetime:\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\u003E5\u201315 minutes\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-interaction\u0022\u003E\uD83D\uDD04 Refresh Interaction\u003C/h2\u003E\n\u003Cp\u003EAccess tokens are never extended directly.\u003C/p\u003E\n\u003Cp\u003EInstead:\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\u003EAccess Token \u2192 expires \u2192 Refresh \u2192 new Access Token\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This ensures:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eforward-only security\u003C/li\u003E\n\u003Cli\u003Eno silent extension\u003C/li\u003E\n\u003Cli\u003Ereplay window minimization\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022claims-model\u0022\u003E\uD83E\uDDFE Claims Model\u003C/h2\u003E\n\u003Cp\u003EJWT tokens may include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esub (user id)\u003C/li\u003E\n\u003Cli\u003Etenant\u003C/li\u003E\n\u003Cli\u003Esid (session id)\u003C/li\u003E\n\u003Cli\u003Ejti (token id)\u003C/li\u003E\n\u003Cli\u003Ecustom claims\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Claims are generated at issuance time\n\uD83D\uDC49 Not dynamically updated afterward\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-implication\u0022\u003E\u26A0\uFE0F Important Implication\u003C/h2\u003E\n\u003Cp\u003EIf something changes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Euser roles\u003C/li\u003E\n\u003Cli\u003Epermissions\u003C/li\u003E\n\u003Cli\u003Esecurity state\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 existing JWTs are NOT updated\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This is why:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Etokens are short-lived\u003C/li\u003E\n\u003Cli\u003Erefresh is required\u003C/li\u003E\n\u003Cli\u003Esession validation may still apply\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-boundaries\u0022\u003E\uD83D\uDEE1 Security Boundaries\u003C/h2\u003E\n\u003Cp\u003EAccess tokens are:\u003C/p\u003E\n\u003Cp\u003E\u274C not revocable individually (JWT)\n\u274C not long-term identity\u003C/p\u003E\n\u003Cp\u003E\u2714 tied to session or refresh flow\n\u2714 bounded by expiration\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-matters\u0022\u003E\uD83D\uDD25 Why This Matters\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth avoids a common mistake:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 treating JWT as the system of record\u003C/p\u003E\n\u003Cp\u003EInstead:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 JWT is a \u003Cstrong\u003Esnapshot\u003C/strong\u003E, not truth\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022design-tradeoff\u0022\u003E\u26A0\uFE0F Design Tradeoff\u003C/h2\u003E\n\u003Cp\u003EShort-lived tokens mean:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Emore refresh calls\u003C/li\u003E\n\u003Cli\u003Eslightly more backend interaction\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 But this enables:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esafer rotation\u003C/li\u003E\n\u003Cli\u003Ebetter revocation\u003C/li\u003E\n\u003Cli\u003Ereduced attack window\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Access tokens are \u003Cstrong\u003Etemporary access grants\u003C/strong\u003E\n\uD83D\uDC49 Not persistent identity\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EPolicy Pipeline Deep Dive\u003C/strong\u003E to understand how access decisions are enforced.\u003C/p\u003E\n", "Headings": [ { "Id": "core-principle", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/policy-pipeline.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/policy-pipeline.json index 85cf991d..db451763 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/policy-pipeline.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/policy-pipeline.json @@ -1,7 +1,7 @@ { "Slug": "security/policy-pipeline", "Title": "Policy Pipeline", - "Html": "\n\u003Cp\u003EUltimateAuth does not rely on simple role checks.\u003C/p\u003E\n\u003Cp\u003EAuthorization is executed through a \u003Cstrong\u003Emulti-stage policy pipeline\u003C/strong\u003E that evaluates:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Einvariants (always enforced rules)\u003C/li\u003E\n\u003Cli\u003Eglobal policies\u003C/li\u003E\n\u003Cli\u003Eruntime (action-based) policies\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-principle\u0022\u003E\uD83E\uDDE0 Core Principle\u003C/h2\u003E\n\u003Cp\u003E\uD83D\uDC49 Authorization is not a single check\u003Cbr /\u003E\n\uD83D\uDC49 It is a \u003Cstrong\u003Edecision pipeline\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022high-level-flow\u0022\u003E\uD83D\uDD01 High-Level Flow\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EAccessContext\n \u2193\nEnrichment (claims, permissions)\n \u2193\nInvariants\n \u2193\nGlobal Policies\n \u2193\nRuntime Policies (action-based)\n \u2193\nFinal Decision\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accesscontext\u0022\u003E\uD83E\uDDE9 AccessContext\u003C/h2\u003E\n\u003Cp\u003EEvery authorization decision is based on an \u003Ccode\u003EAccessContext\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIt contains:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eactor (who is making the request)\u003C/li\u003E\n\u003Cli\u003Etarget (resource / user)\u003C/li\u003E\n\u003Cli\u003Etenant\u003C/li\u003E\n\u003Cli\u003Eaction (string-based, e.g. \u0026quot;users.delete\u0026quot;)\u003C/li\u003E\n\u003Cli\u003Eattributes (claims, permissions, etc.)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Everything in the pipeline reads from this context\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-1-context-enrichment\u0022\u003E\uD83D\uDD04 Step 1: Context Enrichment\u003C/h2\u003E\n\u003Cp\u003EBefore policies run, context is enriched:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Epermissions are loaded\u003C/li\u003E\n\u003Cli\u003Ecompiled into \u003Ccode\u003ECompiledPermissionSet\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003Eattached to context\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This allows policies to run without hitting storage repeatedly\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-2-invariants\u0022\u003E\uD83D\uDEE1 Step 2: Invariants\u003C/h2\u003E\n\u003Cp\u003EInvariants are \u003Cstrong\u003Ealways enforced rules\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThey run first and cannot be bypassed.\u003C/p\u003E\n\u003Cp\u003EExamples:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Euser must be authenticated\u003C/li\u003E\n\u003Cli\u003Ecross-tenant access is denied\u003C/li\u003E\n\u003Cli\u003Erequest must be valid\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 If an invariant fails:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E\u2192 DENY immediately\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-3-global-policies\u0022\u003E\uD83C\uDF10 Step 3: Global Policies\u003C/h2\u003E\n\u003Cp\u003EGlobal policies apply to all requests but are conditional.\u003C/p\u003E\n\u003Cp\u003EExamples:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequireActiveUserPolicy\u003C/li\u003E\n\u003Cli\u003EDenyAdminSelfModificationPolicy\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EEach policy:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Edecides if it applies (\u003Ccode\u003EAppliesTo\u003C/code\u003E)\u003C/li\u003E\n\u003Cli\u003Eevaluates (\u003Ccode\u003EDecide\u003C/code\u003E)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Important:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u201CAllow\u201D means \u003Cem\u003Eno objection\u003C/em\u003E\u003C/li\u003E\n\u003Cli\u003Enot final approval\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-4-runtime-policies\u0022\u003E\uD83C\uDFAF Step 4: Runtime Policies\u003C/h2\u003E\n\u003Cp\u003ERuntime policies are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eaction-based\u003C/li\u003E\n\u003Cli\u003Edynamically resolved\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThey come from:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EAccessPolicyRegistry \u2192 CompiledAccessPolicySet\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EPolicies are selected by:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003Econtext.Action.StartsWith(prefix)\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EExample:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EAction: users.delete.admin\n\nMatches:\n- users.*\n- users.delete.*\n- users.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-5-decision-engine\u0022\u003E\u2696\uFE0F Step 5: Decision Engine\u003C/h2\u003E\n\u003Cp\u003EAll policies are evaluated by:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003EIAccessAuthority\u003C/code\u003E\u003C/p\u003E\n\u003Cp\u003EEvaluation order:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EInvariants\u003C/li\u003E\n\u003Cli\u003EGlobal policies\u003C/li\u003E\n\u003Cli\u003ERuntime policies\u003C/li\u003E\n\u003C/ol\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022decision-rules\u0022\u003EDecision Rules\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EFirst \u003Cstrong\u003Edeny\u003C/strong\u003E \u2192 stops execution\u003C/li\u003E\n\u003Cli\u003E\u201CAllow\u201D \u2192 continue\u003C/li\u003E\n\u003Cli\u003E\u201CRequiresReauthentication\u201D \u2192 tracked\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFinal result:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EAllow\nDeny(reason)\nReauthenticationRequired\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-integration\u0022\u003E\uD83D\uDD10 Permission Integration\u003C/h2\u003E\n\u003Cp\u003EPermissions are not directly checked in services.\u003C/p\u003E\n\u003Cp\u003EInstead:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Epermissions are compiled\u003C/li\u003E\n\u003Cli\u003Epolicies use them\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EExample policy:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EMustHavePermissionPolicy\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EChecks:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ECompiledPermissionSet.IsAllowed(action)\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 This decouples:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Epermission storage\u003C/li\u003E\n\u003Cli\u003Eauthorization logic\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-matters\u0022\u003E\uD83D\uDD25 Why This Matters\u003C/h2\u003E\n\u003Cp\u003EThis pipeline enables:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ecomposable security rules\u003C/li\u003E\n\u003Cli\u003Econsistent enforcement\u003C/li\u003E\n\u003Cli\u003Eseparation of concerns\u003C/li\u003E\n\u003Cli\u003Eextensibility\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ECompared to typical systems:\u003C/p\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EFeature\u003C/th\u003E\n\u003Cth\u003ETraditional\u003C/th\u003E\n\u003Cth\u003EUltimateAuth\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInline checks\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECentral pipeline\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPolicy composition\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAction-based rules\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022design-tradeoff\u0022\u003E\u26A0\uFE0F Design Tradeoff\u003C/h2\u003E\n\u003Cp\u003EThis model introduces:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Emore abstraction\u003C/li\u003E\n\u003Cli\u003Emore components\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 But gives:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Epredictability\u003C/li\u003E\n\u003Cli\u003Eauditability\u003C/li\u003E\n\u003Cli\u003Eflexibility\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Authorization is not \u201Cif (role == admin)\u201D\u003Cbr /\u003E\n\uD83D\uDC49 It is a \u003Cstrong\u003Epipeline of decisions\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\u27A1\uFE0F Summary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth authorization:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eis policy-driven\u003C/li\u003E\n\u003Cli\u003Eruns through a structured pipeline\u003C/li\u003E\n\u003Cli\u003Eseparates invariants, global rules, and action rules\u003C/li\u003E\n\u003Cli\u003Eproduces deterministic decisions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This makes it suitable for complex, multi-tenant, security-sensitive systems\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth does not rely on simple role checks.\u003C/p\u003E\n\u003Cp\u003EAuthorization is executed through a \u003Cstrong\u003Emulti-stage policy pipeline\u003C/strong\u003E that evaluates:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Einvariants (always enforced rules)\u003C/li\u003E\n\u003Cli\u003Eglobal policies\u003C/li\u003E\n\u003Cli\u003Eruntime (action-based) policies\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-principle\u0022\u003E\uD83E\uDDE0 Core Principle\u003C/h2\u003E\n\u003Cp\u003E\uD83D\uDC49 Authorization is not a single check\u003Cbr\u003E\n\uD83D\uDC49 It is a \u003Cstrong\u003Edecision pipeline\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022high-level-flow\u0022\u003E\uD83D\uDD01 High-Level Flow\u003C/h2\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\u003EAccessContext\n \u2193\nEnrichment (claims, permissions)\n \u2193\nInvariants\n \u2193\nGlobal Policies\n \u2193\nRuntime Policies (action-based)\n \u2193\nFinal Decision\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accesscontext\u0022\u003E\uD83E\uDDE9 AccessContext\u003C/h2\u003E\n\u003Cp\u003EEvery authorization decision is based on an \u003Ccode\u003EAccessContext\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIt contains:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eactor (who is making the request)\u003C/li\u003E\n\u003Cli\u003Etarget (resource / user)\u003C/li\u003E\n\u003Cli\u003Etenant\u003C/li\u003E\n\u003Cli\u003Eaction (string-based, e.g. \u0026quot;users.delete\u0026quot;)\u003C/li\u003E\n\u003Cli\u003Eattributes (claims, permissions, etc.)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Everything in the pipeline reads from this context\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-1-context-enrichment\u0022\u003E\uD83D\uDD04 Step 1: Context Enrichment\u003C/h2\u003E\n\u003Cp\u003EBefore policies run, context is enriched:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Epermissions are loaded\u003C/li\u003E\n\u003Cli\u003Ecompiled into \u003Ccode\u003ECompiledPermissionSet\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003Eattached to context\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This allows policies to run without hitting storage repeatedly\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-2-invariants\u0022\u003E\uD83D\uDEE1 Step 2: Invariants\u003C/h2\u003E\n\u003Cp\u003EInvariants are \u003Cstrong\u003Ealways enforced rules\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThey run first and cannot be bypassed.\u003C/p\u003E\n\u003Cp\u003EExamples:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Euser must be authenticated\u003C/li\u003E\n\u003Cli\u003Ecross-tenant access is denied\u003C/li\u003E\n\u003Cli\u003Erequest must be valid\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 If an invariant fails:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E\u2192 DENY immediately\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-3-global-policies\u0022\u003E\uD83C\uDF10 Step 3: Global Policies\u003C/h2\u003E\n\u003Cp\u003EGlobal policies apply to all requests but are conditional.\u003C/p\u003E\n\u003Cp\u003EExamples:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequireActiveUserPolicy\u003C/li\u003E\n\u003Cli\u003EDenyAdminSelfModificationPolicy\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EEach policy:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Edecides if it applies (\u003Ccode\u003EAppliesTo\u003C/code\u003E)\u003C/li\u003E\n\u003Cli\u003Eevaluates (\u003Ccode\u003EDecide\u003C/code\u003E)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Important:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u201CAllow\u201D means \u003Cem\u003Eno objection\u003C/em\u003E\u003C/li\u003E\n\u003Cli\u003Enot final approval\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-4-runtime-policies\u0022\u003E\uD83C\uDFAF Step 4: Runtime Policies\u003C/h2\u003E\n\u003Cp\u003ERuntime policies are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eaction-based\u003C/li\u003E\n\u003Cli\u003Edynamically resolved\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThey come 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\u003EAccessPolicyRegistry \u2192 CompiledAccessPolicySet\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EPolicies are selected by:\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\u003Econtext.Action.StartsWith(prefix)\n\u003C/code\u003E\u003C/pre\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\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\u003EAction: users.delete.admin\n\nMatches:\n- users.*\n- users.delete.*\n- users.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-5-decision-engine\u0022\u003E\u2696\uFE0F Step 5: Decision Engine\u003C/h2\u003E\n\u003Cp\u003EAll policies are evaluated by:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003EIAccessAuthority\u003C/code\u003E\u003C/p\u003E\n\u003Cp\u003EEvaluation order:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EInvariants\u003C/li\u003E\n\u003Cli\u003EGlobal policies\u003C/li\u003E\n\u003Cli\u003ERuntime policies\u003C/li\u003E\n\u003C/ol\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022decision-rules\u0022\u003EDecision Rules\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EFirst \u003Cstrong\u003Edeny\u003C/strong\u003E \u2192 stops execution\u003C/li\u003E\n\u003Cli\u003E\u201CAllow\u201D \u2192 continue\u003C/li\u003E\n\u003Cli\u003E\u201CRequiresReauthentication\u201D \u2192 tracked\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFinal result:\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\u003EAllow\nDeny(reason)\nReauthenticationRequired\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-integration\u0022\u003E\uD83D\uDD10 Permission Integration\u003C/h2\u003E\n\u003Cp\u003EPermissions are not directly checked in services.\u003C/p\u003E\n\u003Cp\u003EInstead:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Epermissions are compiled\u003C/li\u003E\n\u003Cli\u003Epolicies use them\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EExample policy:\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\u003EMustHavePermissionPolicy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EChecks:\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\u003ECompiledPermissionSet.IsAllowed(action)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This decouples:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Epermission storage\u003C/li\u003E\n\u003Cli\u003Eauthorization logic\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-matters\u0022\u003E\uD83D\uDD25 Why This Matters\u003C/h2\u003E\n\u003Cp\u003EThis pipeline enables:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ecomposable security rules\u003C/li\u003E\n\u003Cli\u003Econsistent enforcement\u003C/li\u003E\n\u003Cli\u003Eseparation of concerns\u003C/li\u003E\n\u003Cli\u003Eextensibility\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ECompared to typical systems:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EFeature\u003C/th\u003E\n\u003Cth\u003ETraditional\u003C/th\u003E\n\u003Cth\u003EUltimateAuth\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInline checks\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECentral pipeline\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPolicy composition\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAction-based rules\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\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=\u0022design-tradeoff\u0022\u003E\u26A0\uFE0F Design Tradeoff\u003C/h2\u003E\n\u003Cp\u003EThis model introduces:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Emore abstraction\u003C/li\u003E\n\u003Cli\u003Emore components\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 But gives:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Epredictability\u003C/li\u003E\n\u003Cli\u003Eauditability\u003C/li\u003E\n\u003Cli\u003Eflexibility\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Authorization is not \u201Cif (role == admin)\u201D\u003Cbr\u003E\n\uD83D\uDC49 It is a \u003Cstrong\u003Epipeline of decisions\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\u27A1\uFE0F Summary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth authorization:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eis policy-driven\u003C/li\u003E\n\u003Cli\u003Eruns through a structured pipeline\u003C/li\u003E\n\u003Cli\u003Eseparates invariants, global rules, and action rules\u003C/li\u003E\n\u003Cli\u003Eproduces deterministic decisions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This makes it suitable for complex, multi-tenant, security-sensitive systems\u003C/p\u003E\n", "Headings": [ { "Id": "core-principle", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/refresh-rotation.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/refresh-rotation.json index dd21f370..2782ef36 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/refresh-rotation.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/refresh-rotation.json @@ -1,7 +1,7 @@ { "Slug": "security/refresh-rotation", "Title": "Refresh Rotation", - "Html": "\n\u003Cp\u003ERefresh tokens in UltimateAuth are not simple long-lived tokens.\u003C/p\u003E\n\u003Cp\u003EThey are part of a \u003Cstrong\u003Erotation-based security system\u003C/strong\u003E designed to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eprevent token replay\u003C/li\u003E\n\u003Cli\u003Edetect token theft\u003C/li\u003E\n\u003Cli\u003Eenforce forward-only session progression\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-rotation\u0022\u003E\uD83E\uDDE0 Why Rotation?\u003C/h2\u003E\n\u003Cp\u003EIn traditional systems:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Erefresh tokens are long-lived\u003C/li\u003E\n\u003Cli\u003Ethey can be reused multiple times\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 If stolen, an attacker can:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esilently keep refreshing access tokens\u003C/li\u003E\n\u003Cli\u003Estay undetected\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth solves this with:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Cstrong\u003Esingle-use refresh tokens (rotation)\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-model\u0022\u003E\uD83D\uDD01 Rotation Model\u003C/h2\u003E\n\u003Cp\u003EEach refresh token is:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eused exactly once\u003C/li\u003E\n\u003Cli\u003Ereplaced with a new token\u003C/li\u003E\n\u003Cli\u003Elinked to a chain\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EToken A \u2192 Token B \u2192 Token C \u2192 ...\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EWhen a refresh happens:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EToken A is validated\u003C/li\u003E\n\u003Cli\u003EToken A is \u003Cstrong\u003Erevoked\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003EToken B is issued\u003C/li\u003E\n\u003Cli\u003EToken A is marked as replaced by B\u003C/li\u003E\n\u003C/ol\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-state\u0022\u003E\uD83D\uDD10 Token State\u003C/h2\u003E\n\u003Cp\u003EA refresh token can be:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EActive \u2192 valid and usable\u003C/li\u003E\n\u003Cli\u003ERevoked \u2192 already used or manually revoked\u003C/li\u003E\n\u003Cli\u003EExpired \u2192 lifetime exceeded\u003C/li\u003E\n\u003Cli\u003EReplaced \u2192 already rotated\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Only \u003Cstrong\u003Eactive tokens\u003C/strong\u003E are valid\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022reuse-detection\u0022\u003E\uD83D\uDEA8 Reuse Detection\u003C/h2\u003E\n\u003Cp\u003EThis is the most critical security feature.\u003C/p\u003E\n\u003Cp\u003EIf a refresh token is used \u003Cstrong\u003Eafter it has already been rotated\u003C/strong\u003E, it means:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 The token was reused\u003Cbr /\u003E\n\uD83D\uDC49 Likely stolen\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-happens\u0022\u003EWhat happens?\u003C/h3\u003E\n\u003Cp\u003EWhen reuse is detected:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ethe system identifies the session chain\u003C/li\u003E\n\u003Cli\u003Ethe entire chain can be revoked\u003C/li\u003E\n\u003Cli\u003Eall related sessions become invalid\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This immediately cuts off both:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eattacker\u003C/li\u003E\n\u003Cli\u003Elegitimate user (forcing reauthentication)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-awareness\u0022\u003E\uD83D\uDD17 Chain Awareness\u003C/h2\u003E\n\u003Cp\u003ERefresh tokens belong to a \u003Cstrong\u003Esession chain\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThis enables:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Etracking rotation history\u003C/li\u003E\n\u003Cli\u003Edetecting anomalies\u003C/li\u003E\n\u003Cli\u003Eapplying revocation at the correct scope\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EWithout chains:\u003C/p\u003E\n\u003Cp\u003E\u274C You cannot safely detect reuse\u003Cbr /\u003E\n\u274C You cannot know which tokens belong together\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-flow\u0022\u003E\uD83D\uDD04 Rotation Flow\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EClient \u2192 Refresh(Token A)\n \u2192 Validate A\n \u2192 Revoke A\n \u2192 Issue B\n \u2192 Return new tokens\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022invalid-scenarios\u0022\u003E\u26A0\uFE0F Invalid Scenarios\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022expired-token\u0022\u003E1. Expired Token\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EToken expired \u2192 reject\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoked-token\u0022\u003E2. Revoked Token\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EToken already used \u2192 reuse detected\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-mismatch\u0022\u003E3. Session Mismatch\u003C/h3\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EToken does not belong to expected session \u2192 reject\n\u003C/code\u003E\u003C/pre\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-guarantees\u0022\u003E\uD83E\uDDE0 Security Guarantees\u003C/h2\u003E\n\u003Cp\u003ERotation ensures:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Erefresh tokens are forward-only\u003C/li\u003E\n\u003Cli\u003Eold tokens cannot be reused safely\u003C/li\u003E\n\u003Cli\u003Estolen tokens are detectable\u003C/li\u003E\n\u003Cli\u003Ecompromise triggers containment\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-matters\u0022\u003E\uD83D\uDD25 Why This Matters\u003C/h2\u003E\n\u003Cp\u003ECompared to traditional refresh tokens:\u003C/p\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EFeature\u003C/th\u003E\n\u003Cth\u003ETraditional\u003C/th\u003E\n\u003Cth\u003EUltimateAuth\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReusable tokens\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReuse detection\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EChain awareness\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAutomatic containment\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022design-tradeoff\u0022\u003E\u26A0\uFE0F Design Tradeoff\u003C/h2\u003E\n\u003Cp\u003ERotation requires:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Etoken storage\u003C/li\u003E\n\u003Cli\u003Estate tracking\u003C/li\u003E\n\u003Cli\u003Eadditional validation logic\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 UltimateAuth chooses security over simplicity.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 A refresh token is not a reusable key\u003Cbr /\u003E\n\uD83D\uDC49 It is a \u003Cstrong\u003Eone-time step in a chain\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EAccess Token Behavior\u003C/strong\u003E to understand how short-lived tokens interact with rotation.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003ERefresh tokens in UltimateAuth are not simple long-lived tokens.\u003C/p\u003E\n\u003Cp\u003EThey are part of a \u003Cstrong\u003Erotation-based security system\u003C/strong\u003E designed to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eprevent token replay\u003C/li\u003E\n\u003Cli\u003Edetect token theft\u003C/li\u003E\n\u003Cli\u003Eenforce forward-only session progression\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-rotation\u0022\u003E\uD83E\uDDE0 Why Rotation?\u003C/h2\u003E\n\u003Cp\u003EIn traditional systems:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Erefresh tokens are long-lived\u003C/li\u003E\n\u003Cli\u003Ethey can be reused multiple times\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 If stolen, an attacker can:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esilently keep refreshing access tokens\u003C/li\u003E\n\u003Cli\u003Estay undetected\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth solves this with:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Cstrong\u003Esingle-use refresh tokens (rotation)\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-model\u0022\u003E\uD83D\uDD01 Rotation Model\u003C/h2\u003E\n\u003Cp\u003EEach refresh token is:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eused exactly once\u003C/li\u003E\n\u003Cli\u003Ereplaced with a new token\u003C/li\u003E\n\u003Cli\u003Elinked to a chain\u003C/li\u003E\n\u003C/ul\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\u003EToken A \u2192 Token B \u2192 Token C \u2192 ...\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen a refresh happens:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EToken A is validated\u003C/li\u003E\n\u003Cli\u003EToken A is \u003Cstrong\u003Erevoked\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003EToken B is issued\u003C/li\u003E\n\u003Cli\u003EToken A is marked as replaced by B\u003C/li\u003E\n\u003C/ol\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-state\u0022\u003E\uD83D\uDD10 Token State\u003C/h2\u003E\n\u003Cp\u003EA refresh token can be:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EActive \u2192 valid and usable\u003C/li\u003E\n\u003Cli\u003ERevoked \u2192 already used or manually revoked\u003C/li\u003E\n\u003Cli\u003EExpired \u2192 lifetime exceeded\u003C/li\u003E\n\u003Cli\u003EReplaced \u2192 already rotated\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Only \u003Cstrong\u003Eactive tokens\u003C/strong\u003E are valid\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022reuse-detection\u0022\u003E\uD83D\uDEA8 Reuse Detection\u003C/h2\u003E\n\u003Cp\u003EThis is the most critical security feature.\u003C/p\u003E\n\u003Cp\u003EIf a refresh token is used \u003Cstrong\u003Eafter it has already been rotated\u003C/strong\u003E, it means:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 The token was reused\u003Cbr\u003E\n\uD83D\uDC49 Likely stolen\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-happens\u0022\u003EWhat happens?\u003C/h3\u003E\n\u003Cp\u003EWhen reuse is detected:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ethe system identifies the session chain\u003C/li\u003E\n\u003Cli\u003Ethe entire chain can be revoked\u003C/li\u003E\n\u003Cli\u003Eall related sessions become invalid\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This immediately cuts off both:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eattacker\u003C/li\u003E\n\u003Cli\u003Elegitimate user (forcing reauthentication)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-awareness\u0022\u003E\uD83D\uDD17 Chain Awareness\u003C/h2\u003E\n\u003Cp\u003ERefresh tokens belong to a \u003Cstrong\u003Esession chain\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThis enables:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Etracking rotation history\u003C/li\u003E\n\u003Cli\u003Edetecting anomalies\u003C/li\u003E\n\u003Cli\u003Eapplying revocation at the correct scope\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EWithout chains:\u003C/p\u003E\n\u003Cp\u003E\u274C You cannot safely detect reuse\u003Cbr\u003E\n\u274C You cannot know which tokens belong together\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-flow\u0022\u003E\uD83D\uDD04 Rotation Flow\u003C/h2\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 \u2192 Refresh(Token A)\n \u2192 Validate A\n \u2192 Revoke A\n \u2192 Issue B\n \u2192 Return new tokens\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022invalid-scenarios\u0022\u003E\u26A0\uFE0F Invalid Scenarios\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022expired-token\u0022\u003E1. Expired Token\u003C/h3\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\u003EToken expired \u2192 reject\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoked-token\u0022\u003E2. Revoked Token\u003C/h3\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\u003EToken already used \u2192 reuse detected\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-mismatch\u0022\u003E3. Session Mismatch\u003C/h3\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\u003EToken does not belong to expected session \u2192 reject\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-guarantees\u0022\u003E\uD83E\uDDE0 Security Guarantees\u003C/h2\u003E\n\u003Cp\u003ERotation ensures:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Erefresh tokens are forward-only\u003C/li\u003E\n\u003Cli\u003Eold tokens cannot be reused safely\u003C/li\u003E\n\u003Cli\u003Estolen tokens are detectable\u003C/li\u003E\n\u003Cli\u003Ecompromise triggers containment\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-matters\u0022\u003E\uD83D\uDD25 Why This Matters\u003C/h2\u003E\n\u003Cp\u003ECompared to traditional refresh tokens:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EFeature\u003C/th\u003E\n\u003Cth\u003ETraditional\u003C/th\u003E\n\u003Cth\u003EUltimateAuth\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReusable tokens\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReuse detection\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EChain awareness\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAutomatic containment\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\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=\u0022design-tradeoff\u0022\u003E\u26A0\uFE0F Design Tradeoff\u003C/h2\u003E\n\u003Cp\u003ERotation requires:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Etoken storage\u003C/li\u003E\n\u003Cli\u003Estate tracking\u003C/li\u003E\n\u003Cli\u003Eadditional validation logic\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 UltimateAuth chooses security over simplicity.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 A refresh token is not a reusable key\u003Cbr\u003E\n\uD83D\uDC49 It is a \u003Cstrong\u003Eone-time step in a chain\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EAccess Token Behavior\u003C/strong\u003E to understand how short-lived tokens interact with rotation.\u003C/p\u003E\n", "Headings": [ { "Id": "why-rotation", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security-model.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security-model.json index abb996ca..9c7c1f04 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security-model.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security-model.json @@ -1,7 +1,7 @@ { "Slug": "security/session-security-model", "Title": "Session Security Model", - "Html": "\n\u003Cp\u003EUltimateAuth is built around a \u003Cstrong\u003Ehierarchical session model\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThis is the foundation of its security design.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-a-session-model\u0022\u003E\uD83E\uDDE0 Why a Session Model?\u003C/h2\u003E\n\u003Cp\u003EMany systems treat authentication as one of these:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ea cookie\u003C/li\u003E\n\u003Cli\u003Ea bearer token\u003C/li\u003E\n\u003Cli\u003Ea login flag\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThat works for simple apps.\u003C/p\u003E\n\u003Cp\u003EIt breaks down when you need:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eper-device isolation\u003C/li\u003E\n\u003Cli\u003Etargeted revocation\u003C/li\u003E\n\u003Cli\u003Esecurity state propagation\u003C/li\u003E\n\u003Cli\u003Ereliable reauthentication boundaries\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth solves this by modeling authentication as:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ERoot \u2192 Chain \u2192 Session\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-three-layers\u0022\u003E\uD83E\uDDE9 The Three Layers\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root\u0022\u003E\uD83D\uDD39 Root\u003C/h3\u003E\n\u003Cp\u003EA \u003Cstrong\u003ERoot\u003C/strong\u003E represents the user-level authentication authority.\u003C/p\u003E\n\u003Cp\u003EIt is:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Etenant-bound\u003C/li\u003E\n\u003Cli\u003Euser-bound\u003C/li\u003E\n\u003Cli\u003Elong-lived\u003C/li\u003E\n\u003Cli\u003Esecurity-versioned\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe root is the highest-level trust anchor for authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain\u0022\u003E\uD83D\uDCF1 Chain\u003C/h3\u003E\n\u003Cp\u003EA \u003Cstrong\u003EChain\u003C/strong\u003E represents a device-level authentication boundary.\u003C/p\u003E\n\u003Cp\u003EIt groups sessions that belong to the same device or client context.\u003C/p\u003E\n\u003Cp\u003EA chain is where UltimateAuth models:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Edevice continuity\u003C/li\u003E\n\u003Cli\u003Etouch activity\u003C/li\u003E\n\u003Cli\u003Erotation tracking\u003C/li\u003E\n\u003Cli\u003Edevice-level revoke\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session\u0022\u003E\uD83D\uDD11 Session\u003C/h3\u003E\n\u003Cp\u003EA \u003Cstrong\u003ESession\u003C/strong\u003E is a single authentication instance.\u003C/p\u003E\n\u003Cp\u003EIt is the most granular identity proof in the system.\u003C/p\u003E\n\u003Cp\u003EA session contains:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ecreation time\u003C/li\u003E\n\u003Cli\u003Eexpiration time\u003C/li\u003E\n\u003Cli\u003Erevocation state\u003C/li\u003E\n\u003Cli\u003Esecurity version snapshot\u003C/li\u003E\n\u003Cli\u003Echain relationship\u003C/li\u003E\n\u003Cli\u003Edevice snapshot\u003C/li\u003E\n\u003Cli\u003Eclaims snapshot\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022relationship-model\u0022\u003E\uD83D\uDD17 Relationship Model\u003C/h2\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EUser\n \u2514\u2500\u2500 Root\n \u2514\u2500\u2500 Chain (device)\n \u2514\u2500\u2500 Session (login instance)\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003E\uD83D\uDC49 Root answers: \u003Cstrong\u003Ewhat is the current security authority for this user?\u003C/strong\u003E\u003Cbr /\u003E\n\uD83D\uDC49 Chain answers: \u003Cstrong\u003Ewhich device context is this?\u003C/strong\u003E\u003Cbr /\u003E\n\uD83D\uDC49 Session answers: \u003Cstrong\u003Ewhich authentication instance is being used?\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-versioning\u0022\u003E\uD83D\uDEE1 Security Versioning\u003C/h2\u003E\n\u003Cp\u003EOne of the most important protections in UltimateAuth is \u003Cstrong\u003Esecurity versioning\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EA root maintains a security version.\u003C/p\u003E\n\u003Cp\u003EEach session stores the security version that existed at creation time.\u003C/p\u003E\n\u003Cp\u003EValidation compares them.\u003C/p\u003E\n\u003Cp\u003EIf they no longer match:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003ESession \u2192 invalid\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cp\u003EThis is how UltimateAuth can invalidate existing sessions after events such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Epassword change\u003C/li\u003E\n\u003Cli\u003Ecredential reset\u003C/li\u003E\n\u003Cli\u003Eaccount recovery\u003C/li\u003E\n\u003Cli\u003Eadministrative security action\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022validation-model\u0022\u003E\uD83D\uDD0D Validation Model\u003C/h2\u003E\n\u003Cp\u003ESession validation is not a single check.\u003C/p\u003E\n\u003Cp\u003EIt is a layered verification process.\u003C/p\u003E\n\u003Cp\u003EA session is considered valid only if all of these still hold:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ethe session exists\u003C/li\u003E\n\u003Cli\u003Ethe session is active\u003C/li\u003E\n\u003Cli\u003Ethe chain exists\u003C/li\u003E\n\u003Cli\u003Ethe chain is active\u003C/li\u003E\n\u003Cli\u003Ethe chain belongs to the expected tenant\u003C/li\u003E\n\u003Cli\u003Ethe chain matches the session\u003C/li\u003E\n\u003Cli\u003Ethe root exists\u003C/li\u003E\n\u003Cli\u003Ethe root is not revoked\u003C/li\u003E\n\u003Cli\u003Ethe root matches the chain\u003C/li\u003E\n\u003Cli\u003Ethe root security version matches the session snapshot\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-awareness\u0022\u003E\uD83D\uDCF1 Device Awareness\u003C/h2\u003E\n\u003Cp\u003EChains provide device-level isolation.\u003C/p\u003E\n\u003Cp\u003EWhen a device identifier is available, validation can compare:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Edevice stored on chain\u003C/li\u003E\n\u003Cli\u003Edevice presented by request\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIf they do not match, UltimateAuth can reject the request depending on configuration.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This makes device-bound security enforceable without turning every authentication flow into a custom implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022expiration-and-activity\u0022\u003E\u23F1 Expiration and Activity\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esession expiration\u003C/li\u003E\n\u003Cli\u003Echain activity\u003C/li\u003E\n\u003Cli\u003Eroot security state\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis is important.\u003C/p\u003E\n\u003Cp\u003EA session may expire because of time.\u003Cbr /\u003E\nA chain may become inactive because of idle timeout.\u003Cbr /\u003E\nA root may invalidate everything because security state changed.\u003C/p\u003E\n\u003Cp\u003EThese are different failure modes with different meanings.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-boundaries\u0022\u003E\uD83D\uDEAA Revocation Boundaries\u003C/h2\u003E\n\u003Cp\u003ERevocation is also hierarchical.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-revoke\u0022\u003ESession revoke\u003C/h3\u003E\n\u003Cp\u003EInvalidates one authentication instance.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-revoke\u0022\u003EChain revoke\u003C/h3\u003E\n\u003Cp\u003EInvalidates all sessions for one device.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-revoke\u0022\u003ERoot revoke\u003C/h3\u003E\n\u003Cp\u003EInvalidates all chains and sessions for the user.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This gives UltimateAuth targeted containment.\u003C/p\u003E\n\u003Cp\u003EInstead of \u201Clog out everywhere or nowhere,\u201D\u003Cbr /\u003E\nyou can revoke exactly the right scope.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-matters\u0022\u003E\uD83D\uDD25 Why This Matters\u003C/h2\u003E\n\u003Cp\u003EThis model gives you controls that flat token systems usually do not provide:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Erevoke one device without affecting others\u003C/li\u003E\n\u003Cli\u003Einvalidate sessions after security changes\u003C/li\u003E\n\u003Cli\u003Ereason about device trust explicitly\u003C/li\u003E\n\u003Cli\u003Eseparate authentication lifetime from token lifetime\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022design-tradeoff\u0022\u003E\u26A0\uFE0F Design Tradeoff\u003C/h2\u003E\n\u003Cp\u003EThis model is more sophisticated than plain JWT-only authentication.\u003C/p\u003E\n\u003Cp\u003EThat is intentional.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth chooses:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eexplicit state\u003C/li\u003E\n\u003Cli\u003Erevocability\u003C/li\u003E\n\u003Cli\u003Etraceable security decisions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003Eover:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eminimal infrastructure\u003C/li\u003E\n\u003Cli\u003Epurely stateless assumptions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Authentication in UltimateAuth is not \u201Ca token\u201D\u003Cbr /\u003E\n\uD83D\uDC49 It is a \u003Cstrong\u003Esecurity hierarchy with revocation boundaries\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003ERefresh Token Rotation\u003C/strong\u003E to understand how continuation security works after login.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth is built around a \u003Cstrong\u003Ehierarchical session model\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThis is the foundation of its security design.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-a-session-model\u0022\u003E\uD83E\uDDE0 Why a Session Model?\u003C/h2\u003E\n\u003Cp\u003EMany systems treat authentication as one of these:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ea cookie\u003C/li\u003E\n\u003Cli\u003Ea bearer token\u003C/li\u003E\n\u003Cli\u003Ea login flag\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThat works for simple apps.\u003C/p\u003E\n\u003Cp\u003EIt breaks down when you need:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eper-device isolation\u003C/li\u003E\n\u003Cli\u003Etargeted revocation\u003C/li\u003E\n\u003Cli\u003Esecurity state propagation\u003C/li\u003E\n\u003Cli\u003Ereliable reauthentication boundaries\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth solves this by modeling authentication 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\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\u003ERoot \u2192 Chain \u2192 Session\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-three-layers\u0022\u003E\uD83E\uDDE9 The Three Layers\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root\u0022\u003E\uD83D\uDD39 Root\u003C/h3\u003E\n\u003Cp\u003EA \u003Cstrong\u003ERoot\u003C/strong\u003E represents the user-level authentication authority.\u003C/p\u003E\n\u003Cp\u003EIt is:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Etenant-bound\u003C/li\u003E\n\u003Cli\u003Euser-bound\u003C/li\u003E\n\u003Cli\u003Elong-lived\u003C/li\u003E\n\u003Cli\u003Esecurity-versioned\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe root is the highest-level trust anchor for authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain\u0022\u003E\uD83D\uDCF1 Chain\u003C/h3\u003E\n\u003Cp\u003EA \u003Cstrong\u003EChain\u003C/strong\u003E represents a device-level authentication boundary.\u003C/p\u003E\n\u003Cp\u003EIt groups sessions that belong to the same device or client context.\u003C/p\u003E\n\u003Cp\u003EA chain is where UltimateAuth models:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Edevice continuity\u003C/li\u003E\n\u003Cli\u003Etouch activity\u003C/li\u003E\n\u003Cli\u003Erotation tracking\u003C/li\u003E\n\u003Cli\u003Edevice-level revoke\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session\u0022\u003E\uD83D\uDD11 Session\u003C/h3\u003E\n\u003Cp\u003EA \u003Cstrong\u003ESession\u003C/strong\u003E is a single authentication instance.\u003C/p\u003E\n\u003Cp\u003EIt is the most granular identity proof in the system.\u003C/p\u003E\n\u003Cp\u003EA session contains:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ecreation time\u003C/li\u003E\n\u003Cli\u003Eexpiration time\u003C/li\u003E\n\u003Cli\u003Erevocation state\u003C/li\u003E\n\u003Cli\u003Esecurity version snapshot\u003C/li\u003E\n\u003Cli\u003Echain relationship\u003C/li\u003E\n\u003Cli\u003Edevice snapshot\u003C/li\u003E\n\u003Cli\u003Eclaims snapshot\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022relationship-model\u0022\u003E\uD83D\uDD17 Relationship Model\u003C/h2\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)\n \u2514\u2500\u2500 Session (login instance)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Root answers: \u003Cstrong\u003Ewhat is the current security authority for this user?\u003C/strong\u003E\u003Cbr\u003E\n\uD83D\uDC49 Chain answers: \u003Cstrong\u003Ewhich device context is this?\u003C/strong\u003E\u003Cbr\u003E\n\uD83D\uDC49 Session answers: \u003Cstrong\u003Ewhich authentication instance is being used?\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-versioning\u0022\u003E\uD83D\uDEE1 Security Versioning\u003C/h2\u003E\n\u003Cp\u003EOne of the most important protections in UltimateAuth is \u003Cstrong\u003Esecurity versioning\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EA root maintains a security version.\u003C/p\u003E\n\u003Cp\u003EEach session stores the security version that existed at creation time.\u003C/p\u003E\n\u003Cp\u003EValidation compares them.\u003C/p\u003E\n\u003Cp\u003EIf they no longer match:\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 \u2192 invalid\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is how UltimateAuth can invalidate existing sessions after events such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Epassword change\u003C/li\u003E\n\u003Cli\u003Ecredential reset\u003C/li\u003E\n\u003Cli\u003Eaccount recovery\u003C/li\u003E\n\u003Cli\u003Eadministrative security action\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022validation-model\u0022\u003E\uD83D\uDD0D Validation Model\u003C/h2\u003E\n\u003Cp\u003ESession validation is not a single check.\u003C/p\u003E\n\u003Cp\u003EIt is a layered verification process.\u003C/p\u003E\n\u003Cp\u003EA session is considered valid only if all of these still hold:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ethe session exists\u003C/li\u003E\n\u003Cli\u003Ethe session is active\u003C/li\u003E\n\u003Cli\u003Ethe chain exists\u003C/li\u003E\n\u003Cli\u003Ethe chain is active\u003C/li\u003E\n\u003Cli\u003Ethe chain belongs to the expected tenant\u003C/li\u003E\n\u003Cli\u003Ethe chain matches the session\u003C/li\u003E\n\u003Cli\u003Ethe root exists\u003C/li\u003E\n\u003Cli\u003Ethe root is not revoked\u003C/li\u003E\n\u003Cli\u003Ethe root matches the chain\u003C/li\u003E\n\u003Cli\u003Ethe root security version matches the session snapshot\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-awareness\u0022\u003E\uD83D\uDCF1 Device Awareness\u003C/h2\u003E\n\u003Cp\u003EChains provide device-level isolation.\u003C/p\u003E\n\u003Cp\u003EWhen a device identifier is available, validation can compare:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Edevice stored on chain\u003C/li\u003E\n\u003Cli\u003Edevice presented by request\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIf they do not match, UltimateAuth can reject the request depending on configuration.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This makes device-bound security enforceable without turning every authentication flow into a custom implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022expiration-and-activity\u0022\u003E\u23F1 Expiration and Activity\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esession expiration\u003C/li\u003E\n\u003Cli\u003Echain activity\u003C/li\u003E\n\u003Cli\u003Eroot security state\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis is important.\u003C/p\u003E\n\u003Cp\u003EA session may expire because of time.\u003Cbr\u003E\nA chain may become inactive because of idle timeout.\u003Cbr\u003E\nA root may invalidate everything because security state changed.\u003C/p\u003E\n\u003Cp\u003EThese are different failure modes with different meanings.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-boundaries\u0022\u003E\uD83D\uDEAA Revocation Boundaries\u003C/h2\u003E\n\u003Cp\u003ERevocation is also hierarchical.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-revoke\u0022\u003ESession revoke\u003C/h3\u003E\n\u003Cp\u003EInvalidates one authentication instance.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-revoke\u0022\u003EChain revoke\u003C/h3\u003E\n\u003Cp\u003EInvalidates all sessions for one device.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-revoke\u0022\u003ERoot revoke\u003C/h3\u003E\n\u003Cp\u003EInvalidates all chains and sessions for the user.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This gives UltimateAuth targeted containment.\u003C/p\u003E\n\u003Cp\u003EInstead of \u201Clog out everywhere or nowhere,\u201D\u003Cbr\u003E\nyou can revoke exactly the right scope.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-matters\u0022\u003E\uD83D\uDD25 Why This Matters\u003C/h2\u003E\n\u003Cp\u003EThis model gives you controls that flat token systems usually do not provide:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Erevoke one device without affecting others\u003C/li\u003E\n\u003Cli\u003Einvalidate sessions after security changes\u003C/li\u003E\n\u003Cli\u003Ereason about device trust explicitly\u003C/li\u003E\n\u003Cli\u003Eseparate authentication lifetime from token lifetime\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022design-tradeoff\u0022\u003E\u26A0\uFE0F Design Tradeoff\u003C/h2\u003E\n\u003Cp\u003EThis model is more sophisticated than plain JWT-only authentication.\u003C/p\u003E\n\u003Cp\u003EThat is intentional.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth chooses:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eexplicit state\u003C/li\u003E\n\u003Cli\u003Erevocability\u003C/li\u003E\n\u003Cli\u003Etraceable security decisions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003Eover:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eminimal infrastructure\u003C/li\u003E\n\u003Cli\u003Epurely stateless assumptions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Authentication in UltimateAuth is not \u201Ca token\u201D\u003Cbr\u003E\n\uD83D\uDC49 It is a \u003Cstrong\u003Esecurity hierarchy with revocation boundaries\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003ERefresh Token Rotation\u003C/strong\u003E to understand how continuation security works after login.\u003C/p\u003E\n", "Headings": [ { "Id": "why-a-session-model", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/Components/App.razor b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/Components/App.razor index 8534df7b..d06526e8 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/Components/App.razor +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/Components/App.razor @@ -22,13 +22,13 @@ @* *@ + - - + + - @* *@ @@ -37,10 +37,10 @@ - + + - diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/wwwroot/app.css b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/wwwroot/app.css index 1af0b457..b505f1c4 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/wwwroot/app.css +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/wwwroot/app.css @@ -458,7 +458,287 @@ h1:focus { background: var(--mud-palette-background-gray); } +/* Documentation content */ +.docs-content a { + color: var(--mud-palette-primary); + text-decoration: underline; + text-decoration-color: currentColor; + text-underline-offset: 3px; + text-decoration-thickness: 1px; + font-weight: 500; + transition: color 0.15s ease, text-decoration-thickness 0.15s ease; +} + + .docs-content a:hover { + color: var(--mud-palette-primary-darken); + text-decoration-thickness: 2px; + } + + .docs-content a:focus-visible { + outline: 2px solid var(--mud-palette-primary); + outline-offset: 3px; + border-radius: 2px; + } + + .docs-content h2, .docs-content h3 { scroll-margin-top: 240px; } + +.docs-content h2 { + margin-top: 40px; + margin-bottom: 16px; + font-size: 1.65rem; + font-weight: 650; + line-height: 1.35; +} + +.docs-content h3 { + margin-top: 28px; + margin-bottom: 12px; + font-size: 1.3rem; + font-weight: 600; + line-height: 1.4; +} + +/* Avoid extra spacing at the beginning */ +.docs-content > h2:first-child, +.docs-content > h3:first-child { + margin-top: 0; +} + +/* Documentation tables */ + +.docs-content table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + margin: 20px 0 28px; + font-size: 0.925rem; + line-height: 1.6; +} + +.docs-content th, +.docs-content td { + padding: 12px 16px; + text-align: left; + vertical-align: top; + border-bottom: 1px solid var(--mud-palette-divider); +} + +.docs-content th { + font-weight: 600; + background: var(--mud-palette-background-gray); + border-top: 1px solid var(--mud-palette-divider); +} + + .docs-content th:first-child { + border-top-left-radius: 8px; + } + + .docs-content th:last-child { + border-top-right-radius: 8px; + } + +.docs-content tbody tr:hover { + background: var(--mud-palette-action-default-hover); +} + +.docs-content tbody tr:last-child td { + border-bottom: 0; +} + +.docs-table-wrapper { + width: 100%; + max-width: 100%; + overflow-x: auto; + margin: 20px 0 28px; + border: 1px solid var(--mud-palette-divider); + border-radius: 10px; +} + + .docs-table-wrapper table { + width: 100%; + margin: 0; + border: 0; + } + + .docs-table-wrapper th, + .docs-table-wrapper td { + min-width: 120px; + } + + .docs-table-wrapper th:first-child, + .docs-table-wrapper td:first-child { + min-width: 160px; + } + +@media (max-width: 600px) { + .docs-content h2 { + margin-top: 32px; + margin-bottom: 14px; + font-size: 1.45rem; + } + + .docs-content h3 { + margin-top: 24px; + margin-bottom: 10px; + font-size: 1.2rem; + } +} + +/* Documentation code blocks */ + +.docs-content pre[class*="language-"], +.docs-content pre:has(> code) { + background: #171b24; + color: #e5eaf3; + border: 1px solid #303746; + border-radius: 10px; + padding: 18px 20px; + margin: 20px 0 28px; + overflow-x: auto; + line-height: 1.65; + font-size: 0.9rem; + tab-size: 4; +} + +.docs-content pre code, +.docs-content pre code[class*="language-"] { + background: transparent; + color: #e5eaf3; + font-family: "Cascadia Code", "Consolas", monospace; + font-size: inherit; + line-height: inherit; + text-shadow: none; +} + +/* Prism syntax highlighting */ + +.docs-content pre .token.comment, +.docs-content pre .token.prolog, +.docs-content pre .token.doctype { + color: #8994a8; +} + +.docs-content pre .token.keyword, +.docs-content pre .token.boolean, +.docs-content pre .token.important { + color: #c4a7ff; +} + +.docs-content pre .token.string, +.docs-content pre .token.char, +.docs-content pre .token.attr-value { + color: #b4d989; +} + +.docs-content pre .token.function, +.docs-content pre .token.class-name { + color: #75c7ff; +} + +.docs-content pre .token.number, +.docs-content pre .token.constant { + color: #ffae83; +} + +.docs-content pre .token.operator, +.docs-content pre .token.punctuation { + color: #d5dce8; +} + +.docs-content pre .token.property, +.docs-content pre .token.variable, +.docs-content pre .token.parameter { + color: #ffcc85; +} + +.docs-content pre .token.namespace { + color: #cbd5e1; + opacity: 1; +} + +/* Prevent Prism themes from adding text shadows */ + +.docs-content pre[class*="language-"], +.docs-content code[class*="language-"], +.docs-content pre[class*="language-"] *, +.docs-content code[class*="language-"] * { + text-shadow: none; +} + +/* Code block container */ + +.docs-content .docs-code-wrapper { + margin: 20px 0 28px; + border: 1px solid #303746; + border-radius: 10px; + overflow: hidden; + background: #171b24; +} + +/* Header */ + +.docs-content .docs-code-header { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 42px; + padding: 8px 14px; + background: #252b37; + border-bottom: 1px solid #384252; +} + +.docs-content .docs-code-language { + color: #cdd6e3; + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.3px; +} + +/* Copy button */ + +.docs-content .docs-code-copy { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 5px 10px; + background: transparent; + border: 1px solid #485365; + border-radius: 6px; + color: #cdd6e3; + font-size: 0.75rem; + font-weight: 500; + cursor: pointer; + transition: background 0.15s, border-color 0.15s; +} + + .docs-content .docs-code-copy:hover { + background: #384252; + border-color: #64748b; + } + + .docs-content .docs-code-copy:focus-visible { + outline: 2px solid #75c7ff; + outline-offset: 2px; + } + +/* Code content */ + +.docs-content .docs-code-wrapper pre, +.docs-content .docs-code-wrapper pre[class*="language-"] { + margin: 0; + border: 0; + border-radius: 0; + background: #171b24; + padding: 18px 20px; + overflow-x: auto; +} + +.docs-content .docs-code-copy.copied { + color: #b4d989; + border-color: #5b8664; +} diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/wwwroot/docs.js b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/wwwroot/docs.js index 8d695850..45599217 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/wwwroot/docs.js +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/wwwroot/docs.js @@ -71,3 +71,39 @@ scrollTo }; })(); + +window.highlightDocsCode = () => { + if (window.Prism) { + Prism.highlightAllUnder( + document.querySelector(".docs-content") ?? document + ); + } +}; + +document.addEventListener("click", async (event) => { + const button = event.target.closest(".docs-code-copy"); + + if (!button) return; + + const wrapper = button.closest(".docs-code-wrapper"); + const code = wrapper?.querySelector("pre code"); + + if (!code) return; + + const originalText = button.textContent; + + try { + await navigator.clipboard.writeText(code.textContent); + + button.textContent = "Copied!"; + button.classList.add("copied"); + + setTimeout(() => { + button.textContent = originalText; + button.classList.remove("copied"); + }, 2000); + } + catch (error) { + console.error("Failed to copy code:", error); + } +}); diff --git a/src/utilities/CodeBeam.UltimateAuth.DocsBuilder/Program.cs b/src/utilities/CodeBeam.UltimateAuth.DocsBuilder/Program.cs index 0b6de37a..2696fa50 100644 --- a/src/utilities/CodeBeam.UltimateAuth.DocsBuilder/Program.cs +++ b/src/utilities/CodeBeam.UltimateAuth.DocsBuilder/Program.cs @@ -108,6 +108,8 @@ html = Regex.Replace(html, " ExtractHeadingsFromHtml(string html) return result; } +static string WrapTables(string html) +{ + var doc = new HtmlDocument(); + doc.LoadHtml(html); + + var tables = doc.DocumentNode.SelectNodes("//table"); + + if (tables == null) + return html; + + foreach (var table in tables.ToList()) + { + var wrapper = HtmlNode.CreateNode("
"); + table?.ParentNode?.ReplaceChild(wrapper, table); + wrapper.AppendChild(table); + } + + return doc.DocumentNode.InnerHtml; +} + +static string WrapCodeBlocks(string html) +{ + var doc = new HtmlDocument(); + doc.LoadHtml(html); + + var blocks = doc.DocumentNode.SelectNodes("//pre[code]"); + + if (blocks == null) + return html; + + foreach (var pre in blocks.ToList()) + { + var code = pre.SelectSingleNode("./code"); + if (code == null) + continue; + + var classes = code.GetAttributeValue("class", ""); + + var language = classes.Split(' ', StringSplitOptions.RemoveEmptyEntries) + .FirstOrDefault(x => x.StartsWith("language-"))? + .Replace("language-", "") ?? "text"; + + var label = language.ToLowerInvariant() switch + { + "csharp" or "cs" => "C#", + "javascript" or "js" => "JavaScript", + "typescript" or "ts" => "TypeScript", + "json" => "JSON", + "bash" or "shell" or "sh" => "Bash", + "razor" => "Razor", + "html" => "HTML", + "css" => "CSS", + "xml" => "XML", + "text" => "Text", + _ => language.ToUpperInvariant() + }; + + var wrapper = HtmlNode.CreateNode( + "
" + ); + + var header = HtmlNode.CreateNode( + "
" + ); + + var languageLabel = HtmlNode.CreateNode( + "" + ); + + languageLabel.InnerHtml = HtmlEntity.Entitize(label); + + var copyButton = HtmlNode.CreateNode( + "" + ); + + header.AppendChild(languageLabel); + header.AppendChild(copyButton); + + pre?.ParentNode?.ReplaceChild(wrapper, pre); + + wrapper.AppendChild(header); + wrapper.AppendChild(pre); + } + + return doc.DocumentNode.InnerHtml; +} + internal sealed record DocOutput(string Slug, string Title, string Html, List Headings); internal sealed class DocIndexItem From 721a81e868d06caa4e2fca9150673b485415efaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Fri, 16 Oct 2026 16:48:09 +0300 Subject: [PATCH 2/7] Docs Section Content's Rewrite --- docs/content/_groups.json | 9 +- docs/content/auth-flows/device-management.md | 166 -- docs/content/auth-flows/index.md | 2 +- docs/content/auth-flows/session-lifecycle.md | 201 --- docs/content/auth-flows/token-behavior.md | 169 -- docs/content/client/authentication.md | 1267 ++++++++++++-- docs/content/client/authorization.md | 1393 +++++++++++++++- docs/content/client/credentials.md | 1064 +++++++++++- docs/content/client/identifiers.md | 1028 +++++++++++- docs/content/client/index.md | 306 +++- docs/content/client/session-management.md | 784 ++++++++- docs/content/client/user-management.md | 1210 +++++++++++++- .../configuration/advanced-configuration.md | 1188 ++++++++++++-- docs/content/configuration/client-options.md | 1145 ++++++++++++- .../configuration/configuration-overview.md | 172 -- .../configuration/configuration-sources.md | 166 -- docs/content/configuration/index.md | 844 +++++++++- docs/content/configuration/server-options.md | 1459 +++++++++++++++-- docs/content/getting-started/index.md | 12 +- .../plugin-domains/authorization-domain.md | 567 +++++-- .../plugin-domains/credential-domain.md | 319 +++- docs/content/plugin-domains/index.md | 165 +- docs/content/plugin-domains/policies.md | 421 ++++- docs/content/plugin-domains/users-domain.md | 274 +++- .../content/security/access-token-behavior.md | 172 -- .../security/authorization-pipeline.md | 591 +++++++ docs/content/security/policy-pipeline.md | 230 --- docs/content/security/refresh-rotation.md | 490 ++++-- .../security/session-security-model.md | 500 ++++-- docs/content/security/token-security.md | 560 +++++++ .../sessions-devices/device-management.md | 308 ++++ .../sessions-devices/session-lifecycle.md | 424 +++++ .../wwwroot/docs/auth-flows/index.json | 2 +- .../wwwroot/docs/client/authentication.json | 312 +++- .../wwwroot/docs/client/authorization.json | 302 +++- .../wwwroot/docs/client/credentials.json | 285 +++- .../wwwroot/docs/client/identifiers.json | 263 ++- .../wwwroot/docs/client/index.json | 86 +- .../docs/client/session-management.json | 176 +- .../wwwroot/docs/client/user-management.json | 291 +++- .../configuration/advanced-configuration.json | 359 +++- .../wwwroot/docs/configuration/index.json | 200 ++- .../wwwroot/docs/docs-index.json | 103 +- .../wwwroot/docs/getting-started/index.json | 2 +- .../plugin-domains/authorization-domain.json | 136 +- .../plugin-domains/credential-domain.json | 95 +- .../wwwroot/docs/plugin-domains/index.json | 65 +- .../wwwroot/docs/plugin-domains/policies.json | 109 +- .../docs/plugin-domains/users-domain.json | 69 +- .../docs/security/authorization-pipeline.json | 167 ++ .../docs/security/refresh-rotation.json | 123 +- .../docs/security/session-security-model.json | 167 +- .../wwwroot/docs/security/token-security.json | 232 +++ .../sessions-devices/device-management.json | 127 ++ .../sessions-devices/session-lifecycle.json | 162 ++ 55 files changed, 18071 insertions(+), 3368 deletions(-) delete mode 100644 docs/content/auth-flows/device-management.md delete mode 100644 docs/content/auth-flows/session-lifecycle.md delete mode 100644 docs/content/auth-flows/token-behavior.md delete mode 100644 docs/content/configuration/configuration-overview.md delete mode 100644 docs/content/configuration/configuration-sources.md delete mode 100644 docs/content/security/access-token-behavior.md create mode 100644 docs/content/security/authorization-pipeline.md delete mode 100644 docs/content/security/policy-pipeline.md create mode 100644 docs/content/security/token-security.md create mode 100644 docs/content/sessions-devices/device-management.md create mode 100644 docs/content/sessions-devices/session-lifecycle.md create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/authorization-pipeline.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/token-security.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/device-management.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/session-lifecycle.json diff --git a/docs/content/_groups.json b/docs/content/_groups.json index bae8f92e..e702badb 100644 --- a/docs/content/_groups.json +++ b/docs/content/_groups.json @@ -2,8 +2,9 @@ "getting-started": 1, "fundamentals": 2, "auth-flows": 3, - "plugin-domains": 4, - "client": 5, - "configuration": 6, - "security": 7 + "sessions-devices": 4, + "plugin-domains": 5, + "client": 6, + "configuration": 7, + "security": 8 } \ No newline at end of file diff --git a/docs/content/auth-flows/device-management.md b/docs/content/auth-flows/device-management.md deleted file mode 100644 index a2184b8f..00000000 --- a/docs/content/auth-flows/device-management.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -title: Device Management -order: 7 -group: auth-flows ---- - - -# πŸ“± Device Management - -In UltimateAuth, devices are not an afterthought. - -πŸ‘‰ They are a **first-class concept** - -## 🧠 Why Device Matters - -Most authentication systems ignore devices. - -- A user logs in -- A token is issued -- Everything is treated the same - -πŸ‘‰ This breaks down when you need: - -- Multi-device control -- Session visibility -- Security enforcement - -πŸ‘‰ UltimateAuth solves this with **device-aware authentication** - -## 🧩 Core Concept: Chain = Device - -In UltimateAuth: - -πŸ‘‰ A **SessionChain represents a device** - -``` -Device β†’ Chain β†’ Sessions -``` - -Each chain: - -- Is bound to a device -- Groups sessions -- Tracks activity - -πŸ‘‰ A device is not inferred β€” it is explicitly modeled - -## πŸ”— What Defines a Device? - -A chain includes: - -- DeviceId -- Platform (web, mobile, etc.) -- Operating System -- Browser -- IP (optional binding) - -πŸ‘‰ This forms a **device fingerprint** - -## πŸ”„ Device Lifecycle - -### 1️⃣ First Login - -- New device detected -- New chain is created - -### 2️⃣ Subsequent Logins - -- Same device β†’ reuse chain -- New device β†’ new chain - -πŸ‘‰ Device continuity is preserved - -### 3️⃣ Activity (Touch) - -- Chain `LastSeenAt` updated -- `TouchCount` increases - -πŸ‘‰ Tracks real usage - -### 4️⃣ Token Rotation - -- Session changes -- Chain remains -- `RotationCount` increases - -πŸ‘‰ Device identity stays stable - -### 5️⃣ Logout - -- Session removed -- Chain remains - -πŸ‘‰ Device still trusted - -### 6️⃣ Revoke - -- Chain invalidated -- All sessions removed - -πŸ‘‰ Device trust is reset - -
- -## πŸ” Security Model - -### πŸ”— Device Binding - -Sessions and tokens are tied to: - -- Chain -- Device context - -πŸ‘‰ Prevents cross-device reuse - -### πŸ” Rotation Tracking - -Chains track: - -- RotationCount -- TouchCount - -πŸ‘‰ Enables anomaly detection - -### 🚨 Revoke Cascade - -If a device is compromised: - -- Entire chain can be revoked -- All sessions invalidated - -πŸ‘‰ Immediate containment - -
- -## βš™οΈ Configuration - -Device behavior is configurable via session options: - -- Max chains per user -- Max sessions per chain -- Platform-based limits -- Device mismatch behavior - -πŸ‘‰ Fine-grained control for enterprise scenarios - -
- -## 🧠 Mental Model - -If you remember one thing: - -πŸ‘‰ Device = Chain -πŸ‘‰ Not just metadata - -## πŸ“Œ Key Takeaways - -- Devices are explicitly modeled -- Each device has its own chain -- Sessions belong to chains -- Security is enforced per device -- Logout and revoke operate on device scope - -## ➑️ Next Step - -Continue to **Configuration & Extensibility** diff --git a/docs/content/auth-flows/index.md b/docs/content/auth-flows/index.md index 8a508958..53f3ce21 100644 --- a/docs/content/auth-flows/index.md +++ b/docs/content/auth-flows/index.md @@ -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](./login-flow.md) to see how UltimateAuth turns validated credentials into controlled authenticated access. +Start with [Login Flow](/docs/auth-flows/login) to see how UltimateAuth turns validated credentials into controlled authenticated access. diff --git a/docs/content/auth-flows/session-lifecycle.md b/docs/content/auth-flows/session-lifecycle.md deleted file mode 100644 index 6f79a04c..00000000 --- a/docs/content/auth-flows/session-lifecycle.md +++ /dev/null @@ -1,201 +0,0 @@ ---- -title: Session Lifecycle -order: 5 -group: auth-flows ---- - - -# 🧬 Session Lifecycle -UltimateAuth is built around a structured session model. - -πŸ‘‰ Authentication is not a token -πŸ‘‰ It is a **hierarchical session system** - -## 🧠 Core Model - -UltimateAuth defines three core entities: -``` -Root β†’ Chain β†’ Session -``` - -### πŸ”Ή Root (Identity Authority) -- One per user -- Represents global authentication state -- Holds security version - -πŸ‘‰ Root defines **who the user is** - -### πŸ“± Chain (Device Context) -- One per device -- Represents a device-bound identity context -- Tracks activity (LastSeenAt) -- Manages session rotation and touch - -πŸ‘‰ A chain is a **trusted device boundary** - -### πŸ”‘ Session (Authentication Instance) -- Created on login -- Represents a single authentication event -- Has expiration and revocation state - -πŸ‘‰ A session is a **proof of authentication** - -
- -## πŸ”— Relationship -``` -User -└── Root -└── Chain (Device) -└── Session (Instance) -``` - -πŸ‘‰ Each level adds more specificity: - -- Root β†’ identity -- Chain β†’ device -- Session β†’ login instance - -
- -## πŸ”„ Lifecycle Overview - -### 1️⃣ Creation (Login) -When a user logs in: - -- Root is created (if not exists) -- Chain is resolved or created -- Session is issued - -### 2️⃣ Active Usage -During normal operation: - -- Session is validated -- Chain `LastSeenAt` is updated (touch) -- Sliding expiration may apply - -πŸ‘‰ Activity updates the **chain**, not just the session - -### 3️⃣ Refresh -Depending on mode: - -#### Session-Based (PureOpaque) -- Session remains -- Chain is touched - -#### Token-Based (Hybrid / JWT) -- Session continues -- Tokens are rotated -- Chain rotation count increases - -πŸ‘‰ Chain tracks behavior: - -- RotationCount -- TouchCount - -### 4️⃣ Expiration -A session may expire due to: - -- Lifetime expiration -- Idle timeout -- Absolute expiration - -πŸ‘‰ Expired β‰  revoked - -### 5️⃣ Revocation -Revocation can occur at multiple levels: - -#### Session Revocation -- Single session invalidated - -#### Chain Revocation -- All sessions on device invalidated -- Device trust reset - ---- - -#### Root Revocation - -- All chains and sessions invalidated -- Security version increased - -πŸ‘‰ Revocation is irreversible - -
- -## πŸ” Security Model - -### πŸ”’ Security Versioning -Each root has: -- `SecurityVersion` - -Each session stores: -- `SecurityVersionAtCreation` - ---- - -πŸ‘‰ If mismatch: - -```text -Session becomes invalid -``` - -### πŸ”— Device Binding -Each chain is tied to: - -- DeviceId -- Platform -- OS -- Browser - -πŸ‘‰ Prevents cross-device misuse - -### πŸ” Rotation Tracking -Chains track: - -- RotationCount -- TouchCount - -πŸ‘‰ Enables: - -- replay detection -- anomaly tracking - -### βš™οΈ Lifecycle Configuration -Session behavior is configurable: - -⏱ Lifetime -- Default session duration - -πŸ”„ Sliding Expiration -- Extends session on activity - -πŸ’€ Idle Timeout -- Invalidates inactive sessions - -πŸ“± Device Limits -- Max chains per user -- Max sessions per chain - -πŸ‘‰ These are defined via UAuthSessionOptions - -
- -## 🧠 Mental Model -If you remember one thing: - -πŸ‘‰ Authentication is a living structure -πŸ‘‰ Not a static token - -## πŸ“Œ Key Takeaways -- Sessions are part of a hierarchy -- Device (chain) is a first-class concept -- Root controls global security -- Sessions are short-lived proofs -- Chains manage lifecycle and activity -- Revocation operates at multiple levels - -## ➑️ Next Step - -Continue to Token Behavior - diff --git a/docs/content/auth-flows/token-behavior.md b/docs/content/auth-flows/token-behavior.md deleted file mode 100644 index 18c76126..00000000 --- a/docs/content/auth-flows/token-behavior.md +++ /dev/null @@ -1,169 +0,0 @@ ---- -title: Token Behavior -order: 6 -group: auth-flows ---- - - -# 🎟 Token Behavior -In UltimateAuth, tokens are not the foundation of authentication. - -πŸ‘‰ They are **derived artifacts of a session** - -## 🧠 Rethinking Tokens -In traditional systems: - -- Token = identity -- Token = authentication - -In UltimateAuth: - -πŸ‘‰ Session = identity -πŸ‘‰ Token = transport mechanism - -> Tokens do not define identity -> β†’ Sessions do - -## 🧩 Token Types -UltimateAuth supports two main token types: - -### πŸ”Ή Opaque Tokens -- Random, non-decodable values -- Stored and validated on the server -- Typically reference a session - -πŸ‘‰ Used in: - -- PureOpaque -- Hybrid - -### πŸ”Ή JWT (JSON Web Tokens) -- Self-contained tokens -- Include claims and metadata -- Signed and verifiable without server lookup - -πŸ‘‰ Used in: - -- SemiHybrid -- PureJwt - -
- -## βš–οΈ Mode-Based Behavior -Token behavior depends on the authentication mode: - -| Mode | Access Token | Refresh Token | Behavior | -|-------------|---------------|----------------|---------------------------| -| PureOpaque | ❌ | ❌ | Session-only | -| Hybrid | βœ” (opaque/JWT)| βœ” | Session + token | -| SemiHybrid | βœ” (JWT) | βœ” | JWT + session metadata | -| PureJwt | βœ” (JWT) | βœ” | Fully stateless | - -πŸ‘‰ UltimateAuth selects behavior automatically - -
- -## πŸ”‘ Access Tokens -Access tokens represent: - -πŸ‘‰ A **temporary access grant** - -### Characteristics -- Short-lived -- Mode-dependent format -- May contain session reference (`sid`) -- May include claims - -### Important -Access token is NOT the source of truth. - -πŸ‘‰ It reflects session state, not replaces it - -## πŸ”„ Refresh Tokens -Refresh tokens represent: - -πŸ‘‰ A **continuation mechanism** - -### Characteristics -- Long-lived -- Stored as hashed values -- Bound to session and optionally chain -- Rotated on use - -### Lifecycle -Issued β†’ Used β†’ Replaced β†’ Revoked - -πŸ‘‰ Old tokens are invalidated on rotation - -
- -## πŸ” Security Model - -### πŸ” Rotation -Each refresh: - -- Invalidates previous token -- Issues a new token - -πŸ‘‰ Prevents replay attacks - -### 🚨 Reuse Detection -If a token is reused: - -- Chain may be revoked -- Session may be revoked - -πŸ‘‰ Strong forward security - -### πŸ”— Session Binding -Refresh tokens include: - -- SessionId -- ChainId (optional) - -πŸ‘‰ Prevents cross-context usage - -### πŸ”’ Hashed Storage -Tokens are: - -- Never stored as plaintext -- Hashed using secure algorithms - -πŸ‘‰ Reduces breach impact - -
- -## πŸ”„ Token Issuance -Tokens are issued during: - -- Login -- Refresh - -### Access Token -- May be opaque or JWT -- Includes identity and optional session reference - -### Refresh Token -- Always opaque -- Persisted in secure store -- Used only for rotation - -
- -## 🧠 Mental Model -If you remember one thing: - -πŸ‘‰ Tokens are not identity -πŸ‘‰ They are projections of a session - -## πŸ“Œ Key Takeaways -- Session is the source of truth -- Tokens are optional and mode-dependent -- Opaque tokens require server validation -- JWT tokens allow stateless access -- Refresh tokens enable secure continuation -- Token rotation ensures forward security - -## ➑️ Next Step - -Continue to Device Management diff --git a/docs/content/client/authentication.md b/docs/content/client/authentication.md index 53630cdf..b5ce3180 100644 --- a/docs/content/client/authentication.md +++ b/docs/content/client/authentication.md @@ -4,184 +4,1267 @@ order: 2 group: client --- +# Authentication -# πŸ” Authentication Guide +The UltimateAuth Client SDK provides a high-level API for authentication workflows, including login, PKCE, session validation, refresh, logout, and device-specific session revocation. -This section explains how to use the UltimateAuth client for authentication flows. +These operations are exposed through `UAuthClient.Flows`, which implements the `IFlowClient` contract. -## 🧠 Overview +This 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. -Authentication in UltimateAuth is **flow-based**, not endpoint-based. +## 1. Accessing the Authentication Client -You interact with: +In a Blazor component, inject `IUAuthClient`: -πŸ‘‰ `FlowClient` +```razor +@using CodeBeam.UltimateAuth.Client +@inject IUAuthClient UAuthClient +``` ---- +Authentication operations are available through the `Flows` property. + +```csharp +await UAuthClient.Flows.RefreshAsync(); +await UAuthClient.Flows.ValidateAsync(); +await UAuthClient.Flows.LogoutAsync(); +``` + +The relevant methods are defined by `IFlowClient`. + +| Method | Purpose | +|---|---| +| `LoginAsync` | Submit a direct login flow | +| `TryLoginAsync` | Attempt login with configurable try/commit behavior | +| `BeginPkceAsync` | Start a PKCE authentication flow | +| `CompletePkceLoginAsync` | Complete a PKCE login | +| `TryCompletePkceLoginAsync` | Complete PKCE with structured try/commit behavior | +| `RefreshAsync` | Refresh or continue authentication | +| `ValidateAsync` | Validate current authentication state | +| `LogoutAsync` | Log out the current session | +| `LogoutMyDeviceAsync` | Revoke a selected device-associated chain | +| `LogoutMyOtherDevicesAsync` | Revoke other devices while retaining the current one | +| `LogoutAllMyDevicesAsync` | Revoke all devices for the current user | + +Administrative device logout operations are also available and covered later in this guide. + +## 2. Choosing the Correct Login Flow + +Before implementing login, determine whether your application is a trusted server-hosted client or a public client. + +| Client environment | Recommended flow | +|---|---| +| Blazor Server | Direct login or supported interactive login flow | +| UAuthHub | Server-hosted authentication flow | +| Blazor WebAssembly | PKCE | +| Browser SPA | PKCE | +| Public native application | PKCE | + +Public clients must not rely on embedding a secret or trusting browser-executed code to protect credentials. + +UltimateAuth restricts direct credential posting based on the configured client profile and login options. + +For public clients, use the PKCE flow instead of enabling direct credential posting. -## πŸ”‘ Login +## 3. Direct Login -### Basic Login +`LoginAsync` performs a direct login submission. ```csharp -await UAuthClient.Flows.LoginAsync(new LoginRequest +var request = new LoginRequest { - Identifier = "user@ultimateauth.com", + Identifier = "user@example.com", Secret = "password" -}); +}; + +await UAuthClient.Flows.LoginAsync(request); ``` -πŸ‘‰ This triggers a full login flow: +The method returns `Task`, not a structured login result. -- Sends credentials -- Handles redirect -- Establishes session +It submits the login payload through the configured navigation-based transport. ---- +Depending on the hosting environment, this may initiate navigation or redirect behavior. + +### LoginRequest Properties + +`LoginRequest` supports the following properties: + +| Property | Purpose | +|---|---| +| `Identifier` | Login identifier | +| `Secret` | Credential secret | +| `CredentialType` | Credential type; defaults to `Password` | +| `Metadata` | Optional request metadata | +| `RequestTokens` | Hint to request tokens when supported | +| `PreviewReceipt` | Optional receipt associated with a prior login preview | + +Example: + +```csharp +var request = new LoginRequest +{ + Identifier = email, + Secret = password, + CredentialType = CredentialType.Password, + RequestTokens = true +}; + +await UAuthClient.Flows.LoginAsync(request); +``` + +`RequestTokens` is a hint, not a guarantee that tokens will be issued. The server's authentication mode and policy remain authoritative. + +### Redirect After Login + +Provide an explicit return URL when necessary: -## ⚑ Try Login (Pre-check) +```csharp +await UAuthClient.Flows.LoginAsync(request, returnUrl: "/dashboard"); +``` + +The direct login flow resolves its return URL in the following order: + +1. The explicit `returnUrl` argument. +2. `Login.ReturnUrl` option. +3. `DefaultReturnUrl` option. + +For example, client options can specify a default: + +```csharp +options.Login.ReturnUrl = "/dashboard"; +``` + +The return URL is part of the login workflow. It should not be treated as an authorization grant. + +Applications and servers must still enforce their redirect validation rules. + +## 4. Interactive Login with TryLoginAsync + +For interactive forms, `TryLoginAsync` provides structured feedback before or during the authentication commit process. ```csharp var result = await UAuthClient.Flows.TryLoginAsync( new LoginRequest { - Identifier = "user@mail.com", - Secret = "password" + Identifier = email, + Secret = password }, - UAuthSubmitMode.TryOnly -); + UAuthSubmitMode.TryAndCommit); ``` -### Modes +This is generally the more useful API for a login form that needs to display validation feedback. -| Mode | Behavior | -|--------------|--------------------------------| -| TryOnly | Validate only | -| DirectCommit | Skip validation | -| TryAndCommit | Validate then login if success | +### Submit Modes -πŸ‘‰ Use `DirectCommit` when: -- You need maximum performance while sacrificing interactive SPA capabilities. +`UAuthSubmitMode` defines three submission behaviors. -πŸ‘‰ Use `TryOnly` when: +| Mode | Behavior | +|---|---| +| `TryOnly` | Execute the try operation and return its result without committing authentication | +| `DirectCommit` | Submit directly to the commit endpoint without a separate try operation | +| `TryAndCommit` | Try first, then commit when the try operation succeeds | -- You need validation feedback -- You want custom UI flows +These modes control client workflow, not whether the server validates authentication. -πŸ‘‰ Use `TryAndCommit` when: +**DirectCommit does not bypass credential verification or server security requirements.** -- You need completely interactive SPA experience. +### TryOnly: Validate Without Login + +Use `TryOnly` when you want structured feedback without establishing an authenticated session. + +```csharp +var result = await UAuthClient.Flows.TryLoginAsync( + new LoginRequest + { + Identifier = email, + Secret = password + }, + UAuthSubmitMode.TryOnly); -πŸ‘‰ `TryAndCommit` is the recommended mode for most applications. +if (result.IsSuccess) +{ + // The try operation succeeded. + // Authentication has not been committed. +} +else +{ + // Display the appropriate feedback. +} +``` -It provides: +This can be useful for a custom interactive workflow. -- Validation feedback -- Automatic redirect on success -- Smooth SPA experience +A successful try result must not be interpreted as an authenticated application session. -
+### TryAndCommit: Interactive Login -## πŸ”„ Refresh +Use `TryAndCommit` to combine structured validation with authentication commit. + +```csharp +var result = await UAuthClient.Flows.TryLoginAsync( + new LoginRequest + { + Identifier = email, + Secret = password + }, + UAuthSubmitMode.TryAndCommit, + returnUrl: "/dashboard"); + +if (!result.IsSuccess) +{ + // Show validation feedback. +} +``` + +When the try stage succeeds, the client proceeds to the commit operation through its configured transport. + +This can involve navigation, so application code should not assume that execution always continues within the same component instance. + +### DirectCommit: Single Commit Submission + +```csharp +await UAuthClient.Flows.TryLoginAsync( + new LoginRequest + { + Identifier = email, + Secret = password + }, + UAuthSubmitMode.DirectCommit); +``` + +In this mode, the SDK submits the commit operation directly. + +The method returns a `TryLoginResult` with `IsSuccess = true` after the navigation submission completes, but this is not a structured server-side authentication success confirmation. + +If you need structured credential validation feedback, prefer `TryAndCommit`. + +## 5. Handling Login Results + +`TryLoginAsync` returns `TryLoginResult`. + +Its available properties include: + +| Property | Meaning | +|---|---| +| `IsSuccess` | Whether the try operation succeeded | +| `Reason` | Optional authentication failure reason | +| `RemainingAttempts` | Remaining attempts, when provided | +| `LockoutUntilUtc` | Lockout expiration, when provided | +| `RequiresMfa` | Whether additional authentication is required | +| `PreviewReceipt` | Optional receipt for subsequent flow handling | + +Example: + +```csharp +var result = await UAuthClient.Flows.TryLoginAsync(request, UAuthSubmitMode.TryOnly); + +if (!result.IsSuccess) +{ + if (result.LockoutUntilUtc is not null) + { + errorMessage = $"Login temporarily unavailable until {result.LockoutUntilUtc:u}"; + } + else + { + errorMessage = "Unable to sign in."; + } + + return; +} + +if (result.RequiresMfa) +{ + // Continue with the supported additional authentication flow. +} +``` + +Do not expose sensitive internal authentication details unnecessarily. + +For public-facing login forms, generic failure messages may be preferable to displaying every failure reason. + +### Handling Transport and Protocol Errors + +A structured authentication failure is different from a network or protocol failure. + +For example: + +```csharp +try +{ + var result = await UAuthClient.Flows.TryLoginAsync(request, UAuthSubmitMode.TryAndCommit); + + if (!result.IsSuccess) + { + errorMessage = "Unable to sign in."; + } +} +catch (UAuthTransportException) +{ + errorMessage = "Authentication service is unavailable."; +} +catch (UAuthProtocolException) +{ + errorMessage = "An authentication protocol error occurred."; +} +``` + +Use the corresponding UltimateAuth client error namespace when referencing these exception types. + +A failed authentication attempt should normally be handled through the structured try result where supported. Transport and protocol exceptions indicate different failure categories. + +## 6. PKCE Authentication + +PKCE provides a proof mechanism for authorization-code-based authentication flows. + +UltimateAuth supports PKCE using an S256 code challenge. + +The SDK generates a cryptographically random verifier, derives its challenge, and coordinates the authorization flow. + +Public clients such as Blazor WebAssembly should use PKCE rather than direct credential posting. + +### Start PKCE + +```csharp +await UAuthClient.Flows.BeginPkceAsync(); +``` + +You can provide a return URL: + +```csharp +await UAuthClient.Flows.BeginPkceAsync(returnUrl: "/dashboard"); +``` + +The client performs the following operations: + +1. Generates a PKCE code verifier. +2. Derives an S256 code challenge. +3. Sends the authorization request. +4. Receives an authorization code. +5. Invokes the configured authorization callback, if any. +6. Navigates to the configured UAuthHub login flow when automatic redirection is enabled. + +The authorization code and verifier are used later to complete the flow. + +### Configure PKCE + +The relevant client options include: + +```csharp +options.Pkce.Enabled = true; +options.Pkce.AutoRedirect = true; +options.Pkce.ReturnUrl = "/dashboard"; +``` + +PKCE is enabled by default in `UAuthClientPkceLoginFlowOptions`. + +Automatic redirection is also enabled by default. + +### Observe Authorization + +You can register a callback that runs after the authorization code is received and before the automatic Hub redirect. + +```csharp +options.Pkce.OnAuthorized = async response => +{ + // Observe the authorization response. + await Task.CompletedTask; +}; +``` + +This callback is useful for application-specific coordination. + +Do not log authorization codes or expose them to unrelated components. + +### Disable Automatic Redirection + +```csharp +options.Pkce.AutoRedirect = false; +``` + +With this configuration, `BeginPkceAsync` still performs the authorization request and invokes `OnAuthorized`, but it does not automatically navigate to the Hub login endpoint. + +The application becomes responsible for coordinating the remaining flow. + +This is an advanced integration option. + +A custom implementation must preserve the authorization code, verifier, and relevant context securely and complete the flow using the supported protocol. + +## 7. Complete PKCE Login + +`CompletePkceLoginAsync` submits the PKCE completion request. + +```csharp +var request = new PkceCompleteRequest +{ + AuthorizationCode = authorizationCode, + CodeVerifier = codeVerifier, + Identifier = email, + Secret = password, + ReturnUrl = "/dashboard" +}; + +await UAuthClient.Flows.CompletePkceLoginAsync(request); +``` + +The request model includes: + +| Property | Purpose | +|---|---| +| `AuthorizationCode` | Code obtained from the authorization step | +| `CodeVerifier` | Original PKCE verifier | +| `Identifier` | Login identifier | +| `Secret` | Login credential | +| `ReturnUrl` | Optional post-login destination | +| `HubSessionId` | Optional Hub session context | + +The authorization code and verifier must correspond to the same PKCE flow. + +Do not generate a new verifier when completing an existing authorization. + +### Interactive PKCE Completion + +For structured feedback, use `TryCompletePkceLoginAsync`. + +```csharp +var result = await UAuthClient.Flows.TryCompletePkceLoginAsync( + request, + UAuthSubmitMode.TryAndCommit); + +if (!result.IsSuccess) +{ + // Display PKCE login feedback. +} +``` + +The same submit modes are supported: + +- `TryOnly` +- `DirectCommit` +- `TryAndCommit` + +### Try PKCE Without Commit + +```csharp +var result = await UAuthClient.Flows.TryCompletePkceLoginAsync(request, UAuthSubmitMode.TryOnly); +``` + +This returns a `TryPkceLoginResult` without committing a successful login. + +The result includes: + +```csharp +result.IsSuccess; +result.Reason; +result.RemainingAttempts; +result.LockoutUntilUtc; +result.RequiresMfa; +result.RetryWithNewPkce; +``` + +### RetryWithNewPkce + +`RetryWithNewPkce` indicates that the client should begin a new PKCE authorization sequence rather than continuing with the existing authorization material. + +For example: + +```csharp +if (result.RetryWithNewPkce) +{ + await UAuthClient.Flows.BeginPkceAsync("/login"); + return; +} +``` + +This is useful when the current PKCE context can no longer be used. + +The application should not repeatedly resubmit the same invalid authorization code and verifier. + +## 8. Direct Credential Posting Restrictions + +UltimateAuth restricts direct credential posting according to the configured client profile. + +The client option is: + +```csharp +options.Login.AllowCredentialPost = false; +``` + +This is the default. + +If the current profile is not permitted to post credentials directly and the option has not enabled that capability, direct login methods throw an `InvalidOperationException`. + +For example, attempting direct login from a public client may produce an error indicating that PKCE is required. + +### Trusted Server-Hosted Clients + +The option can be explicitly configured for a trusted server-hosted integration where direct credential posting is appropriate: + +```csharp +options.Login.AllowCredentialPost = true; +``` + +This must not be used to bypass PKCE requirements in a public browser application. + +**Do not enable direct credential posting in production Blazor WebAssembly or SPA clients.** + +A client profile is an integration descriptor, not cryptographic proof that the caller is trusted. + +Server-side security requirements remain authoritative. + +## 9. Refresh Authentication + +`RefreshAsync` requests continuation of the current authentication state. ```csharp var result = await UAuthClient.Flows.RefreshAsync(); ``` -### Possible Outcomes +The method returns `RefreshResult`. -- Success β†’ new tokens/session updated -- Touched β†’ session extended -- Rotated β†’ refresh token rotated -- NoOp β†’ nothing changed -- ReauthRequired β†’ login required +```csharp +public sealed record RefreshResult +{ + public bool IsSuccess { get; init; } + public int Status { get; init; } + public RefreshOutcome Outcome { get; init; } +} +``` -πŸ‘‰ Refresh behavior depends on auth mode: +Refresh behavior depends on the authentication mode. -- PureOpaque β†’ session touch -- Hybrid/JWT β†’ token rotation +A refresh operation may update session activity, rotate authentication material, or determine that reauthentication is required. -In default, successful refresh returns success outcome. If you want to learn success detail such as no-op, touched or rotated, open it via server options: +### Handling Refresh Results ```csharp -builder.Services.AddUltimateAuthServer(o => +var result = await UAuthClient.Flows.RefreshAsync(); + +if (result.IsSuccess) { - o.Diagnostics.EnableRefreshDetails = true; + // Authentication continuation succeeded. +} +else if (result.Outcome == RefreshOutcome.ReauthRequired) +{ + // The current authentication can no longer continue. + // Direct the user to the appropriate login flow. +} +else +{ + // Handle another unsuccessful refresh result. +} +``` + +A `401 Unauthorized` refresh response is handled as a normal reauthentication outcome. + +It returns: + +```csharp +new RefreshResult +{ + IsSuccess = false, + Status = 401, + Outcome = RefreshOutcome.ReauthRequired +}; +``` + +Network and unexpected protocol failures can still produce exceptions. + +### Refresh Outcomes + +`RefreshOutcome` defines the following values: + +| Outcome | Meaning | +|---|---| +| `None` | No specific semantic outcome | +| `Success` | Generic successful refresh | +| `NoOp` | No refresh state change was necessary | +| `Touched` | Applicable session activity was updated | +| `Rotated` | Rotation occurred | +| `ReauthRequired` | Authentication continuation is no longer permitted | + +For example: + +```csharp +var result = await UAuthClient.Flows.RefreshAsync(); + +if (!result.IsSuccess) +{ + return; +} + +switch (result.Outcome) +{ + case RefreshOutcome.Rotated: + // Authentication material was rotated. + break; + + case RefreshOutcome.Touched: + // Applicable session activity was updated. + break; + + case RefreshOutcome.NoOp: + // Successful refresh and no state change was required. + break; + + case RefreshOutcome.Success: + // Refresh succeeded without a more specific outcome. + break; +} +``` + +### Detailed Refresh Diagnostics + +The server may return a generic success outcome rather than exposing the internal refresh action. + +Where supported, detailed refresh outcomes can be enabled through server diagnostics: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Diagnostics.EnableRefreshDetails = true; }); ``` -
+This setting is intended for diagnostics and observability. + +Applications should not require a specific successful outcome such as `Rotated` or `Touched` to consider refresh successful. + +Use `IsSuccess` for the primary success decision. + +### Manual and Automatic Refresh + +`RefreshAsync` has an optional `isAuto` argument. + +```csharp +// Manual refresh +await UAuthClient.Flows.RefreshAsync(); + +// Refresh initiated by automatic coordination +await UAuthClient.Flows.RefreshAsync(isAuto: true); +``` + +The parameter distinguishes automatic refresh activity for client diagnostics. + +It does not select a different authentication mode or override the server's refresh policy. + +## 10. Automatic Refresh Configuration + +UltimateAuth exposes client-side automatic refresh options through `AutoRefresh`. + +```csharp +options.AutoRefresh.Enabled = true; +options.AutoRefresh.Interval = TimeSpan.FromMinutes(5); +``` + +`Enabled` controls background refresh coordination. + +`Interval` specifies the interval used for background refresh attempts. + +The 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. + +### Automatic Refresh Is Not a Security Guarantee + +Automatic refresh is a client-side convenience mechanism. + +It does not guarantee that a session will remain valid indefinitely. -## πŸšͺ Logout +Authentication may still end because of: + +- Expiration. +- Revocation. +- Account security changes. +- Invalid refresh credentials. +- Server-side continuation restrictions. + +The client must remain prepared to handle `ReauthRequired`. + +## 11. Validate Current Authentication + +Use `ValidateAsync` to ask the server to validate the current authentication state. + +```csharp +var result = await UAuthClient.Flows.ValidateAsync(); + +if (result.IsValid) +{ + // Current authentication is valid. +} +else +{ + // Authentication is not currently valid. +} +``` + +The method returns `AuthValidationResult`. + +Its properties include: + +| Property | Purpose | +|---|---| +| `State` | Current session state | +| `IsValid` | Whether the state is `Active` | +| `Snapshot` | Optional authentication state snapshot | +| `ChainId` | Optional associated session chain | +| `RootId` | Optional session root | +| `BoundDeviceId` | Optional bound device identifier | + +### Inspect Session Context + +```csharp +var result = await UAuthClient.Flows.ValidateAsync(); + +if (result.IsValid && result.ChainId is { } chainId) +{ + // Use chainId for supported session-management operations. +} +``` + +Not every authentication mode necessarily provides all session hierarchy fields. + +Treat optional values as optional. + +### ValidateAsync Versus UI Authentication State + +`ValidateAsync` performs an explicit server validation operation. + +It should not be called on every UI render merely to determine whether a component should display authenticated content. + +For UI authentication state, use the supported UltimateAuth state management integration, including `IUAuthStateManager` where appropriate. + +Explicit validation is useful when an application needs to check current server-recognized authentication state. + +## 12. Logout + +`LogoutAsync` performs the current logout flow. ```csharp await UAuthClient.Flows.LogoutAsync(); ``` -πŸ‘‰ This: +The method uses the configured logout endpoint and navigation transport. + +Depending on the client profile and configuration, logout may involve navigation and authentication state changes. + +### Logout Button + +A simple Blazor example: + +```razor +@using CodeBeam.UltimateAuth.Client +@inject IUAuthClient UAuthClient + + + +@code { + private async Task LogoutAsync() + { + await UAuthClient.Flows.LogoutAsync(); + } +} +``` + +Logout is distinct from clearing local UI state. + +Removing a local authentication indicator does not necessarily revoke authoritative server-managed authentication state. -- Ends current session -- Clears authentication state +Use the supported logout flow to terminate authentication. -
+## 13. Logout a Specific Device -## πŸ“± Device Logout Variants +UltimateAuth supports revoking the session chain associated with a particular device context. + +The relevant method is: ```csharp -await UAuthClient.Flows.LogoutMyDeviceAsync(sessionId); -await UAuthClient.Flows.LogoutMyOtherDevicesAsync(); -await UAuthClient.Flows.LogoutAllMyDevicesAsync(); +LogoutMyDeviceAsync(LogoutDeviceRequest request) ``` -πŸ‘‰ These operate on **session chains (devices)** +`LogoutDeviceRequest` requires a `SessionChainId`. -
+```csharp +var request = new LogoutDeviceRequest +{ + ChainId = chainId +}; -## πŸ” PKCE Flow (Public Clients) +var result = await UAuthClient.Flows.LogoutMyDeviceAsync(request); -### Start PKCE +if (result.IsSuccess) +{ + // The requested device logout operation succeeded. +} +``` + +This operation targets a session chain, not an arbitrary individual session ID. + +### Current Device Behavior + +The returned `RevokeResult` can indicate whether the affected chain is the current chain. + +When the operation succeeds and `CurrentChain` is true, the client publishes a logout-related state event. + +This allows the application to react when the currently authenticated device context has been revoked. + +## 14. Logout Other Devices + +Use `LogoutMyOtherDevicesAsync` to revoke other device-associated sessions while retaining the current one. ```csharp -await UAuthClient.Flows.BeginPkceAsync(); +var result = await UAuthClient.Flows.LogoutMyOtherDevicesAsync(); + +if (result.IsSuccess) +{ + // Other devices were logged out. +} ``` -### Complete PKCE +This is useful for account security pages that provide a **Log out other devices** action. + +The current authentication context is intended to remain active. + +The server remains responsible for identifying the current session or chain and enforcing the appropriate revocation scope. + +## 15. Logout All Devices + +Use `LogoutAllMyDevicesAsync` to revoke all applicable sessions for the current user. ```csharp -await UAuthClient.Flows.CompletePkceLoginAsync(request); +var result = await UAuthClient.Flows.LogoutAllMyDevicesAsync(); + +if (result.IsSuccess) +{ + // All applicable device sessions were revoked. +} ``` -> Complete PKCE also has try semantics the same as login flow. UltimateAuth suggests to use `TryCompletePkceLoginAsync` for complete interactive experience. +On success, the client publishes a logout-related state event. -πŸ‘‰ Required for: +This operation is useful when a user suspects account compromise or wants to terminate existing authentication sessions across devices. -- SPA -- Blazor WASM -- Mobile apps +In JWT-based modes, previously issued access tokens may remain cryptographically valid until expiration unless additional revocation enforcement is configured. ---- +## 16. Administrative Device Logout + +UltimateAuth also exposes administrative session revocation operations. -## 🚨 Security Note +These methods act on a specified user and require appropriate server-side authorization. -- Public clients MUST use PKCE -- Server clients MAY allow direct login +### Logout One User Device -Direct credential posting disabled by default and throws exception when you directly call login. You can enable it via options. You should only use it for debugging and development purposes. ```csharp -builder.Services.AddUltimateAuthClientBlazor(o => +var result = await UAuthClient.Flows.LogoutUserDeviceAsync( + userKey, + new LogoutDeviceRequest + { + ChainId = chainId + }); + +if (!result.IsSuccess) { - o.Login.AllowCredentialPost = true; -}); ---- + // Handle the failure. +} +``` -## 🎯 Summary +### Logout Other Devices for a User + +```csharp +var result = await UAuthClient.Flows.LogoutUserOtherDevicesAsync(userKey, request); +``` -Authentication in UltimateAuth: +Here, `request` is a `LogoutOtherDevicesRequest` identifying the chain to retain. -- is flow-driven -- adapts to client type -- enforces security by design +### Logout All Devices for a User ---- +```csharp +var result = await UAuthClient.Flows.LogoutAllUserDevicesAsync(userKey); +``` + +Administrative methods do not grant administrative privileges by themselves. + +The server must authorize the actor and validate the target user and requested revocation scope. + +These operations are particularly relevant to administration panels and account security workflows. + +## 17. Building Login Forms with UAuthLoginForm + +UltimateAuth provides `UAuthLoginForm`, a reusable Blazor component that handles authentication form submission without requiring developers to implement the underlying flow orchestration themselves. + +The component supports password-based login, PKCE completion through UAuthHub, configurable submission modes, structured validation feedback, and customizable UI content. + +**You design the login interface. UltimateAuth handles the authentication submission workflow.** + +### Basic Usage + +A login form can be implemented with the following component: + +```razor +@using CodeBeam.UltimateAuth.Client.Blazor + + + + + + + +@code { + private string email = ""; + private string password = ""; +} +``` + +The component automatically uses `TryAndCommit` as its default submission mode. + +When submitted, it: + +1. Validates that the identifier and secret are present. +2. Creates the appropriate authentication request. +3. Calls the UltimateAuth client flow. +4. Performs the configured try/commit behavior. +5. Exposes structured results through `OnTryResult`. + +The example assumes that UltimateAuth's Blazor services have already been registered and that the hosting environment permits the selected login flow. + +### Customizing the Login UI + +`UAuthLoginForm` uses `ChildContent`, allowing developers to use standard HTML elements or their preferred Blazor component library. + +For example, with MudBlazor: + +```razor +@using CodeBeam.UltimateAuth.Client.Blazor +@using CodeBeam.UltimateAuth.Client + + + + Sign in + + + + Sign in + + @if (!string.IsNullOrWhiteSpace(error)) + { + @error + } + + + +@code { + private string email = ""; + private string password = ""; + private string? error; + + private Task HandleLoginResult(IUAuthTryResult result) + { + error = result.IsSuccess ? null : "Unable to sign in."; + return Task.CompletedTask; + } +} +``` + +The form controls remain under your control. + +UltimateAuth does not require a particular visual design, CSS framework, or component library. + +Because the component renders an HTML form internally, place the submit button inside its child content and avoid nesting another HTML form within it. + +### Submission Modes + +Use `SubmitMode` to control how the authentication attempt is executed. + +```razor + + ... + +``` + +| Mode | Behavior | +|---|---| +| `TryOnly` | Validates the authentication attempt without committing login | +| `TryAndCommit` | Attempts authentication and commits on success | +| `DirectCommit` | Submits directly to the commit flow | + +`TryAndCommit` is the default. + +For interactive login forms that need validation feedback, it is generally the recommended choice. + +`DirectCommit` does not invoke `OnTryResult`, because it does not perform the structured try operation. + +### Handling Authentication Results + +The component exposes an `OnTryResult` callback: + +```razor + + ... + +``` + +The callback receives `IUAuthTryResult`, allowing the same component to report password login and PKCE completion results. + +```csharp +private Task HandleResult(IUAuthTryResult result) +{ + if (!result.IsSuccess) + { + errorMessage = "Authentication failed."; + } + + return Task.CompletedTask; +} +``` + +For flow-specific details, inspect the concrete result type: + +```csharp +private Task HandleResult(IUAuthTryResult result) +{ + if (result is TryLoginResult loginResult) + { + if (loginResult.LockoutUntilUtc is not null) + { + errorMessage = "Your account is temporarily locked."; + } + else if (loginResult.RequiresMfa) + { + // Continue the supported additional authentication workflow. + } + else if (!loginResult.IsSuccess) + { + errorMessage = "Unable to sign in."; + } + } + else if (result is TryPkceLoginResult pkceResult) + { + if (pkceResult.RetryWithNewPkce) + { + errorMessage = "Please restart the authentication flow."; + } + else if (!pkceResult.IsSuccess) + { + errorMessage = "Unable to complete authentication."; + } + } + + return Task.CompletedTask; +} +``` + +The callback reports the structured try result. It does not replace authentication state management or server-side validation. + +### Using UAuthLoginForm with PKCE + +The same component also supports PKCE login completion when hosted within a supported UAuthHub environment. + +Set `LoginType` to `Pkce`: + +```razor + + + + + +``` + +In PKCE mode, the component: + +1. Resolves the relevant UAuthHub session context. +2. Retrieves the authorization code and code verifier through the configured Hub services. +3. Builds a `PkceCompleteRequest`. +4. Invokes the corresponding PKCE completion flow. +5. Reports the structured result when a try-enabled submission mode is used. + +The application does not need to manually construct the PKCE completion request for this form. + +**Important:** PKCE mode requires a supported server-side UAuthHub environment. `UAuthLoginForm` checks the available Hub capabilities and throws when PKCE login is requested in an unsupported client profile. + +A Blazor WebAssembly application should initiate PKCE using `BeginPkceAsync`; the Hub-hosted login form handles the completion stage. + +### Return URL Handling + +You can provide an explicit return URL: + +```razor + + ... + +``` + +The component resolves the effective return URL using the following precedence: + +1. Explicit `ReturnUrl` parameter. +2. UAuthHub flow return URL when using PKCE. +3. Current navigation URL. + +This allows a shared login component to participate in different authentication journeys without hardcoding navigation logic into the form. + +### Custom Login Endpoints + +For advanced integrations, the `Endpoint` parameter allows the form's HTML action endpoint to be overridden: + +```razor + + ... + +``` + +The endpoint is resolved relative to the configured UltimateAuth endpoint base path and multi-tenant settings. + +Note that `Endpoint` affects the form's HTML action. Try-based submissions invoke the configured `UAuthClient.Flows` endpoints, so changing `Endpoint` alone does not redirect those SDK requests to a custom endpoint. + +### Submit the Form Programmatically + +`UAuthLoginForm` exposes `SubmitAsync`, allowing submission through a component reference. + +```razor + + + + + + + +@code { + private UAuthLoginForm? loginForm; + + private string email = ""; + private string password = ""; + + private async Task SubmitLoginAsync() + { + if (loginForm is not null) + { + await loginForm.SubmitAsync(); + } + } +} +``` + +`SubmitAsync` must be called after the component has rendered. + +This is useful when submission is initiated from a custom button, dialog, or application-specific interaction. + +### Controlling Enter-Key Submission + +By default, the component supports Enter-key submission. + +You can disable its built-in hidden submit button: + +```razor + + ... + +``` + +This option controls the component's built-in Enter-key submit mechanism. Other submit buttons inside the form may still trigger normal browser form submission behavior. + +### Reloading PKCE Context + +The component exposes `ReloadAsync` to reload its Hub credentials and flow state. + +```razor + + ... + + +@code { + private UAuthLoginForm? loginForm; + + private async Task ReloadLoginContextAsync() + { + if (loginForm is not null) + { + await loginForm.ReloadAsync(); + } + } +} +``` + +This is an advanced capability intended for scenarios where the associated Hub state changes and the component must refresh its internal context. + +It does not initiate a new PKCE authorization flow by itself. + +### Component Parameters + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `Identifier` | `string?` | `null` | Login identifier | +| `Secret` | `string?` | `null` | Authentication secret | +| `LoginType` | `UAuthLoginType` | `Password` | Password or PKCE login | +| `SubmitMode` | `UAuthSubmitMode` | `TryAndCommit` | Submission strategy | +| `ReturnUrl` | `string?` | `null` | Redirect destination | +| `Endpoint` | `string?` | `null` | Custom HTML form endpoint | +| `HubSessionId` | `HubSessionId?` | `null` | Explicit Hub session context | +| `AllowEnterKeyToSubmit` | `bool` | `true` | Built-in Enter-key submit support | +| `OnTryResult` | `EventCallback` | β€” | Structured try result callback | +| `ChildContent` | `RenderFragment?` | β€” | Custom form content | + +### When to Use UAuthLoginForm + +Use `UAuthLoginForm` when building a Blazor login interface that should integrate directly with UltimateAuth's supported authentication flows. + +It is particularly useful when: + +- You want to use your own component library without implementing the authentication submission logic. +- You need structured login validation feedback. +- You want to switch between try-only and commit behavior. +- You are building a UAuthHub login page that completes PKCE. +- You want to share login UI across supported Blazor hosting environments. + +For application-specific workflows that cannot be expressed through the component, use `UAuthClient.Flows` directly. + +**The component handles the login workflow, while your application retains control over presentation, user experience, and surrounding application logic.** + +## 18. Common Integration Mistakes + +### Calling Direct Login from a Public Client + +**Problem:** `LoginAsync` throws because direct credential posting is disabled. + +**Solution:** Use `BeginPkceAsync` and the supported PKCE completion flow. Do not enable `AllowCredentialPost` to bypass the public-client restriction. + +### Treating TryOnly as Authentication + +**Problem:** The application receives `IsSuccess = true` from `TryOnly` and assumes the user is logged in. + +**Solution:** A successful try result does not commit authentication. Use `TryAndCommit` or the appropriate commit flow. + +### Assuming DirectCommit Skips Server Validation + +**Problem:** DirectCommit is interpreted as a faster but less secure authentication mode. + +**Solution:** DirectCommit skips the separate client try stage. The server still validates the actual authentication request. + +### Assuming Every Refresh Rotates Tokens + +**Problem:** The application requires `RefreshOutcome.Rotated` for refresh success. + +**Solution:** Check `RefreshResult.IsSuccess`. Rotation, touch, and no-op behavior depend on the authentication mode and refresh strategy. + +### Using a Session ID for Device Logout + +**Problem:** A session identifier is passed to a device logout operation. + +**Solution:** `LogoutMyDeviceAsync` requires `LogoutDeviceRequest` with a `SessionChainId`. + +### Assuming Logout Immediately Invalidates All JWTs + +**Problem:** An application expects every issued JWT access token to become unusable immediately after server-side logout. + +**Solution:** Understand the enforcement behavior of SemiHybrid and PureJwt. Local JWT validation may continue accepting previously issued tokens until expiration. + +### Using ValidateAsync on Every Render + +**Problem:** Components repeatedly make validation requests just to display authenticated UI. + +**Solution:** Use the appropriate authentication state integration for UI rendering, and reserve explicit validation for operations that require it. + +## Next Steps -πŸ‘‰ Always use `FlowClient` for authentication operations +- [Session Management](/docs/client/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/authentication-modes) β€” Compare authentication behavior across modes. +- [Device Management](/docs/sessions-devices/device-management) β€” Understand device-associated session chains. \ No newline at end of file diff --git a/docs/content/client/authorization.md b/docs/content/client/authorization.md index b3c4a880..34448c2a 100644 --- a/docs/content/client/authorization.md +++ b/docs/content/client/authorization.md @@ -4,133 +4,1358 @@ order: 7 group: client --- +# Authorization -# πŸ›‘ Authorization Guide +UltimateAuth provides a unified client API for managing roles, assigning permissions, controlling user-role relationships, and requesting authorization decisions from the server. -This section explains how to manage roles, permissions, and access control using the UltimateAuth client. +Through `UAuthClient.Authorization`, applications can build administrative authorization interfaces and check whether the current user is allowed to perform a particular action. -## 🧠 Overview +Authorization 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. -Authorization in UltimateAuth is policy-driven and role-based. +This guide covers the current `IAuthorizationClient` API, practical role management workflows, permission patterns, and secure client integration. -On the client, you interact with: +## 1. Understanding Authorization + +UltimateAuth authorization combines several concepts: + +| Concept | Purpose | +|---|---| +| Roles | Named collections of permissions | +| Permissions | Fine-grained capabilities represented by normalized values | +| User-role assignments | Associations between users and roles | +| Policies | Server-side rules that participate in access decisions | +| Authorization context | Information about the actor, resource, action, and current security state | + +A typical authorization relationship looks like this: + +```text +User + | + +-- Manager + | | + | +-- products.read + | +-- products.create + | +-- products.update + | + +-- Auditor + | + +-- reports.read ``` + +A user can have multiple roles, and each role can contain multiple permissions. + +However, **having a permission does not automatically guarantee access**. The server's authorization pipeline may apply additional requirements, such as resource ownership, session validity, tenant isolation, or application policies. + +For the internal decision model, see [Authorization Pipeline Internals](/docs/security/authorization-pipeline). + +## 2. Accessing the Authorization Client + +Inject `IUAuthClient` into a Blazor component: + +```razor +@using CodeBeam.UltimateAuth.Client +@inject IUAuthClient UAuthClient +``` + +Authorization operations are available through: + +```csharp UAuthClient.Authorization ``` -
+This property exposes `IAuthorizationClient`. + +The interface provides three main groups of operations: + +- **Authorization checks:** Evaluate whether the current user may perform an action. +- **Role management:** Create, query, rename, configure, and delete roles. +- **User-role management:** Retrieve, assign, and remove user roles. + +The SDK sends requests to the corresponding UltimateAuth endpoints and returns structured `UAuthResult` values. + +## 3. Understanding Permissions + +A permission is represented by the `Permission` value type. + +```csharp +var permission = Permission.From("products.create"); +``` + +The current implementation normalizes permission values by trimming surrounding whitespace and converting the value to lowercase. + +For example: + +```csharp +var permission = Permission.From(" PRODUCTS.CREATE "); + +Console.WriteLine(permission.Value); +// products.create +``` + +The constructor rejects null, empty, and whitespace-only values. + +### Permission Patterns + +UltimateAuth recognizes three important forms: + +| Pattern | Example | Meaning | +|---|---|---| +| Exact | `products.create` | A specific permission | +| Prefix wildcard | `products.*` | Permissions within the corresponding prefix | +| Global wildcard | `*` | Unrestricted permission matching within the applicable authorization rules | + +Create them using: + +```csharp +var exact = Permission.From("products.create"); + +var prefix = Permission.From("products.*"); + +var global = Permission.Wildcard; +``` + +The type also exposes: + +```csharp +permission.IsWildcard; +permission.IsPrefix; +permission.IsValid; +``` + +### Permission Normalization + +The following values normalize to the same representation: + +```csharp +Permission.From("Products.Create"); +Permission.From("products.create"); +Permission.From(" PRODUCTS.CREATE "); +``` + +All produce: + +```text +products.create +``` + +Use consistent permission names across application services, policies, and role configuration. + +### Wildcards Are Powerful + +For example: + +```csharp +Permission.From("users.*") +``` + +may grant broad access to operations within the `users` permission prefix. + +The global wildcard: + +```csharp +Permission.Wildcard +``` + +is especially sensitive. + +Avoid assigning broad wildcard permissions merely to simplify development. Use least-privilege permissions for ordinary application roles. + +A wildcard is not a substitute for satisfying independent security policies, authentication requirements, or tenant restrictions. + +## 4. Check Authorization + +Use `CheckAsync` to ask the server whether the current user is authorized to perform an action. + +```csharp +var result = await UAuthClient.Authorization.CheckAsync( + new AuthorizationCheckRequest + { + Action = "read", + Resource = "products" + }); + +if (result.IsSuccess && result.Value is { IsAllowed: true }) +{ + // The server authorized this action. +} +``` + +The method returns: + +```csharp +UAuthResult +``` + +### AuthorizationCheckRequest + +The request contains: + +| Property | Type | Description | +|---|---|---| +| `Action` | `string` | Requested operation | +| `Resource` | `string` | Resource being accessed | +| `ResourceId` | `string?` | Optional identifier for a specific resource instance | + +Both `Action` and `Resource` are required. + +The action and resource values must follow the conventions understood by your server-side authorization configuration. + +### Check a Specific Resource + +For example, check access to a particular product: + +```csharp +var result = await UAuthClient.Authorization.CheckAsync( + new AuthorizationCheckRequest + { + Action = "update", + Resource = "products", + ResourceId = productId.ToString() + }); +``` + +Providing `ResourceId` allows the server to evaluate resource-specific rules where supported by the configured authorization pipeline. + +For example, the application may distinguish between updating any product and updating a product owned by the current user. + +Do not assume that sending `ResourceId` automatically enables ownership checking. The relevant server-side policy must implement the required rule. + +## 5. Understanding Authorization Results + +`CheckAsync` returns an `AuthorizationResult` with the following properties: + +| Property | Description | +|---|---| +| `IsAllowed` | Whether the request was authorized | +| `RequiresReauthentication` | Whether the decision indicates reauthentication is required | +| `DenyReason` | Optional denial reason code | + +### Handle All Decision Outcomes + +```csharp +var result = await UAuthClient.Authorization.CheckAsync( + new AuthorizationCheckRequest + { + Action = "delete", + Resource = "products", + ResourceId = productId.ToString() + }); + +if (!result.IsSuccess) +{ + // The authorization request itself failed. +} +else if (result.Value is { RequiresReauthentication: true }) +{ + // Ask the user to authenticate again. +} +else if (result.Value is { IsAllowed: true }) +{ + // The operation is allowed. +} +else +{ + // The operation was denied. + var reason = result.Value?.DenyReason; +} +``` + +An unsuccessful API request and an explicit authorization denial are different outcomes. + +For example: + +- `result.IsSuccess == false` means the authorization request did not complete successfully at the API level. +- `result.Value.IsAllowed == false` means the server returned a denial decision. +- `result.Value.RequiresReauthentication == true` indicates that the caller needs to reestablish authentication. + +Treat failed or unavailable authorization checks as **not authorized** in the UI. + +Never grant access because an authorization request failed. + +## 6. Use Authorization Checks in Blazor + +A common use case is controlling whether an action button should be displayed. + +```razor +@using CodeBeam.UltimateAuth.Client +@using CodeBeam.UltimateAuth.Authorization.Contracts +@inject IUAuthClient UAuthClient + +@if (_canCreate) +{ + +} + +@code { + private bool _canCreate; + + protected override async Task OnInitializedAsync() + { + var result = await UAuthClient.Authorization.CheckAsync( + new AuthorizationCheckRequest + { + Action = "create", + Resource = "products" + }); + + _canCreate = result.IsSuccess && result.Value is { IsAllowed: true }; + } + + private Task CreateProductAsync() + { + // Invoke the protected application operation. + return Task.CompletedTask; + } +} +``` + +This improves the interface by hiding unavailable actions. + +However, the endpoint handling product creation must still enforce authorization independently. + +### Why Client Checks Are Not Security Boundaries + +A user can bypass a hidden button and send a request directly to an endpoint. + +Therefore: + +```text +Client authorization check + | + v +Improve the user experience + | + v +User submits an operation + | + v +Server independently authorizes the operation + | + v +Execute or deny +``` + +Client authorization checks should never be the only protection for sensitive operations. + +## 7. Get My Roles + +Use `GetMyRolesAsync` to retrieve roles assigned to the current authenticated user. + +```csharp +var result = await UAuthClient.Authorization.GetMyRolesAsync(); + +if (result.IsSuccess && result.Value is { } data) +{ + foreach (var role in data.Roles.Items) + { + Console.WriteLine(role.Name); + } +} +``` + +The method returns: + +```csharp +UAuthResult +``` + +### UserRolesGetResult + +The response contains: + +| Property | Description | +|---|---| +| `UserKey` | User whose roles were requested | +| `Roles` | Paginated collection of role assignments | + +Each entry is a `UserRoleInfo`. + +### UserRoleInfo + +| Property | Description | +|---|---| +| `Tenant` | Tenant associated with the assignment | +| `UserKey` | Assigned user | +| `RoleId` | Assigned role | +| `Name` | Role name | +| `AssignedAt` | Assignment timestamp | + +### Pagination + +```csharp +var result = await UAuthClient.Authorization.GetMyRolesAsync( + new PageRequest + { + PageNumber = 1, + PageSize = 20 + }); +``` + +The returned role collection is paginated. + +Do not assume that the first page contains all assigned roles. + +## 8. Get Another User's Roles + +Administrative interfaces can retrieve the roles assigned to a specific user. + +```csharp +var result = await UAuthClient.Authorization.GetUserRolesAsync(userKey, + new PageRequest + { + PageNumber = 1, + PageSize = 20 + }); + +if (result.IsSuccess && result.Value is { } data) +{ + foreach (var role in data.Roles.Items) + { + Console.WriteLine($"{role.Name} β€” {role.AssignedAt}"); + } +} +``` + +This operation requires appropriate administrative authorization. + +The `UserKey` identifies the target user, not the current actor. + +The server determines whether the current actor may inspect that user's role assignments. + +## 9. Query Roles + +Use `QueryRolesAsync` to retrieve roles with pagination and optional filtering. + +```csharp +var result = await UAuthClient.Authorization.QueryRolesAsync( + new RoleQuery + { + PageNumber = 1, + PageSize = 20 + }); + +if (result.IsSuccess && result.Value is { } page) +{ + foreach (var role in page.Items) + { + Console.WriteLine(role.Name); + } +} +``` + +The method returns: + +```csharp +UAuthResult> +``` + +### RoleQuery + +`RoleQuery` inherits from `PageRequest` and adds: + +| Property | Description | +|---|---| +| `Search` | Optional role-name search | +| `IncludeDeleted` | Include deleted roles when supported and authorized | +| `PageNumber` | Requested page | +| `PageSize` | Requested page size | + +### Search Roles + +```csharp +var result = await UAuthClient.Authorization.QueryRolesAsync( + new RoleQuery + { + Search = "manager", + PageNumber = 1, + PageSize = 10 + }); +``` + +### Include Deleted Roles + +```csharp +var result = await UAuthClient.Authorization.QueryRolesAsync( + new RoleQuery + { + IncludeDeleted = true, + PageNumber = 1, + PageSize = 20 + }); +``` + +Use `IncludeDeleted` only where the application needs administrative access to deleted role records. + +The server remains responsible for enforcing the query's permissions and lifecycle rules. + +## 10. Understanding RoleInfo + +Each role returned by the SDK is represented by `RoleInfo`. + +| Property | Type | Description | +|---|---|---| +| `Id` | `RoleId` | Role identifier | +| `Name` | `string` | Role name | +| `Permissions` | `IReadOnlyCollection` | Assigned permissions | +| `CreatedAt` | `DateTimeOffset` | Creation timestamp | +| `UpdatedAt` | `DateTimeOffset?` | Last update timestamp | + +For example: + +```csharp +var result = await UAuthClient.Authorization.QueryRolesAsync( + new RoleQuery + { + PageNumber = 1, + PageSize = 20 + }); + +if (result.IsSuccess && result.Value is { } page) +{ + foreach (var role in page.Items) + { + Console.WriteLine($"Role: {role.Name}"); + + foreach (var permission in role.Permissions) + { + Console.WriteLine($" {permission.Value}"); + } + } +} +``` + +`RoleId` is a strongly typed identifier rather than a plain `Guid`. + +For example: + +```csharp +RoleId roleId = RoleId.From(existingGuid); +``` + +Use `RoleId` for role management requests. + +Do not confuse it with `UserKey` or other identifier types. + +## 11. Create a Role + +Use `CreateRoleAsync` to create a role. + +```csharp +var result = await UAuthClient.Authorization.CreateRoleAsync( + new CreateRoleRequest + { + Name = "Manager" + }); + +if (result.IsSuccess && result.Value is { } role) +{ + Console.WriteLine($"Created: {role.Name}"); + Console.WriteLine($"Role ID: {role.Id}"); +} +``` + +The method returns: + +```csharp +UAuthResult +``` + +Role names must be unique within the relevant tenant according to the server's role-name rules. + +### Create a Role with Permissions + +`CreateRoleRequest` supports an optional initial permission collection. + +```csharp +var result = await UAuthClient.Authorization.CreateRoleAsync( + new CreateRoleRequest + { + Name = "ProductManager", + Permissions = + [ + Permission.From("products.read"), + Permission.From("products.create"), + Permission.From("products.update") + ] + }); +``` + +This allows applications to create and configure a role in one operation. + +The server validates the requested permissions and whether the caller is authorized to create the role. + +### Create a Read-Only Role + +```csharp +var result = await UAuthClient.Authorization.CreateRoleAsync( + new CreateRoleRequest + { + Name = "Auditor", + Permissions = + [ + Permission.From("reports.read"), + Permission.From("products.read") + ] + }); +``` + +Use role names that describe the responsibility or capability represented by the role. + +## 12. Rename a Role + +Use `RenameRoleAsync` to change a role's name. + +```csharp +var result = await UAuthClient.Authorization.RenameRoleAsync( + new RenameRoleRequest + { + Id = roleId, + Name = "SeniorManager" + }); + +if (result.IsSuccess) +{ + // Role renamed. +} +``` + +`RenameRoleRequest` requires: + +- `Id`: The role's `RoleId`. +- `Name`: The new role name. + +The operation may fail if another role already uses the requested name. + +Role renaming does not mean that permissions are automatically changed. -## πŸ”‘ Core Concepts -Roles -- Named groups of permissions -- Assigned to users +After a successful rename, the SDK publishes an `AuthorizationChanged` state event. -Permissions -- Fine-grained access definitions -- Example: users.create.anonymous, users.delete.self, authorization.roles.admin +## 13. Set Role Permissions + +Use `SetRolePermissionsAsync` to configure a role's permissions. + +```csharp +var result = await UAuthClient.Authorization.SetRolePermissionsAsync( + new SetRolePermissionsRequest + { + RoleId = roleId, + Permissions = + [ + Permission.From("products.read"), + Permission.From("products.create"), + Permission.From("products.update") + ] + }); +``` -Policies -- Runtime decision rules -- Enforced automatically on server +The request contains: + +| Property | Description | +|---|---| +| `RoleId` | Role to modify | +| `Permissions` | New permission collection | + +### Important: This Is a Replacement Operation + +`SetRolePermissionsAsync` replaces the role's existing permission collection. + +It does not append permissions to the current collection. + +For example, suppose a role currently contains: + +```text +products.read +products.create +products.update +``` -
+Submitting: -### πŸ“‹ Query Roles -var result = await UAuthClient.Authorization.QueryRolesAsync(new RoleQuery +```csharp +Permissions = +[ + Permission.From("products.read") +] +``` + +means the intended new permission collection contains only: + +```text +products.read +``` + +The other permissions are removed from the role. + +### Safely Add a Permission to an Existing Role + +To preserve existing permissions, first retrieve the role and construct the complete desired collection. + +```csharp +var query = await UAuthClient.Authorization.QueryRolesAsync( + new RoleQuery + { + Search = "ProductManager", + PageNumber = 1, + PageSize = 50 + }); + +if (query.IsSuccess && query.Value is { } page) { - PageNumber = 1, - PageSize = 10 -}); + var role = page.Items.FirstOrDefault(x => x.Name == "ProductManager"); -### βž• Create Role -await UAuthClient.Authorization.CreateRoleAsync(new CreateRoleRequest + if (role is not null) + { + var permissions = role.Permissions.Append(Permission.From("products.delete")).Distinct().ToArray(); + + var result = + await UAuthClient.Authorization.SetRolePermissionsAsync( + new SetRolePermissionsRequest + { + RoleId = role.Id, + Permissions = permissions + }); + } +} +``` + +This is a read-modify-write pattern, not an atomic append operation. + +If multiple administrators edit the same role concurrently, the last successful replacement may overwrite another administrator's changes. + +Applications requiring concurrency-safe editing should implement suitable coordination or version checking at the application level. + +### Remove a Permission + +```csharp +var updatedPermissions = role.Permissions.Where(x => x.Value != "products.delete").ToArray(); + +var result = await UAuthClient.Authorization.SetRolePermissionsAsync( + new SetRolePermissionsRequest + { + RoleId = role.Id, + Permissions = updatedPermissions + }); +``` + +Again, submit the complete desired permission set. + +### Clear All Permissions + +```csharp +var result = await UAuthClient.Authorization.SetRolePermissionsAsync( + new SetRolePermissionsRequest + { + RoleId = roleId, + Permissions = [] + }); +``` + +An empty collection requests that the role have no permissions. + +The server determines whether the operation is permitted. + +## 14. Assign a Role to a User + +Use `AssignRoleToUserAsync` to assign an existing role to a user. + +```csharp +var result = await UAuthClient.Authorization.AssignRoleToUserAsync( + new AssignRoleRequest + { + UserKey = userKey, + RoleName = "Manager" + }); + +if (result.IsSuccess) +{ + // Role assigned. +} +``` + +The request contains: + +| Property | Description | +|---|---| +| `UserKey` | Target user | +| `RoleName` | Existing role name | + +The role must exist, and the current actor must be authorized to assign it. + +### Assign Multiple Roles + +```csharp +foreach (var roleName in new[] { "Manager", "Auditor" }) { - Name = "Manager" -}); + var result = await UAuthClient.Authorization.AssignRoleToUserAsync( + new AssignRoleRequest + { + UserKey = userKey, + RoleName = roleName + }); -### ✏️ Rename Role -await UAuthClient.Authorization.RenameRoleAsync(new RenameRoleRequest + if (!result.IsSuccess) + { + // Handle the failed assignment. + break; + } +} +``` + +Each call is a separate operation. + +This example does not provide transactional, all-or-nothing behavior across the entire collection. + +For complex provisioning workflows, consider whether the application needs a server-side orchestration operation. + +## 15. Remove a Role from a User + +Use `RemoveRoleFromUserAsync`: + +```csharp +var result = await UAuthClient.Authorization.RemoveRoleFromUserAsync( + new RemoveRoleRequest + { + UserKey = userKey, + RoleName = "Manager" + }); + +if (result.IsSuccess) +{ + // Role removed. +} +``` + +Removing a role changes the user's role assignments. + +It does not delete the role itself. + +Other users assigned to that role are unaffected by this particular operation. + +### Important: Effective Permissions May Remain + +A user may receive the same permission through multiple roles. + +For example: + +```text +User + | + +-- Manager + | +-- products.read + | + +-- Auditor + +-- products.read +``` + +Removing `Manager` does not necessarily remove the user's ability to read products because `Auditor` still provides `products.read`. + +To determine whether the user may perform an action, rely on the server's authorization decision rather than inspecting one role assignment in isolation. + +## 16. Delete a Role + +Use `DeleteRoleAsync` to delete a role. + +```csharp +var result = await UAuthClient.Authorization.DeleteRoleAsync( + new DeleteRoleRequest + { + Id = roleId, + Mode = DeleteMode.Soft + }); +``` + +The request supports a deletion mode. + +### Hard Delete + +```csharp +var result = await UAuthClient.Authorization.DeleteRoleAsync( + new DeleteRoleRequest + { + Id = roleId, + Mode = DeleteMode.Hard + }); +``` + +The availability and effect of each deletion mode are determined by server-side lifecycle rules. + +### RoleDeleteResult + +The method returns: + +```csharp +UAuthResult +``` + +The response contains: + +| Property | Description | +|---|---| +| `RoleId` | Deleted role | +| `RemovedAssignments` | Number of user-role assignments removed | +| `Mode` | Applied deletion mode | +| `DeletedAt` | Deletion timestamp | + +### Inspect the Result + +```csharp +var result = await UAuthClient.Authorization.DeleteRoleAsync( + new DeleteRoleRequest + { + Id = roleId, + Mode = DeleteMode.Soft + }); + +if (result.IsSuccess && result.Value is { } deleted) +{ + Console.WriteLine($"Removed assignments: {deleted.RemovedAssignments}"); +} +``` + +Deleting a role removes its assignments from users. + +This can affect multiple users at once. + +For that reason, role deletion should be presented as a sensitive administrative operation with appropriate confirmation. + +## 17. Build a Role Management Page + +The following Blazor example demonstrates querying roles, creating a role, and deleting a selected role. + +```razor +@using CodeBeam.UltimateAuth.Client +@using CodeBeam.UltimateAuth.Authorization.Contracts +@using CodeBeam.UltimateAuth.Core.Contracts +@inject IUAuthClient UAuthClient + +

Role Management

+ + + + + +@if (_loading) +{ +

Loading roles...

+} +else { - Id = roleId, - Name = "NewName" -}); + @foreach (var role in _roles) + { +
+ @role.Name + @role.Permissions.Count permissions + +
+ } +} -### 🧩 Set Permissions -await UAuthClient.Authorization.SetRolePermissionsAsync(new SetRolePermissionsRequest +

Create Role

+ + + +@if (_message is not null) { - RoleId = roleId, - Permissions = new[] +

@_message

+} + +@code { + private IReadOnlyList _roles = []; + + private string _search = ""; + private string _newRoleName = ""; + private string? _message; + + private bool _loading = true; + private bool _busy; + + protected override async Task OnInitializedAsync() { - Permission.From("users.read"), - Permission.From("users.update") + await LoadRolesAsync(); } -}); -πŸ‘‰ Permissions support: + private async Task LoadRolesAsync() + { + _loading = true; + + try + { + var result = await UAuthClient.Authorization.QueryRolesAsync( + new RoleQuery + { + Search = _search, + PageNumber = 1, + PageSize = 50 + }); + + if (result.IsSuccess && result.Value is { } page) + { + _roles = page.Items; + } + else + { + _message = "Unable to load roles."; + } + } + catch (Exception) + { + _message = "The authorization service is unavailable."; + } + finally + { + _loading = false; + } + } + + private async Task CreateRoleAsync() + { + if (_busy || string.IsNullOrWhiteSpace(_newRoleName)) + return; + + _busy = true; + _message = null; + + try + { + var result = await UAuthClient.Authorization.CreateRoleAsync( + new CreateRoleRequest + { + Name = _newRoleName + }); -- Exact match β†’ users.create -- Prefix β†’ users.* -- Wildcard β†’ * + if (result.IsSuccess) + { + _newRoleName = ""; + await LoadRolesAsync(); + } + else + { + _message = "Unable to create role."; + } + } + finally + { + _busy = false; + } + } + + private async Task DeleteRoleAsync(RoleId roleId) + { + // Add a confirmation dialog before deletion in production. + + var result = await UAuthClient.Authorization.DeleteRoleAsync( + new DeleteRoleRequest + { + Id = roleId, + Mode = DeleteMode.Soft + }); + + if (result.IsSuccess) + { + await LoadRolesAsync(); + } + else + { + _message = "Unable to delete role."; + } + } +} +``` + +This example demonstrates the SDK integration rather than a complete production administration interface. + +A production implementation should include pagination, role assignment controls, permission editing, deletion confirmation, and appropriate loading and error handling for every operation. + +## 18. Build a User Role Assignment Interface + +An administrative user details page can display assigned roles and allow administrators to add or remove them. + +### Load User Roles + +```csharp +var result = await UAuthClient.Authorization.GetUserRolesAsync(userKey, + new PageRequest + { + PageNumber = 1, + PageSize = 50 + }); -### ❌ Delete Role -await UAuthClient.Authorization.DeleteRoleAsync(new DeleteRoleRequest +if (result.IsSuccess && result.Value is { } data) { - Id = roleId -}); + var assignedRoles = data.Roles.Items.ToList(); +} +``` + +### Assign a Selected Role -πŸ‘‰ Automatically removes role assignments from users +```csharp +var result = await UAuthClient.Authorization.AssignRoleToUserAsync( + new AssignRoleRequest + { + UserKey = userKey, + RoleName = selectedRoleName + }); -### πŸ‘€ Assign Role to User -await UAuthClient.Authorization.AssignRoleToUserAsync(new AssignRoleRequest +if (result.IsSuccess) { - UserKey = userKey, - RoleName = "Manager" -}); + // Reload the user's assigned roles. +} +``` + +### Remove a Selected Role + +```csharp +var result = await UAuthClient.Authorization.RemoveRoleFromUserAsync( + new RemoveRoleRequest + { + UserKey = userKey, + RoleName = selectedRoleName + }); -### βž– Remove Role -await UAuthClient.Authorization.RemoveRoleFromUserAsync(new RemoveRoleRequest +if (result.IsSuccess) { - UserKey = userKey, - RoleName = "Manager" -}); + // Reload the user's assigned roles. +} +``` + +This pattern works well alongside `UAuthClient.Users` for building a unified user administration interface. + +See [User Management](/docs/client/user-management). + +## 19. Authorization State Events + +The current `UAuthAuthorizationClient` publishes: + +```csharp +UAuthStateEvent.AuthorizationChanged +``` + +after successful authorization mutations. + +| Operation | Publishes `AuthorizationChanged` | +|---|---| +| `AssignRoleToUserAsync` | Yes | +| `RemoveRoleFromUserAsync` | Yes | +| `RenameRoleAsync` | Yes | +| `SetRolePermissionsAsync` | Yes | +| `DeleteRoleAsync` | Yes | +| `CreateRoleAsync` | No | +| `QueryRolesAsync` | No | +| `GetMyRolesAsync` | No | +| `GetUserRolesAsync` | No | +| `CheckAsync` | No | + +The event follows the configured client state event handling mode. + +It can be used to refresh navigation menus, role assignment views, or permission-sensitive UI. + +However, it is a **local client event**, not a guarantee that all active clients or distributed caches have immediately observed the change. + +For example, an administrator assigning a role to another user does not automatically mean that user's open browser receives the administrator's local event. + +Applications requiring cross-client updates should provide an appropriate synchronization mechanism. + +## 20. Authorization Changes and Active Sessions + +Role and permission changes may affect active users. + +However, the timing of those changes depends on how authorization data is resolved and cached by the server. + +For example: + +1. An administrator removes a role. +2. The server persists the new role assignment state. +3. A subsequent authorization evaluation uses the effective authorization state available to the server. + +If the application caches authorization information, changes may not become visible everywhere immediately. + +Do not assume that a client event invalidates every active session or refreshes every distributed authorization cache. -### πŸ“‹ Get User Roles -var roles = await UAuthClient.Authorization.GetUserRolesAsync(userKey); -πŸ” Check Authorization -var result = await UAuthClient.Authorization.CheckAsync(new AuthorizationCheckRequest +For sensitive operations, authorization should be evaluated against sufficiently current server-side state. + +### Roles Are Not Sessions + +Removing a role and revoking a session are different operations. + +Removing a role changes authorization data. + +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). + +## 21. Multi-Tenant Authorization + +UltimateAuth authorization operations are scoped to the current tenant context. + +Role names are unique within the relevant tenant, and role assignments belong to the applicable tenant. + +For example: + +```text +Tenant A + | + +-- Manager + | +-- products.read + | + +-- User A + +Tenant B + | + +-- Manager + | +-- reports.read + | + +-- User B +``` + +The same role name may represent different permission sets in different tenants. + +The client does not gain cross-tenant access merely by providing another user's `UserKey`. + +The server must enforce tenant isolation and administrative authorization. + +When implementing multi-tenant administration, do not assume role names or permission configurations are globally shared. + +## 22. Handling Authorization Errors + +Authorization methods return `UAuthResult` or `UAuthResult`. + +### Handle Role Creation Errors + +```csharp +var result = await UAuthClient.Authorization.CreateRoleAsync( + new CreateRoleRequest + { + Name = "Manager" + }); + +if (result.IsSuccess) +{ + // Role created. +} +else if (result.Http.IsConflict) +{ + // A conflicting role may already exist. +} +else if (result.Http.IsForbidden) { - Action = "users.delete" -}); + // Caller lacks permission. +} +else if (result.Http.IsBadRequest) +{ + // Invalid request. +} +else +{ + // Handle another failure. +} +``` -πŸ‘‰ Returns: +### Handle Authorization Check Failures -- Allow / Deny -- Reason (if denied) +```csharp +var result = await UAuthClient.Authorization.CheckAsync( + new AuthorizationCheckRequest + { + Action = "update", + Resource = "products" + }); + +var allowed = result.IsSuccess && result.Value is { IsAllowed: true }; +``` + +This fail-closed pattern is appropriate when determining whether the UI should expose a protected operation. + +Use the server's structured error information for diagnostics where appropriate. + +Do not expose sensitive policy internals unnecessarily. + +## 23. Security Considerations + +### Server Authorization Is Authoritative + +Client-side checks improve usability but cannot protect an endpoint by themselves. + +Every protected operation must enforce authorization on the server. + +### Follow Least Privilege + +Assign only the permissions a role actually needs. + +Avoid using `*` or broad prefix wildcards for ordinary users. + +### Protect Administrative Operations -
+Role creation, permission replacement, role assignment, and role deletion are privileged operations. -## 🧠 Permission Model +The server must authorize each mutation independently. -Permissions are normalized and optimized: +### Treat Permission Replacement Carefully -- Full access β†’ * -- Group access β†’ users.* -- Specific β†’ users.create +`SetRolePermissionsAsync` replaces the entire permission collection. -πŸ‘‰ Internally compiled for fast evaluation +Always inspect the existing role configuration before submitting changes. -
+### Handle Reauthentication Requirements + +An authorization decision can indicate `RequiresReauthentication`. + +Applications should handle this state explicitly rather than treating it as an ordinary permission denial. + +### Respect Tenant Boundaries + +Roles and assignments must remain within their authorized tenant context. + +### Do Not Infer Effective Access from One Role + +A user can have multiple roles, overlapping permissions, and additional policy restrictions. + +Use the server's authorization decision when determining whether an action is permitted. + +### Account for Cached Authorization State + +Permission changes may not propagate instantly to every active client or cached authorization context. + +Design sensitive operations around server-side enforcement and appropriate cache invalidation. + +## 24. Putting Everything Together + +A typical administrative authorization workflow may look like this: + +```text +Create role + | + v +Configure permissions + | + v +Assign role to user + | + v +User requests protected action + | + v +Server evaluates authorization + | + +-- Allow --> Execute operation + | + +-- Deny --> Reject operation + | + +-- Reauthentication required + | + v + Authenticate again +``` -## πŸ” Security Notes -- Authorization is enforced server-side -- Client only requests actions -- Policies may override permissions -- Cross-tenant access is denied by default +UltimateAuth separates role management from authorization enforcement. -## 🎯 Summary +This allows applications to provide flexible administrative interfaces while keeping access decisions under server control. -Authorization in UltimateAuth: +## Next Steps -- combines roles + permissions + policies -- is evaluated through a decision pipeline -- supports fine-grained and scalable access control +- [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. +- [Session Security Model](/docs/security/session-security-model) β€” Understand session validity and security boundaries. \ No newline at end of file diff --git a/docs/content/client/credentials.md b/docs/content/client/credentials.md index 6ce81fb6..8d5dd9f4 100644 --- a/docs/content/client/credentials.md +++ b/docs/content/client/credentials.md @@ -4,171 +4,1075 @@ order: 6 group: client --- +# Credentials -# πŸ”‘ Credential Management Guide +UltimateAuth provides a dedicated client API for managing authentication credentials, including password creation, password changes, credential revocation, account recovery, and administrative credential operations. -This section explains how to manage user credentials (such as passwords) using the UltimateAuth client. +Through `UAuthClient.Credentials`, applications can implement credential management without manually constructing requests to the underlying endpoints. -## 🧠 Overview +Credential management is security-sensitive. UltimateAuth separates authenticated credential changes from recovery workflows and provides configurable session invalidation policies. -Credential operations are handled via: +This guide covers the current `ICredentialClient` API, its request and response models, and practical implementation patterns. + +## 1. Understanding Credentials + +A credential is authentication material associated with a user. + +Passwords are the primary credential type supported by the current credential management request contracts. + +UltimateAuth also defines a broader `CredentialType` enum for additional authentication mechanisms. + +```csharp +public enum CredentialType +{ + Password = 0, + OneTimeCode = 10, + EmailOtp = 11, + SmsOtp = 12, + Totp = 30, + Passkey = 100, + Certificate = 200, + ApiKey = 210, + External = 1000 +} +``` + +These enum values do not mean every credential type is currently supported by the credential management API. + +For example, `AddCredentialRequest` currently accepts a secret but does not expose a credential type selector. + +Do not assume that passkeys, API keys, or external authentication credentials can be created through `AddMyAsync`. + +## 2. Accessing the Credential Client + +Inject `IUAuthClient` into your Blazor component: + +```razor +@using CodeBeam.UltimateAuth.Client +@inject IUAuthClient UAuthClient +``` + +Credential operations are available through: ```csharp -UAuthClient.Credentials... +UAuthClient.Credentials ``` -This includes: +The `Credentials` property exposes `ICredentialClient`. + +Its operations fall into three groups: + +| Group | Purpose | +|---|---| +| Self-service | Add, change, and revoke the current user's credentials | +| Recovery | Begin and complete a credential reset | +| Administration | Manage another user's credentials | -- password management -- credential reset flows -- admin credential operations +Recovery operations are designed to support users who cannot authenticate normally. -
+Administrative operations require appropriate server-side authorization. -## πŸ” Change Password (Self) +## 3. Change Your Password + +Use `ChangeMyAsync` when an authenticated user knows their current password and wants to replace it. ```csharp -await UAuthClient.Credentials.ChangeMyAsync(new ChangeCredentialRequest +var result = await UAuthClient.Credentials.ChangeMyAsync( + new ChangeCredentialRequest + { + CurrentSecret = currentPassword, + NewSecret = newPassword + }); + +if (result.IsSuccess && result.Value is { IsSuccess: true }) { - CurrentSecret = "old-password", - NewSecret = "new-password" -}); + // Password changed successfully. +} +``` + +The method returns: + +```csharp +UAuthResult +``` + +### ChangeCredentialRequest + +| Property | Type | Description | +|---|---|---| +| `CurrentSecret` | `string?` | Current password; required for self-service changes | +| `NewSecret` | `string` | New password | + +`CurrentSecret` is nullable in the shared request contract because administrative changes do not require the user's existing password. + +For self-service password changes, provide the current secret. + +### CredentialChangeResult + +The response contains: + +| Property | Description | +|---|---| +| `IsSuccess` | Whether the credential change succeeded | +| `Error` | Optional operation-level failure | +| `Type` | Credential type affected by the change | + +For example: + +```csharp +var result = await UAuthClient.Credentials.ChangeMyAsync( + new ChangeCredentialRequest + { + CurrentSecret = currentPassword, + NewSecret = newPassword + }); + +if (!result.IsSuccess) +{ + errorMessage = "Unable to change your password."; +} +else if (result.Value is { IsSuccess: false } change) +{ + errorMessage = change.Error ?? "Password change failed."; +} +else +{ + successMessage = "Your password has been changed."; +} ``` -πŸ‘‰ Requires current password -πŸ‘‰ Triggers `CredentialsChangedSelf` event +Check both the API-level result and the operation-level result when processing credential changes. -## πŸ” Reset Password (Self) +### Client State Event -### Begin Reset +After a successful `ChangeMyAsync` operation, the SDK publishes: ```csharp -var begin = await UAuthClient.Credentials.BeginResetMyAsync( +UAuthStateEvent.CredentialsChangedSelf +``` + +The event uses the configured client state event handling mode. + +It can help the application refresh authentication-related UI. + +It does not replace the server's credential or session security checks. + +## 4. Build a Password Change Form + +The following example demonstrates an authenticated password change form in Blazor. + +```razor +@using CodeBeam.UltimateAuth.Client +@using CodeBeam.UltimateAuth.Credentials.Contracts +@inject IUAuthClient UAuthClient + +

Change Password

+ + + + + + + + + + + + + +@if (!string.IsNullOrWhiteSpace(_message)) +{ +

@_message

+} + +@code { + private readonly PasswordChangeModel _model = new(); + private bool _busy; + private string? _message; + + private async Task ChangePasswordAsync() + { + if (_busy) + return; + + _busy = true; + _message = null; + + try + { + var result = await UAuthClient.Credentials.ChangeMyAsync( + new ChangeCredentialRequest + { + CurrentSecret = _model.CurrentPassword, + NewSecret = _model.NewPassword + }); + + if (result.IsSuccess && + result.Value is { IsSuccess: true }) + { + _model.CurrentPassword = ""; + _model.NewPassword = ""; + _message = "Password changed successfully."; + } + else + { + _message = "Unable to change password."; + } + } + catch (Exception) + { + _message = "The credential service is unavailable."; + } + finally + { + _busy = false; + } + } + + private sealed class PasswordChangeModel + { + [System.ComponentModel.DataAnnotations.Required] + public string CurrentPassword { get; set; } = ""; + + [System.ComponentModel.DataAnnotations.Required] + public string NewPassword { get; set; } = ""; + } +} +``` + +This example demonstrates the client integration. + +A production form should additionally provide password confirmation, appropriate password policy feedback, and safeguards against repeated submissions. + +Never log password values or include them in application telemetry. + +## 5. Add a Credential + +Use `AddMyAsync` to add a credential to the current user. + +```csharp +var result = await UAuthClient.Credentials.AddMyAsync( + new AddCredentialRequest + { + Secret = password + }); + +if (result.IsSuccess && result.Value is { IsSuccess: true } added) +{ + Console.WriteLine($"Credential ID: {added.Id}"); +} +``` + +The method returns: + +```csharp +UAuthResult +``` + +### AddCredentialRequest + +The current request contract is: + +```csharp +public sealed record AddCredentialRequest +{ + public required string Secret { get; init; } +} +``` + +Credential type and source selection are not currently exposed by this request. + +### CredentialAddResult + +The response contains: + +| Property | Description | +|---|---| +| `IsSuccess` | Whether the credential was added | +| `Error` | Optional failure information | +| `Id` | Created credential ID, when available | +| `Type` | Created credential type, when available | + +Adding a credential is subject to the server's credential policies. + +For example, the server may reject attempts to create a duplicate or otherwise unsupported credential. + +## 6. Password Change Versus Password Recovery + +UltimateAuth distinguishes between two different workflows. + +| Workflow | Typical situation | API | +|---|---|---| +| Password change | Authenticated user knows the current password | `ChangeMyAsync` | +| Password recovery | User cannot authenticate using the existing password | `BeginResetAsync` β†’ `CompleteResetAsync` | + +A password change verifies the existing secret. + +Password recovery uses a separate proof mechanism, such as a reset token or code delivered through a trusted channel. + +**Do not use administrative password changes as a replacement for public password recovery.** + +## 7. Begin Password Recovery + +Use `BeginResetAsync` to initiate a credential recovery workflow. + +```csharp +var result = await UAuthClient.Credentials.BeginResetAsync( new BeginResetCredentialRequest { - Identifier = "user@mail.com" + Identifier = "john@example.com", + CredentialType = CredentialType.Password, + ResetCodeType = ResetCodeType.Token }); + +if (result.IsSuccess && result.Value is { } reset) +{ + Console.WriteLine($"Expires at: {reset.ExpiresAt}"); +} +``` + +The method returns: + +```csharp +UAuthResult ``` -### Complete Reset +### BeginResetCredentialRequest + +| Property | Description | +|---|---| +| `Identifier` | User identifier associated with the recovery request | +| `CredentialType` | Credential type to recover | +| `ResetCodeType` | Reset proof format | + +### Reset Code Types ```csharp -await UAuthClient.Credentials.CompleteResetMyAsync( +public enum ResetCodeType +{ + Token = 0, + Code = 10 +} +``` + +Use `Token` for a token-based recovery workflow. + +Use `Code` for a code-based recovery workflow. + +The delivery mechanism is determined by the application's configured server-side notification implementation. + +### CredentialBeginResetResult + +The result contains: + +```csharp +public sealed record CredentialBeginResetResult +{ + public DateTimeOffset ExpiresAt { get; init; } +} +``` + +**The reset token or code is not returned in the API response.** + +This prevents the recovery endpoint from directly exposing the secret needed to take over an account. + +The proof must be delivered through the configured trusted notification channel. + +## 8. Complete Password Recovery + +After the user receives a reset token or code, call `CompleteResetAsync`. + +```csharp +var result = await UAuthClient.Credentials.CompleteResetAsync( new CompleteResetCredentialRequest { - Token = "reset-token", - NewSecret = "new-password" + Identifier = "john@example.com", + CredentialType = CredentialType.Password, + ResetToken = resetToken, + NewSecret = newPassword }); + +if (result.IsSuccess && result.Value is { IsSuccess: true }) +{ + // Password recovery completed. +} ``` -πŸ‘‰ Typically used in: +The method returns: + +```csharp +UAuthResult +``` -- forgot password flows -- email-based reset flows +### CompleteResetCredentialRequest -## βž• Add Credential (Self) +| Property | Description | +|---|---| +| `Identifier` | Identifier used for the recovery workflow | +| `CredentialType` | Credential type being recovered | +| `ResetToken` | Delivered recovery proof | +| `NewSecret` | Replacement secret | + +The property is named `ResetToken` even when the selected reset format is a code. + +Use the exact contract property name. + +### CredentialActionResult ```csharp -await UAuthClient.Credentials.AddMyAsync(new AddCredentialRequest +public sealed record CredentialActionResult { - Secret = "password" -}); + public bool IsSuccess { get; init; } + public string? Error { get; init; } +} ``` -## ❌ Revoke Credential (Self) +For example: ```csharp -await UAuthClient.Credentials.RevokeMyAsync(new RevokeCredentialRequest +if (!result.IsSuccess) { - CredentialId = credentialId -}); + errorMessage = "Unable to complete password recovery."; +} +else if (result.Value is { IsSuccess: false } action) +{ + errorMessage = action.Error ?? "Password recovery failed."; +} +else +{ + successMessage = "Your password has been reset."; +} +``` + +After a successful `CompleteResetAsync` operation, the SDK publishes: + +```csharp +UAuthStateEvent.CredentialsChanged ``` -πŸ‘‰ Useful for: +The event is not the same as `CredentialsChangedSelf`, which is published by `ChangeMyAsync`. -- removing login methods -- invalidating compromised credentials +## 9. Complete Recovery Using a Numeric Code -
+UltimateAuth supports code-based recovery through `ResetCodeType.Code`. + +Begin the recovery: + +```csharp +var begin = await UAuthClient.Credentials.BeginResetAsync( + new BeginResetCredentialRequest + { + Identifier = email, + CredentialType = CredentialType.Password, + ResetCodeType = ResetCodeType.Code + }); +``` -## πŸ‘‘ Admin: Change User Credential +Then submit the code received by the user: ```csharp -await UAuthClient.Credentials.ChangeUserAsync(userKey, new ChangeCredentialRequest +var complete = await UAuthClient.Credentials.CompleteResetAsync( + new CompleteResetCredentialRequest + { + Identifier = email, + CredentialType = CredentialType.Password, + ResetToken = enteredCode, + NewSecret = newPassword + }); +``` + +The same completion API handles token-based and code-based recovery. + +The server is responsible for validating the submitted proof against the active recovery state. + +## 10. Build a Forgot Password Form + +A typical forgot-password interface has two stages: + +1. Request a recovery token or code. +2. Submit the received proof with a new password. + +### Step One: Request Recovery + +```razor +@using CodeBeam.UltimateAuth.Client +@using CodeBeam.UltimateAuth.Core.Domain +@using CodeBeam.UltimateAuth.Credentials.Contracts +@inject IUAuthClient UAuthClient + +

Forgot Password

+ + + + + +@if (_message is not null) { - NewSecret = "new-password" -}); +

@_message

+} + +@code { + private string _email = ""; + private string? _message; + private bool _busy; + + private async Task BeginRecoveryAsync() + { + if (_busy || string.IsNullOrWhiteSpace(_email)) + return; + + _busy = true; + + try + { + var result = await UAuthClient.Credentials.BeginResetAsync( + new BeginResetCredentialRequest + { + Identifier = _email, + CredentialType = CredentialType.Password, + ResetCodeType = ResetCodeType.Token + }); + + _message = result.IsSuccess + ? "If the account is eligible, recovery instructions will be sent." + : "Unable to process the recovery request."; + } + catch (Exception) + { + _message = "Recovery is temporarily unavailable."; + } + finally + { + _busy = false; + } + } +} ``` -πŸ‘‰ Does NOT require current password +The confirmation message intentionally avoids revealing whether an account exists. + +Applications should avoid introducing account enumeration through UI messages, timing differences, or diagnostic details. + +### Step Two: Complete Recovery + +```razor +@using CodeBeam.UltimateAuth.Client +@using CodeBeam.UltimateAuth.Core.Domain +@using CodeBeam.UltimateAuth.Credentials.Contracts +@inject IUAuthClient UAuthClient + +

Reset Password

+ + + + + + + +@if (_message is not null) +{ +

@_message

+} + +@code { + private string _email = ""; + private string _token = ""; + private string _newPassword = ""; + + private string? _message; + private bool _busy; + + private async Task CompleteRecoveryAsync() + { + if (_busy) + return; -## πŸ‘‘ Admin: Add Credential + _busy = true; + _message = null; + + try + { + var result = await UAuthClient.Credentials.CompleteResetAsync( + new CompleteResetCredentialRequest + { + Identifier = _email, + CredentialType = CredentialType.Password, + ResetToken = _token, + NewSecret = _newPassword + }); + + if (result.IsSuccess && result.Value is { IsSuccess: true }) + { + _token = ""; + _newPassword = ""; + _message = "Password reset successfully."; + } + else + { + _message = "Unable to reset password."; + } + } + catch (Exception) + { + _message = "Recovery is temporarily unavailable."; + } + finally + { + _busy = false; + } + } +} +``` + +These examples show the SDK integration. + +A production implementation should also validate user input, protect recovery proofs from accidental disclosure, and avoid logging reset tokens or passwords. + +## 11. Configure Reset Notifications + +UltimateAuth uses `ICredentialResetNotifier` to deliver recovery tokens and codes. ```csharp -await UAuthClient.Credentials.AddUserAsync(userKey, new AddCredentialRequest +public interface ICredentialResetNotifier { - Secret = "password" -}); + Task NotifyAsync(CredentialResetNotification notification, CancellationToken ct = default); +} +``` + +The notification includes: + +```csharp +public sealed record CredentialResetNotification( + TenantKey Tenant, + UserKey UserKey, + CredentialType CredentialType, + ResetCodeType CodeType, + string Token, + DateTimeOffset ExpiresAt); +``` + +The application must provide a secure delivery implementation. + +### Register the Notifier + +```csharp +builder.Services.AddScoped(); +``` + +`ApplicationCredentialResetNotifier` is an application-defined implementation. + +It might deliver the proof through email, SMS, or another trusted channel associated with the user's account. + +The notifier should not expose the proof to an arbitrary address supplied by an unauthenticated caller. + +### Missing Notifier Configuration + +UltimateAuth provides a `NotConfiguredCredentialResetNotifier` fallback. + +When recovery requires notification but no implementation has been configured, the fallback throws an explanatory exception. + +This prevents a reset flow from silently succeeding without a configured proof delivery mechanism. + +**Never return recovery tokens to anonymous clients as a substitute for configuring the notifier.** + +## 12. Recovery Security Behavior + +Credential recovery is designed around several security properties. + +### Account Enumeration Resistance + +Recovery initiation should not reveal whether a submitted identifier belongs to an account. + +Applications should preserve this behavior in their own UI and logging policies. + +### Proof Expiration + +Recovery proofs have a server-controlled lifetime. + +The client does not choose an arbitrary expiration time. + +### Single-Use Proofs + +A successfully consumed recovery proof must not be reusable. + +After successful completion, a second attempt using the same proof should be rejected. + +### Identifier and Target Binding + +Recovery must apply to the correct user and credential. + +Administrative recovery must not permit an identifier belonging to one user to reset another user's credential. + +### Session Revocation + +Successful recovery may invalidate existing sessions according to the configured credential session policy. + +By default, UltimateAuth revokes all session chains after recovery. + +For further details, see [Session Security Model](/docs/security/session-security-model). + +## 13. Revoke Your Credential + +Use `RevokeMyAsync` to revoke a credential belonging to the authenticated user. + +```csharp +var result = await UAuthClient.Credentials.RevokeMyAsync( + new RevokeCredentialRequest + { + Id = credentialId + }); + +if (result.IsSuccess) +{ + // Credential revoked. +} ``` -## πŸ‘‘ Admin: Revoke Credential +The request contract is: ```csharp -await UAuthClient.Credentials.RevokeUserAsync(userKey, new RevokeCredentialRequest +public sealed record RevokeCredentialRequest { - CredentialId = credentialId -}); + public Guid Id { get; init; } +} +``` + +Use the credential record ID, not a user ID or session ID. + +Credential revocation is distinct from credential deletion. + +Revocation changes the credential's usability, while deletion uses a separate lifecycle operation available through the administrative API. + +### State Event + +After successful `RevokeMyAsync`, the SDK publishes: + +```csharp +UAuthStateEvent.CredentialsChanged +``` + +The application can use this event to refresh credential-related UI. + +Credential revocation may affect authentication eligibility and session continuation depending on server policy. + +## 14. Administrative Credential Management + +Administrative credential operations target a specific user. + +They require appropriate server-side authorization. + +### Add a Credential + +```csharp +var result = await UAuthClient.Credentials.AddUserAsync(userKey, + new AddCredentialRequest + { + Secret = temporaryPassword + }); + +if (result.IsSuccess && result.Value is { IsSuccess: true } added) +{ + Console.WriteLine(added.Id); +} +``` + +The server validates whether the administrative actor may add a credential for the target user. + +### Change a User's Password + +```csharp +var result = await UAuthClient.Credentials.ChangeUserAsync(userKey, + new ChangeCredentialRequest + { + NewSecret = newPassword + }); + +if (result.IsSuccess && result.Value is { IsSuccess: true }) +{ + // Administrative password change succeeded. +} +``` + +`CurrentSecret` is not required for an authorized administrative credential change. + +This makes administrative credential changes a privileged operation. + +Do not expose this capability through ordinary self-service UI. + +### Revoke a User's Credential + +```csharp +var result = await UAuthClient.Credentials.RevokeUserAsync(userKey, + new RevokeCredentialRequest + { + Id = credentialId + }); +``` + +### Begin Administrative Recovery + +```csharp +var result = await UAuthClient.Credentials.BeginResetUserAsync(userKey, + new BeginResetCredentialRequest + { + Identifier = userEmail, + CredentialType = CredentialType.Password, + ResetCodeType = ResetCodeType.Token + }); +``` + +### Complete Administrative Recovery + +```csharp +var result = await UAuthClient.Credentials.CompleteResetUserAsync(userKey, + new CompleteResetCredentialRequest + { + Identifier = userEmail, + CredentialType = CredentialType.Password, + ResetToken = recoveryToken, + NewSecret = newPassword + }); ``` -## πŸ‘‘ Admin: Reset Credential +Administrative recovery remains a proof-based workflow. -### Begin +The server validates the relationship between the target user and the supplied identifier. + +### Delete a User's Credential ```csharp -await UAuthClient.Credentials.BeginResetUserAsync(userKey, request); +var result = await UAuthClient.Credentials.DeleteUserAsync(userKey, + new DeleteCredentialRequest + { + Id = credentialId, + Mode = DeleteMode.Soft + }); ``` -### Complete +### Hard Delete ```csharp -await UAuthClient.Credentials.CompleteResetUserAsync(userKey, request); +var result = await UAuthClient.Credentials.DeleteUserAsync(userKey, + new DeleteCredentialRequest + { + Id = credentialId, + Mode = DeleteMode.Hard + }); ``` -## ❌ Delete Credential (Admin) +Hard deletion is a destructive administrative operation. + +Its availability and effects depend on server authorization and credential lifecycle policies. + +Administrative operations do not automatically publish the same local credential state events as self-service operations. + +## 15. Credential Changes and Session Invalidation + +UltimateAuth allows applications to configure how existing sessions are handled after credential changes and recovery. + +The server exposes: ```csharp -await UAuthClient.Credentials.DeleteUserAsync(userKey, new DeleteCredentialRequest +UAuthServerOptions.CredentialSessionPolicy +``` + +The policy contains: + +| Option | Default | Purpose | +|---|---|---| +| `OnSelfChange` | `PreserveCurrent` | Sessions after self-service credential changes | +| `OnAdminChange` | `RevokeAll` | Sessions after administrative credential changes | +| `OnRecovery` | `RevokeAll` | Sessions after credential recovery | + +### CredentialSessionBehavior + +```csharp +public enum CredentialSessionBehavior +{ + PreserveAll = 0, + PreserveCurrent = 10, + RevokeAll = 20 +} +``` + +| Behavior | Meaning | +|---|---| +| `PreserveAll` | Preserve existing session chains | +| `PreserveCurrent` | Preserve the current chain and revoke other chains | +| `RevokeAll` | Revoke all applicable session chains | + +### Configure the Policy + +```csharp +builder.Services.AddUltimateAuthServer(options => { - CredentialId = credentialId + options.CredentialSessionPolicy.OnSelfChange = CredentialSessionBehavior.PreserveCurrent; + options.CredentialSessionPolicy.OnAdminChange = CredentialSessionBehavior.RevokeAll; + options.CredentialSessionPolicy.OnRecovery = CredentialSessionBehavior.RevokeAll; }); ``` -## 🧠 Credential Model +The defaults are intended to balance usability and security. + +For example, an authenticated user changing their password can retain their current device while other devices are invalidated. + +An administrator changing a user's password typically requires broader session revocation. + +### Recovery and PreserveCurrent + +`PreserveCurrent` is not meaningful for many recovery workflows because the user may not have an authenticated current session. + +UltimateAuth's recovery policy documentation specifies that `PreserveCurrent` is treated as `RevokeAll` in this context. + +### Authentication Mode Considerations + +The precise effect of session invalidation depends on the authentication mode. + +In particular, a locally validated JWT access token may remain usable until expiration if the resource server does not consult current revocation state. + +Credential session policies should be understood together with the application's token validation strategy. + +See [Token Security](/docs/security/token-security). + +## 16. Handling Credential Results + +Credential operations use `UAuthResult` or `UAuthResult`. + +Several operations also return a domain-level success flag. + +### Credential Creation + +```csharp +var result = await UAuthClient.Credentials.AddMyAsync(request); + +if (!result.IsSuccess) +{ + // API-level failure. +} +else if (result.Value is { IsSuccess: false } add) +{ + // Credential creation was rejected. + Console.WriteLine(add.Error); +} +else if (result.Value is { IsSuccess: true } added) +{ + Console.WriteLine(added.Id); +} +``` + +### Credential Recovery + +```csharp +var result = await UAuthClient.Credentials.CompleteResetAsync(request); + +if (!result.IsSuccess) +{ + // API-level failure. +} +else if (result.Value is { IsSuccess: false } action) +{ + // Recovery did not complete successfully. +} +else +{ + // Recovery succeeded. +} +``` + +### HTTP Status Helpers + +```csharp +if (result.Http.IsUnauthorized) +{ + // Authentication required. +} +else if (result.Http.IsForbidden) +{ + // Operation not permitted. +} +else if (result.Http.IsConflict) +{ + // Conflicting or expired operation state. +} +else if (result.Http.IsBadRequest) +{ + // Invalid request. +} +``` + +The actual status depends on the server's response. + +Avoid exposing sensitive internal credential errors directly to end users. + +## 17. Credential State Events + +The current `UAuthCredentialClient` publishes the following events after successful self-service operations. + +| Operation | Event | +|---|---| +| `ChangeMyAsync` | `CredentialsChangedSelf` | +| `RevokeMyAsync` | `CredentialsChanged` | +| `CompleteResetAsync` | `CredentialsChanged` | + +The following operations do not publish those events in the current client implementation: + +- `AddMyAsync` +- `BeginResetAsync` +- Administrative credential operations + +Applications should refresh relevant data explicitly where needed. + +Events help synchronize client state but do not themselves establish that a credential or session has been invalidated on the server. + +## 18. Security Considerations + +### Never Expose Stored Secrets + +Credential secrets must not be returned through credential management responses. + +Applications should never log passwords, reset proofs, or other authentication secrets. + +### Use Secure Transport + +Credential operations must be performed over appropriately secured transport. + +### Protect Recovery Proofs + +Recovery tokens and codes should be short-lived, single-use, and delivered through trusted channels. + +Do not store recovery proofs in persistent client storage unless the application has a carefully justified security requirement. + +### Keep Self-Service and Administrative Operations Separate + +Self-service changes require the current credential. + +Administrative changes are privileged and require server-side authorization. + +Do not substitute one for the other. + +### Preserve Account Recovery Safety -Credentials are: +Avoid revealing account existence through public recovery responses. -- user-bound -- security-version aware -- lifecycle-managed +Apply appropriate rate limiting, proof attempt limits, and abuse controls. -πŸ‘‰ Changing credentials may: +### Consider Session Consequences -- invalidate sessions -- trigger security updates +Changing or resetting a password can affect existing sessions. -
+Applications should explain the expected consequences to users, especially when other devices will be logged out. -## πŸ” Security Notes +### Do Not Assume Every Credential Type Is Implemented -- Passwords are never sent back to client -- All secrets are hashed server-side -- Reset flows should be protected (email, OTP, etc.) -- Admin operations are policy-protected +The existence of a `CredentialType` enum member does not establish that its complete lifecycle is supported. -## 🎯 Summary +Use the currently implemented credential contracts and documented authentication flows. -Credential management in UltimateAuth: +## Next Steps -- supports self-service and admin flows -- integrates with security lifecycle -- enables safe credential rotation +- [Authentication](/docs/client/authentication) β€” Login, logout, refresh, and authentication flows. +- [Identifiers](/docs/client/identifiers) β€” Manage user identifiers and ownership verification. +- [User Management](/docs/client/user-management) β€” Manage profiles and user lifecycle. +- [Session Management](/docs/client/session-management) β€” Inspect and revoke sessions. +- [Token Security](/docs/security/token-security) β€” Understand token validation and revocation behavior. +- [Session Security Model](/docs/security/session-security-model) β€” Understand session invalidation and account-level security boundaries. \ No newline at end of file diff --git a/docs/content/client/identifiers.md b/docs/content/client/identifiers.md index ead1b971..fedcd840 100644 --- a/docs/content/client/identifiers.md +++ b/docs/content/client/identifiers.md @@ -4,182 +4,1042 @@ order: 5 group: client --- +# Identifiers -# πŸ†” User Identifiers Guide +UltimateAuth treats user identifiers as independent, managed entities rather than simple fields stored directly on a user profile. -This section explains how UltimateAuth manages user identifiers such as email, username, and phone. +Through `UAuthClient.Identifiers`, applications can manage usernames, email addresses, phone numbers, and custom identifiers, including primary designation, ownership verification, availability checks, and administrative operations. -## 🧠 Overview +This guide covers the public `IUserIdentifierClient` API with practical examples for account settings, registration forms, and administration interfaces. -In UltimateAuth, identifiers are **first-class entities**. +## 1. Understanding User Identifiers -They are NOT just fields on the user. +An identifier represents a value associated with a user that may be used for identification, authentication, or communication, depending on the application's configuration. -πŸ‘‰ On the client, you interact with: +Supported identifier types are: ```csharp -UAuthClient.Identifiers... +public enum UserIdentifierType +{ + Username = 0, + Email = 10, + Phone = 20, + Custom = 100 +} +``` + +A single user can have multiple identifiers. + +For example: + +```text +User + | + +-- Username + | johndoe [Primary] + | + +-- Email + | john@example.com [Primary, Verified] + | john@business.com [Verified] + | + +-- Phone + +1 555 0100 [Primary, Verified] +``` + +Each identifier has its own value, type, verification state, and primary designation. + +**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). + +## 2. Accessing the Identifier Client + +Inject `IUAuthClient` into a Blazor component: + +```razor +@using CodeBeam.UltimateAuth.Client +@inject IUAuthClient UAuthClient +``` + +Identifier operations are available through: + +```csharp +UAuthClient.Identifiers +``` + +This property exposes `IUserIdentifierClient`. + +The client supports three main categories of operations: + +| Category | Purpose | +|---|---| +| Self-service | Manage identifiers belonging to the current user | +| Administration | Manage identifiers belonging to a specified user | +| Availability | Check whether an identifier value is valid and available | + +All operations are subject to server-side authorization and identifier policies. + +## 3. Get My Identifiers + +Use `GetMyAsync` to retrieve identifiers belonging to the authenticated user. + +```csharp +var result = await UAuthClient.Identifiers.GetMyAsync(); + +if (result.IsSuccess && result.Value is { } page) +{ + foreach (var identifier in page.Items) + { + Console.WriteLine($"{identifier.Type}: {identifier.Value}"); + } +} +``` + +The method returns: + +```csharp +UAuthResult> +``` + +### UserIdentifierInfo + +Each identifier is represented by `UserIdentifierInfo`. + +| Property | Type | Description | +|---|---|---| +| `Id` | `Guid` | Identifier record ID | +| `Type` | `UserIdentifierType` | Identifier type | +| `Value` | `string` | Identifier value | +| `NormalizedValue` | `string` | Server-normalized value | +| `IsPrimary` | `bool` | Whether it is primary for its type | +| `IsVerified` | `bool` | Whether ownership has been verified | +| `CreatedAt` | `DateTimeOffset` | Creation timestamp | +| `VerifiedAt` | `DateTimeOffset?` | Verification timestamp | +| `Version` | `long` | Entity version | + +The `Id` identifies the managed identifier record and is used by update, verification, primary designation, and deletion operations. + +### Filter Identifiers in the UI + +For example, display verified email addresses from a returned page: + +```csharp +var result = await UAuthClient.Identifiers.GetMyAsync(); + +if (result.IsSuccess && result.Value is { } page) +{ + var verifiedEmails = page.Items + .Where(x => + x.Type == UserIdentifierType.Email && + x.IsVerified) + .ToList(); +} +``` + +This filters the current page only. Retrieve additional pages when the application needs the complete collection. + +## 4. Pagination + +`GetMyAsync` accepts an optional `PageRequest`. + +```csharp +var result = await UAuthClient.Identifiers.GetMyAsync( + new PageRequest + { + PageNumber = 1, + PageSize = 20 + }); +``` + +The server applies its configured pagination policy. + +The returned `PagedResult` includes: + +- `Items` +- `TotalCount` +- `PageNumber` +- `PageSize` +- `HasNext` + +For example: + +```csharp +if (result.IsSuccess && result.Value is { } page) +{ + Console.WriteLine($"Total: {page.TotalCount}"); + + if (page.HasNext) + { + // Enable the next-page control. + } +} +``` + +Do not assume the first page contains every identifier. + +## 5. Add an Identifier + +Use `AddMyAsync` to associate a new identifier with the current user. + +### Add an Email Address + +```csharp +var result = await UAuthClient.Identifiers.AddMyAsync( + new AddUserIdentifierRequest + { + Type = UserIdentifierType.Email, + Value = "john@business.com" + }); + +if (result.IsSuccess) +{ + // Identifier added. +} +``` + +### Add a Phone Number + +```csharp +var result = await UAuthClient.Identifiers.AddMyAsync( + new AddUserIdentifierRequest + { + Type = UserIdentifierType.Phone, + Value = "+15550100" + }); +``` + +### Add a Username + +```csharp +var result = await UAuthClient.Identifiers.AddMyAsync( + new AddUserIdentifierRequest + { + Type = UserIdentifierType.Username, + Value = "johndoe" + }); +``` + +### Add a Custom Identifier + +```csharp +var result = await UAuthClient.Identifiers.AddMyAsync( + new AddUserIdentifierRequest + { + Type = UserIdentifierType.Custom, + Value = "external-customer-123" + }); +``` + +Custom identifier support does not automatically mean that arbitrary custom values can be used for authentication. + +Login eligibility depends on server configuration and the relevant identifier policies. + +### Add as Primary + +Set `IsPrimary` when creating the identifier: + +```csharp +var result = await UAuthClient.Identifiers.AddMyAsync( + new AddUserIdentifierRequest + { + Type = UserIdentifierType.Email, + Value = "john@example.com", + IsPrimary = true + }); ``` -
+The server determines whether the new value can be assigned as primary. + +Adding an identifier does not automatically establish verified ownership. + +## 6. Identifier Uniqueness and Normalization + +UltimateAuth normalizes identifier values and enforces uniqueness within the relevant tenant boundary. -## πŸ”‘ What is an Identifier? +This allows applications to work with consistent identifier representations. -An identifier represents a way to identify a user: +For example, email or username normalization may affect how equivalent input values are compared. -- Email -- Username -- Phone number -- Custom identifiers +The exact normalization rules depend on the identifier type and server implementation. -πŸ‘‰ Each identifier has: +### Use Server Results as Authoritative -- Value (e.g. user@ultimateauth.com) -- Type (email, username, etc.) -- Verification state -- Primary flag +Do not implement a separate client-side normalization algorithm and assume it exactly matches the server. -
+Client-side formatting can improve the user experience, but the server must remain responsible for: -## ⭐ Primary Identifier +- Validating identifier format. +- Normalizing identifier values. +- Enforcing uniqueness. +- Checking tenant ownership. +- Applying identifier-specific policies. -A user can have multiple identifiers, but only one can be **primary**. Setting an identifier to primary automatically unset the current primary identifier if exists. +An identifier value that appears available in the UI may become unavailable before the user submits it. + +Always handle the result of the actual add or update operation. + +## 7. Check Identifier Availability + +Use `CheckAvailabilityAsync` to check whether an identifier value is valid and available. + +This is useful for registration forms and account settings. + +### Check a Username ```csharp -await UAuthClient.Identifiers.SetMyPrimaryAsync(new SetPrimaryUserIdentifierRequest +var result = await UAuthClient.Identifiers.CheckAvailabilityAsync( + new CheckUserIdentifierAvailabilityRequest + { + Type = UserIdentifierType.Username, + Value = "johndoe" + }); + +if (result.IsSuccess && result.Value is { } availability) { - Id = identifierId -}); + if (!availability.IsValid) + { + // The identifier value is invalid. + } + else if (!availability.IsAvailable) + { + // The identifier is already unavailable. + } + else + { + // The identifier is currently available. + } +} ``` -πŸ‘‰ Primary identifier is typically: +### Check an Email Address -- Used for display -- Preferred for communication +```csharp +var result = await UAuthClient.Identifiers.CheckAvailabilityAsync( + new CheckUserIdentifierAvailabilityRequest + { + Type = UserIdentifierType.Email, + Value = "john@example.com" + }); +``` +### Availability Result -## πŸ” Login Identifiers +The operation returns: -Not all identifiers are used for login. +```csharp +UAuthResult +``` + +The response value contains: + +| Property | Description | +|---|---| +| `IsValid` | Whether the value passes validation | +| `IsAvailable` | Whether the value is currently available | +| `NormalizedValue` | Normalized identifier value, when available | +| `Errors` | Validation errors | + +### Display Validation Feedback + +```csharp +var result = await UAuthClient.Identifiers.CheckAvailabilityAsync( + new CheckUserIdentifierAvailabilityRequest + { + Type = UserIdentifierType.Username, + Value = username + }); + +if (result.IsSuccess && result.Value is { } availability) +{ + if (!availability.IsValid) + { + foreach (var error in availability.Errors) + { + // Display the appropriate validation feedback. + } + } + else if (!availability.IsAvailable) + { + message = "This username is unavailable."; + } + else + { + message = "This username is available."; + } +} +``` -πŸ‘‰ **Login identifiers are a subset of identifiers** +**An availability check is advisory, not a reservation.** -πŸ‘‰ This is configurable: +The value may become unavailable between the availability check and the subsequent creation operation. -- Enable/disable per type -- Custom logic can be applied +For public registration forms, also consider whether exposing detailed availability information could enable account enumeration. Apply appropriate server-side controls. -
+## 8. Update an Identifier -## πŸ“‹ Get My Identifiers +Use `UpdateMyAsync` to change the value of an existing identifier. + +```csharp +var result = await UAuthClient.Identifiers.UpdateMyAsync( + new UpdateUserIdentifierRequest + { + Id = identifierId, + NewValue = "newemail@example.com" + }); + +if (result.IsSuccess) +{ + // Identifier updated. +} +``` + +`UpdateUserIdentifierRequest` contains: + +| Property | Description | +|---|---| +| `Id` | Existing identifier record ID | +| `NewValue` | Replacement identifier value | + +Unlike profile updates, this operation targets one identifier record. + +It does not replace the user's entire identifier collection. + +### Verification After an Update + +Changing an identifier's value may require ownership verification again. + +For example, verification of an old email address does not prove ownership of a new email address. + +Applications should reload the identifier after updating it and inspect its current `IsVerified` state. + +Do not assume the previous verification state remains valid. + +## 9. Understanding Primary Identifiers + +UltimateAuth supports **one primary identifier per type**, rather than one global primary identifier for the entire user. + +For example, a user may have: + +- One primary username. +- One primary email. +- One primary phone number. + +These primary designations are independent. + +The primary flag identifies the preferred identifier within its type. + +How the application uses primary identifiers for display, communication, or authentication depends on its configuration and business rules. + +### Set a Primary Identifier + +Use `SetMyPrimaryAsync`: + +```csharp +var result = await UAuthClient.Identifiers.SetMyPrimaryAsync( + new SetPrimaryUserIdentifierRequest + { + Id = identifierId + }); + +if (result.IsSuccess) +{ + // Primary designation updated. +} +``` + +When a different identifier becomes primary within the same type, the server maintains the per-type primary constraint. + +Setting an email address as primary does not unset the primary phone number or username. + +### Unset a Primary Identifier + +Use `UnsetMyPrimaryAsync`: + +```csharp +var result = await UAuthClient.Identifiers.UnsetMyPrimaryAsync( + new UnsetPrimaryUserIdentifierRequest + { + Id = identifierId + }); +``` + +Whether a primary identifier can be unset without selecting a replacement depends on server policy. + +Applications should handle rejection when the server requires a primary identifier of that type. + +### Display Primary Identifiers ```csharp var result = await UAuthClient.Identifiers.GetMyAsync(); + +if (result.IsSuccess && result.Value is { } page) +{ + var primaryIdentifiers = page.Items.Where(x => x.IsPrimary).ToList(); + + foreach (var identifier in primaryIdentifiers) + { + Console.WriteLine($"{identifier.Type}: {identifier.Value}"); + } +} ``` +Remember that this example searches only the returned page. + +## 10. Verify Identifier Ownership + +Verification is a security-sensitive operation. + +A user must not be able to mark an email address or phone number as verified merely by knowing or submitting its identifier ID. -## βž• Add Identifier +UltimateAuth requires ownership proof for self-service verification. + +### Verify an Identifier + +Use `VerifyMyAsync` with a proof obtained through your application's verification workflow. ```csharp -await UAuthClient.Identifiers.AddMyAsync(new AddUserIdentifierRequest +var result = await UAuthClient.Identifiers.VerifyMyAsync( + new VerifyUserIdentifierRequest + { + Id = identifierId, + Proof = verificationProof + }); + +if (result.IsSuccess) { - Identifier = "new@ultimateauth.com", - Type = UserIdentifierType.Email, - IsPrimary = true -}); + // Identifier verification succeeded. +} +``` + +The `Proof` property is required for self-service verification. + +For example, a verification workflow may deliver a code to the email address or phone number being verified. + +The application then submits the received proof through `VerifyMyAsync`. + +The SDK does not itself guarantee delivery of verification messages. + +### Verification Flow + +A typical verification workflow looks like this: + +```text +User adds identifier + | + v +Application initiates verification + | + v +Verification proof is delivered + | + v +User submits proof + | + v +Identifiers.VerifyMyAsync(...) + | + v +Server validates ownership proof + | + +-- Valid --> Identifier verified + | + +-- Invalid --> Verification rejected ``` -
+The mechanism used to issue and deliver the proof is application-specific. -## ✏️ Update Identifier +The server-side verification implementation must validate the proof before updating the identifier's verified state. + +## 11. Server-Side Verification with IUserIdentifierVerifier + +UltimateAuth defines `IUserIdentifierVerifier` for validating identifier ownership proof. ```csharp -UpdateUserIdentifierRequest updateRequest = new() +public interface IUserIdentifierVerifier { - Id = item.Id, - NewValue = item.Value -}; + Task VerifyAsync(UserIdentifierVerificationContext context, CancellationToken ct = default); +} +``` + +The verification context includes: -await UAuthClient.Identifiers.UpdateMyAsync(updateRequest); +```csharp +public sealed record UserIdentifierVerificationContext( + TenantKey Tenant, + UserKey UserKey, + Guid IdentifierId, + UserIdentifierType Type, + string Value, + string Proof); ``` +A secure verifier must bind the proof to: + +- The tenant. +- The user. +- The identifier record. +- The current identifier value. + +It must also enforce appropriate expiration, attempt limits, and single-use consumption. + +### Register Your Verifier -## βœ… Verify Identifier +In the server application, register your implementation: ```csharp -await UAuthClient.Identifiers.VerifyMyAsync(new VerifyUserIdentifierRequest +builder.Services.AddScoped(); +``` + +`ApplicationIdentifierVerifier` represents your own implementation of the interface. + +UltimateAuth includes a `NotConfiguredUserIdentifierVerifier` fallback that throws an explanatory exception when verification is attempted without a configured implementation. + +This prevents an unconfigured verifier from silently approving identifier ownership. + +**Do not implement a verifier that returns `true` unconditionally in production.** + +## 12. Delete an Identifier + +Use `DeleteMyAsync` to remove an identifier. + +```csharp +var result = await UAuthClient.Identifiers.DeleteMyAsync( + new DeleteUserIdentifierRequest + { + Id = identifierId, + Mode = DeleteMode.Soft + }); + +if (result.IsSuccess) { - Id = identifierId -}); + // Identifier deleted. +} ``` +The request includes a deletion mode: + +```csharp +public enum DeleteMode +{ + Soft = 0, + Hard = 10 +} +``` -## ❌ Delete Identifier +### Hard Delete ```csharp -await UAuthClient.Identifiers.DeleteMyAsync(new DeleteUserIdentifierRequest +var result = await UAuthClient.Identifiers.DeleteMyAsync( + new DeleteUserIdentifierRequest + { + Id = identifierId, + Mode = DeleteMode.Hard + }); +``` + +Deletion behavior depends on the server's identifier lifecycle and authorization rules. + +An application should not assume that every caller can select either deletion mode. + +### Deleting Primary Identifiers + +Deleting a primary identifier may be restricted. + +For example, the server may require another identifier of the same type to become primary first. + +Applications should inspect the result and provide appropriate feedback. + +### Deleting Login Identifiers + +Removing an identifier that is used for authentication may affect future login attempts. + +Before exposing deletion, consider whether the user will retain a supported method of authentication. + +Identifier deletion should not be treated as equivalent to revoking all existing sessions. + +## 13. Build an Identifier Management Page + +The following example demonstrates a basic Blazor page that lists identifiers, adds an email address, and allows users to change primary designation. + +```razor +@using CodeBeam.UltimateAuth.Client +@using CodeBeam.UltimateAuth.Core.Contracts +@using CodeBeam.UltimateAuth.Users.Contracts +@inject IUAuthClient UAuthClient + +

My Identifiers

+ +@if (_loading) +{ +

Loading identifiers...

+} +else +{ + @foreach (var item in _identifiers) + { +
+ @item.Value + @item.Type + + @if (item.IsVerified) + { + Verified + } + else + { + Not verified + } + + @if (item.IsPrimary) + { + Primary + } + else + { + + } +
+ } +} + +

Add Email

+ + + +@if (!string.IsNullOrWhiteSpace(_message)) { - Id = identifierId -}); +

@_message

+} + +@code { + private IReadOnlyList _identifiers = []; + + private string _newEmail = ""; + private string? _message; + + private bool _loading = true; + private bool _busy; + + protected override async Task OnInitializedAsync() + { + await LoadAsync(); + } + + private async Task LoadAsync() + { + _loading = true; + + try + { + var result = await UAuthClient.Identifiers.GetMyAsync( + new PageRequest + { + PageNumber = 1, + PageSize = 50 + }); + + if (result.IsSuccess && result.Value is { } page) + { + _identifiers = page.Items; + } + else + { + _message = "Unable to load identifiers."; + } + } + catch (Exception) + { + _message = "The identifier service is unavailable."; + } + finally + { + _loading = false; + } + } + + private async Task AddEmailAsync() + { + if (_busy || string.IsNullOrWhiteSpace(_newEmail)) + return; + + _busy = true; + _message = null; + + try + { + var result = await UAuthClient.Identifiers.AddMyAsync( + new AddUserIdentifierRequest + { + Type = UserIdentifierType.Email, + Value = _newEmail + }); + + if (result.IsSuccess) + { + _newEmail = ""; + await LoadAsync(); + } + else + { + _message = "Unable to add email."; + } + } + finally + { + _busy = false; + } + } + + private async Task SetPrimaryAsync(Guid id) + { + var result = await UAuthClient.Identifiers.SetMyPrimaryAsync( + new SetPrimaryUserIdentifierRequest + { + Id = id + }); + + if (result.IsSuccess) + { + await LoadAsync(); + } + else + { + _message = "Unable to update primary identifier."; + } + } +} ``` -
+This example demonstrates the core SDK integration. -## πŸ‘€ Admin Operations +It 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. -### Get User Identifiers +For sensitive changes, consider requiring recent authentication or additional verification according to application policy. + +## 14. Administrative Identifier Management + +UltimateAuth provides administrative methods for managing identifiers belonging to another user. + +These operations require appropriate server-side authorization. + +### Get a User's Identifiers + +```csharp +var result = await UAuthClient.Identifiers.GetUserAsync(userKey, + new PageRequest + { + PageNumber = 1, + PageSize = 20 + }); + +if (result.IsSuccess && result.Value is { } page) +{ + foreach (var identifier in page.Items) + { + Console.WriteLine(identifier.Value); + } +} +``` + +### Add an Identifier ```csharp -await UAuthClient.Identifiers.GetUserAsync(userKey); +var result = await UAuthClient.Identifiers.AddUserAsync(userKey, + new AddUserIdentifierRequest + { + Type = UserIdentifierType.Email, + Value = "employee@example.com" + }); ``` +### Update an Identifier + +```csharp +var result = await UAuthClient.Identifiers.UpdateUserAsync(userKey, + new UpdateUserIdentifierRequest + { + Id = identifierId, + NewValue = "updated@example.com" + }); +``` -### Add Identifier to User +### Set Primary ```csharp -await UAuthClient.Identifiers.AddUserAsync(userKey, request); +var result = await UAuthClient.Identifiers.SetUserPrimaryAsync(userKey, + new SetPrimaryUserIdentifierRequest + { + Id = identifierId + }); ``` +### Unset Primary -### Update Identifier +```csharp +var result = await UAuthClient.Identifiers.UnsetUserPrimaryAsync(userKey, + new UnsetPrimaryUserIdentifierRequest + { + Id = identifierId + }); +``` + +### Verify an Identifier as Administrator ```csharp -await UAuthClient.Identifiers.UpdateUserAsync(userKey, request); +var result = await UAuthClient.Identifiers.VerifyUserAsync(userKey, + new VerifyUserIdentifierRequest + { + Id = identifierId + }); ``` +The request contract does not require `Proof` for authorized administrative verification. + +However, this does not mean any authenticated caller may bypass ownership verification. -### Delete Identifier +The server must authorize the administrative verification operation and enforce its security requirements. + +Administrative verification should be reserved for trusted workflows with appropriate auditability. + +### Delete an Identifier ```csharp -await UAuthClient.Identifiers.DeleteUserAsync(userKey, request); +var result = await UAuthClient.Identifiers.DeleteUserAsync(userKey, + new DeleteUserIdentifierRequest + { + Id = identifierId, + Mode = DeleteMode.Soft + }); +``` + +Administrative operations act on the specified target user. + +They do not automatically publish the same local identifier state events as self-service operations. + +## 15. Handling Identifier Operation Results + +Most identifier mutations return `UAuthResult`. + +Retrieval and availability methods return `UAuthResult`. + +### Handle Success and Failure + +```csharp +var result = await UAuthClient.Identifiers.AddMyAsync(request); + +if (result.IsSuccess) +{ + // Operation succeeded. +} +else if (result.Http.IsConflict) +{ + // Identifier conflict. +} +else if (result.Http.IsForbidden) +{ + // Operation not permitted. +} +else if (result.Http.IsBadRequest) +{ + // Invalid request. +} +else +{ + // Handle another failure. +} ``` +The actual status code depends on the server's response. + +`UAuthResult` also exposes `Problem`, `ErrorText`, `CorrelationId`, and `TraceId`. + +Use structured errors when appropriate, but avoid displaying sensitive internal information directly to users. + +## 16. Identifier State Events + +The client publishes `IdentifiersChanged` after successful self-service identifier mutations. + +| Operation | Event | +|---|---| +| `AddMyAsync` | `IdentifiersChanged` | +| `UpdateMyAsync` | `IdentifiersChanged` | +| `SetMyPrimaryAsync` | `IdentifiersChanged` | +| `UnsetMyPrimaryAsync` | `IdentifiersChanged` | +| `VerifyMyAsync` | `IdentifiersChanged` | +| `DeleteMyAsync` | `IdentifiersChanged` | + +For add and update operations, the event carries the corresponding request payload. + +For primary designation, verification, and deletion, the SDK publishes the event without a request payload. + +The event handling mode follows the configured client state event options. + +Read operations and availability checks do not publish `IdentifiersChanged`. + +Administrative mutations also do not automatically publish this local self-service event. + +### Refresh Identifier Data After Changes + +A component may respond to an identifier state event by reloading its identifier list. + +Alternatively, after a successful mutation, the calling component can explicitly refresh the relevant data. + +Client events are synchronization mechanisms, not authoritative security decisions. + +## 17. Security Considerations + +### Verification Requires Proof + +Self-service verification must establish ownership through a valid proof. + +Knowing an identifier ID is not sufficient. + +The server must validate proof binding, expiration, attempt limits, and single-use consumption. + +### Primary Does Not Mean Verified + +An identifier's primary designation and verification state are separate properties. + +Applications should not assume that a primary email or phone number has been verified. + +Check `IsVerified` where verified ownership is required. + +### Login Eligibility Is Separate + +An identifier may exist on an account without being permitted for login. + +Login eligibility depends on the configured authentication and identifier policies. + +Do not assume that every custom identifier is automatically an authentication identifier. + +### Tenant Boundaries Matter + +Identifier uniqueness and ownership are enforced within the relevant tenant context. -## πŸ”„ State Events +Client code must not attempt to bypass tenant isolation by modifying request paths or target user identifiers. -Identifier changes trigger events: +### Availability Checks Are Not Reservations -- IdentifiersChanged +Availability can change between requests. -πŸ‘‰ Useful for: +Always enforce uniqueness during the actual identifier mutation. -- UI updates -- Cache invalidation +### Administrative Verification Is Privileged -
+The administrative verification API has different proof requirements from self-service verification. -## πŸ” Security Considerations +Only appropriately authorized actors should be able to use it. -- Identifiers may require verification -- Login identifiers can be restricted -- Primary identifier can be controlled +### Identifier Changes May Affect Authentication +Changing or deleting a login identifier may affect future authentication. -## 🎯 Summary +Applications should distinguish identifier management from session revocation and credential management. -UltimateAuth identifiers: +## Next Steps -- are independent entities -- support multiple types -- separate login vs non-login identifiers -- are fully manageable via client +- [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. +- [Users](/docs/plugin-domains/users) β€” Explore the user domain architecture. +- [Session Security Model](/docs/security/session-security-model) β€” Understand authentication security boundaries. \ No newline at end of file diff --git a/docs/content/client/index.md b/docs/content/client/index.md index 7f75a687..49d018b2 100644 --- a/docs/content/client/index.md +++ b/docs/content/client/index.md @@ -1,118 +1,284 @@ --- -title: Client & API +title: Client SDK Overview order: 1 group: client --- +# Client SDK Overview -# πŸš€ Client Usage Guide +The UltimateAuth Client SDK provides a unified way to integrate authentication, session management, user operations, credentials, and authorization into .NET applications. -UltimateAuth Client is a **high-level SDK** designed to simplify authentication flows. +Instead of manually constructing requests for every authentication endpoint, applications can use dedicated client services that understand UltimateAuth's API contracts and authentication workflows. -It is NOT just an HTTP wrapper. +The SDK is designed for applications that need to interact with an UltimateAuth server, including Blazor WebAssembly, Blazor Server, and other supported .NET client environments. -## 🧠 What Makes the Client Different? +**This section is the practical developer guide to using UltimateAuth from application code.** -The client: +## What the Client SDK Provides -- Handles full authentication flows (login, PKCE, refresh) -- Manages redirects automatically -- Publishes state events -- Provides structured results -- Works across multiple client types (SPA, server, hybrid) +The Client SDK is more than a collection of HTTP methods. -πŸ‘‰ You SHOULD use the client instead of calling endpoints manually. +It brings together several responsibilities: -
+- **Authentication workflows** β€” Start login, refresh, logout, and other supported authentication operations. +- **Session management** β€” Interact with available session and device management capabilities. +- **User management** β€” Retrieve and manage user information through the appropriate API contracts. +- **Credential operations** β€” Manage credentials and initiate supported recovery workflows. +- **Authorization management** β€” Work with roles, permissions, and other exposed authorization operations. +- **Structured results** β€” Receive operation results through the SDK's response contracts. +- **Client state integration** β€” Coordinate supported authentication state changes and notifications. -## 🧱 Client Architecture +The SDK helps applications interact with UltimateAuth consistently while keeping the server responsible for authentication and authorization enforcement. -The client is split into multiple specialized services: +It does not replace server-side security checks. -| Service | Responsibility | -|----------------------|-------------------------------------| -| FlowClient | Login, logout, refresh, PKCE | -| SessionClient | Session & device management | -| UserClient | User profile & lifecycle | -| IdentifierClient | Email / username / phone management | -| CredentialClient | Password management | -| AuthorizationClient | Roles & permissions | +## The Client Architecture -
+UltimateAuth separates client responsibilities into specialized services. -## 🧩 Client Entry Point +The main client entry point is `UAuthClient`, which provides access to the SDK's functional areas. -UltimateAuth exposes a single entry point: +Conceptually: -πŸ‘‰ `UAuthClient` +```text +Application + | + v +UAuthClient + | + +-- Flows + | Login, logout, refresh, PKCE + | + +-- Sessions + | Sessions and device-associated chains + | + +-- Users + | User information and lifecycle + | + +-- Identifiers + | User identifiers and verification + | + +-- Credentials + | Credential management and recovery + | + +-- Authorization + Roles, permissions, and access management +``` -```csharp -[Inject] IUAuthClient UAuthClient { get; set; } = null!; +These represent functional responsibilities. The precise available members and operations depend on the installed packages and supported SDK contracts. -await UAuthClient.Flows.LoginAsync(...); -await UAuthClient.Users.GetMeAsync(); -await UAuthClient.Sessions.GetMyChainsAsync(); -``` +### Why Specialized Clients? +Authentication-related operations have different requirements. -## πŸ”‘ Core Concept: Flow-Based Design +A login flow may establish authentication state and involve redirects. A user management operation may retrieve or update account information. A credential recovery operation may require additional verification and security handling. -UltimateAuth is **flow-oriented**, not endpoint-oriented. +Keeping these responsibilities separate provides a clearer programming model and makes individual APIs easier to discover. -Instead of calling endpoints: +## Getting Started with UAuthClient -❌ POST /auth/login -βœ” flowClient.LoginAsync() +In a supported dependency injection environment, application components can consume the configured UltimateAuth client. -
+For example, a Blazor component can receive the client through dependency injection: + +```razor +@inject UAuthClient UAuthClient +``` -## ⚑ Example +Once configured, the application uses the appropriate client service for the operation it needs. + +The following illustrates the intended programming model: ```csharp -await UAuthClient.Flows.LoginAsync(new LoginRequest -{ - Identifier = "user@ultimateauth.com", - Secret = "password" -}); +var user = await UAuthClient.Users.GetMeAsync(); ``` -πŸ‘‰ This automatically: +The SDK handles the corresponding client operation and returns its structured result. + +Before using the client, the application must register the appropriate UltimateAuth services and configure its connection to the authentication server. + +For a complete setup, follow the Getting Started guide for your application environment. + +## Choose Your Application Environment + +UltimateAuth supports different application architectures, but their authentication and transport requirements are not identical. + +### Blazor WebAssembly + +Blazor WebAssembly executes application code in the browser. + +The client communicates with the authentication server and protected APIs through browser-compatible HTTP requests. + +Browser security rules, credential handling, CORS configuration, and authentication state synchronization are relevant to this environment. + +### Blazor Server + +Blazor Server executes application code on the server while maintaining an interactive connection to the browser. + +Authentication state, cookie behavior, and server-side execution require different considerations from a browser-hosted WebAssembly application. + +Server-side code may also invoke application services directly rather than making an HTTP request. + +Such operations still require appropriate server-side authorization enforcement. + +### Interactive Web Applications + +Applications combining server rendering and WebAssembly interactivity may have multiple execution environments. + +Developers should distinguish between operations performed on the server and operations performed in the browser. + +Shared UI components do not automatically imply identical transport or credential handling. + +### Other .NET Clients + +Other supported .NET application environments can use the relevant client SDK capabilities according to their platform requirements. + +Credential storage, HTTP transport, redirect handling, and authentication state integration may differ from browser applications. + +The SDK's shared contracts help maintain consistency, while platform-specific integration handles the environment's requirements. + +## Working with Authentication State + +Authentication is not simply a method call that returns a token. + +The client may need to coordinate several related concerns: + +- Completing the selected authentication flow. +- Handling the resulting authentication state. +- Renewing authentication when continuation is supported. +- Reacting to logout or session invalidation. +- Updating application components when relevant state changes. + +UltimateAuth provides client-side abstractions and notifications for supported operations. + +For example, user or credential changes may publish SDK events after a successful operation. + +Applications can use the available state integration mechanisms to update their UI without treating every API response as a complete authentication state transition. + +The exact behavior depends on the client environment and configured integration. + +## Understanding SDK Results + +UltimateAuth client operations use structured response contracts. + +Applications should inspect the result of an operation rather than assuming that completing an asynchronous call means the requested action succeeded. + +A typical application workflow is: + +```text +Call SDK operation + | + v +Receive structured result + | + +-- Success + | | + | v + | Continue application workflow + | + +-- Failure + | + v + Handle error +``` + +Different operations may return different result types. + +The relevant guide for each client service explains its request and response contracts, including how to handle failures. + +## Client Events and UI Updates + +Some SDK operations can publish notifications when a successful operation changes application-relevant state. + +For example, credential or user management operations may notify subscribers that related information has changed. + +These notifications help decouple an operation from the UI components that react to it. + +A component can respond by refreshing its data rather than requiring the caller to manually update every affected view. + +However, client events are not a security authority. + +An event does not replace server-side validation, and the absence of an event does not prove that the underlying security state has remained unchanged. + +Applications should continue to treat the server as authoritative wherever the selected authentication mode requires it. + +## Calling Protected Resource APIs + +Authentication is often only the first step. + +Applications also need to call protected business APIs after the user has authenticated. + +UltimateAuth supports integration with protected resource requests, allowing the client application to use the authentication mechanisms appropriate to its configured environment. + +A protected resource request involves two distinct responsibilities: + +1. The client sends the request with the appropriate authentication material. +2. The receiving server validates authentication and enforces authorization. + +The Client SDK can help with the client-side responsibility. + +It cannot grant access to an operation that the receiving server does not authorize. + +When building applications with separate resource APIs, developers must configure the relevant API integration and server-side authentication enforcement. + +## Client SDK Guides + +The following guides cover the major client responsibilities with practical C# examples. + +| Guide | What you will learn | +|---|---| +| **Authentication** | Login, logout, refresh, PKCE, and authentication state handling | +| **Session Management** | Retrieve, inspect, and manage sessions and device-associated chains | +| **User Management** | Retrieve user information and perform supported user lifecycle operations | +| **Identifiers** | Manage email addresses, usernames, phone numbers, and verification workflows | +| **Credentials** | Add, change, revoke, and recover credentials | +| **Authorization** | Work with roles, permissions, and exposed authorization management operations | +| **Resource API Requests** | Call protected application APIs using the appropriate authentication integration | + +Each guide focuses on a specific developer task and includes the relevant SDK methods, request models, response handling, and implementation considerations. + +### Recommended Learning Path + +If you are integrating UltimateAuth for the first time, start with **Authentication**. + +Once login and authentication state handling work correctly, continue with **Resource API Requests** to connect authentication to your application's protected functionality. + +After that, use the domain-specific guides for the features your application needs. + +For example, an account settings page may require the User Management, Identifiers, and Credentials guides, while an administration panel may additionally require Authorization and Session Management. -- Builds request payload -- Handles redirect -- Integrates with configured endpoints +## Client SDK and Server Responsibilities -
+The Client SDK simplifies integration, but it does not independently enforce the security of server-side operations. -## πŸ”„ State Events +| Client responsibility | Server responsibility | +|---|---| +| Submit authentication requests | Validate authentication requirements | +| Manage supported client state | Establish and validate authoritative session state | +| Send protected resource requests | Authenticate and authorize protected operations | +| Handle structured responses | Enforce domain rules and security policies | +| React to state notifications | Apply revocation and account security controls | -Client automatically publishes events: +This separation is particularly important for applications that share UI components across different hosting models. -- SessionRevoked -- ProfileChanged -- AuthorizationChanged +A shared component can use the same SDK abstractions, while the hosting environment and server determine how authentication material is transported and validated. -
+## When to Use the Client SDK -## 🧭 How to Use This Section +Use the Client SDK when building an application that interacts with UltimateAuth's exposed authentication and management APIs. -Follow these guides: +It is generally preferable to manually reproducing the corresponding HTTP contracts because it provides a consistent integration surface and reduces duplicated request-handling code. -- authentication.md β†’ login, refresh, logout -- session-management.md β†’ sessions & devices -- user-management.md β†’ user operations -- identifiers.md β†’ login identifiers -- authorization.md β†’ roles & permissions +Direct endpoint integration may still be appropriate for clients that cannot use the .NET SDK or for specialized interoperability requirements. -## 🎯 Summary +Regardless of the client implementation, the same server-side authentication and authorization requirements apply. -UltimateAuth Client: +## Next Steps -- abstracts complexity -- enforces correct flows -- reduces security mistakes +Continue with the Client SDK guides to implement real application workflows. +Start with **Authentication** to configure and use login, refresh, and logout operations. -πŸ‘‰ Think of it as: +Then explore session management, user operations, credentials, authorization, and protected resource API integration according to your application's requirements. -**β€œAuthentication runtime for your frontend / app”** +For the concepts behind these operations, refer to [Authentication Modes](/docs/fundamentals/authentication-modes) and [Plugin Domains](/docs/plugin-domains). \ No newline at end of file diff --git a/docs/content/client/session-management.md b/docs/content/client/session-management.md index 67e86767..ef3e2416 100644 --- a/docs/content/client/session-management.md +++ b/docs/content/client/session-management.md @@ -4,168 +4,794 @@ order: 3 group: client --- +# Session Management -# πŸ“± Session Management Guide +UltimateAuth provides a dedicated client API for inspecting and managing authentication sessions across devices and application instances. -This section explains how to manage sessions and devices using the UltimateAuth client. +Through `UAuthClient.Sessions`, applications can list session chains, inspect authentication history, revoke individual chains, terminate sessions across devices, and perform administrative session management. -## 🧠 Overview +This guide demonstrates the available operations using the current `ISessionClient` contract. -In UltimateAuth, sessions are **not just tokens**. +## 1. Understanding the Session Hierarchy -They are structured as: +In session-managed authentication modes, UltimateAuth organizes authentication state into three levels: -- Root β†’ user-level security -- Chain β†’ device (browser / mobile / app) -- Session β†’ individual authentication instance +```text +Session Root + | + +-- Session Chain A (Browser) + | | + | +-- Session A1 + | +-- Session A2 + | + +-- Session Chain B (Mobile) + | + +-- Session B1 +``` + +| Level | Responsibility | +|---|---| +| **Root** | Account-level session authority | +| **Chain** | Device-associated authentication continuity | +| **Session** | Individual authentication instance | + +These levels provide different boundaries for session management and revocation. + +A chain is associated with a device or client context, but it is not cryptographic proof of physical device identity. + +The available hierarchy and enforcement behavior depend on the authentication mode. + +For architectural details, see [Session Model & Lifecycle](/docs/sessions-devices/session-model). -πŸ‘‰ On the client, you interact with: +## 2. Accessing the Session Client + +Inject `IUAuthClient` into your Blazor component: + +```razor +@using CodeBeam.UltimateAuth.Client +@inject IUAuthClient UAuthClient +``` + +Session operations are available through: ```csharp -[Inject] IUAuthClient UAuthClient { get; set; } = null!; +UAuthClient.Sessions +``` + +The `Sessions` property exposes `ISessionClient`. -await UAuthClient.Sessions... +The interface separates operations for the current user from administrative operations targeting another user. + +## 3. List My Devices and Session Chains + +Use `GetMyChainsAsync` to retrieve the current user's session chains. + +```csharp +var result = await UAuthClient.Sessions.GetMyChainsAsync(); + +if (result.IsSuccess && result.Value is { } page) +{ + foreach (var chain in page.Items) + { + Console.WriteLine($"{chain.Browser} - {chain.OperatingSystem}"); + } +} ``` -## πŸ“‹ Get Active Sessions (Devices) +The method returns: + +```csharp +UAuthResult> +``` + +The response contains a paginated collection of session chain summaries. + +### SessionChainSummary + +Each summary provides information suitable for building a device management interface. + +| Property | Type | Description | +|---|---|---| +| `ChainId` | `SessionChainId` | Unique chain identifier | +| `DeviceType` | `string?` | Device category | +| `OperatingSystem` | `string?` | Reported operating system | +| `Platform` | `string?` | Client platform | +| `Browser` | `string?` | Reported browser | +| `CreatedAt` | `DateTimeOffset` | Chain creation time | +| `LastSeenAt` | `DateTimeOffset?` | Last recorded activity | +| `RotationCount` | `int` | Recorded rotation count | +| `TouchCount` | `int` | Recorded touch count | +| `IsRevoked` | `bool` | Whether the chain is revoked | +| `RevokedAt` | `DateTimeOffset?` | Revocation timestamp | +| `ActiveSessionId` | `AuthSessionId?` | Associated active session identifier | +| `IsCurrentDevice` | `bool` | Whether this is the current device context | +| `State` | `SessionChainState` | Current chain state | + +Device metadata is descriptive information, not a trusted physical device identifier. + +### Identify the Current Device + +Use `IsCurrentDevice` to distinguish the current chain in your UI. ```csharp var result = await UAuthClient.Sessions.GetMyChainsAsync(); + +if (result.IsSuccess && result.Value is { } page) +{ + var current = page.Items.FirstOrDefault(x => x.IsCurrentDevice); + + if (current is not null) + { + Console.WriteLine($"Current chain: {current.ChainId}"); + } +} +``` + +This is useful when displaying a device list with a **Current device** indicator. + +The property is determined by the server's current authentication context. + +## 4. Pagination + +`GetMyChainsAsync` accepts an optional `PageRequest`. + +```csharp +var result = await UAuthClient.Sessions.GetMyChainsAsync( + new PageRequest + { + PageNumber = 1, + PageSize = 10, + SortBy = "CreatedAt", + Descending = true + }); +``` + +`PageRequest` supports: + +| Property | Default | Description | +|---|---|---| +| `PageNumber` | `1` | Requested page | +| `PageSize` | `0` | Requested page size; zero uses the server pagination default | +| `SortBy` | `null` | Optional sorting field | +| `Descending` | `false` | Descending sort order | + +The server applies its pagination policy, including default and maximum page sizes. + +Supported sorting fields depend on the server endpoint. + +### Navigating Pages + +```csharp +var pageNumber = 1; + +var result = await UAuthClient.Sessions.GetMyChainsAsync( + new PageRequest + { + PageNumber = pageNumber, + PageSize = 10 + }); + +if (result.IsSuccess && result.Value is { } page) +{ + foreach (var chain in page.Items) + { + // Render the chain. + } + + if (page.HasNext) + { + // Enable the next-page action. + } +} +``` + +`PagedResult` includes `TotalCount`, `PageNumber`, `PageSize`, and `HasNext`. + +You can use these properties directly when building pagination controls. + +## 5. Inspect a Session Chain + +Use `GetMyChainDetailAsync` to retrieve detailed information about a specific chain. + +```csharp +SessionChainId chainId = selectedChain.ChainId; + +var result = await UAuthClient.Sessions.GetMyChainDetailAsync(chainId); + +if (result.IsSuccess && result.Value is { } detail) +{ + Console.WriteLine($"State: {detail.State}"); + Console.WriteLine($"Created: {detail.CreatedAt}"); + Console.WriteLine($"Last seen: {detail.LastSeenAt}"); +} +``` + +The method returns: + +```csharp +UAuthResult +``` + +### SessionChainDetail + +The detail model includes: + +- Chain identity and device metadata. +- Creation and last-seen timestamps. +- Chain state and revocation information. +- Rotation and touch counters. +- Active session identifier. +- Session history. + +### Inspect Session History + +```csharp +var result = await UAuthClient.Sessions.GetMyChainDetailAsync(chainId); + +if (result.IsSuccess && result.Value is { } detail) +{ + foreach (var session in detail.Sessions) + { + Console.WriteLine($"Session: {session.SessionId}"); + Console.WriteLine($"Created: {session.CreatedAt}"); + Console.WriteLine($"Expires: {session.ExpiresAt}"); + Console.WriteLine($"Revoked: {session.IsRevoked}"); + } +} +``` + +Each session is represented by `SessionInfo`. + +```csharp +public sealed record SessionInfo( + AuthSessionId SessionId, + DateTimeOffset CreatedAt, + DateTimeOffset ExpiresAt, + bool IsRevoked); +``` + +This allows applications to display individual authentication instances within a device-associated chain. + +## 6. Understanding Chain States + +`SessionChainState` defines three values: + +| State | Meaning | +|---|---| +| `Active` | Active chain state | +| `Passive` | Passive chain state | +| `Revoked` | Revoked chain state | + +For example: + +```csharp +switch (chain.State) +{ + case SessionChainState.Active: + // Display as active. + break; + + case SessionChainState.Passive: + // Display as passive. + break; + + case SessionChainState.Revoked: + // Display as revoked. + break; +} +``` + +A passive chain should not automatically be presented as a revoked chain. + +Use the returned state rather than inferring revocation from the absence of an active session. + +## 7. Revoke One of My Devices + +Use `RevokeMyChainAsync` to revoke a selected session chain. + +```csharp +var result = await UAuthClient.Sessions.RevokeMyChainAsync(chainId); + +if (result.IsSuccess) +{ + // Refresh the device list. +} +``` + +The method returns: + +```csharp +UAuthResult +``` + +Revoking a chain invalidates its associated session continuity according to the server's revocation model. + +It does not physically disable the device or prevent the user from authenticating again. + +### Detect Revocation of the Current Chain + +`RevokeResult` contains: + +```csharp +public sealed record RevokeResult +{ + public bool CurrentChain { get; init; } + public bool RootRevoked { get; init; } +} +``` + +You can inspect these properties: + +```csharp +var result = await UAuthClient.Sessions.RevokeMyChainAsync(chainId); + +if (result.IsSuccess && result.Value is { } revoke) +{ + if (revoke.CurrentChain) + { + // The current authentication chain was affected. + } + + if (revoke.RootRevoked) + { + // The result reports root-level revocation. + } +} ``` -πŸ‘‰ Returns: +When `RevokeMyChainAsync` succeeds and `CurrentChain` is true, the SDK publishes a `SessionRevoked` state event. -- Active devices -- Session chains -- Metadata (device, timestamps) +This helps the application react when its own authentication context has been invalidated. -
+## 8. Revoke All Other Devices -## πŸ” Get Session Detail +Use `RevokeMyOtherChainsAsync` to revoke other session chains while retaining the current chain. ```csharp -var detail = await UAuthClient.Sessions.GetMyChainDetailAsync(chainId); +var result = await UAuthClient.Sessions.RevokeMyOtherChainsAsync(); + +if (result.IsSuccess) +{ + // Other device chains were revoked. +} ``` -πŸ‘‰ Use this to: +This is suitable for a **Log out other devices** action in account settings. + +The server determines the current chain and applies the corresponding revocation scope. + +Applications should not attempt to identify the current device by comparing browser names, operating systems, or other descriptive metadata. + +## 9. Revoke All My Devices + +Use `RevokeAllMyChainsAsync` to revoke every session chain associated with the current user. + +```csharp +var result = await UAuthClient.Sessions.RevokeAllMyChainsAsync(); + +if (result.IsSuccess) +{ + // All applicable chains were revoked. +} +``` + +This includes the current device-associated chain. + +When the operation succeeds, the SDK publishes a `SessionRevoked` event. + +A typical use case is an account security page offering **Log out everywhere**. + +### Security Considerations + +Revoking server-managed sessions prevents their continued use where the authentication mode enforces that state. -- Inspect a specific device -- View session history +However, previously issued JWT access tokens may remain valid until expiration if the receiving resource server validates them locally without consulting current revocation state. -
+Do not assume that chain revocation automatically invalidates every access token in every authentication mode. -## πŸšͺ Logout vs Revoke (Important) +## 10. Logout Versus Session Revocation -UltimateAuth distinguishes between: +UltimateAuth exposes authentication logout operations through `Flows` and session management operations through `Sessions`. -### Logout +Both may affect authentication state, but they serve different application responsibilities. + +| Operation | Recommended API | +|---|---| +| Normal logout | `UAuthClient.Flows.LogoutAsync()` | +| Logout a selected device | `UAuthClient.Flows.LogoutMyDeviceAsync(request)` | +| Logout other devices | `UAuthClient.Flows.LogoutMyOtherDevicesAsync()` | +| Logout all devices | `UAuthClient.Flows.LogoutAllMyDevicesAsync()` | +| Inspect session chains | `UAuthClient.Sessions.GetMyChainsAsync()` | +| Revoke a selected chain | `UAuthClient.Sessions.RevokeMyChainAsync(chainId)` | +| Revoke other chains | `UAuthClient.Sessions.RevokeMyOtherChainsAsync()` | +| Revoke all chains | `UAuthClient.Sessions.RevokeAllMyChainsAsync()` | + +### Which API Should You Use? + +For ordinary authentication actions, prefer `Flows`. ```csharp await UAuthClient.Flows.LogoutAsync(); ``` -- Ends **current session** -- User can login again normally -- Does NOT affect other devices +For a session management interface that lists and controls chains, use `Sessions`. + +```csharp +var result = await UAuthClient.Sessions.RevokeMyChainAsync(chainId); +``` + +The APIs expose related capabilities through different client surfaces. + +The `Flows` API emphasizes authentication workflows, while `Sessions` exposes the session management model more directly. + +### Logout a Device Through Flows -### Revoke (Session Control) +The device logout method requires `LogoutDeviceRequest`. ```csharp -await UAuthClient.Sessions.RevokeMyChainAsync(chainId); +var result = await UAuthClient.Flows.LogoutMyDeviceAsync( + new LogoutDeviceRequest + { + ChainId = chainId + }); ``` -- Invalidates entire **device chain** -- All sessions under that device are revoked -- Cannot be restored -- New login creates a new chain +Pass a `SessionChainId`, not an individual `AuthSessionId`. + +For complete logout examples, see [Authentication](/docs/client/authentication). + +## 11. Build a Device Management Page + +A common application requirement is to show all devices associated with the current account and allow the user to revoke them. + +The following example uses the session client to build a simple Blazor device list. + +```razor +@using CodeBeam.UltimateAuth.Client +@using CodeBeam.UltimateAuth.Core.Contracts +@using CodeBeam.UltimateAuth.Core.Domain +@inject IUAuthClient UAuthClient + +

My Devices

+ +@if (_loading) +{ +

Loading devices...

+} +else if (_error is not null) +{ +

@_error

+} +else +{ + @foreach (var chain in _chains) + { +
+ @(chain.Browser ?? "Unknown browser") +

@(chain.OperatingSystem ?? "Unknown OS")

+

State: @chain.State

+ + @if (chain.IsCurrentDevice) + { + Current device + } + + +
+ } +} + +@code { + private IReadOnlyList _chains = []; + private bool _loading = true; + private string? _error; + + protected override async Task OnInitializedAsync() + { + await LoadAsync(); + } + + private async Task LoadAsync() + { + _loading = true; + _error = null; + + try + { + var result = await UAuthClient.Sessions.GetMyChainsAsync(new PageRequest + { + PageNumber = 1, + PageSize = 20 + }); + + if (result.IsSuccess && result.Value is { } page) + { + _chains = page.Items; + } + else + { + _error = "Unable to load devices."; + } + } + catch (Exception) + { + _error = "The session service is unavailable."; + } + finally + { + _loading = false; + } + } + + private async Task RevokeAsync(SessionChainId chainId) + { + var result = await UAuthClient.Sessions.RevokeMyChainAsync(chainId); + + if (result.IsSuccess) + { + await LoadAsync(); + } + else + { + _error = "Unable to revoke the selected device."; + } + } +} +``` + +This example demonstrates the core integration. + +A 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. -πŸ‘‰ Key difference: +The example displays the first page. Use the pagination API to expose additional chains. -- Logout = end current session -- Revoke = destroy device identity +## 12. Administrative Session Management -For standard auth process, use `UAuthClient.Flows.LogoutMyDeviceAsync(chainId)` instead of `UAuthClient.Sessions.RevokeMyChainAsync(chainId)` +Administrative operations allow authorized actors to inspect and manage another user's session state. -
+These methods require appropriate server-side permissions. -## πŸ“± Revoke Other Devices +The presence of an administrative SDK method does not grant administrative access. + +### List a User's Devices ```csharp -await UAuthClient.Sessions.RevokeMyOtherChainsAsync(); +var result = await UAuthClient.Sessions.GetUserChainsAsync(userKey); + +if (result.IsSuccess && result.Value is { } page) +{ + foreach (var chain in page.Items) + { + Console.WriteLine(chain.ChainId); + } +} ``` -πŸ‘‰ This: +The method accepts an optional `PageRequest`. -- Keeps current device active -- Logs out all other devices +```csharp +var result = await UAuthClient.Sessions.GetUserChainsAsync(userKey, + new PageRequest + { + PageNumber = 1, + PageSize = 20 + }); +``` -
+### Inspect a User's Chain -## πŸ’₯ Revoke All Sessions +Use `GetUserChainDetailAsync`: ```csharp -await UAuthClient.Sessions.RevokeAllMyChainsAsync(); +var result = await UAuthClient.Sessions.GetUserChainDetailAsync(userKey, chainId); + +if (result.IsSuccess && result.Value is { } detail) +{ + foreach (var session in detail.Sessions) + { + Console.WriteLine(session.SessionId); + } +} ``` -πŸ‘‰ This: +This provides the same `SessionChainDetail` model used for self-service chain inspection. -- Logs out ALL devices (including current) -- Forces full reauthentication everywhere +### Revoke an Individual Session -
+Use `RevokeUserSessionAsync` when you need to revoke one authentication instance rather than its entire chain. -## πŸ‘€ Admin Session Management +```csharp +var result = await UAuthClient.Sessions.RevokeUserSessionAsync(userKey, sessionId); + +if (result.IsSuccess) +{ + // The session revocation operation succeeded. +} +``` -### Get User Devices +The method requires an `AuthSessionId`. + +This is different from chain revocation. + +A session-level operation targets a single authentication instance; a chain-level operation targets the associated session continuity boundary. + +### Revoke a User's Device Chain ```csharp -await UAuthClient.Sessions.GetUserChainsAsync(userKey); +var result = await UAuthClient.Sessions.RevokeUserChainAsync(userKey, chainId); + +if (result.IsSuccess && result.Value is { } revoke) +{ + // Inspect revoke.CurrentChain or revoke.RootRevoked if needed. +} ``` -### Revoke Specific Session +The method returns `UAuthResult`. + +### Revoke All User Chains ```csharp -await UAuthClient.Sessions.RevokeUserSessionAsync(userKey, sessionId); +var result = await UAuthClient.Sessions.RevokeAllUserChainsAsync(userKey); + +if (result.IsSuccess) +{ + // All applicable user chains were revoked. +} ``` -### Revoke Device (Chain) +This is useful for administrative account security operations. + +### Revoke the User's Session Root + +UltimateAuth also exposes root-level revocation: ```csharp -await UAuthClient.Sessions.RevokeUserChainAsync(userKey, chainId); +var result = await UAuthClient.Sessions.RevokeUserRootAsync(userKey); + +if (result.IsSuccess) +{ + // Root-level revocation succeeded. +} ``` -### Revoke All User Sessions +Root revocation is a stronger account-level security operation than revoking one session or one chain. + +It is intended for situations where the user's broader session authority must be invalidated. + +Examples include account compromise response or other security-critical administrative actions. + +Do not use root revocation as a routine substitute for ordinary logout. + +## 13. Choosing the Correct Revocation Scope + +UltimateAuth provides multiple revocation levels because different security events require different responses. + +| Scope | API | Intended effect | +|---|---|---| +| Current logout | `Flows.LogoutAsync()` | End current authentication | +| Individual session | `Sessions.RevokeUserSessionAsync()` | Revoke one authentication instance | +| One chain | `Sessions.RevokeMyChainAsync()` | Revoke a device-associated chain | +| Other chains | `Sessions.RevokeMyOtherChainsAsync()` | Retain current chain, revoke others | +| All chains | `Sessions.RevokeAllMyChainsAsync()` | Revoke all current user's chains | +| User root | `Sessions.RevokeUserRootAsync()` | Invalidate account-level session authority | + +Use the narrowest scope that satisfies the security requirement. + +For example, removing one unrecognized device from an account should not automatically require revoking the entire user's session root. + +Conversely, a confirmed account-wide compromise may justify broader revocation. + +## 14. Handling Session Operation Results + +Session client methods return `UAuthResult` or `UAuthResult`. + +A typical result-handling pattern is: ```csharp -await UAuthClient.Sessions.RevokeAllUserChainsAsync(userKey); +var result = await UAuthClient.Sessions.GetMyChainsAsync(); + +if (result.IsSuccess && result.Value is { } page) +{ + // Use the returned data. +} +else if (result.Http.IsUnauthorized) +{ + // Authentication is required. +} +else if (result.Http.IsForbidden) +{ + // The operation is not authorized. +} +else +{ + // Handle another failure. +} ``` -
+Available response information includes: + +- `IsSuccess` +- `Status` +- `Problem` +- `ErrorText` +- `CorrelationId` +- `TraceId` + +The `Http` helper exposes status checks such as `IsBadRequest`, `IsUnauthorized`, `IsForbidden`, and `IsConflict`. + +### Administrative Authorization Failures + +An administrative session operation may return `403 Forbidden` when the actor lacks the required permission. + +```csharp +var result = await UAuthClient.Sessions.GetUserChainsAsync(userKey); + +if (result.Http.IsForbidden) +{ + // The current actor cannot inspect this user's sessions. +} +``` + +Do not attempt to work around authorization failures by switching to a different endpoint. + +The server is responsible for enforcing the operation's access policy. + +## 15. Session State Events + +The session client publishes state events for certain successful revocation operations. + +The current implementation publishes `SessionRevoked` when: + +- `RevokeMyChainAsync` succeeds and the result identifies the current chain. +- `RevokeAllMyChainsAsync` succeeds. + +Other session management methods do not necessarily publish the same event. + +Applications should not assume that every successful administrative revocation produces a local state event. + +State events help the client update its authentication-related UI. + +They do not replace authoritative server-side session validation. + +## 16. Security and Operational Considerations + +### Device Metadata Is Descriptive + +Browser, operating system, platform, and device type are useful for displaying session information. + +They are not reliable proof of physical device ownership. + +A malicious or modified client may be able to influence some reported metadata. + +### Session Chains Are Not Permanent Device Identities + +Revoking a chain invalidates its authentication continuity. + +It does not permanently ban the physical device. + +A later successful login may establish new authentication state. -## 🧠 Device Model +### Revocation Is Server-Controlled -Each chain represents a **device identity**: +Client-side removal of a device card does not revoke the underlying session. -- Browser instance -- Mobile device -- Application instance +Always use the appropriate session or logout operation and inspect its result. -πŸ‘‰ Sessions are grouped under chains. +### Authentication Modes Matter -
+The Root β†’ Chain β†’ Session hierarchy is relevant to server-managed session modes. -## πŸ” Security Implications +Do not assume identical session management semantics in PureJwt or other modes that do not enforce the same hierarchy during ordinary request validation. -Session operations are security-critical: +### Protect Administrative Operations -- Revoke is irreversible -- Device isolation is enforced -- Cross-device attacks are contained +Administrative session revocation can interrupt another user's authentication. -## 🎯 Summary +These operations should be protected by appropriate permissions and, where required, stronger authentication or administrative confirmation. -Session management in UltimateAuth: +## Next Steps -- is device-aware -- separates logout vs revoke -- gives full control over user sessions +- [Authentication](/docs/client/authentication) β€” Login, logout, refresh, and authentication state validation. +- [Session Model & Lifecycle](/docs/sessions-devices/session-model) β€” Session roots, chains, lifecycle, and expiration. +- [Device Management](/docs/sessions-devices/device-management) β€” Device-associated authentication contexts. +- [Session Security Model](/docs/security/session-security-model) β€” Revocation and session security boundaries. \ No newline at end of file diff --git a/docs/content/client/user-management.md b/docs/content/client/user-management.md index f6c7c72e..68c785c4 100644 --- a/docs/content/client/user-management.md +++ b/docs/content/client/user-management.md @@ -4,179 +4,1209 @@ order: 4 group: client --- +# User Management -# πŸ‘€ User Management Guide +The UltimateAuth Client SDK provides a comprehensive API for managing users, profiles, account status, and user lifecycle operations. -This section explains how to manage users using the UltimateAuth client. +Through `UAuthClient.Users`, applications can implement account settings, profile management, user registration, and administrative user management without manually constructing API requests. -## 🧠 Overview +UltimateAuth distinguishes between **user identity**, **user profiles**, and **account lifecycle state**. -User operations are handled via: +A user has a stable identity and may have multiple profiles. Account status controls lifecycle behavior independently of profile data. + +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). + +## 1. Accessing the User Client + +In a Blazor component, inject `IUAuthClient`: + +```razor +@using CodeBeam.UltimateAuth.Client +@inject IUAuthClient UAuthClient +``` + +User operations are available through: + +```csharp +UAuthClient.Users +``` + +The `Users` property exposes `IUserClient`. + +Its operations are divided into three functional areas: + +| Area | Capabilities | +|---|---| +| User lifecycle | Create users, change status, delete accounts | +| Self-service profiles | Retrieve, update, create, and delete your own profiles | +| Administration | Query users and manage another user's account and profiles | + +Self-service methods operate on the authenticated user. + +Administrative methods operate on an explicitly identified target and require the appropriate server-side authorization. + +## 2. Get the Current User + +Use `GetMeAsync` to retrieve the current user's profile. + +```csharp +var result = await UAuthClient.Users.GetMeAsync(); + +if (result.IsSuccess && result.Value is { } user) +{ + Console.WriteLine(user.DisplayName); + Console.WriteLine(user.Status); + Console.WriteLine(user.UserKey); +} +``` + +The method returns: + +```csharp +UAuthResult +``` + +When no profile is specified, UltimateAuth returns the default profile. + +### UserView + +`UserView` contains account information and the selected profile's data. + +| Property | Description | +|---|---| +| `UserKey` | Stable user identifier | +| `Status` | Current account status | +| `UserName` | Username | +| `PrimaryEmail` | Primary email address | +| `PrimaryPhone` | Primary phone number | +| `ProfileKey` | Selected profile | +| `FirstName`, `LastName` | Profile name fields | +| `DisplayName` | Display name | +| `Bio` | Biography | +| `BirthDate` | Birth date | +| `Gender` | Profile gender field | +| `Language` | Preferred language | +| `TimeZone` | Preferred time zone | +| `Culture` | Culture preference | +| `EmailVerified`, `PhoneVerified` | Identifier verification information | +| `CreatedAt` | Account creation timestamp | +| `Metadata` | Custom profile metadata | + +The returned profile is a view of server-managed user information. + +It should not be treated as a substitute for authoritative authentication or authorization state. + +## 3. Update the Current User's Profile + +Use `UpdateMyProfileAsync` to update the authenticated user's profile. + +```csharp +var result = await UAuthClient.Users.UpdateMyProfileAsync( + new UpdateProfileRequest + { + DisplayName = "John Doe", + FirstName = "John", + LastName = "Doe", + Language = "en", + TimeZone = "Europe/London" + }); + +if (result.IsSuccess) +{ + // Profile update succeeded. +} +``` + +The method returns `UAuthResult`. + +### Important: Profile Updates Replace Editable Fields + +`UpdateProfileRequest` uses replacement semantics rather than partial-update semantics. + +This means: + +- Editable fields supplied in the request replace their previous values. +- Explicit `null` values clear existing values. +- Omitted editable properties are also treated as `null`. + +For example: + +```csharp +await UAuthClient.Users.UpdateMyProfileAsync( + new UpdateProfileRequest + { + DisplayName = "John Doe" + }); +``` + +This does **not** mean β€œchange only the display name.” + +Other editable profile fields may be cleared. + +### Recommended: Read, Modify, and Submit + +When building a profile editor, retrieve the current profile and preserve values that should remain unchanged. + +```csharp +var currentResult = await UAuthClient.Users.GetMeAsync(); + +if (!currentResult.IsSuccess || currentResult.Value is not { } current) +{ + return; +} + +var update = new UpdateProfileRequest +{ + FirstName = current.FirstName, + LastName = current.LastName, + DisplayName = "John Doe", + BirthDate = current.BirthDate, + Gender = current.Gender, + Bio = current.Bio, + Language = current.Language, + TimeZone = current.TimeZone, + Culture = current.Culture, + Metadata = current.Metadata +}; + +var result = await UAuthClient.Users.UpdateMyProfileAsync(update); +``` + +This preserves the values returned by the initial read, but it is not an atomic patch operation. + +If another request modifies the profile between the read and update, the later replacement may overwrite those changes. + +Applications with concurrent profile editing requirements should account for this behavior. + +### Profile State Events + +After a successful `UpdateMyProfileAsync` operation, the client publishes a `ProfileChanged` event. + +The event carries the `UpdateProfileRequest` used for the operation. + +The configured client state event handling mode determines how the application processes the notification. + +The event is published only when the SDK operation reports success. + +## 4. Understanding Multiple Profiles + +UltimateAuth supports multiple profiles for a single user when enabled through server configuration. + +For example, one account may have: + +```text +User + | + +-- default + | + +-- business + | + +-- public +``` + +All profiles belong to the same user identity. + +They do not represent separate authentication accounts or independently authenticated principals. + +Profiles allow applications to organize different sets of user-facing information under one account. + +### Default Profile + +Every newly created user receives a default profile. + +Its key is: + +```csharp +ProfileKey.Default +``` + +The default profile cannot be deleted. + +When a profile request omits `ProfileKey`, the default profile is selected. + +### Profile Keys + +`ProfileKey` is a strongly typed value. + +Use `TryCreate` to construct a custom key: + +```csharp +if (!ProfileKey.TryCreate("business", out var profileKey)) +{ + throw new InvalidOperationException("Invalid profile key."); +} +``` + +Profile keys are normalized by trimming whitespace and converting the value to lowercase. + +A valid key must not be empty or exceed 64 characters. + +## 5. Retrieve a Specific Profile + +Use `GetProfileRequest` to select a profile. + +```csharp +if (!ProfileKey.TryCreate("business", out var profileKey)) +{ + return; +} + +var result = await UAuthClient.Users.GetMeAsync( + new GetProfileRequest + { + ProfileKey = profileKey + }); + +if (result.IsSuccess && result.Value is { } profile) +{ + Console.WriteLine(profile.DisplayName); + Console.WriteLine(profile.ProfileKey); +} +``` + +The response is still `UserView`. + +The account identity and status remain associated with the same user; the selected profile determines which profile information is returned. + +## 6. Create an Additional Profile + +Use `CreateMyProfileAsync` to create a new profile for the authenticated user. + +```csharp +if (!ProfileKey.TryCreate("business", out var profileKey)) +{ + return; +} + +var result = await UAuthClient.Users.CreateMyProfileAsync( + new CreateProfileRequest + { + ProfileKey = profileKey, + DisplayName = "John Doe Consulting", + Bio = "Business profile", + Language = "en" + }); + +if (result.IsSuccess) +{ + // Profile created. +} +``` + +`CreateProfileRequest` requires `ProfileKey`. + +It also supports the same general profile information used for user-facing profiles. + +### Clone an Existing Profile + +A new profile can be created using an existing profile as its source. + +```csharp +ProfileKey.TryCreate("business", out var businessKey); + +var result = await UAuthClient.Users.CreateMyProfileAsync( + new CreateProfileRequest + { + ProfileKey = businessKey, + CloneFrom = ProfileKey.Default + }); +``` + +`CloneFrom` identifies the source profile. + +This is useful when creating a new profile that should initially reuse information from an existing one. + +The exact merge and override behavior is determined by the server's profile creation implementation. + +### Profile Creation Events + +After successful self-service profile creation, the SDK publishes `ProfileChanged`. + +This allows application components to refresh profile-related views. + +## 7. Update a Specific Profile + +Set `ProfileKey` in `UpdateProfileRequest` to select the profile being updated. + +```csharp +ProfileKey.TryCreate("business", out var businessKey); + +var result = await UAuthClient.Users.UpdateMyProfileAsync( + new UpdateProfileRequest + { + ProfileKey = businessKey, + DisplayName = "John Doe Consulting", + Bio = "Updated business profile" + }); +``` + +The same replacement semantics apply to custom profiles. + +If the application intends to preserve existing fields, retrieve the selected profile first and include the values that should remain unchanged. + +## 8. Delete a Profile + +Use `DeleteMyProfileAsync` to remove a custom profile. + +```csharp +ProfileKey.TryCreate("business", out var businessKey); + +var result = await UAuthClient.Users.DeleteMyProfileAsync( + new DeleteProfileRequest + { + ProfileKey = businessKey + }); + +if (result.IsSuccess) +{ + // Profile deleted. +} +``` + +The default profile cannot be deleted. + +Deleting a profile is different from deleting the user account. + +The user identity and other profiles remain subject to their own lifecycle and server-side state. + +On successful self-service profile deletion, the SDK publishes `ProfileChanged` with the deleted profile key. + +## 9. Create a User + +Use `CreateAsync` to request user creation through the standard user creation endpoint. + +```csharp +var result = await UAuthClient.Users.CreateAsync( + new CreateUserRequest + { + UserName = "johndoe", + Email = "john@example.com", + Password = password, + FirstName = "John", + LastName = "Doe", + DisplayName = "John Doe" + }); + +if (result.IsSuccess && result.Value is { IsSuccess: true } created) +{ + Console.WriteLine($"Created user: {created.UserKey}"); +} +``` + +The method returns: + +```csharp +UAuthResult +``` + +User creation availability depends on the server's configuration and access policies. + +### CreateUserRequest + +The request supports several groups of fields: + +| Category | Properties | +|---|---| +| Identifiers | `UserName`, `Email`, `Phone` | +| Verification flags | `UserNameVerified`, `EmailVerified`, `PhoneVerified` | +| Credentials | `Password` | +| Profile | `FirstName`, `LastName`, `DisplayName`, `BirthDate`, `Gender`, `Bio` | +| Preferences | `Language`, `TimeZone`, `Culture` | +| Extensions | `Metadata` | + +Verification flags must not be treated as client-controlled proof of identifier ownership. + +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). + +### UserCreateResult + +The returned value includes: + +```csharp +public sealed record UserCreateResult +{ + public required bool IsSuccess { get; init; } + public string? UserKey { get; init; } + public string? FailureReason { get; init; } +} +``` + +The `UserKey` is returned as a string when creation succeeds. + +Check both the transport-level `UAuthResult.IsSuccess` and the operation-level `UserCreateResult.IsSuccess` where applicable. + +## 10. Administrative User Creation + +Use `CreateAsAdminAsync` when creating a user through an administrative context. + +```csharp +var result = await UAuthClient.Users.CreateAsAdminAsync( + new CreateUserRequest + { + UserName = "newemployee", + Email = "employee@example.com", + DisplayName = "New Employee" + }); + +if (result.IsSuccess && result.Value is { IsSuccess: true } created) +{ + Console.WriteLine(created.UserKey); +} +``` + +Administrative creation is distinct from ordinary user creation. + +The server enforces the required permissions and applies the relevant user creation policies. + +Applications should not assume that calling the administrative endpoint bypasses validation or identifier ownership rules. + +## 11. Query Users + +Use `QueryAsync` to search and paginate users. + +```csharp +var result = await UAuthClient.Users.QueryAsync( + new UserQuery + { + Search = "john", + PageNumber = 1, + PageSize = 20 + }); + +if (result.IsSuccess && result.Value is { } page) +{ + foreach (var user in page.Items) + { + Console.WriteLine(user.DisplayName); + Console.WriteLine(user.Status); + } +} +``` + +The method returns: + +```csharp +UAuthResult> +``` + +### UserQuery Filters + +`UserQuery` extends `PageRequest` and supports: + +| Property | Description | +|---|---| +| `Search` | Search term | +| `Status` | Filter by user status | +| `IncludeDeleted` | Include deleted users when authorized | +| `ProfileKey` | Select the profile context for the query | +| `PageNumber` | Page number | +| `PageSize` | Page size | +| `SortBy` | Optional sort field | +| `Descending` | Descending sort order | + +### Search Behavior + +The query model documents case-insensitive partial matching for: + +- Display name. +- Username. +- Primary email. +- Primary phone. + +A complete valid `UserKey` can also be used as a search term. + +Partial `UserKey` matching is not supported. + +### Filter by Status + +```csharp +var result = await UAuthClient.Users.QueryAsync( + new UserQuery + { + Status = UserStatus.Active, + PageNumber = 1, + PageSize = 20 + }); +``` + +### Include Deleted Users ```csharp -UAuthClient.Users... +var result = await UAuthClient.Users.QueryAsync( + new UserQuery + { + IncludeDeleted = true, + PageNumber = 1, + PageSize = 20 + }); ``` -This includes: +The ability to query deleted accounts depends on server-side authorization and data retention behavior. + +## 12. UserSummary and Pagination + +`QueryAsync` returns `UserSummary` items. + +Each summary contains: + +| Property | Description | +|---|---| +| `UserKey` | User identifier | +| `UserName` | Username | +| `DisplayName` | Display name | +| `PrimaryEmail` | Primary email | +| `PrimaryPhone` | Primary phone | +| `Status` | Account status | +| `CreatedAt` | Creation timestamp | + +This model is suitable for administrative user lists. + +### Navigating Pages -- Profile management -- User lifecycle -- Admin operations +```csharp +var result = await UAuthClient.Users.QueryAsync( + new UserQuery + { + PageNumber = 2, + PageSize = 25 + }); + +if (result.IsSuccess && result.Value is { } page) +{ + Console.WriteLine($"Total users: {page.TotalCount}"); + Console.WriteLine($"Current page: {page.PageNumber}"); + Console.WriteLine($"Has next page: {page.HasNext}"); +} +``` + +Use `PagedResult` rather than assuming every query returns the complete user collection. -
+## 13. Get a Specific User -## πŸ™‹ Get Current User +Use `GetUserAsync` to retrieve a user's profile through the administrative API. ```csharp -var me = await UAuthClient.Users.GetMeAsync(); +var result = await UAuthClient.Users.GetUserAsync(userKey); + +if (result.IsSuccess && result.Value is { } user) +{ + Console.WriteLine(user.DisplayName); + Console.WriteLine(user.Status); +} ``` -πŸ‘‰ Returns: +The method returns `UAuthResult`. + +When no profile is specified, the default profile is selected. + +### Retrieve a Specific User Profile + +```csharp +ProfileKey.TryCreate("business", out var businessKey); + +var result = await UAuthClient.Users.GetUserAsync( + userKey, + new GetProfileRequest + { + ProfileKey = businessKey + }); +``` -- User profile -- Status -- Basic identity data +The actor must have permission to access the target user's requested information. -
+## 14. Update Another User's Profile -## ✏️ Update Profile +Use `UpdateUserProfileAsync` to update a selected user's profile. ```csharp -await UAuthClient.Users.UpdateMeAsync(new UpdateProfileRequest +var result = await UAuthClient.Users.UpdateUserProfileAsync( + userKey, + new UpdateProfileRequest + { + DisplayName = "Updated Display Name", + FirstName = "John", + LastName = "Doe" + }); + +if (result.IsSuccess) { - DisplayName = "John Doe" -}); + // Profile updated. +} ``` -πŸ‘‰ Triggers: +This operation uses the same replacement semantics as `UpdateMyProfileAsync`. -- Profile update -- State event (ProfileChanged) +Omitted editable fields may be cleared. -
+For partial UI editing, retrieve the target profile and preserve values that should not change. -## ❌ Delete Current User +### Update a Named Profile ```csharp -await UAuthClient.Users.DeleteMeAsync(); +ProfileKey.TryCreate("business", out var businessKey); + +var result = await UAuthClient.Users.UpdateUserProfileAsync(userKey, + new UpdateProfileRequest + { + ProfileKey = businessKey, + DisplayName = "Updated Business Profile" + }); ``` -πŸ‘‰ This: +Administrative profile updates require the appropriate authorization for the target user. + +## 15. Manage Another User's Profiles + +Administrators can create and delete profiles for a selected user. + +### Create a Profile -- Deletes user (based on configured mode) -- Ends session -- Triggers state update +```csharp +ProfileKey.TryCreate("business", out var businessKey); -
+var result = await UAuthClient.Users.CreateUserProfileAsync(userKey, + new CreateProfileRequest + { + ProfileKey = businessKey, + DisplayName = "Business Profile" + }); +``` -## πŸ‘‘ Admin: Query Users +### Delete a Profile ```csharp -var result = await UAuthClient.Users.QueryAsync(new UserQuery +ProfileKey.TryCreate("business", out var businessKey); + +var result = await UAuthClient.Users.DeleteUserProfileAsync(userKey, + new DeleteProfileRequest + { + ProfileKey = businessKey + }); +``` + +The default profile cannot be deleted. + +Administrative profile operations do not automatically publish the same local `ProfileChanged` event as self-service profile operations. + +Refresh affected administrative UI data explicitly when needed. + +## 16. Understanding User Status + +UltimateAuth separates account lifecycle status from profile information. + +A user may be active, suspended, disabled, locked, awaiting activation, or in another supported lifecycle state. + +Status can affect authentication eligibility, session continuation, and authorization. + +However, the exact enforcement behavior depends on the configured server policies and authentication mode. + +### Self-Service and Administrative Statuses + +UltimateAuth exposes separate status enums for self-service and administrative operations. + +This prevents the client contract from treating every administrative lifecycle transition as a self-service capability. + +**SelfAssignableUserStatus** + +```csharp +public enum SelfAssignableUserStatus { - Search = "john", - PageNumber = 1, - PageSize = 10 -}); + Active = 0, + SelfSuspended = 10 +} ``` -πŸ‘‰ Supports: +**AdminAssignableUserStatus** -- search -- pagination -- filtering (status, etc.) +```csharp +public enum AdminAssignableUserStatus +{ + Active = 0, + Disabled = 20, + Suspended = 30, + Locked = 40, + RiskHold = 50, + PendingActivation = 60, + PendingVerification = 70, + Unknown = 100 +} +``` + +These enums describe requestable statuses. + +They do not mean every transition is permitted in every application context. + +The server validates the requested transition. -
+## 17. Change Your Own Status -## βž• Create User +Use `ChangeMyStatusAsync` for supported self-service status transitions. ```csharp -await UAuthClient.Users.CreateAsync(new CreateUserRequest +var result = await UAuthClient.Users.ChangeMyStatusAsync( + new ChangeUserStatusSelfRequest + { + NewStatus = SelfAssignableUserStatus.SelfSuspended + }); + +if (result.IsSuccess && result.Value is { IsSuccess: true } change) { - UserName = "john", - Email = "john@mail.com", - Password = "123456" -}); + Console.WriteLine($"New status: {change.CurrentStatus}"); +} ``` -## πŸ›  Admin Create +This operation can be used to implement account self-suspension. + +The server determines whether the transition is permitted. + +### UserStatusChangeResult + +The result model includes: + +| Property | Description | +|---|---| +| `IsSuccess` | Whether the status operation succeeded | +| `PreviousStatus` | Status before the operation | +| `CurrentStatus` | Status after the operation | +| `FailureReason` | Optional failure description | + +For example: ```csharp -await UAuthClient.Users.CreateAsAdminAsync(request); +if (result.IsSuccess && result.Value is { } change) +{ + if (change.IsSuccess) + { + Console.WriteLine($"{change.PreviousStatus} -> {change.CurrentStatus}"); + } + else + { + Console.WriteLine(change.FailureReason ?? "Status change failed."); + } +} ``` -
+The result may also report a successful no-change outcome when the requested state already matches the current state. + +### State Event + +After a successful SDK operation, `ChangeMyStatusAsync` publishes `ProfileChanged`. -## πŸ”„ Change Status +This is the current client event behavior, even though the operation changes account status rather than editable profile fields. -### Self +Applications should not assume that a status change always produces a dedicated `AuthorizationChanged` event. + +## 18. Change Another User's Status + +Use `ChangeUserStatusAsync` for administrative lifecycle management. ```csharp -await UAuthClient.Users.ChangeMyStatusAsync(new ChangeUserStatusSelfRequest +var result = await UAuthClient.Users.ChangeUserStatusAsync(userKey, + new ChangeUserStatusAdminRequest + { + NewStatus = AdminAssignableUserStatus.Suspended + }); + +if (result.IsSuccess && result.Value is { IsSuccess: true } change) { - Status = UserStatus.SelfSuspended -}); + Console.WriteLine($"Current status: {change.CurrentStatus}"); +} ``` -### Admin +This operation requires administrative authorization. + +### Example: Disable an Account + +```csharp +var result = await UAuthClient.Users.ChangeUserStatusAsync(userKey, + new ChangeUserStatusAdminRequest + { + NewStatus = AdminAssignableUserStatus.Disabled + }); +``` + +### Example: Restore an Active Status ```csharp -await UAuthClient.Users.ChangeUserStatusAsync(userKey, new ChangeUserStatusAdminRequest +var result = await UAuthClient.Users.ChangeUserStatusAsync(userKey, + new ChangeUserStatusAdminRequest + { + NewStatus = AdminAssignableUserStatus.Active + }); +``` + +Whether a particular transition is permitted depends on the server's lifecycle rules. + +Do not assume that setting `Active` automatically restores every form of previously revoked authentication state. + +## 19. Delete the Current User + +Use `DeleteMeAsync` to request deletion of the current account. + +```csharp +var result = await UAuthClient.Users.DeleteMeAsync(); + +if (result.IsSuccess) { - Status = UserStatus.Suspended -}); + // Account deletion succeeded. +} ``` -
+Self-service deletion is a soft-delete operation. + +According to the `IUserClient` contract, this operation removes associated profiles and sessions. + +After successful deletion, the client publishes a `UserDeleted` state event using patch handling mode. + +### Important Considerations + +Account deletion is not equivalent to ordinary logout. -## ❌ Delete User (Admin) +It changes the user's lifecycle state and may affect associated data across multiple domains. + +Before exposing a delete-account action, applications should provide an appropriate confirmation flow and explain the consequences. + +The server remains responsible for authorization, data cleanup, and deletion policy enforcement. + +## 20. Administrative User Deletion + +Administrators can delete users through `DeleteUserAsync`. + +The method accepts a `DeleteUserRequest` specifying the deletion mode. + +### Soft Delete + +```csharp +var result = await UAuthClient.Users.DeleteUserAsync(userKey, + new DeleteUserRequest + { + Mode = DeleteMode.Soft + }); + +if (result.IsSuccess && result.Value is { IsSuccess: true } deleted) +{ + Console.WriteLine($"Deletion mode: {deleted.Mode}"); +} +``` + +### Hard Delete ```csharp -await UAuthClient.Users.DeleteUserAsync(userKey, new DeleteUserRequest +var result = await UAuthClient.Users.DeleteUserAsync(userKey, + new DeleteUserRequest + { + Mode = DeleteMode.Hard + }); +``` + +Hard deletion is a more destructive operation and should be restricted to appropriately authorized administrative workflows. + +### DeleteMode + +```csharp +public enum DeleteMode { - Mode = DeleteMode.Soft -}); + Soft = 0, + Hard = 10 +} +``` + +### UserDeleteResult + +The operation returns `UAuthResult`. + +`UserDeleteResult` includes: + +- `IsSuccess` +- `Mode` +- `FailureReason` + +For example: + +```csharp +if (!result.IsSuccess) +{ + // Handle transport or API-level failure. +} +else if (result.Value is { IsSuccess: false } deletion) +{ + // Handle the operation-level failure. + Console.WriteLine(deletion.FailureReason); +} +``` + +A previously deleted account may produce an operation-level success result indicating that no additional deletion was necessary. + +Do not infer the complete persistence or retention behavior from the `DeleteMode` value alone. + +## 21. Build a User Administration Table + +The following example demonstrates a basic Blazor user list using `QueryAsync`. + +```razor +@using CodeBeam.UltimateAuth.Client +@using CodeBeam.UltimateAuth.Core.Contracts +@using CodeBeam.UltimateAuth.Users.Contracts +@inject IUAuthClient UAuthClient + +

Users

+ + + + +@if (_loading) +{ +

Loading users...

+} +else if (_error is not null) +{ +

@_error

+} +else +{ + + + + + + + + + + @foreach (var user in _users) + { + + + + + + } + +
NameUsernameStatus
@user.DisplayName@user.UserName@user.Status
+ + + +} + +@code { + private string _search = ""; + private int _pageNumber = 1; + private bool _hasNext; + private bool _loading; + private string? _error; + private IReadOnlyList _users = []; + + protected override async Task OnInitializedAsync() + { + await LoadAsync(); + } + + private async Task SearchAsync() + { + _pageNumber = 1; + await LoadAsync(); + } + + private async Task PreviousPageAsync() + { + if (_pageNumber <= 1) + return; + + _pageNumber--; + await LoadAsync(); + } + + private async Task NextPageAsync() + { + if (!_hasNext) + return; + + _pageNumber++; + await LoadAsync(); + } + + private async Task LoadAsync() + { + _loading = true; + _error = null; + + try + { + var result = await UAuthClient.Users.QueryAsync( + new UserQuery + { + Search = _search, + PageNumber = _pageNumber, + PageSize = 20 + }); + + if (result.IsSuccess && result.Value is { } page) + { + _users = page.Items; + _hasNext = page.HasNext; + } + else + { + _error = "Unable to load users."; + _users = []; + _hasNext = false; + } + } + catch (Exception) + { + _error = "The user service is unavailable."; + _users = []; + _hasNext = false; + } + finally + { + _loading = false; + } + } +} +``` + +This example demonstrates searching and pagination. + +A production administration interface should additionally include loading indicators, action confirmation, permission-aware UI controls, and appropriate handling for concurrent changes. + +All administrative operations must remain protected by server-side authorization. + +## 22. Handling User Operation Results + +UltimateAuth uses structured result contracts for user operations. + +Some methods return: + +```csharp +UAuthResult ``` -## πŸ” Get User +Others return: ```csharp -var user = await UAuthClient.Users.GetUserAsync(userKey); +UAuthResult ``` -## ✏️ Update User (Admin) +For operations such as user creation, status changes, and deletion, the returned value also contains an operation-level `IsSuccess` property. + +### Transport-Level Versus Operation-Level Success ```csharp -await UAuthClient.Users.UpdateUserAsync(userKey, request); +var result = await UAuthClient.Users.CreateAsync(request); + +if (!result.IsSuccess) +{ + // API-level failure. +} +else if (result.Value is { IsSuccess: false } operation) +{ + // User creation was processed but did not succeed. + Console.WriteLine(operation.FailureReason); +} +else if (result.Value is { IsSuccess: true } created) +{ + Console.WriteLine(created.UserKey); +} ``` -## 🧠 Lifecycle Model +Check the appropriate success level for the operation. + +### HTTP Status Helpers + +`UAuthResult` exposes convenient status helpers. + +```csharp +if (result.Http.IsUnauthorized) +{ + // Authentication required. +} +else if (result.Http.IsForbidden) +{ + // Insufficient permission. +} +else if (result.Http.IsConflict) +{ + // Conflicting operation or state. +} +else if (result.Http.IsBadRequest) +{ + // Invalid request. +} +``` + +Additional diagnostic fields include `CorrelationId`, `TraceId`, `Problem`, and `ErrorText`. + +Avoid exposing sensitive internal failure details directly to end users. + +## 23. Client State Events + +`UAuthUserClient` publishes state events for successful self-service operations. + +| Operation | Event | +|---|---| +| `UpdateMyProfileAsync` | `ProfileChanged` | +| `CreateMyProfileAsync` | `ProfileChanged` | +| `DeleteMyProfileAsync` | `ProfileChanged` | +| `ChangeMyStatusAsync` | `ProfileChanged` | +| `DeleteMeAsync` | `UserDeleted` | + +The event payload depends on the operation. + +Administrative user operations do not automatically publish these same local self-service events. + +For example, after updating another user's profile, an administration panel should refresh the affected user data as needed. + +Client events help synchronize application state, but they do not replace authoritative server-side account status or permission checks. + +## 24. Security and Lifecycle Considerations + +### Self-Service and Administrative Boundaries + +Self-service methods operate on the current authenticated user. + +Administrative methods identify an explicit target user. + +Do not assume that access to the SDK automatically authorizes an operation. + +The server must enforce actor, target, tenant, and action requirements. + +### Status Changes and Existing Sessions + +Changing account status may affect authentication eligibility and session continuation. + +However, immediate invalidation of existing credentials depends on the server's security policy and authentication mode. + +A client-side status update is not itself proof that every previously issued token has been invalidated. + +### Profile Data Is Not Authorization Data + +A user's display name, biography, or profile metadata should not be used as proof of permissions. -Users have a lifecycle: +Authorization decisions must use the relevant trusted security context. -- Active -- Suspended -- Disabled -- Deleted (soft/hard) +### Profile Replacement Can Cause Data Loss -πŸ‘‰ Status impacts: +`UpdateProfileRequest` is not a patch contract. -- login ability -- session validity -- authorization +Submitting an incomplete request may clear existing profile fields. -
+Applications should preserve intended values and consider concurrency when multiple editors can modify the same profile. -## πŸ” Security Notes +### Deletion Is a Lifecycle Operation -- Status changes may invalidate sessions -- Delete may trigger cleanup across domains -- Admin actions are policy-protected +Soft deletion and hard deletion have different implications. -## 🎯 Summary +Applications should consider retention, recovery, audit requirements, and dependent data before exposing destructive actions. -User management in UltimateAuth: +## Next Steps -- is lifecycle-aware -- supports self + admin flows -- integrates with session & security model +- [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. +- [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/advanced-configuration.md b/docs/content/configuration/advanced-configuration.md index 7ff33835..0023948a 100644 --- a/docs/content/configuration/advanced-configuration.md +++ b/docs/content/configuration/advanced-configuration.md @@ -1,177 +1,1167 @@ --- title: Advanced Configuration -order: 6 +order: 4 group: configuration --- +# Advanced Configuration -# 🧠 Advanced Configuration +UltimateAuth supports advanced customization of authentication behavior through effective server options, authentication mode resolution, dependency injection, custom infrastructure, and lifecycle events. -UltimateAuth is designed to be flexible β€” but not fragile. +These mechanisms serve different purposes: -πŸ‘‰ You can customize almost every part of the system -πŸ‘‰ Without breaking its security guarantees +- **Options** define configurable behavior. +- **Effective options** adapt that behavior to the selected authentication mode. +- **Resolvers** determine information required by authentication flows. +- **Authorities and validators** participate in security decisions. +- **Stores** persist authentication and application state. +- **Events** allow applications to react to completed operations. -## ⚠️ Philosophy +Most applications should begin with the standard Server and Client options. Advanced customization becomes useful when an application needs specialized authentication behavior, custom infrastructure, or integration with existing systems. -Customization in UltimateAuth follows one rule: +This guide explains the supported extension mechanisms, their execution order, and the security boundaries developers must preserve. -πŸ‘‰ You can extend behavior -πŸ‘‰ You should not bypass security +## 1. Configuration Versus Extensibility -
+UltimateAuth distinguishes between configuring existing behavior and replacing an implementation. -## 🧩 Extension Points +For example, changing a session lifetime is configuration: -UltimateAuth exposes multiple extension points: +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Session.Lifetime = TimeSpan.FromHours(8); +}); +``` + +Providing a custom clock is an infrastructure extension: + +```csharp +builder.Services.AddSingleton(); +``` -- Resolvers -- Validators -- Authorities -- Orchestrators -- Stores -- Events +`ApplicationClock` is an implementation supplied by your application. -πŸ‘‰ You don’t replace the system -πŸ‘‰ You plug into it +These approaches are not interchangeable. -
+| Requirement | Recommended mechanism | +|---|---| +| Change session lifetime | Server options | +| Change token lifetime for a specific mode | `ConfigureMode()` | +| Select authentication mode differently | `IEffectiveAuthModeResolver` | +| Resolve tenants from a custom source | `ITenantIdResolver` | +| Customize login decisions | `ILoginAuthority` | +| Supply signing keys | `IJwtSigningKeyProvider` | +| Persist sessions in another backend | Store implementation | +| React to successful login | `UAuthEvents` | +| Add application-specific access rules | Policies | -## πŸ”Œ Replacing Services +Prefer the most specific supported mechanism rather than replacing an entire authentication component. -All core services can be overridden using DI: +## 2. Understanding Effective Server Options + +The server does not necessarily use the original `UAuthServerOptions` instance unchanged for every authentication flow. + +Instead, UltimateAuth can resolve an effective configuration based on the client profile and authentication mode. + +The relevant types are: ```csharp -services.AddScoped(); +IEffectiveServerOptionsProvider +EffectiveUAuthServerOptions +IEffectiveAuthModeResolver ``` -πŸ‘‰ This allows deep customization -πŸ‘‰ While preserving the pipeline +### Configured Options -
+`UAuthServerOptions` represents the application's configured server settings. -## 🧠 Authorities & Decisions +For example: -Authorities are responsible for decisions: +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Session.Lifetime = TimeSpan.FromHours(8); + options.Token.Issuer = "MyApplication"; +}); +``` -- LoginAuthority -- AccessAuthority +These settings form the base configuration. ---- +### Effective Options -You can override them: +The provider produces an `EffectiveUAuthServerOptions` instance containing: ```csharp -services.AddScoped(); +public sealed class EffectiveUAuthServerOptions +{ + public UAuthMode Mode { get; init; } + public UAuthServerOptions Options { get; init; } = default!; +} +``` + +`Mode` identifies the resolved authentication mode. + +`Options` contains the effective server configuration for that mode. + +The effective configuration is derived from the original settings rather than replacing the application's registered options globally. + +## 3. Effective Configuration Resolution + +The current `EffectiveServerOptionsProvider` follows this sequence: + +```text +Configured UAuthServerOptions + | + v + Resolve mode + | + v + Clone base options + | + v + Apply mode defaults + | + v + Apply ConfigureMode override + | + v + EffectiveUAuthServerOptions ``` -πŸ‘‰ This changes decision logic -πŸ‘‰ Without touching flows +In simplified form: + +```csharp +var original = baseOptions.Value; + +var mode = modeResolver.Resolve(clientProfile); + +var options = original.Clone(); -
+ConfigureDefaults.ApplyModeDefaults(mode, options); -## πŸ”„ Orchestrators +if (original.ModeConfigurations.TryGetValue(mode, out var configure)) +{ + configure(options); +} -Orchestrators coordinate execution: +return new EffectiveUAuthServerOptions +{ + Mode = mode, + Options = options +}; +``` -- Validate -- Authorize -- Execute command +This execution order is important. -πŸ‘‰ They enforce invariants -πŸ‘‰ Do not bypass them, unless you exact know what you are doing +**Mode defaults are applied after the original configuration is cloned.** -
+Consequently, certain values configured globally may be replaced by mode-specific defaults. -## πŸ—„ Store Customization +Explicit mode overrides are applied last. -You can provide custom stores: +### What Changes Between Requests? -- Session store -- Refresh token store -- User store +Different client profiles can resolve to different authentication modes. -πŸ‘‰ Supports EF Core, in-memory, or custom implementations +The provider therefore can produce different effective configurations for different client profiles. -## πŸ“‘ Events +However, this does not mean every setting is independently recalculated from arbitrary request data. -UltimateAuth provides event hooks: +In the current implementation, mode selection is based on the supplied `UAuthClientProfile`. -- Login -- Logout -- Refresh -- Revoke +The provider also accepts tenant context, but the default effective-options construction does not apply an additional tenant-specific configuration override. ---- +Applications should not assume that per-tenant options customization exists automatically. + +## 4. Default Authentication Mode Resolution + +The default `EffectiveAuthModeResolver` maps client profiles to authentication modes. + +| Client profile | Resolved mode | +|---|---| +| `BlazorServer` | `PureOpaque` | +| `WebServer` | `Hybrid` | +| `BlazorWasm` | `Hybrid` | +| `Maui` | `Hybrid` | +| `Api` | `PureJwt` | +| Other / unknown | `Hybrid` | + +These mappings describe the current default resolver. + +They do not imply that every resolved mode is fully supported in the current release. + +In particular, the current server options validator rejects explicit activation of `SemiHybrid` and `PureJwt` through `AllowedModes`. + +Applications should verify that their client profiles resolve to a supported authentication mode before deploying. + +### AllowedModes Is Not a Mode Selector + +For example: ```csharp -o.Events.OnUserLoggedIn = ctx => +builder.Services.AddUltimateAuthServer(options => { - // custom logic - return Task.CompletedTask; -}; + options.AllowedModes = + [ + UAuthMode.PureOpaque, + UAuthMode.Hybrid + ]; +}); ``` -πŸ‘‰ Use events for side-effects -πŸ‘‰ Not for core logic +This restricts which modes are allowed. + +It does not instruct the resolver to choose `Hybrid` for every client. -
+Mode selection and mode restrictions are separate responsibilities. -## βš™οΈ Mode Configuration Overrides +## 5. Configure Authentication Modes -You can customize behavior per mode: +Use the public `ConfigureMode()` extension to customize settings for a particular authentication mode. ```csharp -o.ModeConfigurations[UAuthMode.Hybrid] = options => +builder.Services.AddUltimateAuthServer(options => { - options.Token.AccessTokenLifetime = TimeSpan.FromMinutes(5); -}; + options.ConfigureMode(UAuthMode.Hybrid, mode => + { + mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(5); + }); +}); +``` + +This configuration changes the effective access token lifetime for `Hybrid`. + +It does not change the effective `PureOpaque` configuration. + +### Why ConfigureMode Matters + +Consider this configuration: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Token.AccessTokenLifetime = TimeSpan.FromMinutes(30); +}); ``` -πŸ‘‰ Runs after defaults -πŸ‘‰ Allows fine-grained control +The current `Hybrid` mode defaults explicitly set: -
+```csharp +Token.AccessTokenLifetime = TimeSpan.FromMinutes(10); +``` -## πŸ” Custom Resolvers +Because mode defaults are applied after the base options are cloned, the effective `Hybrid` access token lifetime becomes ten minutes rather than thirty. -You can override how data is resolved: +To override that behavior, use: -- Tenant resolver -- Session resolver -- Device resolver +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.ConfigureMode(UAuthMode.Hybrid, mode => + { + mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(30); + }); +}); +``` -πŸ‘‰ Enables full control over request interpretation +The explicit mode override is applied after the mode defaults. -## πŸ›‘ Safety Boundaries +### Configure Multiple Modes -UltimateAuth enforces: +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.ConfigureMode(UAuthMode.Hybrid, mode => + { + mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(10); + }); + + options.ConfigureMode(UAuthMode.PureOpaque, mode => + { + mode.Session.IdleTimeout = TimeSpan.FromDays(3); + }); +}); +``` -- Invariants -- Validation -- Fail-fast behavior +Each override is associated with its specified mode. -πŸ‘‰ Unsafe overrides will fail early +### Important Limitations -## 🧠 Mental Model +`ConfigureMode()` configures the effective options for a mode. -If you remember one thing: +It does not: -πŸ‘‰ Extend the system -πŸ‘‰ Don’t fight the system +- Enable an unsupported authentication mode. +- Bypass the server's allowed-mode restrictions. +- Replace the authentication mode resolver. +- Automatically create a tenant-specific configuration. +- Guarantee that every combination of effective settings is safe. ---- +The current implementation applies mode overrides during effective-options resolution. -## πŸ“Œ Key Takeaways +Do not assume that every mode-specific combination receives the same startup validation as the original `UAuthServerOptions` instance. -- Everything is replaceable via DI -- Authorities control decisions -- Orchestrators enforce flow -- Events are for side-effects -- Security boundaries are protected +Test mode overrides carefully, especially when changing token issuance, session lifetimes, or credential delivery. ---- +## 6. Mode Defaults + +UltimateAuth provides mode-specific defaults for authentication behavior. + +The current implementation includes templates for: + +- `PureOpaque` +- `Hybrid` +- `SemiHybrid` +- `PureJwt` + +These templates configure relevant token, session, cookie, and response behavior. + +### PureOpaque + +The `PureOpaque` defaults include: + +```csharp +Token.IssueJwt = false; +Token.IssueOpaque = false; +Token.IssueRefresh = false; +``` + +In this mode, the session identifier is the primary opaque authentication credential. + +The defaults also configure a longer idle timeout and session touch interval where no explicit values were supplied. + +### Hybrid + +The `Hybrid` defaults include: + +```csharp +Token.IssueJwt = true; +Token.IssueOpaque = true; +Token.AccessTokenLifetime = TimeSpan.FromMinutes(10); +Token.RefreshTokenLifetime = TimeSpan.FromDays(7); +``` + +Hybrid also configures refresh-token delivery through a cookie. + +### Unsupported Modes + +Although default templates exist for `SemiHybrid` and `PureJwt`, their presence does not mean those modes are ready for production use. + +The server's current validator explicitly rejects these modes when they are enabled through `AllowedModes`. + +Only use modes supported by the current release. + +## 7. Inspect Effective Options + +Advanced integrations can use `IEffectiveServerOptionsProvider` to inspect configured and effective server options. + +The interface exposes: + +```csharp +public interface IEffectiveServerOptionsProvider +{ + UAuthServerOptions GetOriginal(HttpContext context); + EffectiveUAuthServerOptions GetEffective(HttpContext context, UAuthClientProfile clientProfile); + EffectiveUAuthServerOptions GetEffective(TenantKey tenant, UAuthClientProfile clientProfile); +} +``` + +### Inspect the Original Configuration + +```csharp +var original = provider.GetOriginal(httpContext); +var configuredLifetime = original.Token.AccessTokenLifetime; +``` + +### Inspect the Effective Configuration + +```csharp +var effective = provider.GetEffective(httpContext, UAuthClientProfile.BlazorWasm); +var mode = effective.Mode; +var lifetime = effective.Options.Token.AccessTokenLifetime; +``` + +This is useful when diagnosing unexpected differences between configured and runtime behavior. + +For example, a globally configured access token lifetime may differ from the effective value because a mode template overrides it. + +### Do Not Mutate the Original Options + +`GetOriginal()` returns the original options instance. + +Treat it as read-only application configuration. + +Runtime customization should use the supported configuration mechanisms rather than mutating registered options during requests. + +## 8. Custom Authentication Mode Resolution + +UltimateAuth exposes: + +```csharp +IEffectiveAuthModeResolver +``` + +The interface contains: + +```csharp +public interface IEffectiveAuthModeResolver +{ + UAuthMode Resolve(UAuthClientProfile clientProfile); +} +``` + +The default implementation selects the mode using the client profile. + +An application can provide its own resolver when the default mapping does not meet its requirements. + +### Custom Resolver Example + +```csharp +public sealed class ApplicationAuthModeResolver : IEffectiveAuthModeResolver +{ + public UAuthMode Resolve(UAuthClientProfile clientProfile) + { + return clientProfile switch + { + UAuthClientProfile.BlazorServer => UAuthMode.PureOpaque, + UAuthClientProfile.BlazorWasm => UAuthMode.Hybrid, + UAuthClientProfile.Maui => UAuthMode.Hybrid, + + _ => UAuthMode.Hybrid + }; + } +} +``` + +Register the implementation: + +```csharp +builder.Services.AddSingleton(); +builder.Services.AddUltimateAuthServer(); +``` + +The server registers its default resolver using `TryAddSingleton`, so an existing registration can take precedence. + +### Security Considerations + +The custom resolver must return a mode compatible with the server's supported modes and security requirements. + +Do not choose authentication modes based solely on untrusted client-provided values without appropriate validation. + +Also note that the current resolver interface receives a client profile, not an arbitrary HTTP request context. + +For more complex context-dependent selection, additional application integration may be necessary. + +## 9. Dependency Injection Extension Points + +UltimateAuth registers many internal services through dependency injection. + +Several default registrations use `TryAddScoped` or `TryAddSingleton`, allowing applications to supply an implementation before the default registration is added. + +For example: + +```csharp +builder.Services.AddScoped(); +builder.Services.AddUltimateAuthServer(); +``` + +The application-defined validator is registered before the UltimateAuth server's default registration. + +Because the server uses `TryAddScoped` for this service, it does not add its default implementation when an existing registration is present. + +### Registration Order Matters + +For services registered using `TryAdd...`, prefer: + +```csharp +builder.Services.AddScoped(); +builder.Services.AddUltimateAuthServer(); +``` + +Do not assume that registering a replacement after the server will always produce the same behavior. + +The result depends on the service's registration pattern and how it is resolved. + +### Not Every Service Is Replaceable in the Same Way + +Some UltimateAuth services use ordinary `AddSingleton` or `AddScoped` registrations. + +Others use `TryAdd...`. + +Some are registered as collections. + +These patterns have different override semantics. + +Therefore, do not assume that every internal service can be replaced safely or that every replacement preserves the intended security behavior. + +## 10. Custom Login Authority + +The login authority participates in deciding whether a login attempt is permitted. + +UltimateAuth exposes: + +```csharp +ILoginAuthority +``` + +The interface is: + +```csharp +public interface ILoginAuthority +{ + LoginDecision Decide(LoginDecisionContext context); +} +``` + +An application can implement its own login decision logic. + +For example: + +```csharp +public sealed class ApplicationLoginAuthority : ILoginAuthority +{ + public LoginDecision Decide(LoginDecisionContext context) + { + // Evaluate application-specific login rules. + // Return a valid LoginDecision. + throw new NotImplementedException(); + } +} +``` + +The example illustrates the extension contract; it is not a usable implementation. + +Register the implementation: + +```csharp +builder.Services.AddScoped(); +builder.Services.AddUltimateAuthServer(); +``` + +### Important Security Boundary + +Replacing the login authority changes a security decision component. + +The replacement must preserve required authentication checks and correctly handle all decision outcomes. + +Do not replace the authority merely to add logging, notifications, or metrics. + +Use lifecycle events for those purposes. + +If the application only needs additional access rules, consider the policy system before replacing a core authority. + +## 11. Authorities, Policies, and Invariants + +UltimateAuth separates different responsibilities within its security architecture. + +### Authorities + +Authorities evaluate security decisions. + +Examples include: + +```csharp +IAuthAuthority +IAccessAuthority +ILoginAuthority +``` + +### Policies + +Policies define application-specific rules used by the authorization system. + +They should be the preferred extension mechanism when the requirement is to add or refine access rules. + +### Invariants + +Invariants enforce foundational security requirements. + +The server registers invariants including: + +```csharp +DeviceRequiredInvariant +ExpiredSessionInvariant +InvalidOrRevokedSessionInvariant +TenantResolvedInvariant +``` + +These are registered as a collection of `IAuthorityInvariant` implementations. + +Unlike ordinary application policies, security invariants protect fundamental authentication requirements. + +**Do not remove or bypass invariants to make a custom authentication flow succeed.** + +For example, an expired or revoked session should not become valid merely because a custom access policy grants permission. + +## 12. Custom Tenant Resolution + +UltimateAuth supports route, header, and host-based tenant resolution. + +These mechanisms are controlled through server options. + +For example: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.MultiTenant.EnableHeader = true; +}); +``` + +For applications with a different tenant identification strategy, UltimateAuth exposes: + +```csharp +ITenantIdResolver +``` + +The server's default registration uses `TryAddSingleton`. + +A custom resolver can therefore be registered before the server: + +```csharp +builder.Services.AddSingleton(); +builder.Services.AddUltimateAuthServer(); +``` + +`ApplicationTenantIdResolver` represents an application-defined implementation. + +### When to Use a Custom Resolver + +A custom tenant resolver may be appropriate when tenants are identified through: + +- An application-specific host naming convention. +- A trusted gateway-provided tenant identifier. +- A custom routing structure. +- An existing tenant management system. + +### Security Considerations + +Tenant identification is not the same as tenant authorization. + +The server must still verify that the authenticated user or application is allowed to access the resolved tenant. + +Never trust arbitrary tenant identifiers supplied by clients without applying the required validation and isolation rules. + +## 13. Custom Session and Device Resolution + +UltimateAuth separates session identifier resolution from session validation. + +For example: + +```csharp +ISessionIdResolver +ISessionValidator +IDeviceResolver +``` + +These interfaces have different responsibilities. + +| Component | Responsibility | +|---|---| +| `ISessionIdResolver` | Determine which session identifier is supplied | +| `ISessionValidator` | Validate the session | +| `IDeviceResolver` | Resolve device information | + +The server supports multiple session identifier sources through internal resolvers, including cookies, bearer credentials, headers, and query parameters. + +The active resolution behavior is controlled by the configured session resolution options. + +### Custom Session Validator + +```csharp +builder.Services.AddScoped(); +builder.Services.AddUltimateAuthServer(); +``` + +A custom validator must preserve the relevant expiration, revocation, and security-state checks. + +### Custom Device Resolver + +```csharp +builder.Services.AddScoped(); +builder.Services.AddUltimateAuthServer(); +``` + +Device resolution may affect session identification and security decisions. + +Custom implementations should produce consistent, trustworthy device information. + +Avoid replacing these services unless the application's requirements cannot be met through standard options. + +## 14. Custom Stores + +UltimateAuth separates domain behavior from persistence through store interfaces. + +Examples include: + +```csharp +ISessionStore +IRefreshTokenStore +IRoleStore +IUserRoleStore +``` + +Some stores are resolved through corresponding factory interfaces. + +UltimateAuth provides persistence implementations in separate packages, including in-memory and Entity Framework Core implementations. + +### Choosing a Store Implementation + +Use in-memory implementations for suitable development, testing, or demonstration scenarios. + +Use durable persistence for production authentication state that must survive process restarts or be shared across application instances. + +### Custom Store Requirements + +A custom store must preserve the semantics expected by the authentication services. + +Depending on the store, these may include: + +- Correct session expiration handling. +- Reliable revocation state. +- Refresh token consumption and replay protection. +- Consistent user and tenant scoping. +- Appropriate concurrency behavior. +- Atomic state transitions where required. +- Correct deletion and lifecycle behavior. + +For example, refresh token rotation may depend on preventing two concurrent requests from successfully consuming the same token. + +A store implementation that does not enforce the required concurrency semantics can undermine the security of the refresh flow. + +### Store Registration + +Custom store registration depends on the specific store interface and its factory. + +Do not assume that replacing one store interface is sufficient when the runtime resolves stores through a factory. + +Use the relevant domain package's documented registration pattern. + +## 15. Authentication Lifecycle Events + +UltimateAuth provides application-wide authentication event hooks through: + +```csharp +UAuthEvents +``` + +These hooks allow applications to react to authentication lifecycle events without replacing the authentication flow. + +The current event options expose: + +```csharp +OnAnyEvent +OnUserLoggedIn +OnUserLoggedOut +``` + +### Login Event + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Events.OnUserLoggedIn = context => + { + // Record a successful login. + return Task.CompletedTask; + }; +}); +``` + +### Logout Event + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Events.OnUserLoggedOut = context => + { + // Record a logout event. + return Task.CompletedTask; + }; +}); +``` + +### Global Event Hook + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Events.OnAnyEvent = context => + { + // Process an authentication event. + return Task.CompletedTask; + }; +}); +``` + +The global hook receives an `IAuthEventContext`. + +### Appropriate Event Uses + +Lifecycle events are suitable for: + +- Audit logging. +- Application metrics. +- Monitoring. +- Notifications. +- Lightweight post-operation integration. + +They should not be used to implement critical authentication checks. + +### Events Are Not Decision Hooks + +The documented event delegates run after their corresponding successful operations. + +For example, `OnUserLoggedIn` is not the place to decide whether the login should have been permitted. + +Use an appropriate authority or policy for security decisions. + +### Avoid Complex Work Inside Events + +Long-running or failure-prone external operations should generally be delegated to a reliable application service or message-processing mechanism. + +Do not make critical authentication behavior depend on an unreliable external notification system. + +## 16. Custom JWT Signing Keys + +UltimateAuth exposes: + +```csharp +IJwtSigningKeyProvider +``` + +The server registers a development signing key provider as its default implementation. + +Production applications should supply an appropriate signing key strategy. + +For example: + +```csharp +builder.Services.AddSingleton(); +builder.Services.AddUltimateAuthServer(); +``` + +`ApplicationJwtSigningKeyProvider` is an implementation supplied by the application. + +A production implementation should use appropriately protected signing keys and support the application's operational requirements. + +### Signing Key Responsibilities + +Consider: + +- Key confidentiality. +- Key rotation. +- Key identification. +- Consistent validation across resource servers. +- Secure key storage. +- Recovery and deployment procedures. + +Do not use development signing keys as a production security strategy. + +## 17. Endpoint Customization + +UltimateAuth provides an advanced endpoint configuration hook: + +```csharp +options.OnConfigureEndpoints +``` + +The hook receives: + +```csharp +IEndpointRouteBuilder +``` + +For example: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.OnConfigureEndpoints = endpoints => + { + // Apply supported application-specific + // endpoint configuration here. + }; +}); +``` + +The hook is invoked after UltimateAuth registers its default endpoints. + +It can support advanced endpoint integration, but it should not be used to remove, remap, or weaken the security requirements of UltimateAuth endpoints. + +### Prefer Endpoint Options First + +For ordinary endpoint configuration, use: + +```csharp +options.Endpoints +``` + +For example: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Endpoints.Authentication = true; + options.Endpoints.Credentials = true; + options.Endpoints.Authorization = true; +}); +``` + +The advanced endpoint hook is intended for cases that cannot be addressed through the supported endpoint options. + +## 18. Configuration Sources and Precedence + +UltimateAuth integrates with the standard ASP.NET Core configuration system. + +For the server, the configuration section is: + +```text +UltimateAuth:Server +``` + +For the client: + +```text +UltimateAuth:Client +``` + +For Resource APIs: + +```text +UltimateAuth:ResourceApi +``` + +### Registration Order + +The standard server registration applies: + +1. Option type defaults. +2. The `AddUltimateAuthServer()` configuration delegate. +3. Values bound from `UltimateAuth:Server`. +4. Post-configuration processing. +5. Registered options validation. + +For example: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Login.MaxFailedAttempts = 5; +}); +``` + +With: + +```json +{ + "UltimateAuth": { + "Server": { + "Login": { + "MaxFailedAttempts": 3 + } + } + } +} +``` + +The effective configured value is `3`. + +The configuration binding occurs after the registration delegate. + +### Environment Variables + +ASP.NET Core supports hierarchical configuration keys through double underscores. + +For example: + +```text +UltimateAuth__Server__Login__MaxFailedAttempts=3 +``` + +This corresponds to: + +```text +UltimateAuth:Server:Login:MaxFailedAttempts +``` + +Environment-specific configuration can therefore override values without requiring changes to application code. + +### Environment-Specific Files + +Standard ASP.NET Core applications commonly use: + +```text +appsettings.json +appsettings.Development.json +appsettings.Staging.json +appsettings.Production.json +``` + +The active environment determines which environment-specific file is loaded. + +For example: + +```text +ASPNETCORE_ENVIRONMENT=Production +``` + +The final value depends on the configured ASP.NET Core configuration providers and their precedence. + +### Configuration Binding Does Not Replace Mode Resolution + +A value supplied through `appsettings.json` becomes part of the configured options. + +It may still be replaced later when UltimateAuth applies mode defaults. + +For settings explicitly controlled by mode templates, use `ConfigureMode()` when the desired behavior must apply to a specific mode. + +## 19. Startup Validation and Runtime Safety + +UltimateAuth validates server options through registered `IValidateOptions` implementations. + +The server uses: + +```csharp +ValidateOnStart() +``` + +This allows many invalid configurations to fail during application startup. + +Examples include: + +- Invalid endpoint base paths. +- Invalid or unsupported allowed modes. +- Invalid session lifetimes. +- Incompatible token and session lifetime constraints. +- Invalid login settings. +- Invalid tenant configuration. +- Invalid pagination settings. + +### Validation Has Boundaries + +Startup validation applies to the configured options according to the registered validators. + +It does not automatically prove that every custom dependency injection replacement is safe. + +It also does not guarantee that every mode-specific override has been validated against all possible security invariants. + +Custom implementations must preserve the relevant contracts and security requirements. + +### Recommended Validation Strategy + +For advanced configurations: + +1. Verify that the application starts successfully. +2. Test each enabled authentication mode. +3. Test login, refresh, validation, and logout. +4. Test session expiration and revocation. +5. Test tenant isolation. +6. Test authorization denial paths. +7. Test concurrent refresh requests where applicable. +8. Verify that effective options contain the intended values. + +These checks are especially important when customizing authorities, validators, stores, or authentication mode behavior. + +## 20. Common Configuration Mistakes + +### Configuring Core Options Separately from Server Options + +When using `AddUltimateAuthServer()`, configure Core-defined behavior through `UAuthServerOptions`. + +Do not expect a separate `UAuthOptions` configuration to override the server's composed options. + +### Assuming Global Values Always Survive Mode Defaults + +Mode templates can overwrite values such as access token lifetime. + +Use `ConfigureMode()` for explicit mode-specific customization. + +### Treating AllowedModes as Mode Selection + +`AllowedModes` restricts permitted modes. + +It does not replace `IEffectiveAuthModeResolver`. + +### Enabling an Unsupported Mode + +An enum value or default template does not establish that a mode is ready for production. + +Use only modes supported by the current implementation. + +### Replacing Security Components Unnecessarily + +Replacing a validator or authority can change fundamental authentication behavior. + +Prefer options and policies when they satisfy the requirement. + +### Assuming Every DI Service Is Replaceable + +Different services use different registration patterns. + +Review the actual registration behavior before replacing a service. + +### Using Events for Authorization Decisions + +Lifecycle events are for post-operation reactions. + +Use authorities and policies for decisions. + +### Ignoring Store Concurrency + +Authentication stores must preserve the required consistency and atomicity semantics. + +This is particularly important for refresh token rotation and revocation. + +### Assuming Runtime Overrides Receive Full Startup Validation + +Mode-specific overrides are applied during effective-options construction. + +Validate their results through tests and appropriate application safeguards. + +## 21. Advanced Configuration Reference + +| Mechanism | API / Type | Purpose | +|---|---|---| +| Server configuration | `UAuthServerOptions` | Base server settings | +| Mode customization | `ConfigureMode()` | Apply mode-specific overrides | +| Effective configuration | `IEffectiveServerOptionsProvider` | Resolve effective server settings | +| Effective result | `EffectiveUAuthServerOptions` | Resolved mode and options | +| Mode selection | `IEffectiveAuthModeResolver` | Resolve mode from client profile | +| Login decisions | `ILoginAuthority` | Evaluate login decisions | +| Authentication authority | `IAuthAuthority` | Authentication security decisions | +| Access authority | `IAccessAuthority` | Access decisions | +| Tenant identification | `ITenantIdResolver` | Resolve tenant identifiers | +| Session validation | `ISessionValidator` | Validate sessions | +| Session identification | `ISessionIdResolver` | Resolve session identifiers | +| Device identification | `IDeviceResolver` | Resolve device information | +| Signing keys | `IJwtSigningKeyProvider` | Supply JWT signing keys | +| Lifecycle events | `UAuthEvents` | Observe completed authentication operations | +| Endpoint customization | `OnConfigureEndpoints` | Advanced endpoint configuration | +| Session persistence | `ISessionStore` | Session storage | +| Refresh persistence | `IRefreshTokenStore` | Refresh token storage | + +## 22. Recommended Extension Strategy + +When an application requires customization, follow this order: + +1. **Use existing options.** Prefer supported configuration properties. +2. **Use mode overrides.** Apply `ConfigureMode()` when behavior must differ by authentication mode. +3. **Use application policies.** Extend authorization rules without replacing the authorization pipeline. +4. **Use lifecycle events.** Implement logging, monitoring, and notifications without changing authentication decisions. +5. **Use focused resolvers or providers.** Customize tenant identification, signing keys, or other specific infrastructure concerns. +6. **Replace authorities, validators, or stores only when necessary.** Preserve all relevant security invariants and verify the implementation through tests. + +The objective is not to replace as much of UltimateAuth as possible. + +It is to customize the smallest component necessary while retaining the guarantees of the surrounding system. -## ➑️ Next Step +## Next Steps -Return to **Auth Flows** or explore **Plugin Domains** +- [Configuration Overview](/docs/configuration/configuration) β€” Understand registration, configuration sources, and architecture. +- [Server Options](/docs/configuration/server-options) β€” Configure authentication and server security behavior. +- [Client Options](/docs/configuration/client-options) β€” Configure SDK flows and client behavior. +- [Authentication Modes](/docs/fundamentals/authentication-modes) β€” Understand supported authentication modes. +- [Authorization](/docs/client/authorization) β€” Use the client authorization API. +- [Session Management](/docs/client/session-management) β€” Manage sessions and revocation. \ No newline at end of file diff --git a/docs/content/configuration/client-options.md b/docs/content/configuration/client-options.md index 4ce9c7c3..b7d09678 100644 --- a/docs/content/configuration/client-options.md +++ b/docs/content/configuration/client-options.md @@ -1,125 +1,1148 @@ --- title: Client Options -order: 4 +order: 3 group: configuration --- +# Client Options -# 🧩 Client Options +`UAuthClientOptions` controls how an UltimateAuth client communicates with the authentication server, executes authentication flows, maintains client-side authentication state, and responds when authentication must be renewed. -Client Options define how UltimateAuth behaves on the **client side**. +The client configuration is intentionally separate from server security configuration. -πŸ‘‰ While Server controls the system, -πŸ‘‰ Client controls how it is **used** +The server determines which authentication operations are permitted, how credentials are validated, and when sessions expire. The client determines how those operations are initiated and how the application responds to their outcomes. -## 🧠 What Are Client Options? +This guide covers the current client configuration API, platform integration, defaults, practical examples, and important security considerations. -Client options configure: +## 1. Register the UltimateAuth Client -- Client profile (WASM, Server, MAUI, API) -- Endpoint communication -- PKCE behavior -- Token refresh -- Re-authentication behavior +UltimateAuth provides a platform-independent client SDK and platform-specific adapters. -
+For Blazor applications, use: -## βš™οΈ Basic Usage +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.AutoDetectClientProfile = true; + options.DefaultReturnUrl = "/"; +}); +``` + +`AddUltimateAuthClientBlazor()` registers the underlying client services and the Blazor-specific infrastructure, including browser transport, storage, device identification, and authentication state integration. + +### Core Client Registration + +The platform-independent registration method is: ```csharp -builder.Services.AddUltimateAuthClientBlazor(o => +builder.Services.AddUltimateAuthClient(options => { - o.AutoDetectClientProfile = false; + options.AutoDetectClientProfile = true; }); ``` -
+This registers the shared client SDK. -## 🧩 Client Profile +However, it does not provide every platform-specific implementation needed to run authentication flows. -UltimateAuth automatically detects client type by default: +Applications should use an appropriate platform adapter rather than relying on the core client registration alone. -- Blazor WASM -- Blazor Server -- MAUI -- WebServer -- API +### Do Not Register Both Client Methods -You can override manually: +When using: ```csharp -o.ClientProfile = UAuthClientProfile.BlazorWasm; +builder.Services.AddUltimateAuthClientBlazor(); ``` -πŸ‘‰ Manual override is useful for testing or special scenarios +do not also call: -
+```csharp +builder.Services.AddUltimateAuthClient(); +``` + +The Blazor registration already includes the core client services. -## 🌐 Endpoints +### Server and Client in One Application -Defines where requests are sent: +A Blazor Server application can register both UltimateAuth Server and the Blazor client adapter: ```csharp -o.Endpoints.BasePath = "https://localhost:5001/auth"; +builder.Services.AddUltimateAuthServer(options => +{ + options.Session.Lifetime = TimeSpan.FromDays(7); +}); + +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.ClientProfile = UAuthClientProfile.BlazorServer; + options.AutoDetectClientProfile = false; +}); ``` -πŸ‘‰ Required for WASM / remote clients +These registrations configure different components. -
+The server controls authentication security and lifecycle rules. -## πŸ” PKCE Configuration +The client controls the application-side integration. -Used for browser-based login flows: +## 2. Client Options Architecture + +`UAuthClientOptions` contains the following properties: + +| Property | Type | Purpose | +|---|---|---| +| `ClientProfile` | `UAuthClientProfile` | Identifies the client platform | +| `AutoDetectClientProfile` | `bool` | Enables automatic profile detection | +| `DefaultReturnUrl` | `string?` | Default navigation destination after authentication | +| `Endpoints` | `UAuthClientEndpointOptions` | Authentication endpoint paths | +| `Login` | `UAuthClientLoginFlowOptions` | Login flow configuration | +| `Pkce` | `UAuthClientPkceLoginFlowOptions` | PKCE flow configuration | +| `AutoRefresh` | `UAuthClientAutoRefreshOptions` | Background refresh coordination | +| `Reauth` | `UAuthClientReauthOptions` | Reauthentication behavior | +| `MultiTenant` | `UAuthClientMultiTenantOptions` | Tenant propagation | +| `StateEvents` | `UAuthStateEventOptions` | Authentication state event handling | + +These options are configured through the same `UAuthClientOptions` instance. + +For example: ```csharp -o.Pkce.ReturnUrl = "https://localhost:5002/home"; +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.DefaultReturnUrl = "/dashboard"; + options.Pkce.Enabled = true; + options.AutoRefresh.Interval = TimeSpan.FromMinutes(5); + options.StateEvents.HandlingMode = UAuthStateEventHandlingMode.Patch; +}); ``` -πŸ‘‰ Required for WASM scenarios +## 3. Client Profiles + +The client profile identifies the application environment and influences authentication flow behavior. + +Configure it through: + +```csharp +options.ClientProfile +``` + +UltimateAuth defines profiles including: + +| Profile | Typical application | +|---|---| +| `BlazorServer` | Interactive Blazor Server applications | +| `BlazorWasm` | Browser-hosted Blazor WebAssembly | +| `Maui` | .NET MAUI applications | +| `WebServer` | Traditional ASP.NET Core web applications | +| `Api` | API-oriented clients | +| `UAuthHub` | UltimateAuth Hub | +| `NotSpecified` | Profile not explicitly selected | + +Different client profiles may result in different authentication modes on the server. + +For example, the default server mode resolver maps Blazor Server to `PureOpaque` and Blazor WebAssembly to `Hybrid`. -
+The client profile does not itself authorize an authentication mode. The server must support and permit the resolved mode. -## πŸ” Auto Refresh +## 4. Automatic Client Profile Detection -Controls token/session refresh behavior: +Automatic profile detection is enabled by default: ```csharp -o.AutoRefresh.Interval = TimeSpan.FromMinutes(1); +options.AutoDetectClientProfile = true; ``` -πŸ‘‰ Keeps authentication alive automatically +When enabled, UltimateAuth detects the client environment if `ClientProfile` remains `NotSpecified`. -
+The current detector checks for: -## πŸ”„ Re-authentication +1. UAuthHub registration. +2. .NET MAUI. +3. Blazor WebAssembly. +4. Blazor Server. +5. A fallback to `WebServer`. -Controls behavior when session expires: +### Default Configuration ```csharp -o.Reauth.Behavior = ReauthBehavior.RaiseEvent; +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.AutoDetectClientProfile = true; +}); ``` -πŸ‘‰ Allows silent or interactive re-login +No explicit profile is required for ordinary supported environments where detection succeeds. -
+### Manual Configuration -## 🧠 Mental Model +For applications that need predictable behavior independent of assembly-based detection: + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.AutoDetectClientProfile = false; + options.ClientProfile = UAuthClientProfile.BlazorWasm; +}); +``` -If you remember one thing: +Manual configuration can be useful for: -πŸ‘‰ Server decides -πŸ‘‰ Client adapts +- Specialized hosting environments. +- Integration tests. +- Applications with multiple hosting models. +- Cases where assembly-based detection is ambiguous. -## πŸ“Œ Key Takeaways +### Important Validation Rule -- Client options control runtime behavior -- Profile detection is automatic -- PKCE is required for public clients -- Refresh and re-auth are configurable -- Works together with Server options +This configuration is invalid: ---- +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.AutoDetectClientProfile = false; + options.ClientProfile = UAuthClientProfile.NotSpecified; +}); +``` + +When automatic detection is disabled, an explicit client profile must be provided. + +### Explicit Profiles Take Precedence + +The post-configuration detector does not replace a profile that has already been explicitly selected. + +For example: + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.ClientProfile = UAuthClientProfile.BlazorServer; +}); +``` + +The explicit profile is retained even when automatic detection remains enabled. + +### Detection Limitations + +The current detector uses service registration and loaded assemblies to identify the environment. + +Assembly-based detection may not be completely reliable in every hosting arrangement. + +For complex Blazor Web App hosting scenarios, explicitly configuring the profile can make authentication behavior easier to reason about. + +## 5. Endpoint Configuration + +`options.Endpoints` defines the endpoint paths used by the client SDK. + +The default base path is: + +```text +/auth +``` + +### Configure a Remote Authentication Server + +For a client communicating with a separately hosted UAuthHub: + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.Endpoints.BasePath = "https://auth.example.com/auth"; +}); +``` + +For an application using same-origin authentication endpoints: + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.Endpoints.BasePath = "/auth"; +}); +``` + +The base path must match the deployed authentication server's routing configuration. + +Changing a client endpoint does not create or expose the corresponding server endpoint. + +### Default Endpoint Paths + +| Property | Default | +|---|---| +| `BasePath` | `/auth` | +| `Login` | `/login` | +| `TryLogin` | `/try-login` | +| `Logout` | `/logout` | +| `Refresh` | `/refresh` | +| `Reauth` | `/reauth` | +| `Validate` | `/validate` | +| `PkceAuthorize` | `/pkce/authorize` | +| `PkceTryComplete` | `/pkce/try-complete` | +| `PkceComplete` | `/pkce/complete` | +| `HubLoginPath` | `/uauthhub/entry` | + +### Customize Individual Paths + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.Endpoints.BasePath = "/auth"; + options.Endpoints.Login = "/login"; + options.Endpoints.Logout = "/logout"; + options.Endpoints.Refresh = "/refresh"; +}); +``` + +These values should reflect the actual routes registered by UltimateAuth Server. + +### Required Endpoint Validation + +The client options validator requires: + +- `Endpoints.BasePath` +- `Endpoints.Login` +- `Endpoints.Logout` +- `Endpoints.Refresh` +- `Endpoints.Validate` + +These properties must not be null, empty, or whitespace-only. + +The current validator does not apply the same required-value checks to every optional endpoint path. + +### Remote Clients and Browser Security + +When using a remote authentication server, also consider: + +- HTTPS. +- Cross-origin request configuration. +- Cookie `SameSite` and `Secure` attributes. +- Allowed client origins. +- PKCE redirects. +- Browser credential policies. + +A correct endpoint URL alone does not guarantee that cross-origin authentication will work. + +## 6. Login Flow Options + +`options.Login` controls client-side login behavior. + +The available properties are: + +| Property | Default | Purpose | +|---|---|---| +| `ReturnUrl` | `null` | Login-specific return destination | +| `AllowCredentialPost` | `false` | Allow direct credential posting | + +### Configure Login Navigation + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.Login.ReturnUrl = "/dashboard"; +}); +``` + +The login-specific return URL is used when the login flow does not provide a more specific destination. + +If it is not set, UltimateAuth can fall back to `DefaultReturnUrl` or the current location according to the flow. + +### Direct Credential Posting + +The default is: + +```csharp +options.Login.AllowCredentialPost = false; +``` + +This is an important security setting. + +Directly posting user credentials is not appropriate for public browser clients such as Blazor WebAssembly applications. + +Public clients should use the supported PKCE-based login flow. + +### Trusted Server-Hosted Clients + +A trusted server-hosted application may explicitly enable direct credential posting where its authentication architecture requires it: + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.ClientProfile = UAuthClientProfile.BlazorServer; + options.Login.AllowCredentialPost = true; +}); +``` + +Do not enable this setting for a public client merely to simplify login implementation. + +The server still determines whether the requested authentication operation is permitted. + +## 7. PKCE Configuration + +`options.Pkce` controls client-side PKCE login behavior. + +PKCE allows a public client to complete an authorization flow without relying on a confidential client secret. + +The current options are: + +| Property | Default | Description | +|---|---|---| +| `Enabled` | `true` | Enable client PKCE support | +| `ReturnUrl` | `null` | Post-login return destination | +| `AutoRedirect` | `true` | Automatically navigate after beginning PKCE | +| `OnAuthorized` | `null` | Callback after the authorization response is issued | + +### Basic Configuration + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.Pkce.Enabled = true; + options.Pkce.ReturnUrl = "/dashboard"; + options.Pkce.AutoRedirect = true; +}); +``` + +### Disable Automatic Redirect + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.Pkce.AutoRedirect = false; +}); +``` + +When `AutoRedirect` is disabled, `BeginPkceAsync` does not automatically navigate to the next location. + +The application is responsible for handling navigation. + +This can be useful when integrating UltimateAuth into an existing routing or navigation system. + +### Authorization Callback + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.Pkce.OnAuthorized = response => + { + // Inspect or process the authorization response. + return Task.CompletedTask; + }; +}); +``` + +The callback receives: + +```csharp +PkceAuthorizeResponse +``` + +It runs after the authorization response is issued and before the normal Hub navigation. + +Use the callback for application-specific integration, not to bypass PKCE verification. + +### Client and Server PKCE Settings Are Different + +Client configuration: + +```csharp +options.Pkce.AutoRedirect = true; +``` + +Server configuration: + +```csharp +options.Pkce.AuthorizationCodeLifetimeSeconds = 120; +``` + +The client controls how the flow is initiated and navigated. + +The server controls the authorization code's validity and verification. + +## 8. Return URL Configuration + +UltimateAuth supports several return URL configuration points. + +| Property | Purpose | +|---|---| +| `DefaultReturnUrl` | Global default for interactive authentication | +| `Login.ReturnUrl` | Login-specific default | +| `Pkce.ReturnUrl` | PKCE-specific default | + +### Global Default + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.DefaultReturnUrl = "/dashboard"; +}); +``` + +### Flow-Specific Defaults + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.DefaultReturnUrl = "/"; + options.Login.ReturnUrl = "/account"; + options.Pkce.ReturnUrl = "/dashboard"; +}); +``` + +Flow-specific values allow different authentication paths to use different destinations. + +An explicitly supplied return URL for an individual operation may take precedence over these defaults. + +### Security Considerations + +Return URLs are navigation destinations and must be treated carefully. + +Do not redirect users to arbitrary external URLs without appropriate validation. + +Open redirects can be abused for phishing and other attacks. + +Prefer local application routes unless cross-origin navigation is an intentional and validated part of the authentication architecture. + +## 9. Automatic Refresh + +`options.AutoRefresh` controls background refresh coordination. + +The available properties are: + +| Property | Description | +|---|---| +| `Enabled` | Enables or disables automatic background refresh | +| `Interval` | Interval between background refresh attempts | + +### Configure Automatic Refresh + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.AutoRefresh.Enabled = true; + options.AutoRefresh.Interval = TimeSpan.FromMinutes(5); +}); +``` + +### Disable Automatic Refresh + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.AutoRefresh.Enabled = false; +}); +``` + +### Default Interval + +If `Interval` is not supplied, the client registration applies: + +```csharp +options.AutoRefresh.Interval ??= TimeSpan.FromMinutes(5); +``` + +The resulting default interval is five minutes. + +### Platform Behavior + +The effective `Enabled` setting should be considered together with the platform's post-configuration and session coordination behavior. + +The current Blazor session coordinator checks `AutoRefresh.Enabled` before starting its background loop. + +When enabled, it periodically invokes: + +```csharp +UAuthClient.Flows.RefreshAsync(true); +``` + +### Automatic Refresh Is Not a Security Policy + +Automatic refresh does not control: + +- Server session lifetime. +- Refresh token lifetime. +- Session revocation. +- Credential recovery policies. +- Refresh token replay protection. + +These remain server-side responsibilities. + +### Automatic Refresh Does Not Guarantee Session Continuity + +Background refresh may fail because: + +- The session has expired. +- The refresh credential has been revoked. +- The browser is suspended. +- Network connectivity is unavailable. +- The server requires reauthentication. + +Automatic refresh is a convenience and continuity mechanism, not a guarantee that authentication remains valid indefinitely. + +### Coordinator Lifecycle + +The Blazor adapter provides an `ISessionCoordinator` implementation. + +The coordinator must be started by the application's supported initialization lifecycle. + +Setting `AutoRefresh.Enabled = true` does not mean that a background task automatically runs without the coordinator being started. + +Applications should follow the client initialization guidance for their hosting model. + +## 10. Reauthentication Options + +`options.Reauth` controls how the Blazor session coordinator responds when refresh reports that reauthentication is required. + +The available properties are: + +| Property | Default | +|---|---| +| `Behavior` | `ReauthBehavior.Redirect` | +| `RedirectPath` | `null` | + +### Available Behaviors + +```csharp +ReauthBehavior.Redirect +ReauthBehavior.None +ReauthBehavior.RaiseEvent +``` + +### Redirect + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.Reauth.Behavior = ReauthBehavior.Redirect; + options.Reauth.RedirectPath = "/login"; +}); +``` + +When the coordinator receives a `ReauthRequired` refresh outcome, it navigates to the configured redirect path. + +If `RedirectPath` is null, the coordinator falls back to `Endpoints.Login`. + +The current Blazor coordinator uses forced navigation for this redirect. + +### Raise an Event + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.Reauth.Behavior = ReauthBehavior.RaiseEvent; +}); +``` + +This allows an application to handle reauthentication through the coordinator's `ReauthRequired` event. + +For example, an application may display a reauthentication dialog rather than immediately navigating. + +### No Automatic Action + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.Reauth.Behavior = ReauthBehavior.None; +}); +``` + +With `None`, the coordinator does not automatically redirect or raise its reauthentication event. + +The application must still handle unauthenticated states appropriately. + +### Important + +Reauthentication behavior does not make an expired or revoked session valid. + +It only controls how the client responds when authentication must be reestablished. + +## 11. Multi-Tenant Client Configuration + +`options.MultiTenant` controls tenant propagation from the client to the authentication server. + +The client does not independently authorize or resolve tenant ownership. + +It carries tenant context using the configured transport. + +### Available Properties + +| Property | Default | Description | +|---|---|---| +| `Enabled` | `false` | Enable tenant propagation | +| `Tenant` | `null` | Tenant identifier to propagate | +| `Transport` | `TenantTransport.None` | Tenant transport mechanism | + +### Available Transports + +```csharp +TenantTransport.None +TenantTransport.Header +TenantTransport.Route +``` + +### Header-Based Tenant Propagation + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.MultiTenant.Enabled = true; + options.MultiTenant.Tenant = "tenant-a"; + options.MultiTenant.Transport = TenantTransport.Header; +}); +``` + +The server must be configured to resolve tenant information from the corresponding transport. + +For example: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.MultiTenant.Enabled = true; + options.MultiTenant.EnableHeader = true; +}); +``` + +### Route-Based Tenant Propagation + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.MultiTenant.Enabled = true; + options.MultiTenant.Tenant = "tenant-a"; + options.MultiTenant.Transport = TenantTransport.Route; +}); +``` + +The server must expose routes compatible with tenant-based routing. + +### Security Boundary + +A client-provided tenant identifier is not proof of tenant membership. + +The server must validate the resolved tenant and enforce tenant isolation. + +Never rely on client-side tenant selection as an authorization boundary. + +## 12. Authentication State Events + +`options.StateEvents` controls how the client processes authentication-related state events. + +The available property is: + +```csharp +options.StateEvents.HandlingMode +``` + +Its default is: + +```csharp +UAuthStateEventHandlingMode.Patch +``` + +### Available Modes + +| Mode | Behavior | +|---|---| +| `Patch` | Apply a local authentication state update | +| `Validate` | Revalidate authentication state after the event | +| `None` | Do not update authentication state in response to the event | + +### Patch + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.StateEvents.HandlingMode = UAuthStateEventHandlingMode.Patch; +}); +``` + +`Patch` is the default. + +It allows the client to update local state without performing a full authentication validation for every event. + +### Validate + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.StateEvents.HandlingMode = UAuthStateEventHandlingMode.Validate; +}); +``` + +`Validate` causes the client to revalidate authentication state in response to events. + +This may introduce additional requests but can be appropriate when the application prefers server-confirmed state updates. + +### None + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.StateEvents.HandlingMode = UAuthStateEventHandlingMode.None; +}); +``` + +`None` disables automatic authentication state updates in response to these events. + +Applications using this mode must provide any required state synchronization through other mechanisms. + +### Local Events Are Not Distributed Notifications + +Client state events do not automatically notify every browser, device, or application instance. + +For example, an administrator changing a user's role does not automatically push an event to that user's open browser. + +Distributed synchronization requires additional application infrastructure. + +## 13. Configuration from appsettings.json + +UltimateAuth Client binds the configuration section: + +```text +UltimateAuth:Client +``` + +For example: + +```json +{ + "UltimateAuth": { + "Client": { + "AutoDetectClientProfile": true, + "DefaultReturnUrl": "/dashboard", + "Endpoints": { + "BasePath": "https://auth.example.com/auth" + }, + "Pkce": { + "Enabled": true, + "ReturnUrl": "/dashboard", + "AutoRedirect": true + }, + "AutoRefresh": { + "Enabled": true, + "Interval": "00:05:00" + }, + "Reauth": { + "Behavior": "Redirect", + "RedirectPath": "/login" + }, + "StateEvents": { + "HandlingMode": "Patch" + } + } + } +} +``` + +The example uses the standard ASP.NET Core configuration binding conventions. + +### Configuration Precedence + +The registration pipeline applies: + +1. Option type defaults. +2. The configuration delegate. +3. Values from `UltimateAuth:Client`. +4. Post-configuration processing. +5. Registered options validation when the options are resolved. + +For example: + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.DefaultReturnUrl = "/"; +}); +``` + +With: + +```json +{ + "UltimateAuth": { + "Client": { + "DefaultReturnUrl": "/dashboard" + } + } +} +``` + +The configured return URL becomes `/dashboard`. + +### Environment Variables + +ASP.NET Core supports double underscores for hierarchical configuration keys. + +For example: + +```text +UltimateAuth__Client__Endpoints__BasePath=https://auth.example.com/auth +``` + +This corresponds to: + +```text +UltimateAuth:Client:Endpoints:BasePath +``` + +Use environment-specific configuration for deployment-dependent values such as authentication server addresses and application return paths. + +## 14. Client Options Validation + +UltimateAuth registers validators for: + +```csharp +UAuthClientOptions +UAuthClientEndpointOptions +``` + +### Client Profile Validation + +The validator rejects a configuration where: + +```csharp +options.AutoDetectClientProfile = false; +options.ClientProfile = UAuthClientProfile.NotSpecified; +``` + +An explicit profile is required when detection is disabled. + +### Endpoint Validation + +The following endpoint properties must be present: + +```text +Endpoints.BasePath +Endpoints.Login +Endpoints.Logout +Endpoints.Refresh +Endpoints.Validate +``` + +For example, this configuration is invalid: + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.Endpoints.BasePath = ""; +}); +``` + +### Validation Timing + +Unlike the server registration, the current client registration does not explicitly call `ValidateOnStart()`. + +Validation therefore occurs through the normal options resolution lifecycle. + +Applications that require early validation can arrange for the configured options to be resolved during startup. + +### Validation Scope + +The current validators check essential client profile and endpoint requirements. + +They do not automatically validate every possible combination of client flow, server deployment, cookie behavior, and authentication mode. + +Applications should test their actual authentication flows against the deployed server configuration. + +## 15. Complete Blazor WebAssembly Example + +The following example configures a Blazor WebAssembly client using a remote authentication server. + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + // Client profile + options.AutoDetectClientProfile = false; + options.ClientProfile = UAuthClientProfile.BlazorWasm; + + // Authentication server + options.Endpoints.BasePath = "https://auth.example.com/auth"; + + // Navigation + options.DefaultReturnUrl = "/"; + options.Login.ReturnUrl = "/dashboard"; + + // Public client security + options.Login.AllowCredentialPost = false; + + // PKCE + options.Pkce.Enabled = true; + options.Pkce.ReturnUrl = "/dashboard"; + options.Pkce.AutoRedirect = true; + + // Background refresh + options.AutoRefresh.Enabled = false; + + // Reauthentication + options.Reauth.Behavior = ReauthBehavior.Redirect; + options.Reauth.RedirectPath = "/login"; + + // State updates + options.StateEvents.HandlingMode = UAuthStateEventHandlingMode.Patch; +}); +``` + +This example emphasizes PKCE-based authentication and avoids direct credential posting from a public browser client. + +The remote server must be configured with compatible endpoints, origins, authentication modes, and credential delivery policies. + +## 16. Complete Blazor Server Example + +The following example configures a Blazor Server client. + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + // Client profile + options.AutoDetectClientProfile = false; + options.ClientProfile = UAuthClientProfile.BlazorServer; + + // Same-origin authentication endpoints + options.Endpoints.BasePath = "/auth"; + + // Navigation + options.DefaultReturnUrl = "/"; + options.Login.ReturnUrl = "/dashboard"; + + // Background refresh + options.AutoRefresh.Enabled = true; + options.AutoRefresh.Interval = TimeSpan.FromMinutes(5); + + // Reauthentication + options.Reauth.Behavior = ReauthBehavior.Redirect; + options.Reauth.RedirectPath = "/login"; + + // Authentication state + options.StateEvents.HandlingMode = UAuthStateEventHandlingMode.Patch; +}); +``` + +Blazor Server applications can use session-oriented authentication and the corresponding server-side security policies. + +The application must still initialize the client and session coordinator through its supported lifecycle. + +## 17. Server and Client Configuration Together + +An application that hosts both UltimateAuth Server and the Blazor client adapter can configure them independently. + +```csharp +// Server: authentication and security policies +builder.Services.AddUltimateAuthServer(options => +{ + options.AllowedModes = + [ + UAuthMode.PureOpaque, + UAuthMode.Hybrid + ]; + + options.Session.Lifetime = TimeSpan.FromDays(7); + options.Login.MaxFailedAttempts = 5; +}); + +// Client: platform integration and flow behavior +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.ClientProfile = UAuthClientProfile.BlazorServer; + options.AutoDetectClientProfile = false; + options.Endpoints.BasePath = "/auth"; + options.AutoRefresh.Enabled = true; +}); +``` + +The two configuration objects have different responsibilities. + +| Requirement | Configure on | +|---|---| +| Session lifetime | Server | +| Access token lifetime | Server | +| Refresh token replay policy | Server | +| Credential recovery rules | Server | +| Allowed authentication modes | Server | +| Client profile | Client | +| PKCE navigation | Client | +| Automatic background refresh | Client | +| Reauthentication navigation | Client | +| Authentication state event handling | Client | +| Tenant validation | Server | +| Tenant propagation | Client | + +The client cannot override server-side security decisions. + +## 18. Common Configuration Mistakes + +### Registering the Core Client and Blazor Adapter Separately + +Use `AddUltimateAuthClientBlazor()` for Blazor applications. + +It already registers the underlying client SDK. + +### Disabling Profile Detection Without Selecting a Profile + +When `AutoDetectClientProfile` is false, set `ClientProfile` explicitly. + +### Relying on Automatic Detection in an Ambiguous Hosting Environment + +For complex hosting arrangements, consider specifying the client profile explicitly. + +### Enabling Direct Credential Posting in a Public Client + +Do not enable `AllowCredentialPost` for browser-hosted public clients. + +Use PKCE. + +### Assuming Client Auto-Refresh Extends Server Lifetimes + +Automatic refresh only initiates refresh operations. + +The server controls whether refresh is permitted. + +### Assuming Auto-Refresh Starts Automatically + +The Blazor session coordinator must be started through the supported application lifecycle. + +### Configuring Only the Client Endpoint + +The server must expose compatible routes and allow the intended cross-origin interaction. + +### Treating Tenant Propagation as Authorization + +A tenant identifier supplied by the client does not grant access to that tenant. + +### Using State Events as Distributed Notifications + +Client events update local client state; they do not automatically synchronize all connected devices. + +### Assuming Every Client Setting Is Validated at Startup + +The current client registration validates options when they are resolved, rather than explicitly requiring startup validation. + +## 19. Client Options Quick Reference + +| Option | Default | Description | +|---|---|---| +| `ClientProfile` | `NotSpecified` | Client environment | +| `AutoDetectClientProfile` | `true` | Automatic profile detection | +| `DefaultReturnUrl` | `null` | Global return URL | +| `Endpoints.BasePath` | `/auth` | Authentication endpoint base | +| `Login.ReturnUrl` | `null` | Login return URL | +| `Login.AllowCredentialPost` | `false` | Direct credential posting | +| `Pkce.Enabled` | `true` | PKCE flow support | +| `Pkce.ReturnUrl` | `null` | PKCE return URL | +| `Pkce.AutoRedirect` | `true` | Automatic PKCE navigation | +| `Pkce.OnAuthorized` | `null` | PKCE authorization callback | +| `AutoRefresh.Interval` | 5 minutes after post-configuration | Background refresh interval | +| `Reauth.Behavior` | `Redirect` | Reauthentication handling | +| `Reauth.RedirectPath` | `null` | Reauthentication redirect | +| `MultiTenant.Enabled` | `false` | Tenant propagation | +| `MultiTenant.Tenant` | `null` | Tenant identifier | +| `MultiTenant.Transport` | `None` | Tenant transport | +| `StateEvents.HandlingMode` | `Patch` | State event handling | + +`AutoRefresh.Enabled` should be evaluated with the applicable platform and post-configuration behavior rather than inferred solely from its property initializer. -## ➑️ Next Step +## Next Steps -Continue to **Configuration Sources** +- **Configuration Overview** β€” Understand the configuration architecture and source precedence. +- **Server Options** β€” Configure authentication security, sessions, tokens, and endpoints. +- **Advanced Configuration** β€” Explore effective options, mode resolution, and extension points. +- **Client Authentication** β€” Implement login, PKCE, refresh, and logout flows. +- **Client Session Management** β€” Manage authenticated sessions and revocation. \ No newline at end of file diff --git a/docs/content/configuration/configuration-overview.md b/docs/content/configuration/configuration-overview.md deleted file mode 100644 index fe9bd7a7..00000000 --- a/docs/content/configuration/configuration-overview.md +++ /dev/null @@ -1,172 +0,0 @@ ---- -title: Configuration Overview -order: 2 -group: configuration ---- - - -# 🧠 Configuration Overview - -UltimateAuth is not configured as a static system. - -πŸ‘‰ It is configured as a **runtime-adaptive system** - -## ⚠️ A Common Misunderstanding - -Many frameworks expect you to configure authentication once: - -- Choose JWT or cookies -- Set token lifetimes -- Configure behavior globally - -πŸ‘‰ And that configuration applies everywhere - -πŸ‘‰ UltimateAuth does NOT work like this - -
- -## 🧩 Layered Configuration Model - -UltimateAuth separates configuration into distinct layers: - -### πŸ”Ή Core (Behavior Definition) - -Core defines **what authentication means**: - -- Session lifecycle -- Token behavior -- PKCE rules -- Multi-tenant handling - -πŸ‘‰ Core is the foundation - -πŸ‘‰ But you typically do NOT configure it directly - -### πŸ”Ή Server (Application Configuration) - -Server is where you configure the system: - -```csharp -builder.Services.AddUltimateAuthServer(o => -{ - o.Login.MaxFailedAttempts = 5; -}); -``` - -This layer controls: - -- Allowed authentication modes -- Endpoint exposure -- Cookie behavior -- Security policies - -πŸ‘‰ This is your main configuration surface - -### πŸ”Ή Client (Runtime Behavior) - -Client configuration controls: - -- Client profile (WASM, Server, MAUI, API) -- PKCE behavior -- Auto-refresh -- Re-authentication - -πŸ‘‰ Client influences how flows are executed - -
- -## ⚑ Runtime Configuration (The Key Idea) - -Here is the most important concept: - -πŸ‘‰ UltimateAuth does NOT use configuration as-is - -Instead, it computes **Effective Configuration per request** - -### 🧠 How It Works - -At runtime: - -1. Client profile is detected -2. Flow type is determined (Login, Refresh, etc.) -3. Auth mode is resolved -4. Defaults are applied -5. Mode-specific overrides are applied - -πŸ‘‰ This produces: - -``` -EffectiveOptions -``` - -
- -## πŸ”„ From Static to Dynamic - -``` -UAuthServerOptions (startup) - ↓ -Mode Resolver - ↓ -Apply Defaults - ↓ -Mode Overrides - ↓ -EffectiveUAuthServerOptions (runtime) -``` - -πŸ‘‰ Every request can have a different effective configuration - -
- -## 🎯 Why This Matters - -This allows UltimateAuth to: - -- Use different auth modes per client -- Adapt behavior per flow -- Enforce security dynamically -- Avoid global misconfiguration - -πŸ‘‰ You don’t configure β€œone system” - -πŸ‘‰ You configure a **decision engine** - -
- -## πŸ›‘ Safety by Design - -Even with dynamic behavior: - -- Invalid combinations fail early -- Disallowed modes are rejected -- Security invariants are enforced - -πŸ‘‰ Flexibility does not reduce safety - -
- -## βš™οΈ Core vs Effective - -| Concept | Meaning | -|-------------------|---------------------------------| -| Core Options | Base behavior definitions | -| Server Options | Application-level configuration | -| Effective Options | Runtime-resolved behavior | - -πŸ‘‰ Effective options are what actually run - -
- -## 🧠 Mental Model - -If you remember one thing: - -πŸ‘‰ You don’t configure authentication - -πŸ‘‰ You configure how it is **resolved** - -## ➑️ Next Step - -- Control runtime β†’ Server Options -- Configure clients β†’ Client Options diff --git a/docs/content/configuration/configuration-sources.md b/docs/content/configuration/configuration-sources.md deleted file mode 100644 index 3386a379..00000000 --- a/docs/content/configuration/configuration-sources.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -title: Configuration Sources -order: 5 -group: configuration ---- - - -# βš™οΈ Configuration Sources - -UltimateAuth supports multiple configuration sources. - -πŸ‘‰ But more importantly, it defines a **clear and predictable precedence model** - -## 🧠 Two Ways to Configure - -UltimateAuth can be configured using: - -### πŸ”Ή Code (Program.cs) - -```csharp -builder.Services.AddUltimateAuthServer(o => -{ - o.Login.MaxFailedAttempts = 5; -}); -``` - -### πŸ”Ή Configuration Files (appsettings.json) - -```json -{ - "UltimateAuth": { - "Server": { - "Login": { - "MaxFailedAttempts": 5 - } - } - } -} -``` - -## βš–οΈ Precedence Rules - -This is the most important rule: - -πŸ‘‰ **Configuration files override code** - -Execution order: - -``` -Program.cs configuration - ↓ -appsettings.json binding - ↓ -Final options -``` - -πŸ‘‰ This means: - -- Defaults can be defined in code -- Environments can override them safely - -
- -## 🌍 Environment-Based Configuration - -ASP.NET Core supports environment-specific configuration: - -- appsettings.Development.json -- appsettings.Staging.json -- appsettings.Production.json - -Example: - -```json -{ - "UltimateAuth": { - "Server": { - "Session": { - "IdleTimeout": "7.00:00:00" - } - } - } -} -``` - -πŸ‘‰ You can use different values per environment without changing code - -
- -## 🧩 Recommended Strategy - -For real-world applications: - -### βœ” Use Program.cs for: -- Defaults -- Development setup -- Local testing - -### βœ” Use appsettings for: -- Environment-specific overrides -- Production tuning -- Deployment configuration - -πŸ‘‰ This keeps your system flexible and maintainable - -
- -## πŸ›‘ Safety & Validation - -UltimateAuth validates configuration at startup: - -- Invalid combinations are rejected -- Missing required values fail fast -- Unsafe configurations are blocked - - -πŸ‘‰ You will not run with a broken configuration - - -## ⚠️ Common Pitfalls - -### ❌ Assuming code overrides config - -It does not. - -πŸ‘‰ appsettings.json always wins - -### ❌ Hardcoding production values - -Avoid: - -```csharp -o.Token.AccessTokenLifetime = TimeSpan.FromHours(1); -``` - -πŸ‘‰ Use environment config instead for maximum flexibility - - -### ❌ Mixing environments unintentionally - -Ensure correct environment is set: - -``` -ASPNETCORE_ENVIRONMENT=Production -``` - -
- -## 🧠 Mental Model - -If you remember one thing: - -πŸ‘‰ Code defines defaults -πŸ‘‰ Configuration defines reality - -## πŸ“Œ Key Takeaways - -- UltimateAuth supports code + configuration -- appsettings.json overrides Program.cs -- Environment-based configuration is first-class -- Validation prevents unsafe setups -- Designed for real-world deployment - -## ➑️ Next Step - -Continue to **Advanced Configuration** diff --git a/docs/content/configuration/index.md b/docs/content/configuration/index.md index 2aff7b31..981df390 100644 --- a/docs/content/configuration/index.md +++ b/docs/content/configuration/index.md @@ -4,111 +4,839 @@ order: 1 group: configuration --- +# Configuration & Extensibility + +UltimateAuth provides a structured configuration system for controlling authentication behavior, session lifecycles, token policies, client integration, and server security. + +Configuration is organized around the application components that actually consume it. + +For most applications, the two primary configuration types are: + +- **`UAuthServerOptions`** β€” Controls authentication and security behavior on the UltimateAuth server. +- **`UAuthClientOptions`** β€” Controls how an application client communicates with UltimateAuth and executes authentication flows. + +UltimateAuth 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 `AddUltimateAuthServer()`. + +This guide explains where configuration belongs, how options are registered, how configuration sources interact, and how to customize behavior safely. + +## 1. Configuration Architecture + +UltimateAuth separates reusable option definitions from the runtime components that consume them. + +```text +UltimateAuth +β”‚ +β”œβ”€β”€ Core +β”‚ └── Shared option definitions +β”‚ β”œβ”€β”€ Login +β”‚ β”œβ”€β”€ Session +β”‚ β”œβ”€β”€ Token +β”‚ β”œβ”€β”€ PKCE +β”‚ └── Multi-tenancy +β”‚ +β”œβ”€β”€ Server +β”‚ └── UAuthServerOptions +β”‚ β”œβ”€β”€ Core-defined options +β”‚ β”œβ”€β”€ Authentication mode restrictions +β”‚ β”œβ”€β”€ Cookie and session resolution +β”‚ β”œβ”€β”€ Endpoints +β”‚ β”œβ”€β”€ Hub configuration +β”‚ β”œβ”€β”€ Credential policies +β”‚ └── Server-specific behavior +β”‚ +└── Client + └── UAuthClientOptions + β”œβ”€β”€ Client profile + β”œβ”€β”€ Login and PKCE flows + β”œβ”€β”€ Endpoint configuration + β”œβ”€β”€ Auto-refresh + β”œβ”€β”€ Reauthentication + └── Client state events +``` + +The important distinction is between **where an option type is defined** and **where its value is consumed**. + +For example, `UAuthSessionOptions` is defined in Core, but a normal UltimateAuth Server application configures it through: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Session.Lifetime = TimeSpan.FromHours(8); +}); +``` + +The server consumes `options.Session` from `UAuthServerOptions`. + +There is no need to configure a separate Core options instance for this setting. + +## 2. Configure the Server + +The main server registration method is: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + // Server configuration. +}); +``` + +This registers the UltimateAuth server infrastructure and its configuration. + +The method accepts an optional configuration delegate: + +```csharp +Action +``` + +It also supports a separate optional delegate for registering access policies. + +### Example: Authentication and Session Settings + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Login.MaxFailedAttempts = 5; + + options.Session.Lifetime = TimeSpan.FromHours(8); + + options.Token.AccessTokenLifetime = + TimeSpan.FromMinutes(15); +}); +``` + +These settings control different parts of the authentication lifecycle: + +| Option | Purpose | +|---|---| +| `Login.MaxFailedAttempts` | Maximum failed login attempts under the configured login policy | +| `Session.Lifetime` | Session lifetime | +| `Token.AccessTokenLifetime` | Access token lifetime | + +The server applies its validation rules to the resulting configuration. + +**Configuration values should be chosen together.** For example, token lifetime settings must remain compatible with the configured session lifetime limits. + +## 3. Core Options Are Composed into Server Options + +`UAuthServerOptions` includes the following Core-defined option groups: + +```csharp +public UAuthLoginOptions Login { get; set; } = new(); + +public UAuthSessionOptions Session { get; set; } = new(); + +public UAuthTokenOptions Token { get; set; } = new(); + +public UAuthPkceOptions Pkce { get; set; } = new(); + +public UAuthMultiTenantOptions MultiTenant { get; set; } = new(); +``` + +This is composition, not duplication. -# βš™οΈ Configuration & Extensibility +The Core package owns the shared option definitions, while the Server package uses those definitions as part of its effective server configuration. -UltimateAuth is designed to be flexible. +### Recommended -But flexibility without structure leads to chaos. +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Session.Lifetime = TimeSpan.FromHours(8); + options.Pkce.Enabled = true; +}); +``` + +### Not Recommended for Server Configuration + +```csharp +builder.Services.AddUltimateAuth(options => +{ + options.Session.Lifetime = TimeSpan.FromHours(8); +}); -πŸ‘‰ Configuration in UltimateAuth is structured, layered, and safe by default. +builder.Services.AddUltimateAuthServer(); +``` -## 🧠 What You Configure +The second example configures `UAuthOptions`, not the `UAuthServerOptions` instance consumed by the server's authentication runtime. -In UltimateAuth, you don’t just configure values. +**When using UltimateAuth Server, configure Core-defined behavior through `AddUltimateAuthServer()` or the `UltimateAuth:Server` configuration section.** -πŸ‘‰ You configure behavior. +Direct Core configuration is intended for scenarios that explicitly consume `UAuthOptions` rather than the server configuration model. -This includes: +## 4. Server Configuration Groups -- How sessions are created and managed -- How tokens are issued and refreshed -- How tenants are resolved -- How clients interact with the system -- Which features are enabled or restricted +`UAuthServerOptions` organizes server behavior into focused option groups. -
+| Property | Responsibility | +|---|---| +| `Login` | Login attempts and authentication behavior | +| `Session` | Session lifetime and lifecycle | +| `Token` | Token issuance, lifetimes, and refresh behavior | +| `Pkce` | PKCE flow configuration | +| `MultiTenant` | Tenant resolution and multi-tenant behavior | +| `AllowedModes` | Restricts permitted authentication modes | +| `HubDeploymentMode` | Hub deployment arrangement | +| `Hub` | Hub-specific configuration | +| `Cookie` | Authentication cookie behavior | +| `SessionResolution` | Resolving session identifiers from incoming requests | +| `Endpoints` | Server endpoint configuration | +| `CredentialSessionPolicy` | Session handling after credential changes and recovery | +| `ResetCredential` | Credential reset configuration | +| `PrimaryCredential` | Primary credential policy | +| `Identifiers` | Identifier management behavior | +| `IdentifierValidation` | Identifier validation rules | +| `LoginIdentifiers` | Identifier types permitted for login | +| `UserProfile` | User profile behavior | +| `Pagination` | Pagination limits and defaults | +| `AuthResponse` | Authentication response behavior | +| `Navigation` | Navigation and redirect behavior | +| `Diagnostics` | Diagnostic configuration | +| `Events` | Server event configuration | -## 🧩 Configuration Layers +These groups allow applications to customize specific behavior without replacing the overall authentication architecture. -UltimateAuth separates configuration into three layers: +The individual option types and their defaults are documented in [Server Options](/docs/configuration/server-options). -### πŸ”Ή Core +## 5. Configure the Client -Defines authentication behavior: +UltimateAuth clients have their own configuration model. -- Session lifecycle -- Token policies -- PKCE flows -- Multi-tenancy +```csharp +builder.Services.AddUltimateAuthClient(options => +{ + options.AutoDetectClientProfile = true; + options.DefaultReturnUrl = "/"; +}); +``` -### πŸ”Ή Server +The registration accepts: -Defines runtime behavior: +```csharp +Action +``` -- Allowed authentication modes -- Endpoint exposure -- Cookie and transport behavior -- Hub deployment +The client options control how the client behaves, not the server's security policy. -### πŸ”Ή Client +For example: -Defines client-side behavior: +```csharp +builder.Services.AddUltimateAuthClient(options => +{ + options.AutoRefresh.Enabled = true; + options.DefaultReturnUrl = "/dashboard"; +}); +``` -- Client profile -- PKCE configuration -- Auto-refresh and re-authentication +Enabling client auto-refresh does not change the server's refresh token lifetime, session expiration rules, or rotation policy. -πŸ‘‰ These layers are independent but work together. +Those are server-side responsibilities. -## βš™οΈ Configuration Sources +### Client Configuration Groups -UltimateAuth supports two configuration styles: +| Property | Responsibility | +|---|---| +| `ClientProfile` | Explicit client profile | +| `AutoDetectClientProfile` | Automatic client profile detection | +| `DefaultReturnUrl` | Default post-authentication navigation | +| `Login` | Client login flow behavior | +| `Pkce` | Client PKCE flow behavior | +| `AutoRefresh` | Automatic refresh behavior | +| `Reauth` | Reauthentication behavior | +| `Endpoints` | Server endpoint paths used by the client | +| `MultiTenant` | Client tenant propagation | +| `StateEvents` | Client state event handling | -### Code-based (Program.cs) +See [Client Options](/docs/configuration/client-options) for the full reference. + +## 6. Server and Client Configuration Are Independent + +A project may register both UltimateAuth Server and UltimateAuth Client. + +This is common in applications that host authentication services and also use the client SDK. + +For example: ```csharp -builder.Services.AddUltimateAuthServer(o => +builder.Services.AddUltimateAuthServer(options => { - o.Login.MaxFailedAttempts = 5; + options.Session.Lifetime = TimeSpan.FromHours(8); +}); + +builder.Services.AddUltimateAuthClient(options => +{ + options.AutoDetectClientProfile = true; + options.DefaultReturnUrl = "/"; }); ``` -### Configuration-based (appsettings.json) +These registrations configure different components. + +| Setting | Server | Client | +|---|---|---| +| Session lifetime | Controls | Does not control | +| Token lifetime | Controls | Does not control | +| Credential reset policy | Controls | Does not control | +| Allowed authentication modes | Controls | Does not control | +| Client profile | Does not control | Controls | +| Client auto-refresh | Does not control | Controls | +| Client navigation | Does not control | Controls | +| Server endpoint exposure | Controls | Uses configured routes | + +The client must be compatible with the server's configuration, but it cannot override server-side security requirements. + +For example, enabling PKCE in client options does not make a disabled or unsupported PKCE server flow available. + +## 7. Configuration Sources + +UltimateAuth supports configuration through registration delegates and the standard ASP.NET Core configuration system. + +### Code-Based Configuration + +Use `Program.cs` for explicit configuration. + ```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Login.MaxFailedAttempts = 5; + options.Session.Lifetime = TimeSpan.FromHours(8); +}); +``` + +This is useful when configuration is closely tied to application behavior or requires strongly typed values. + +### Configuration-Based Setup + +The server binds settings from: + +```text +UltimateAuth:Server +``` + +For example, in `appsettings.json`: + +```json { "UltimateAuth": { "Server": { "Login": { "MaxFailedAttempts": 5 + }, + "Session": { + "Lifetime": "08:00:00" + } + } + } +} +``` + +The client binds settings from: + +```text +UltimateAuth:Client +``` + +For example: + +```json +{ + "UltimateAuth": { + "Client": { + "AutoDetectClientProfile": true, + "DefaultReturnUrl": "/dashboard", + "AutoRefresh": { + "Enabled": true } } } } ``` -πŸ‘‰ appsettings.json overrides Program.cs +These sections are independent. + +### Using Both Sources + +You can combine code-based and configuration-based settings: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Login.MaxFailedAttempts = 5; + options.Session.Lifetime = TimeSpan.FromHours(8); +}); +``` + +With: + +```json +{ + "UltimateAuth": { + "Server": { + "Login": { + "MaxFailedAttempts": 3 + } + } + } +} +``` + +The effective `Login.MaxFailedAttempts` is `3`, because the registration binds the configuration section after applying the configuration delegate. + +The session lifetime remains eight hours because the configuration section does not override it. + +## 8. Configuration Precedence + +The current registration pipeline applies options in this order: + +1. Option type defaults. +2. Values supplied by the registration delegate. +3. Values bound from the corresponding configuration section. +4. Applicable post-configuration processing. +5. Options validation. + +For the server: + +```csharp +services.AddOptions() + .Configure(options => + { + configure?.Invoke(options); + }) + .BindConfiguration("UltimateAuth:Server") + .ValidateOnStart(); +``` + +For the client, the corresponding configuration section is `UltimateAuth:Client`. + +### Important Distinction + +When we say configuration values override `Program.cs`, we mean that the **later configuration binding takes precedence for the same property**. + +The exact value also depends on the normal ASP.NET Core configuration provider order. + +For example, environment-specific configuration files and environment variables may override values from the base `appsettings.json` file. + +### Environment Variable Example + +ASP.NET Core configuration supports double underscores as section separators. + +For example: + +```text +UltimateAuth__Server__Login__MaxFailedAttempts=3 +``` + +This maps to: + +```text +UltimateAuth:Server:Login:MaxFailedAttempts +``` + +For a client setting: + +```text +UltimateAuth__Client__DefaultReturnUrl=/dashboard +``` + +This makes it possible to provide environment-specific settings without modifying application code. + +## 9. Authentication Mode Configuration + +UltimateAuth supports multiple authentication mode concepts. + +The server exposes: + +```csharp +options.AllowedModes +``` + +This setting defines which modes the server is permitted to use. + +It is a **restriction**, not a direct mode selector. + +### Restrict Allowed Modes + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.AllowedModes = + [ + UAuthMode.PureOpaque, + UAuthMode.Hybrid + ]; +}); +``` + +The effective authentication mode is resolved by the server's authentication mode resolution mechanism. + +Setting `AllowedModes` does not directly choose which mode every client will use. + +### Unsupported Modes + +In the current implementation, `SemiHybrid` and `PureJwt` are not yet accepted as enabled server modes. + +The server options validator rejects them when explicitly included in `AllowedModes`. + +For example, this configuration is invalid: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.AllowedModes = + [ + UAuthMode.PureJwt + ]; +}); +``` + +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). + +## 10. Configure Credential Session Policies + +UltimateAuth supports configurable session invalidation after credential changes. + +For example: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.CredentialSessionPolicy.OnSelfChange = + CredentialSessionBehavior.PreserveCurrent; + + options.CredentialSessionPolicy.OnAdminChange = + CredentialSessionBehavior.RevokeAll; + + options.CredentialSessionPolicy.OnRecovery = + CredentialSessionBehavior.RevokeAll; +}); +``` + +The available behaviors are: + +| Behavior | Meaning | +|---|---| +| `PreserveAll` | Preserve existing session chains | +| `PreserveCurrent` | Preserve the current chain and revoke others | +| `RevokeAll` | Revoke all applicable session chains | + +This is an example of configuration controlling **security behavior**, rather than merely adjusting a numeric value. + +The client does not decide which sessions remain valid after a credential change. + +That decision belongs to the server. + +## 11. Configure Endpoint Exposure + +UltimateAuth allows server applications to configure endpoint groups. + +The relevant options are available through: + +```csharp +options.Endpoints +``` + +For example: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Endpoints.BasePath = "/uauth"; +}); +``` + +The client must use compatible endpoint paths. + +```csharp +builder.Services.AddUltimateAuthClient(options => +{ + options.Endpoints.BasePath = "/uauth"; +}); +``` + +The server controls which endpoints are registered and exposed. + +The client controls where it sends requests. + +Changing a client endpoint path does not create or enable a server endpoint. + +See [Server Options](/docs/configuration/server-options) and [Client Options](/docs/configuration/client-options). + +## 12. Multi-Tenant Configuration + +UltimateAuth supports tenant-aware authentication and authorization. + +The server's multi-tenant configuration is available through: + +```csharp +options.MultiTenant +``` + +The server uses this configuration to determine how tenant information is resolved. + +Supported resolution mechanisms include: + +- Route-based resolution. +- Header-based resolution. +- Host-based resolution. + +The corresponding server options include: + +```csharp +options.MultiTenant.EnableRoute; +options.MultiTenant.EnableHeader; +options.MultiTenant.EnableDomain; +``` + +For example: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.MultiTenant.EnableHeader = true; +}); +``` + +The client has its own multi-tenant options for propagating tenant information to the server. + +Client-side tenant selection does not authorize cross-tenant access. + +The server remains responsible for tenant resolution, validation, and isolation. + +For more details, see [Advanced Configuration](/docs/configuration/advanced-configuration). + +## 13. Resource API Configuration + +UltimateAuth also supports a separate Resource API integration. + +Resource APIs use their own configuration model: + +```csharp +UAuthResourceApiOptions +``` + +Register a Resource API with: + +```csharp +builder.Services.AddUltimateAuthResourceApi(options => +{ + options.UAuthHubBaseUrl = "https://auth.example.com"; +}); +``` + +The configuration section is: + +```text +UltimateAuth:ResourceApi +``` + +For example: + +```json +{ + "UltimateAuth": { + "ResourceApi": { + "UAuthHubBaseUrl": "https://auth.example.com" + } + } +} +``` + +A Resource API does not become a full UltimateAuth authentication server merely by registering this integration. + +Its role is to protect application resources using the configured UltimateAuth authentication infrastructure. + +### Important: Resource API CORS Configuration + +The current Resource API registration constructs its CORS policy using the values supplied to the registration delegate. + +Therefore, when configuring `AllowedClientOrigins`, prefer supplying them directly in the registration delegate rather than assuming values bound only from `appsettings.json` will automatically populate that CORS policy. + +For example: + +```csharp +builder.Services.AddUltimateAuthResourceApi(options => +{ + options.UAuthHubBaseUrl = "https://auth.example.com"; + options.AllowedClientOrigins.Add("https://app.example.com"); +}); +``` + +Keep CORS configuration explicit and restrict allowed origins to trusted applications. + +## 14. Validation and Safe Configuration + +UltimateAuth uses ASP.NET Core options validation to reject invalid configurations. + +The server registers validators for areas including: + +- General server configuration. +- Login settings. +- Session settings. +- Token settings. +- Multi-tenancy. +- User identifiers. +- Session resolution. +- Pagination. + +For example, the server validates that: + +- The endpoint base path is specified. +- The base path does not contain invalid double slashes. +- Allowed authentication modes are valid. +- Explicitly unsupported modes are not enabled. +- Session lifetimes satisfy applicable constraints. +- Access token lifetime does not exceed the configured session maximum lifetime. + +### Example: Invalid Session Lifetime + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Session.Lifetime = TimeSpan.Zero; +}); +``` + +This configuration is rejected when the applicable session lifetime validation runs. + +### Example: Invalid Endpoint Path + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Endpoints.BasePath = "//invalid"; +}); +``` + +This configuration fails server options validation. + +### Validation Timing + +Server options use `ValidateOnStart()`. + +This allows configuration errors to surface during application startup instead of waiting until the first authentication request. + +Client options also have registered validators and post-configuration processing, but their validation timing follows the client options registration and resolution lifecycle. + +**Validation is an important safeguard, not a guarantee that every logically undesirable configuration is automatically detected.** + +Applications must still choose appropriate production settings and securely implement custom extensions. + +## 15. Extensibility Through Dependency Injection + +UltimateAuth uses dependency injection to support customization of selected runtime behaviors. + +Configuration controls the behavior of existing components. + +Dependency injection allows applications to supply implementations for supported extension points. + +Examples include: + +| Extension point | Purpose | +|---|---| +| `ICredentialResetNotifier` | Deliver credential recovery proofs | +| `IUserIdentifierVerifier` | Validate identifier ownership proof | +| `ITenantIdResolver` | Customize tenant identification | +| `IClock` | Provide a clock implementation | +| `IJwtSigningKeyProvider` | Supply JWT signing keys | + +For example, a credential reset notifier can be registered as: + +```csharp +builder.Services.AddScoped(); +``` + +`ApplicationCredentialResetNotifier` represents an implementation supplied by your application. + +The server's supported registration and resolution behavior must be respected when replacing services. + +Not every internal service is intended to be replaced, and custom implementations must preserve UltimateAuth's security invariants. + +### Configuration Versus Extension + +Use configuration when you want to adjust a supported behavior: + +```csharp +options.Session.Lifetime = TimeSpan.FromHours(8); +``` + +Use an extension point when your application needs to provide its own implementation: + +```csharp +builder.Services.AddScoped(); +``` + +This separation helps keep ordinary configuration simple while allowing advanced integration where necessary. + +## 16. Endpoint Customization + +The server exposes an advanced endpoint configuration hook: + +```csharp +options.OnConfigureEndpoints +``` + +The hook receives an `IEndpointRouteBuilder`. + +It is intended for advanced endpoint configuration after UltimateAuth registers its default endpoints. + +For example, an application may use the hook to apply additional endpoint metadata or filters where appropriate. + +However, it must not remove or remap UltimateAuth endpoints in ways that invalidate expected security behavior. + +Endpoint customization should be treated as an advanced integration mechanism, not a replacement for the supported endpoint configuration options. + +See [Advanced Configuration](/docs/configuration/advanced-configuration). + +## 17. Recommended Configuration Strategy + +For most applications, use the following approach. + +### Keep Server Security Settings on the Server + +Configure authentication modes, token lifetimes, session lifetimes, credential policies, and tenant resolution through `UAuthServerOptions`. + +### Keep Client Behavior in Client Options + +Configure client profiles, navigation, auto-refresh, reauthentication, and endpoint usage through `UAuthClientOptions`. + +### Use Configuration Files for Environment Differences + +Use `appsettings.json`, environment-specific configuration, and environment variables for values that differ between development, staging, and production. + +### Prefer Strongly Typed Configuration + +Use the provided option types rather than inventing custom configuration keys for features already supported by UltimateAuth. + +### Preserve Secure Defaults + +Only change security-sensitive defaults when the application's requirements justify the change. + +### Avoid Duplicate Core Configuration + +When using `AddUltimateAuthServer()`, do not separately configure Core options and expect them to override the server's composed options. + +### Validate Production Configuration -This allows: +Test startup validation and authentication flows using the effective configuration that will be deployed. -- Environment-based configuration -- Centralized management -- Production-safe overrides +## 18. Configuration Reference -## πŸ›‘ Safety by Design +| Application role | Registration method | Options type | Configuration section | +|---|---|---|---| +| Authentication server | `AddUltimateAuthServer()` | `UAuthServerOptions` | `UltimateAuth:Server` | +| UltimateAuth client | `AddUltimateAuthClient()` | `UAuthClientOptions` | `UltimateAuth:Client` | +| Resource API | `AddUltimateAuthResourceApi()` | `UAuthResourceApiOptions` | `UltimateAuth:ResourceApi` | +| Core-only infrastructure | `AddUltimateAuth()` | `UAuthOptions` | `UltimateAuth:Core` | -UltimateAuth does not allow unsafe configurations silently. +For normal server applications, **`UAuthServerOptions` is the effective configuration entry point for both Core-defined authentication settings and server-specific behavior**. -- Invalid combinations fail at startup -- Unsupported modes are rejected -- Security invariants are enforced +The Core-only registration exists for infrastructure scenarios that explicitly consume `UAuthOptions`; it should not be treated as an additional server configuration layer. -πŸ‘‰ Flexibility is allowed -πŸ‘‰ Unsafe behavior is not +## Next Steps -## 🎯 What’s Next? -- Understand configuration layers β†’ Configuration Overview -- Learn Core behavior β†’ Core Options -- Customize server β†’ Server Options -- Control clients β†’ Client Options -- Go deeper β†’ Advanced Configuration +- [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/configuration/server-options.md b/docs/content/configuration/server-options.md index 1656c29a..62f53aa7 100644 --- a/docs/content/configuration/server-options.md +++ b/docs/content/configuration/server-options.md @@ -1,214 +1,1455 @@ --- title: Server Options -order: 3 +order: 2 group: configuration --- +# Server Options -# 🧩 Server Options +`UAuthServerOptions` is the primary configuration surface for UltimateAuth Server. -UltimateAuth is configured primarily through **Server Options**. +It controls authentication behavior, session lifecycles, token issuance, credential security, tenant resolution, endpoint exposure, and integration with UAuthHub. -πŸ‘‰ This is the main entry point for configuring authentication behavior. +UltimateAuth composes its Core-defined options directly into `UAuthServerOptions`. When using UltimateAuth Server, you configure both shared authentication behavior and server-specific features through the same options object. -## 🧠 What Are Server Options? +This guide covers the available configuration groups, their defaults, practical examples, and the distinction between configured and effective runtime behavior. -Server options define how UltimateAuth behaves **inside your application**. +## 1. Register UltimateAuth Server -They control: +Configure the server through `AddUltimateAuthServer()`: -- Authentication behavior -- Security policies -- Token issuance -- Session lifecycle -- Endpoint exposure +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Login.MaxFailedAttempts = 5; + options.Session.Lifetime = TimeSpan.FromDays(7); + options.Token.AccessTokenLifetime = TimeSpan.FromMinutes(10); +}); +``` -
+The configuration delegate receives: -## βš™οΈ Basic Usage +```csharp +UAuthServerOptions +``` -You configure server options in `Program.cs`: +The server registration also supports an optional policy configuration delegate. ```csharp -builder.Services.AddUltimateAuthServer(o => -{ - o.Login.MaxFailedAttempts = 5; - o.Session.IdleTimeout = TimeSpan.FromDays(7); -}); +builder.Services.AddUltimateAuthServer( + configure: options => + { + options.Login.MaxFailedAttempts = 5; + }, + configurePolicies: policies => + { + // Register application access policies. + }); ``` ---- +The server automatically registers its Core infrastructure. You do not need to call `AddUltimateAuth()` separately. + +### Configuration from appsettings.json + +UltimateAuth Server binds the configuration section: + +```text +UltimateAuth:Server +``` -You can also use `appsettings.json`: +For example: ```json { "UltimateAuth": { "Server": { "Login": { - "MaxFailedAttempts": 5 + "MaxFailedAttempts": 5, + "LockoutDuration": "00:15:00" }, "Session": { - "IdleTimeout": "07.00.00.00" + "Lifetime": "7.00:00:00" + }, + "Token": { + "AccessTokenLifetime": "00:10:00" } } } } ``` -πŸ‘‰ `appsettings.json` overrides `Program.cs` +ASP.NET Core represents `TimeSpan` values using strings such as `00:15:00` for fifteen minutes and `7.00:00:00` for seven days. + +The configuration section is bound after the registration delegate, so configuration provider values take precedence for matching properties. + +For configuration source ordering and environment-specific overrides, see [Configuration Overview](/docs/configuration/configuration). -## 🧩 Core Composition +## 2. Server Options Architecture -Server options include Core behavior: +`UAuthServerOptions` contains two categories of configuration. -- Login -- Session -- Token -- PKCE -- Multi-tenancy +### Core-defined options -πŸ‘‰ These are defined in Core +These types are defined in UltimateAuth Core and composed into the server options: -πŸ‘‰ But configured via Server +| Property | Type | Purpose | +|---|---|---| +| `Login` | `UAuthLoginOptions` | Login attempts and lockout behavior | +| `Session` | `UAuthSessionOptions` | Session lifetime and lifecycle | +| `Token` | `UAuthTokenOptions` | Access and refresh token behavior | +| `Pkce` | `UAuthPkceOptions` | PKCE authorization code lifetime | +| `MultiTenant` | `UAuthMultiTenantOptions` | Tenant identification and resolution | +| `Pagination` | `UAuthPaginationOptions` | Pagination defaults and limits | +| `Events` | `UAuthEvents` | Authentication lifecycle callbacks | -
+### Server-specific options + +| Property | Purpose | +|---|---| +| `AllowedModes` | Restrict permitted authentication modes | +| `HubDeploymentMode` | Describe the relationship between UAuthHub and the application | +| `Hub` | Hub integration and flow settings | +| `Cookie` | Cookie names, security attributes, and lifetime behavior | +| `SessionResolution` | Session identifier sources and precedence | +| `Endpoints` | Endpoint groups and disabled actions | +| `PrimaryCredential` | Primary credential selection for UI and API requests | +| `AuthResponse` | Authentication credential delivery and redirect behavior | +| `CredentialSessionPolicy` | Session invalidation after credential changes | +| `ResetCredential` | Credential recovery code and token settings | +| `Identifiers` | Identifier behavior, normalization, and uniqueness | +| `IdentifierValidation` | Username, email, and phone validation | +| `LoginIdentifiers` | Identifier types allowed for login | +| `UserProfile` | User profile capabilities | +| `Navigation` | Authentication navigation behavior | +| `Diagnostics` | Optional diagnostic response details | + +Advanced configuration hooks are also available, including `ConfigureMode()` and `OnConfigureEndpoints`. + +## 3. Login Options + +`options.Login` controls login attempt limits, lockout behavior, and related security settings. + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Login.MaxFailedAttempts = 5; + options.Login.LockoutDuration = TimeSpan.FromMinutes(15); + options.Login.FailureWindow = TimeSpan.FromMinutes(15); + options.Login.ExtendLockOnFailure = false; +}); +``` + +### Available Properties + +| Property | Default | Description | +|---|---|---| +| `MaxFailedAttempts` | `10` | Failed login threshold; `0` disables lockout | +| `LockoutDuration` | 15 minutes | Lockout duration | +| `FailureWindow` | 15 minutes | Window for counting failed attempts | +| `ExtendLockOnFailure` | `false` | Extend lockout when another attempt fails during lockout | +| `IncludeFailureDetails` | `true` | Include detailed authentication failure information | +| `TryLoginDuration` | 180 seconds | Duration associated with the try-login flow | + +### Example: Restrict Login Failure Details + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Login.IncludeFailureDetails = false; +}); +``` -## ⚠️ Important: You Don’t Configure Modes Directly +This setting is useful when an application does not want to expose detailed lockout information to callers. -UltimateAuth does NOT expect you to select a single auth mode. +### Important -Instead: +Lockout configuration is not equivalent to complete network-level rate limiting. -πŸ‘‰ Mode is resolved at runtime +Production deployments should also consider infrastructure protections against abusive traffic and distributed login attempts. -
+## 4. Session Options -## πŸ›‘ Allowed Modes (Guardrail) +`options.Session` controls session lifetime, expiration, sliding behavior, and session-chain limits. ```csharp -o.AllowedModes = new[] +builder.Services.AddUltimateAuthServer(options => { - UAuthMode.Hybrid, - UAuthMode.PureOpaque -}; + options.Session.Lifetime = TimeSpan.FromDays(7); + options.Session.SlidingExpiration = true; + options.Session.IdleTimeout = TimeSpan.FromDays(3); + options.Session.MaxLifetime = TimeSpan.FromDays(30); +}); ``` -πŸ‘‰ This does NOT select a mode -πŸ‘‰ It restricts which modes are allowed +### Session Lifetime Properties -If a resolved mode is not allowed: +| Property | Default | Description | +|---|---|---| +| `Lifetime` | 7 days | Standard session lifetime | +| `MaxLifetime` | `null` | Optional absolute lifetime limit | +| `SlidingExpiration` | `true` | Allow eligible activity or refresh operations to extend expiration | +| `IdleTimeout` | `null` | Optional inactivity timeout | +| `TouchInterval` | 5 minutes | Minimum interval between last-seen updates | -πŸ‘‰ Request fails early +These settings have different purposes. -
+`Lifetime` determines the standard lifetime assigned to a session. -## ⚑ Runtime Behavior (Effective Options) +`MaxLifetime` establishes an optional hard boundary that cannot be extended indefinitely. -Server options are not used directly. +`IdleTimeout` limits how long a session can remain inactive. -They are transformed into: +`TouchInterval` reduces unnecessary persistence operations caused by frequent activity updates. -πŸ‘‰ `EffectiveUAuthServerOptions` +### Session-Chain Properties -This happens per request: +| Property | Default | Description | +|---|---|---| +| `MaxChainsPerUser` | `0` | Reserved user-level chain limit | +| `MaxSessionsPerChain` | `100` | Session-chain rotation limit | +| `MaxChainsPerPlatform` | `null` | Reserved platform-specific chain limits | +| `PlatformCategories` | `null` | Reserved platform group definitions | +| `MaxChainsPerCategory` | `null` | Reserved category-specific chain limits | +| `EnableIpBinding` | `false` | Reserved IP binding setting | +| `EnableUserAgentBinding` | `false` | Reserved User-Agent binding setting | +| `DeviceMismatchBehavior` | `Reject` | Reserved device mismatch behavior | -- Mode is resolved -- Defaults are applied -- Overrides are applied +**Current implementation limitation:** The source identifies several device limits and binding settings as reserved for future enforcement. Setting these properties must not be treated as proof that the corresponding protection is active. -πŸ‘‰ What actually runs is **Effective Options** +For example, do not rely on: -
+```csharp +options.Session.MaxChainsPerUser = 1; +``` -## πŸ”„ Mode-Based Defaults +as a production guarantee that only one device chain can exist unless the current runtime implementation has been verified to enforce that restriction. -Each auth mode applies different defaults automatically: +### Mode-Specific Session Defaults -- PureOpaque β†’ session-heavy -- Hybrid β†’ session + token -- PureJwt β†’ token-only +Session settings can also be affected by authentication mode defaults. +For example, `PureOpaque` supplies a longer idle timeout and touch interval when those values have not been configured. -πŸ‘‰ You don’t need to manually configure everything +`Hybrid` explicitly sets `TouchInterval` to `null`. -
+See [Advanced Configuration](/docs/configuration/advanced-configuration) for the effective-options resolution order. -## πŸŽ› Endpoint Control +## 5. Token Options -You can control which features are enabled: +`options.Token` configures access and refresh token behavior. ```csharp -o.Endpoints.Authentication = true; -o.Endpoints.Session = true; -o.Endpoints.Authorization = true; +builder.Services.AddUltimateAuthServer(options => +{ + options.Token.Issuer = "MyApplication"; + options.Token.Audience = "MyApplicationApi"; + options.Token.AccessTokenLifetime = TimeSpan.FromMinutes(10); + options.Token.RefreshTokenLifetime = TimeSpan.FromDays(7); +}); ``` +### Available Properties -You can also disable specific actions: +| Property | Default | Description | +|---|---|---| +| `IssueJwt` | `true` | Enable JWT issuance where supported by the effective mode | +| `IssueOpaque` | `true` | Enable opaque token issuance where supported | +| `IssueRefresh` | `true` | Enable refresh token issuance where supported | +| `AccessTokenLifetime` | 10 minutes | Access token lifetime | +| `RefreshTokenLifetime` | 7 days | Refresh token lifetime | +| `OpaqueIdBytes` | `32` | Random byte length used for opaque token identifiers | +| `RefreshTokenConcurrentRequestWindow` | 2 seconds | Concurrent duplicate refresh classification window | +| `Issuer` | `UAuth` | JWT issuer | +| `Audience` | `UAuthClient` | JWT audience | +| `AddJwtIdClaim` | `false` | Include a JWT identifier claim | +| `KeyId` | `null` | Optional signing key identifier | + +### Refresh Token Concurrency Window ```csharp -o.Endpoints.DisabledActions.Add("UAuthActions.Users.Create.Anonymous"); +builder.Services.AddUltimateAuthServer(options => +{ + options.Token.RefreshTokenConcurrentRequestWindow = TimeSpan.FromSeconds(2); +}); ``` -πŸ‘‰ Useful for API hardening +This option controls how the server distinguishes a closely timed duplicate refresh request from a confirmed replay. + +A duplicate refresh request is still rejected. + +The window affects the replay-response classification, including whether the token family should be revoked. + +It does **not** allow the same consumed refresh token to be used successfully more than once. -
+### Mode Defaults May Override Token Values -## πŸͺ Cookie & Transport Behavior +The effective `Hybrid` configuration explicitly sets: -Server options define how credentials are transported: +```csharp +Token.AccessTokenLifetime = TimeSpan.FromMinutes(10); +Token.RefreshTokenLifetime = TimeSpan.FromDays(7); +``` -- Cookies -- Headers -- Tokens +Therefore, a different value configured globally may be replaced when the effective mode is resolved. -πŸ‘‰ Unsafe combinations are rejected at startup +To customize the lifetime for `Hybrid`, use: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.ConfigureMode(UAuthMode.Hybrid, mode => + { + mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(5); + }); +}); +``` -
+This distinction is essential when configuring token lifetimes. -## 🌐 Hub Configuration +## 6. PKCE Options -If using UAuthHub: +`options.Pkce` configures the lifetime of PKCE authorization codes. ```csharp -o.HubDeploymentMode = UAuthHubDeploymentMode.Integrated; +builder.Services.AddUltimateAuthServer(options => +{ + options.Pkce.AuthorizationCodeLifetimeSeconds = 120; +}); ``` -πŸ‘‰ Defines how auth server is deployed +| Property | Default | Description | +|---|---|---| +| `AuthorizationCodeLifetimeSeconds` | `120` | Lifetime of a PKCE authorization code | + +PKCE is particularly important for browser-based clients such as Blazor WebAssembly. + +A shorter authorization code lifetime reduces the period during which an intercepted or exposed code could potentially be used. + +However, the value must remain long enough for legitimate authorization exchanges to complete. -
+The server's PKCE configuration is separate from the client's PKCE flow settings. -## πŸ” Session Resolution +## 7. Authentication Modes -Controls how session IDs are extracted: +UltimateAuth supports authentication mode resolution based on the client profile. -- Cookie -- Header -- Bearer -- Query +The server exposes: -πŸ‘‰ Fully configurable +```csharp +options.AllowedModes +``` -
+This property restricts the modes the server is permitted to use. -## 🧠 Mental Model +### Restrict Allowed Modes + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.AllowedModes = + [ + UAuthMode.PureOpaque, + UAuthMode.Hybrid + ]; +}); +``` -If you remember one thing: +`AllowedModes` is a restriction, not a mode selector. -πŸ‘‰ Server options define **what is allowed** -πŸ‘‰ Runtime determines **what is used** +The effective mode is resolved by `IEffectiveAuthModeResolver`. -## πŸ“Œ Key Takeaways +### Default Mode Resolution -- Server options are the main configuration entry -- Core behavior is configured via server -- Modes are not selected manually -- Effective options are computed per request -- Security is enforced by design +| Client profile | Default resolved mode | +|---|---| +| `BlazorServer` | `PureOpaque` | +| `WebServer` | `Hybrid` | +| `BlazorWasm` | `Hybrid` | +| `Maui` | `Hybrid` | +| `Api` | `PureJwt` | ---- +The current implementation has mode templates for `PureOpaque`, `Hybrid`, `SemiHybrid`, and `PureJwt`. + +However, `SemiHybrid` and `PureJwt` are not yet accepted as enabled modes by the current server options validator. + +**Do not enable an unsupported mode merely because it exists in the enum or has a mode-default template.** + +The default `Api` profile mapping to `PureJwt` is a current compatibility limitation to consider when deploying API-profile authentication flows. + +### Configure Mode-Specific Behavior + +Use `ConfigureMode()` for behavior that should differ by mode. + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.ConfigureMode(UAuthMode.PureOpaque, mode => + { + mode.Session.IdleTimeout = TimeSpan.FromDays(7); + }); + + options.ConfigureMode(UAuthMode.Hybrid, mode => + { + mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(5); + }); +}); +``` + +The mode override runs after the built-in mode defaults. + +See [Advanced Configuration](/docs/configuration/advanced-configuration) for details. + +## 8. Endpoint Options + +`options.Endpoints` controls endpoint paths, feature groups, and individual action restrictions. + +### Base Path + +The default server endpoint base path is: + +```text +/auth +``` + +Configure it through: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Endpoints.BasePath = "/auth"; +}); +``` + +Changing the base path can help avoid route conflicts when integrating UltimateAuth into an existing application. + +The client must use compatible endpoint paths. + +### Endpoint Groups + +| Property | Default | Purpose | +|---|---|---| +| `Authentication` | `true` | Authentication endpoints | +| `Pkce` | `true` | PKCE endpoints | +| `Session` | `true` | Session management endpoints | +| `UserLifecycle` | `true` | User lifecycle endpoints | +| `UserProfile` | `true` | User profile endpoints | +| `UserIdentifier` | `true` | Identifier management endpoints | +| `Credentials` | `true` | Credential management endpoints | +| `Authorization` | `true` | Authorization endpoints | + +For example: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Endpoints.Authentication = true; + options.Endpoints.Session = true; + options.Endpoints.Authorization = false; +}); +``` + +Disabling an endpoint group controls endpoint exposure. It does not automatically remove the underlying domain services or invalidate previously issued credentials. + +### Disable Individual Actions + +`DisabledActions` is a collection of action identifiers: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Endpoints.DisabledActions.Add("users.create.anonymous"); +}); +``` + +The string must match the action identifier used by the server's endpoint registration and action policy. + +Do not pass a C# constant name as a string unless that exact text is also the registered action value. + +For example, these are not necessarily equivalent: + +```csharp +"UAuthActions.Users.Create.Anonymous" +``` + +and: + +```csharp +"users.create.anonymous" +``` + +Use the actual action value defined by the corresponding `UAuthActions` constant. + +### Endpoint Hardening + +Disable endpoints that are not required by the application. + +For example, an application that delegates user provisioning to administrators may choose to disable anonymous user creation. + +Endpoint exposure is a separate concern from authorization: an exposed endpoint must still enforce the applicable security policy. + +## 9. Session Resolution + +`options.SessionResolution` controls how incoming requests provide session identifiers. + +UltimateAuth supports four resolution sources: + +- Bearer credentials. +- Custom headers. +- Cookies. +- Query parameters. + +### Default Configuration + +| Property | Default | +|---|---| +| `EnableBearer` | `true` | +| `EnableHeader` | `true` | +| `EnableCookie` | `true` | +| `EnableQuery` | `true` | +| `HeaderName` | `X-UAuth-Session` | +| `QueryParameterName` | `session_id` | + +The default precedence order is: + +```text +Bearer +Header +Cookie +Query +``` + +### Configure Resolution Sources + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.SessionResolution.EnableBearer = true; + options.SessionResolution.EnableHeader = false; + options.SessionResolution.EnableCookie = true; + options.SessionResolution.EnableQuery = false; +}); +``` + +This example disables custom-header and query-string session resolution. + +### Change Resolution Order + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.SessionResolution.Order = + [ + "Cookie", + "Bearer", + "Header", + "Query" + ]; +}); +``` + +Resolution order matters when a request contains multiple candidate credentials. + +The selected credential must still pass authentication and session validation. + +### Security Recommendation + +Avoid query-string credentials unless the application has a specific, well-understood requirement. + +URLs may be recorded in browser history, reverse-proxy logs, analytics systems, and other infrastructure. + +For most browser and API integrations, cookies or authorization headers are preferable. + +## 10. Cookie Configuration + +`options.Cookie` controls cookies used for authentication credentials. + +UltimateAuth defines three cookie groups: + +```csharp +options.Cookie.Session +options.Cookie.AccessToken +options.Cookie.RefreshToken +``` + +### Default Cookie Names + +| Cookie | Default name | +|---|---| +| Session | `uas` | +| Access token | `uat` | +| Refresh token | `uar` | + +The default cookie options use: + +- `HttpOnly = true` +- `SecurePolicy = CookieSecurePolicy.Always` +- `Path = "/"` +- `Domain = null` +- `MaxAge = null` + +`SameSite` is nullable and can be resolved through the applicable cookie and deployment policies. + +### Customize Cookie Names + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Cookie.Session.Name = "myapp_session"; + options.Cookie.RefreshToken.Name = "myapp_refresh"; +}); +``` + +### Configure Cookie Scope + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Cookie.Session.Path = "/"; + options.Cookie.Session.Domain = null; +}); +``` + +A null domain produces a host-only cookie. + +Setting a parent domain can broaden cookie availability to subdomains and should be done only when necessary. + +### Cookie Security + +Avoid disabling `HttpOnly` or relaxing secure transport requirements for authentication cookies. + +Cookie settings interact with: + +- The deployment mode. +- Cross-site requests. +- Browser cookie policies. +- Credential delivery configuration. +- HTTPS requirements. + +For cross-site deployments, configure cookie behavior together with CORS and the relevant browser security requirements. + +## 11. Hub Deployment Mode + +`options.HubDeploymentMode` describes how UAuthHub is deployed relative to the application. + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.HubDeploymentMode = UAuthHubDeploymentMode.Embedded; +}); +``` + +### Available Modes + +| Mode | Description | +|---|---| +| `Embedded` | Hub and application share the same origin | +| `Integrated` | Hub is separate but within the same-site boundary | +| `External` | Hub is hosted on a different site or domain | + +The default is: + +```csharp +UAuthHubDeploymentMode.Integrated +``` + +### Embedded + +Suitable for applications hosting UltimateAuth within the same application and origin. + +```csharp +options.HubDeploymentMode = UAuthHubDeploymentMode.Embedded; +``` + +### Integrated + +Suitable for deployments such as: + +```text +auth.example.com +app.example.com +``` + +where the authentication service and application are separate origins within the same site. + +### External + +Suitable for a separate authentication provider domain. + +```csharp +options.HubDeploymentMode = UAuthHubDeploymentMode.External; +``` + +External deployment requires careful coordination of cookie policies, redirects, allowed origins, and browser restrictions. + +The deployment mode influences authentication transport behavior; it is not merely descriptive metadata. + +## 12. Hub Options + +`options.Hub` configures UAuthHub integration. + +### Available Properties + +| Property | Default | Description | +|---|---|---| +| `ClientBaseAddress` | `null` | Optional client application base address | +| `AllowedClientOrigins` | Empty set | Allowed client origins | +| `FlowLifetime` | 5 minutes | Lifetime of Hub flow artifacts | +| `LoginPath` | `/login` | Hub login page path | + +### Example + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Hub.ClientBaseAddress = "https://app.example.com"; + options.Hub.AllowedClientOrigins.Add("https://app.example.com"); + options.Hub.FlowLifetime = TimeSpan.FromMinutes(5); + options.Hub.LoginPath = "/login"; +}); +``` + +The Hub flow lifetime is not the same as the authenticated session lifetime. + +Hub artifacts support authentication orchestration and should generally be short-lived. + +Only trusted origins should be included in `AllowedClientOrigins`. + +## 13. Primary Credential Policy + +`options.PrimaryCredential` controls which credential kind is preferred for UI and API requests. + +The defaults are: + +| Property | Default | +|---|---| +| `Ui` | `PrimaryTokenKind.Session` | +| `Api` | `PrimaryTokenKind.AccessToken` | + +### Example + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.PrimaryCredential.Ui = PrimaryTokenKind.Session; + options.PrimaryCredential.Api = PrimaryTokenKind.AccessToken; +}); +``` + +This allows UltimateAuth to distinguish between session-oriented browser interactions and access-token-oriented API interactions. + +The primary credential policy does not bypass authentication validation. + +It determines which credential kind should be preferred within the applicable request-processing rules. + +## 14. Credential Session Policy + +`options.CredentialSessionPolicy` determines what happens to active session chains after credential changes. + +### Available Properties + +| Property | Default | Purpose | +|---|---|---| +| `OnSelfChange` | `PreserveCurrent` | User changes their own credential | +| `OnAdminChange` | `RevokeAll` | Administrator changes a user's credential | +| `OnRecovery` | `RevokeAll` | Credential recovery completes | + +### Example + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.CredentialSessionPolicy.OnSelfChange = CredentialSessionBehavior.PreserveCurrent; + options.CredentialSessionPolicy.OnAdminChange = CredentialSessionBehavior.RevokeAll; + options.CredentialSessionPolicy.OnRecovery = CredentialSessionBehavior.RevokeAll; +}); +``` + +### Available Behaviors + +| Behavior | Effect | +|---|---| +| `PreserveAll` | Keep existing session chains | +| `PreserveCurrent` | Keep the current chain and revoke other applicable chains | +| `RevokeAll` | Revoke all applicable session chains | + +For recovery flows, `PreserveCurrent` is not always meaningful because the user may not have an authenticated current session. + +The current implementation treats this situation using the more restrictive `RevokeAll` behavior. + +### Security Recommendation + +Preserving the current session after a normal password change can provide a reasonable user experience. + +Revoking all sessions after administrative credential changes or account recovery is generally more conservative. + +Choose the policy according to the application's security requirements. + +## 15. Credential Reset Options + +`options.ResetCredential` configures credential recovery proof lifetimes and attempt limits. + +### Defaults + +| Property | Default | Description | +|---|---|---| +| `TokenValidity` | 30 minutes | Reset token lifetime | +| `CodeValidity` | 10 minutes | Numeric reset code lifetime | +| `MaxAttempts` | `3` | Maximum permitted recovery attempts | +| `CodeLength` | `6` | Numeric reset code length | + +### Example + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.ResetCredential.TokenValidity = TimeSpan.FromMinutes(20); + options.ResetCredential.CodeValidity = TimeSpan.FromMinutes(5); + options.ResetCredential.MaxAttempts = 3; + options.ResetCredential.CodeLength = 6; +}); +``` + +`CodeLength` applies to numeric reset codes, not token-based recovery proofs. + +### Important Security Boundary + +Reset proofs must be delivered through a trusted server-side mechanism. + +Do not return recovery tokens to anonymous callers simply to simplify a client-side reset form. + +The application should provide the appropriate `ICredentialResetNotifier` implementation for supported recovery flows. + +## 16. Login Identifier Options + +`options.LoginIdentifiers` determines which identifier types can be used for login. + +The default allowed types are: + +```csharp +UserIdentifierType.Username +UserIdentifierType.Email +UserIdentifierType.Phone +``` + +### Restrict Login Identifier Types + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.LoginIdentifiers.AllowedTypes = + new HashSet + { + UserIdentifierType.Email + }; +}); +``` + +This configures email as the allowed login identifier type. + +### Require Verification + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.LoginIdentifiers.RequireVerificationForEmail = true; + options.LoginIdentifiers.RequireVerificationForPhone = true; +}); +``` + +These settings control whether the corresponding identifiers must be verified before being accepted for login. + +### Custom Resolvers + +| Property | Default | +|---|---| +| `EnableCustomResolvers` | `true` | +| `CustomResolversFirst` | `true` | + +These options control the participation and ordering of custom login identifier resolvers. + +An identifier existing on a user account does not necessarily mean it is permitted for login. + +Identifier management and login eligibility are separate concerns. + +## 17. Identifier Validation + +`options.IdentifierValidation` provides rules for usernames, email addresses, and phone numbers. + +### Username Rules + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.IdentifierValidation.UserName.Enabled = true; + options.IdentifierValidation.UserName.MinLength = 3; + options.IdentifierValidation.UserName.MaxLength = 32; + options.IdentifierValidation.UserName.AllowedRegex = "^[a-zA-Z0-9._-]+$"; +}); +``` + +### Default Validation Rules + +| Identifier | Enabled | Minimum length | Maximum length | +|---|---|---|---| +| Username | `true` | `3` | `64` | +| Email | `true` | `3` | `256` | +| Phone | `true` | `3` | `24` | + +Username validation additionally supports `AllowedRegex`. + +The default username expression is: + +```regex +^[a-zA-Z0-9._-]+$ +``` + +### Identifier Behavior + +`options.Identifiers` contains additional groups: + +```csharp +options.Identifiers.Behavior +options.Identifiers.Validation +options.Identifiers.Normalization +options.Identifiers.Uniqueness +``` + +These configure identifier behavior beyond the top-level validation rules. + +Identifier normalization and uniqueness are particularly important because they affect how duplicate identifiers are detected. + +Use the provided options rather than implementing inconsistent normalization independently in the client. + +## 18. User Profile Options + +`options.UserProfile` controls profile-related capabilities. + +The current option is: + +```csharp +options.UserProfile.EnableMultiProfile +``` + +Its default is `false`. + +### Enable Multiple Profiles + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.UserProfile.EnableMultiProfile = true; +}); +``` + +This allows applications to use UltimateAuth's multiple-profile capability where supported by the user domain. + +For example, an application may maintain separate profile contexts for different purposes. + +Multi-profile support does not create separate user identities or independent authentication accounts. + +## 19. Multi-Tenant Options + +`options.MultiTenant` controls whether tenant-aware behavior is enabled and how tenant identifiers are resolved. + +### Available Properties + +| Property | Default | Description | +|---|---|---| +| `Enabled` | `false` | Enable multi-tenant behavior | +| `NormalizeToLowercase` | `true` | Normalize tenant identifiers | +| `EnableRoute` | `false` | Resolve tenants from routes | +| `EnableHeader` | `false` | Resolve tenants from headers | +| `EnableDomain` | `false` | Resolve tenants from hosts | +| `HeaderName` | `X-Tenant` | Tenant header name | + +### Example: Header-Based Tenant Resolution + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.MultiTenant.Enabled = true; + options.MultiTenant.EnableHeader = true; + options.MultiTenant.HeaderName = "X-Tenant"; +}); +``` + +### Example: Domain-Based Resolution + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.MultiTenant.Enabled = true; + options.MultiTenant.EnableDomain = true; +}); +``` + +Multiple resolution mechanisms can be enabled. + +The server composes the configured tenant resolvers according to its resolution infrastructure. + +### Security Boundary + +Tenant identification is not tenant authorization. + +A request providing another tenant identifier must not gain access to that tenant without appropriate authorization. + +The server remains responsible for tenant isolation. + +## 20. Pagination Options + +`options.Pagination` controls pagination defaults and maximum page sizes. + +| Property | Default | +|---|---| +| `DefaultPageSize` | `250` | +| `MaxPageSize` | `1000` | + +### Example + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Pagination.DefaultPageSize = 50; + options.Pagination.MaxPageSize = 200; +}); +``` + +These settings affect paginated queries exposed by UltimateAuth domain services. + +For administrative interfaces, choosing a moderate page size can reduce unnecessary data transfer and processing. + +## 21. Navigation Options + +`options.Navigation` configures automatic authentication navigation behavior. + +### Available Properties + +| Property | Default | +|---|---| +| `EnableAutomaticNavigationRedirect` | `true` | +| `LoginResolver` | `null` | +| `AccessDeniedResolver` | `null` | + +### Disable Automatic Redirects + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Navigation.EnableAutomaticNavigationRedirect = false; +}); +``` + +### Custom Login Redirect + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Navigation.LoginResolver = context => + { + return "/account/login"; + }; +}); +``` + +### Custom Access Denied Redirect + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Navigation.AccessDeniedResolver = context => + { + return "/account/access-denied"; + }; +}); +``` + +These delegates receive the current `HttpContext`. + +The application should return valid, trusted navigation destinations. + +Avoid constructing redirects from unvalidated user-controlled URLs. + +## 22. Authentication Response Options + +`options.AuthResponse` controls how authentication credentials and redirect behavior are represented in responses. + +The available groups are: + +```csharp +options.AuthResponse.SessionIdDelivery +options.AuthResponse.AccessTokenDelivery +options.AuthResponse.RefreshTokenDelivery + +options.AuthResponse.Login +options.AuthResponse.Logout +``` + +These settings govern credential delivery and authentication response behavior. + +### Important: Mode-Specific Response Defaults + +UltimateAuth provides response templates for authentication modes. + +For example, `PureOpaque` uses a session-oriented cookie response, while `Hybrid` combines session and token delivery behavior. + +The effective response may therefore depend on the resolved authentication mode and flow. + +Do not assume that a single globally configured response delivery setting applies unchanged to every authentication mode. + +### Security Considerations + +Credential delivery settings determine whether sensitive authentication material is exposed through cookies, headers, or other response mechanisms. + +Only customize them when the application has a clear requirement and the selected transport is appropriate for the client platform. + +For advanced response customization, see [Advanced Configuration](/docs/configuration/advanced-configuration). + +## 23. Diagnostics Options + +`options.Diagnostics` provides optional diagnostic behavior. + +The current property is: + +```csharp +options.Diagnostics.EnableRefreshDetails +``` + +Its default is `false`. + +### Example + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Diagnostics.EnableRefreshDetails = false; +}); +``` + +When enabled, the option allows additional refresh-related diagnostic response information, such as the `X-UAuth-Refresh` header. + +This is primarily useful during development and debugging. + +Avoid enabling detailed authentication diagnostics in production unless the exposure is understood and justified. + +## 24. Authentication Lifecycle Events + +`options.Events` exposes server-side authentication lifecycle callbacks. + +### Available Events + +```csharp +options.Events.OnAnyEvent +options.Events.OnUserLoggedIn +options.Events.OnUserLoggedOut +``` + +### Example: Login Logging + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Events.OnUserLoggedIn = context => + { + // Record the successful login. + return Task.CompletedTask; + }; +}); +``` + +### Example: Global Event Handler + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Events.OnAnyEvent = context => + { + // Process an authentication lifecycle event. + return Task.CompletedTask; + }; +}); +``` + +Lifecycle events are intended for lightweight reactions such as logging, metrics, and notifications. + +They should not replace security decisions, authentication validation, or application policies. + +See [Advanced Configuration](/docs/configuration/advanced-configuration) for event and extension guidance. + +## 25. Effective Server Options + +The configured `UAuthServerOptions` instance is the base configuration. + +At runtime, UltimateAuth can resolve: + +```csharp +EffectiveUAuthServerOptions +``` + +The current resolution process is: + +```text +Configured UAuthServerOptions + | + v + Resolve auth mode + | + v + Clone base options + | + v + Apply mode defaults + | + v + Apply ConfigureMode overrides + | + v + EffectiveUAuthServerOptions +``` + +This means that the effective settings used for a `Hybrid` client can differ from those used for a `PureOpaque` client. + +### Example + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Session.Lifetime = TimeSpan.FromDays(7); + + options.ConfigureMode(UAuthMode.Hybrid, mode => + { + mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(5); + }); + + options.ConfigureMode(UAuthMode.PureOpaque, mode => + { + mode.Session.IdleTimeout = TimeSpan.FromDays(3); + }); +}); +``` + +The original options remain the common starting point. + +Mode defaults and overrides determine the effective values for the resolved mode. + +For implementation details and inspection of effective options, see [Advanced Configuration](/docs/configuration/advanced-configuration). + +## 26. Validation and Configuration Safety + +UltimateAuth Server registers multiple options validators and enables startup validation. + +These validators cover areas including: + +- Login settings. +- Session settings. +- Token settings. +- PKCE. +- Multi-tenancy. +- Identifier configuration. +- Session resolution. +- Pagination. +- General server configuration. + +### Example: Invalid Session Lifetime + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Session.Lifetime = TimeSpan.Zero; +}); +``` + +The server rejects an invalid session lifetime under the applicable validation rules. + +### Example: Invalid Base Path + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Endpoints.BasePath = "//auth"; +}); +``` + +The general server validator rejects paths containing `//`. + +### Unsupported Modes + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.AllowedModes = + [ + UAuthMode.PureJwt + ]; +}); +``` + +The current validator rejects explicitly enabling `PureJwt` because that mode is not yet implemented as a supported server mode. + +### Validation Limitations + +Startup validation protects the configured options according to the registered validators. + +It does not guarantee that every custom service implementation or mode-specific override is secure. + +When using advanced customization, test the resulting effective configuration and authentication flows. + +## 27. Complete Configuration Example + +The following example combines several commonly used server settings. + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + // Authentication modes + options.AllowedModes = + [ + UAuthMode.PureOpaque, + UAuthMode.Hybrid + ]; + + // Login security + options.Login.MaxFailedAttempts = 5; + options.Login.LockoutDuration = TimeSpan.FromMinutes(15); + options.Login.IncludeFailureDetails = false; + + // Sessions + options.Session.Lifetime = TimeSpan.FromDays(7); + options.Session.MaxLifetime = TimeSpan.FromDays(30); + + // Token identity + options.Token.Issuer = "MyApplication"; + options.Token.Audience = "MyApplicationApi"; + + // Hybrid-specific token lifetime + options.ConfigureMode(UAuthMode.Hybrid, mode => + { + mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(10); + }); + + // PKCE + options.Pkce.AuthorizationCodeLifetimeSeconds = 120; + + // Hub deployment + options.HubDeploymentMode = UAuthHubDeploymentMode.Integrated; + options.Hub.ClientBaseAddress = "https://app.example.com"; + options.Hub.AllowedClientOrigins.Add("https://app.example.com"); + + // Credential security + options.CredentialSessionPolicy.OnSelfChange = CredentialSessionBehavior.PreserveCurrent; + options.CredentialSessionPolicy.OnAdminChange = CredentialSessionBehavior.RevokeAll; + options.CredentialSessionPolicy.OnRecovery = CredentialSessionBehavior.RevokeAll; + + // Credential recovery + options.ResetCredential.TokenValidity = TimeSpan.FromMinutes(30); + options.ResetCredential.MaxAttempts = 3; + + // Identifier login rules + options.LoginIdentifiers.RequireVerificationForEmail = true; + + // Session transport + options.SessionResolution.EnableCookie = true; + options.SessionResolution.EnableBearer = true; + options.SessionResolution.EnableQuery = false; + + // Endpoint exposure + options.Endpoints.Authentication = true; + options.Endpoints.Session = true; + options.Endpoints.Credentials = true; + options.Endpoints.Authorization = true; + + // Pagination + options.Pagination.DefaultPageSize = 50; + options.Pagination.MaxPageSize = 200; + + // Diagnostics + options.Diagnostics.EnableRefreshDetails = false; +}); +``` + +This is an illustrative configuration, not a universal production preset. + +Applications should choose session lifetimes, authentication modes, credential transport, tenant behavior, and endpoint exposure according to their own deployment and security requirements. + +## 28. Common Configuration Mistakes + +### Configuring Core Options Separately + +When using `AddUltimateAuthServer()`, configure Core-defined options through `UAuthServerOptions`. + +A separate `UAuthOptions` configuration does not automatically override the server's composed options. + +### Assuming AllowedModes Selects the Mode + +`AllowedModes` is a restriction. + +Mode selection is performed by the authentication mode resolver. + +### Ignoring Mode Defaults + +Mode-specific defaults can replace globally configured values. + +Use `ConfigureMode()` for explicit mode-specific behavior. + +### Enabling Unsupported Modes + +An authentication mode appearing in an enum does not guarantee runtime support. + +### Treating Reserved Session Options as Enforced Security Features + +Some device and session-chain options are reserved for future enforcement. + +Verify actual runtime support before relying on them. + +### Exposing Unnecessary Endpoints + +Disable endpoint groups or actions that the application does not require. + +### Allowing Query-String Credentials Without a Specific Need + +Query-string credentials can leak through URLs and infrastructure logs. + +### Weakening Cookie Security + +Do not relax secure transport or cookie protections without understanding the consequences. + +### Assuming Configuration Validation Covers Everything + +Startup validation does not replace testing, infrastructure security, or validation of custom extension implementations. + +## 29. Server Options Quick Reference + +| Configuration group | Typical use | +|---|---| +| `Login` | Login attempts and lockout | +| `Session` | Session expiration and lifecycle | +| `Token` | Token issuance and lifetime | +| `Pkce` | PKCE code lifetime | +| `AllowedModes` | Permitted authentication modes | +| `MultiTenant` | Tenant resolution | +| `Pagination` | Query page limits | +| `Endpoints` | Endpoint exposure | +| `SessionResolution` | Credential source precedence | +| `Cookie` | Cookie security and scope | +| `HubDeploymentMode` | Hub deployment topology | +| `Hub` | Hub integration | +| `PrimaryCredential` | UI/API primary credential selection | +| `AuthResponse` | Credential delivery | +| `CredentialSessionPolicy` | Session invalidation after credential changes | +| `ResetCredential` | Credential recovery rules | +| `Identifiers` | Identifier behavior | +| `IdentifierValidation` | Identifier validation | +| `LoginIdentifiers` | Login identifier eligibility | +| `UserProfile` | Multi-profile capability | +| `Navigation` | Authentication redirects | +| `Diagnostics` | Refresh diagnostics | +| `Events` | Authentication lifecycle callbacks | +| `ConfigureMode()` | Mode-specific option overrides | +| `OnConfigureEndpoints` | Advanced endpoint customization | -## ➑️ Next Step +## Next Steps -Continue to **Client Options** +- [Configuration Overview](/docs/configuration/configuration) β€” Understand the configuration architecture and precedence. +- [Client Options](/docs/configuration/client-options) β€” Configure client SDK behavior. +- [Advanced Configuration](/docs/configuration/advanced-configuration) β€” Explore effective options, mode resolution, and extension points. +- [Session Management](/docs/client/session-management) β€” Manage sessions through the client. +- [Authentication](/docs/client/authentication) β€” Use the client authentication flows. \ No newline at end of file diff --git a/docs/content/getting-started/index.md b/docs/content/getting-started/index.md index cc8c3fde..f3bc3edd 100644 --- a/docs/content/getting-started/index.md +++ b/docs/content/getting-started/index.md @@ -68,9 +68,9 @@ Where session-backed modes apply, UltimateAuth models authentication through a h ```text User - └── Root Global session-security boundary - └── Chain Device-associated lifecycle - └── Session Individual authentication instance + └── Root Global session-security boundary + └── Chain Device-associated lifecycle + └── Session Individual authentication instance ``` This structure gives the system a vocabulary for different security actions. A session can be invalidated, a device-associated chain can be terminated, and a broader security boundary can be revoked. @@ -95,7 +95,7 @@ Request This is not a claim that pipelines or orchestration are new inventions. The value is in applying them **consistently across authentication operations**, so the rules are easier to reason about, test, and extend. -For the implementation model, see [Auth Flows](../auth-flows/auth-flows.md). +For the implementation model, see [Auth Flows](/docs/auth-flows/index). ## Tenant-Aware from the Request Boundary @@ -133,7 +133,7 @@ Security is not finished when login succeeds. UltimateAuth's architecture accoun A key distinction is **logout versus revoke**. Ordinary logout ends authentication according to its scope; revocation is an irreversible invalidation of the targeted security state. Signing in again may establish *new* authentication, but it does not restore what was revoked. -Explore the details in [Login Flow](../auth-flows/login-flow.md), [Refresh Flow](../auth-flows/refresh-flow.md), and [Logout Flow](../auth-flows/logout-flow.md). +Explore the details in [Login Flow](/docs/auth-flows/login), [Refresh Flow](/docs/auth-flows/refresh), and [Logout Flow](/docs/auth-flows/logout). ## Modular and Extensible @@ -167,6 +167,6 @@ The ambition is to build a framework that developers can adopt, inspect, contrib You do not need to understand every internal component before getting started. -**Continue to Quick Start** to set up your first UltimateAuth application. Then explore [Auth Flows](../auth-flows/auth-flows.md) to understand how the pieces work together. +**Continue to Quick Start** to set up your first UltimateAuth application. Then explore [Auth Flows](/docs/auth-flows/index) to understand how the pieces work together. **The idea to remember:** UltimateAuth unifies authentication concepts without erasing their differences. One framework, a consistent lifecycle, and room for the security model your application actually needs. diff --git a/docs/content/plugin-domains/authorization-domain.md b/docs/content/plugin-domains/authorization-domain.md index 1b9e0c37..d9754b53 100644 --- a/docs/content/plugin-domains/authorization-domain.md +++ b/docs/content/plugin-domains/authorization-domain.md @@ -4,185 +4,532 @@ order: 4 group: plugin-domains --- +# Authorization & Policies -# πŸ” Authorization & Policies +Authentication establishes an identity. Authorization determines what that identity is allowed to do. -UltimateAuth provides a flexible and extensible authorization system based on: +UltimateAuth treats authorization as a separate responsibility, built around **structured actions, permissions, roles, and policies**. -- Roles -- Permissions -- Policies -- Access orchestration +Rather than relying exclusively on role names or scattered permission checks, UltimateAuth provides a consistent model for describing protected operations and evaluating access within their security context. -## 🧩 Core Concepts +The Authorization Plugin Domain extends this model with permission management, role assignments, and supporting infrastructure. -### πŸ”‘ Permissions +## Understanding Authorization -In UltimateAuth, permissions are not just arbitrary strings. +A successful authentication does not automatically grant access to protected operations. -They follow a **structured action model**. +An authenticated identity may be permitted to read its own profile while being prohibited from modifying another user's account. An administrator may have elevated permissions within one tenant without having authority over another tenant. -#### 🧩 Permission Structure +These decisions require more than knowing whether the caller is authenticated. -Permissions are built using a consistent format: +UltimateAuth separates authorization into several responsibilities: -``` +| Concept | Responsibility | +|---|---| +| **Action** | Identifies the protected operation | +| **Permission** | Represents authority to perform an action | +| **Role** | Groups permissions for assignment | +| **Policy** | Evaluates the conditions required to allow access | +| **AccessContext** | Carries the identity, target, tenant, and operation context | +| **AccessOrchestrator** | Coordinates policy evaluation and the access decision | + +These concepts work together but are not interchangeable. + +A permission can indicate that an identity is authorized to perform a class of operations. A policy may still deny a particular request because its target, tenant, or security context is invalid. + +**Authorization is therefore a decision process, not simply a permission lookup.** + +## Structured Actions + +UltimateAuth identifies protected operations through a structured action model. + +Actions use a hierarchical naming convention: + +```text resource.operation.scope ``` -or -``` + +For operations involving a more specific resource: + +```text resource.subresource.operation.scope ``` -#### βœ… Examples +Examples include: -- `users.create.admin` -- `users.profile.update.self` -- `sessions.revokechain.admin` -- `credentials.change.self` +```text +users.create.admin +users.delete.self +users.profile.update.self +credentials.change.self +sessions.revokechain.admin +``` -πŸ‘‰ This structure is not accidental β€” -it is **designed for consistency, readability, and policy evaluation**. +The structure makes the intended operation and its access boundary explicit. ---- +| Segment | Example | Meaning | +|---|---|---| +| Resource | `users` | The primary protected domain | +| Subresource | `profile` | An optional, more specific resource | +| Operation | `update` | The requested behavior | +| Scope | `self` | The intended access boundary | + +This convention supports consistent naming, grouping, permission matching, and policy evaluation. + +It also helps developers understand an operation without needing to inspect its endpoint implementation. + +### UAuthActions + +`UAuthActions` is UltimateAuth's built-in action catalog. + +It provides predefined identifiers for framework operations so that services, endpoints, and authorization logic can refer to the same action consistently. + +Examples of built-in actions include: + +```text +flows.logout.self +sessions.listchains.admin +users.delete.self +credentials.revoke.admin +authorization.roles.assign.admin +``` + +Using a shared action catalog avoids duplicating string literals throughout the framework and reduces the risk of inconsistent permission names. + +Actions are identifiers, not authorization decisions. + +Referencing an action does not automatically authorize a request. The action must be evaluated against the applicable authorization rules. + +Applications can also define actions for their own protected resources using the same naming convention. + +## Understanding Scope + +The final segment of an action describes its intended security scope. + +UltimateAuth distinguishes three principal scopes: + +| Scope | Meaning | +|---|---| +| `self` | An operation targeting the caller's own identity or resources | +| `admin` | An operation performed with elevated authority over another target | +| `anonymous` | An operation that can be initiated without an authenticated identity | + +The scope is part of the action's identity. + +### Self and Admin Are Different Permissions + +Consider: + +```text +users.delete.self +users.delete.admin +``` + +The first represents the authority to delete one's own account. + +The second represents the authority to perform an administrative deletion. + +Granting `users.delete.self` must not implicitly grant `users.delete.admin`. -### βš™οΈ Built-in Action Catalog +Likewise, an authenticated user must not gain administrative access simply by supplying another user's identifier to a self-service operation. -UltimateAuth provides a predefined action catalog. +The application must establish the actual relationship between the actor and the target. + +**The requested scope is not proof that the caller satisfies that scope.** + +A self operation requires a valid self relationship. An administrative operation requires the corresponding authority and must satisfy the applicable target and tenant policies. + +### Anonymous Does Not Mean Unrestricted + +An anonymous action is designed to be initiated without an authenticated session. + +Examples include certain login and credential recovery operations. + +However, anonymous availability does not remove the need for validation, proof requirements, abuse protection, or operation-specific security policies. + +An anonymous credential reset request, for example, must not be allowed to replace a password without completing the required recovery proof. + +## Permissions + +A permission represents authority associated with a protected action. + +In UltimateAuth, permission identifiers follow the same structured action convention. + +For example: + +```text +users.profile.update.self +``` + +A user granted this permission may be eligible to update their own profile, provided the other required policies succeed. + +Permissions do not replace those policies. + +A permission match answers whether the relevant authority is present. It does not independently establish that the target belongs to the caller, that the tenant boundary is valid, or that the account satisfies the required security conditions. + +### Hierarchical Permissions + +UltimateAuth supports hierarchical permission matching. Examples: -- `flows.logout.self` -- `sessions.listchains.admin` -- `users.delete.self` -- `credentials.revoke.admin` -- `authorization.roles.assign.admin` +| Permission | Intended coverage | +|---|---| +| `users.profile.update.self` | One specific action | +| `users.profile.*` | Actions under the profile group | +| `users.*` | Actions under the users resource | +| `*` | All actions covered by the permission matcher | -πŸ‘‰ This ensures: +This allows permissions to be granted at different levels of granularity. -- No magic strings -- Discoverable permissions -- Consistent naming across the system +An application can assign a narrowly scoped permission to ordinary users while granting broader permission groups to trusted administrative roles. -
+### Permission Normalization + +UltimateAuth provides permission normalization to reduce redundant representations. + +When a permission set fully covers a known group of actions, the corresponding permissions can be represented by a wildcard for that group. + +For example, complete coverage of a defined group may be normalized to: + +```text +users.profile.* +``` + +Normalization is intended to preserve the effective permission coverage, not expand it. + +A wildcard must not be introduced merely because several permissions share a prefix. The relevant group must actually be fully covered according to the known permission catalog. + +### Compiled Permission Sets + +Permission resolution may involve exact matches, hierarchical groups, and wildcards. + +`CompiledPermissionSet` provides an optimized representation for evaluating these permissions at runtime. + +This allows the authorization system to avoid repeatedly performing the same permission-processing work during access checks. + +The optimization does not change the authorization model: permission matching remains one part of the overall access decision. + +## Roles and Role Assignments + +Roles provide a convenient way to manage collections of permissions. + +Instead of assigning every permission directly to every user, an application can define roles corresponding to its operational responsibilities. + +For example: + +```text +SupportAgent +AccountManager +TenantAdministrator +``` -### 🧠 Scope Semantics +Each role can contain the permissions required for that responsibility. -The last part of the permission defines **scope**: +A role is not inherently a security bypass or a substitute for policy evaluation. -| Scope | Meaning | -|------------|----------------------------------| -| `self` | User acts on own resources | -| `admin` | User acts on other users | -| `anonymous`| No authentication required | +### Role Management -
+The reference authorization model supports role management and user-role assignments. -### 🌲 Wildcards & Grouping +Its responsibilities include: -Permissions support hierarchical matching: +- Defining roles and their permissions. +- Assigning roles to users. +- Managing role membership. +- Resolving effective permissions. +- Maintaining the relevant tenant boundaries. -- `users.*` β†’ all user actions -- `users.profile.*` β†’ all profile operations -- `*` β†’ full access +Roles and assignments are designed to support tenant-aware authorization. -### ⚑ Normalization +An administrative role in one tenant must not automatically imply authority in another tenant. -Permissions are automatically normalized: +### Roles Versus Permissions -- Full coverage β†’ replaced with `*` -- Full group β†’ replaced with `prefix.*` +Roles describe how authority is organized and assigned. -
+Permissions describe which operations that authority covers. -### Role +Policies determine whether the current operation is permitted under the relevant conditions. -A role is a collection of permissions. +Keeping these concepts separate allows applications to change role structures without redesigning their protected operations. + +## Authorization Policies + +Policies are the enforcement layer of UltimateAuth's authorization model. + +A policy evaluates a security requirement against the current access context. + +UltimateAuth includes policy concepts such as: + +| Policy | Responsibility | +|---|---| +| `RequireAuthenticated` | Requires an authenticated actor | +| `RequireActiveUser` | Requires the relevant active-user condition | +| `RequireSelf` | Enforces the self-access relationship | +| `RequireSystem` | Restricts operations to an appropriate system context | +| `DenyCrossTenant` | Enforces tenant isolation | +| `MustHavePermission` | Requires the applicable permission | + +These policies address different questions. + +For example, `MustHavePermission` may confirm that the actor has `users.delete.admin`, while `DenyCrossTenant` may independently prevent the operation from targeting a user in an unauthorized tenant. + +A successful permission match must not bypass another mandatory security policy. + +### Policies Are Composable + +A protected operation may require multiple conditions. + +An administrative account operation might require: + +1. An authenticated actor. +2. A valid and active identity. +3. Permission for the requested administrative action. +4. A valid target identity. +5. Compliance with tenant isolation rules. + +Authorization succeeds only when the required conditions are satisfied. + +This approach allows UltimateAuth to separate reusable security requirements from the business operation being protected. + +## AccessContext and AccessOrchestrator + +Authorization needs a trustworthy representation of the operation being evaluated. + +`AccessContext` carries the relevant information for the decision, including the actor, target, tenant context, and requested operation. + +The `AccessOrchestrator` coordinates the applicable policy evaluations and produces the authorization decision. + +Conceptually, the process is: + +```text +Authenticated identity + | + v +Requested action + | + v +Build AccessContext + | + v +Evaluate required policies + | + +---- Denied ---> Reject operation + | + v + Allowed + | + v +Execute protected operation +``` + +The context must be constructed from trusted authentication state and validated operation data. + +A caller-provided user ID, tenant ID, or scope string must not automatically be accepted as authoritative. + +This is particularly important for administrative operations and multi-tenant applications. + +### Authorization and Authentication Are Separate + +`AuthFlowContext` and `AccessContext` serve different purposes. + +`AuthFlowContext` belongs to authentication flow execution. + +`AccessContext` belongs to authorization evaluation. + +A successfully completed authentication flow may establish the identity required for an access context, but it does not replace authorization evaluation. + +See [Flow-Based Authentication](/docs/fundamentals/flow-based-authentication) and [Request Lifecycle](/docs/fundamentals/request-lifecycle). + +## Claims and Permission Resolution + +UltimateAuth can integrate authorization information with authentication claims. + +Relevant claims may include: + +```text +ClaimTypes.Role +permission +tenant +``` + +Claims allow authorization information to participate in token-based and ASP.NET Core authorization scenarios. + +However, claims are representations of authorization information, not necessarily its authoritative source. + +### Session-Based Authorization + +In session-authoritative authentication modes, authorization can rely on server-side state and the relevant runtime resolution mechanisms. + +Changes to roles, permissions, or account security state can be reflected according to the session validation and authorization configuration. + +### JWT-Based Authorization + +In JWT-based modes, roles or permissions may be included in issued tokens. + +A protected resource can evaluate those claims without contacting the authentication server on every request. + +This introduces an important tradeoff. + +**A permission removed after token issuance may remain represented in an already-issued JWT until that token expires.** + +Applications requiring immediate permission revocation must account for this behavior through their token lifetimes, refresh strategy, or additional server-side validation. + +The authorization model remains consistent, but the freshness of authorization information depends on how it is resolved. + +## Protecting Application Endpoints + +UltimateAuth's authorization architecture is not limited to its built-in identity management endpoints. + +Applications can protect their own business resources using ASP.NET Core authorization and, where integrated, UltimateAuth's action and policy model. + +### Requiring Authentication + +For a controller-based resource API, standard ASP.NET Core authorization attributes can require an authenticated caller. + +```csharp +[ApiController] +[Route("api/products")] +[Authorize] +public sealed class ProductsController : ControllerBase +{ + [HttpGet] + public IActionResult GetProducts() + { + return Ok(); + } +} +``` + +When the application is configured with the appropriate UltimateAuth authentication integration, `[Authorize]` requires a valid authenticated identity. + +This is the simplest protection boundary. + +It does not, by itself, require a particular UltimateAuth action permission. + +### Requiring a Named Policy + +ASP.NET Core also supports policy-based endpoint authorization. + +For example: + +```csharp +[Authorize(Policy = "Products.Read")] +[HttpGet] +public IActionResult GetProducts() +{ + return Ok(); +} +``` + +The named policy must be registered in the application's authorization configuration. + +A standard ASP.NET Core policy can require a particular permission claim: + +```csharp +builder.Services.AddAuthorization(options => +{ + options.AddPolicy("Products.Read", policy => + { + policy.RequireAuthenticatedUser(); + policy.RequireClaim( + "permission", + "products.read.self"); + }); +}); +``` + +This example uses ASP.NET Core's built-in claim authorization. + +It checks for the specified claim in the authenticated principal. It does not automatically execute UltimateAuth's `AccessOrchestrator` or its domain policies. + +It is suitable when the application's security model permits authorization based on the available claims. + +### Using UltimateAuth Actions for Application Resources + +Applications can define their own action identifiers using the same convention as UltimateAuth's built-in catalog. + +For example: + +```csharp +public static class ProductActions +{ + public const string ReadSelf = "products.read.self"; + public const string CreateAdmin = "products.create.admin"; + public const string DeleteAdmin = "products.delete.admin"; +} +``` -- Roles are tenant-scoped -- Roles can be dynamically updated -- Permissions are normalized internally +These constants establish consistent names for application permissions. -### UserRole +They can be used by permission management, policy registration, and endpoint authorization integrations. -Users are assigned roles: +Defining the constants alone does not register them with the authorization system or enforce them at an endpoint. -- Many-to-many relationship -- Assignment is timestamped -- Role resolution is runtime-based +For operations requiring actor-target validation, tenant isolation, or other UltimateAuth domain policies, the application must use the appropriate UltimateAuth authorization integration rather than relying solely on a permission claim. -
+The exact registration and invocation APIs depend on the configured authorization packages and hosting model. -## πŸ”„ Permission Resolution +## Authorization in Different Hosting Models -Permissions are evaluated using: +UltimateAuth supports different hosting architectures, and the enforcement boundary matters. -- Exact match -- Prefix match -- Wildcard match +### Resource APIs -CompiledPermissionSet optimizes runtime checks. +For HTTP resource APIs, authentication and authorization are normally enforced through the ASP.NET Core request pipeline. -
+Controllers and endpoints can use standard authorization attributes and policies. -## 🧠 Claims Integration +The UltimateAuth resource API integration establishes the authentication context required for protected requests. -Authorization integrates with authentication via claims: +### Integrated Server Applications -- Roles β†’ `ClaimTypes.Role` -- Permissions β†’ `permission` claim -- Tenant β†’ `tenant` +In an integrated server application, business operations may execute directly through application services without making an HTTP request. -This allows: +Such calls do not automatically pass through ASP.NET Core endpoint authorization attributes. -- Token-based authorization -- Stateless permission checks (for JWT modes) +If an operation bypasses the HTTP endpoint, authorization must be enforced at an appropriate service or application boundary. -
+This is particularly relevant for Blazor Server applications that invoke services directly. -## βš™οΈ Authorization Flow +**Protecting an HTTP controller does not automatically protect every direct call to the underlying service.** -Authorization is executed through: +Authorization must remain effective at the actual execution boundary. -πŸ‘‰ AccessOrchestrator +## Extending Authorization -Steps: +Applications may need policies beyond the framework's built-in requirements. -1. Build AccessContext -2. Execute policies -3. Allow or deny operation +Examples include: -
+- Resource ownership rules. +- Organization-specific access restrictions. +- Context-dependent administrative authority. +- Business workflow conditions. +- Additional tenant or resource boundaries. -## πŸ›‘ Policies +UltimateAuth's policy-driven architecture is intended to support such requirements without embedding them directly into the authentication engine. -Policies are the core of authorization logic. +The reference Authorization domain provides a foundation for roles, permissions, and their management. -Default policies include: +Applications can build on that foundation while keeping application-specific authorization rules close to the resources and operations they protect. -- RequireAuthenticated -- DenyCrossTenant -- RequireActiveUser -- RequireSelf -- RequireSystem -- MustHavePermission +Detailed customization, custom policy implementation, and advanced permission registration are covered separately. -
+## Architectural Perspective -## πŸ”Œ Plugin Integration +UltimateAuth's authorization model is built around a distinction: -Authorization is a plugin domain. +**Actions describe operations. Permissions represent authority. Policies enforce security requirements.** -It: +Roles make permissions manageable, while `AccessContext` and `AccessOrchestrator` provide the context and coordination needed for authorization decisions. -- Does NOT depend on other domains -- Uses contracts only -- Integrates via policies and claims +This allows applications to use consistent authorization semantics across authentication modes, hosting models, and business domains without reducing security to a collection of role checks. -## 🎯 Key Takeaways +## Next Steps -- Authorization is policy-driven -- Roles are permission containers -- Permissions support wildcard & prefix -- Policies enforce rules -- Fully extensible and replaceable +- [Users](/docs/plugin-domains/users) β€” Identity ownership, account lifecycle, and user management. +- [Credentials](/docs/plugin-domains/credentials) β€” Authentication evidence and credential security. +- [Plugin Domains](/docs/plugin-domains) β€” The broader Plugin Domain architecture. +- [Request Lifecycle](/docs/fundamentals/request-lifecycle) β€” How authentication and authorization participate in protected requests. \ No newline at end of file diff --git a/docs/content/plugin-domains/credential-domain.md b/docs/content/plugin-domains/credential-domain.md index 1f9f5813..f8a50a76 100644 --- a/docs/content/plugin-domains/credential-domain.md +++ b/docs/content/plugin-domains/credential-domain.md @@ -4,119 +4,304 @@ order: 3 group: plugin-domains --- +# Credentials Domain -# πŸ” Credentials Domain +Authentication establishes whether an entity can be trusted to act as a particular identity. A credential provides the evidence used to establish that trust. -Credentials in UltimateAuth define how a user proves their identity. +UltimateAuth treats credentials as a distinct domain rather than embedding authentication secrets directly into user records or authentication flows. -## 🧠 Core Concept +The **Credentials Plugin Domain** manages the lifecycle, verification, and security of authentication credentials while allowing the authentication engine to remain independent of a particular credential type. -Authentication is not tied to users directly. +The 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. -πŸ‘‰ It is performed through credentials. +## Understanding Credentials -
+A credential represents authentication material or a mechanism through which an identity can demonstrate possession of an accepted authentication factor. -## πŸ”‘ What is a Credential? +Credentials may take different forms: -A credential represents a secret or factor used for authentication. +| Credential mechanism | Authentication evidence | +|---|---| +| **Password** | Knowledge of a secret verified against a protected representation | +| **One-time code** | Possession of a time-limited or single-use authentication value | +| **Cryptographic credential** | Proof of control over a private key or authenticator | +| **External identity provider** | A trusted authentication assertion established through an external protocol | -Examples: +These mechanisms do not necessarily share the same storage, verification, or lifecycle requirements. -- Password -- OTP (future) -- External providers (future) +For 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. -
+UltimateAuth's credential architecture is intended to accommodate these differences through explicit credential-specific behavior. -## πŸ”’ Password Credential +**Current implementation:** 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. -The default credential type is password. +## Credentials and User Identity -A password credential contains: +A user identity and a credential are separate concepts. -- Hashed secret (never raw password) -- Security state (active, revoked, expired) -- Metadata (last used, source, etc.) +The Users domain manages identity ownership, identifiers, and account lifecycle. The Credentials domain manages the authentication material associated with an identity. -πŸ‘‰ Credentials are always stored securely and validated through hashing. +This separation means: -
+- A user can exist independently of a particular credential. +- Credential state can change without redefining the user's identity. +- An identity can conceptually be associated with multiple authentication mechanisms. +- Credential verification does not replace user lifecycle or account eligibility checks. -## βš™οΈ Credential Validation +A username or email address is generally an **identifier**, not a credential. -Credential validation is handled by a validator: +An 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. -- Verifies secret using hashing -- Checks credential usability (revoked, expired, etc.) +For a password-based login, these responsibilities work together: -πŸ‘‰ Validation is isolated from business logic. +1. The submitted identifier is resolved to an identity. +2. The applicable password credential is located. +3. The submitted secret is verified. +4. Authentication policies and relevant security conditions are evaluated. +5. The authentication engine establishes authentication state if the flow succeeds. -
+The credential domain participates in establishing identity proof, but it does not independently determine the entire authentication outcome. -## πŸ”— Integration with Users +## Responsibilities Across the Framework -Credentials are NOT created directly inside user logic. +Credential support spans several architectural layers, each with a different responsibility. -Instead: +| Layer | Responsibility | +|---|---| +| **Core** | Foundational authentication contracts and security concepts | +| **Server** | Authentication flow execution, orchestration, and authentication-state management | +| **Credentials Bridge** | Contracts connecting credential capabilities to the authentication server | +| **Credentials Contracts** | Shared credential operation models and results | +| **Credentials Reference Implementation** | Credential management, validation, recovery, and lifecycle behavior | +| **Persistence** | Storage of credential records and related security state | -πŸ‘‰ They are integrated via lifecycle hooks +The distinction is between **using credentials to authenticate** and **managing credentials as domain resources**. -Example: +The authentication engine coordinates authentication flows. The Credentials domain owns the rules governing credential records, their usability, and their lifecycle. -- When a user is created β†’ password credential may be created -- When a user is deleted β†’ credentials are removed +An application may integrate a custom credential system through the relevant authentication contracts without necessarily adopting the reference credential management implementation. -πŸ‘‰ This keeps domains decoupled. +## Password Credentials -
+Password is the first supported credential mechanism in UltimateAuth. -## πŸ”„ Credential Lifecycle +A password credential represents a password-based authentication factor associated with an identity. -Credentials support: +The reference implementation separates the submitted secret from its protected representation and the credential's operational state. -- Creation -- Secret change -- Revocation -- Expiration -- Deletion +### Secret Protection -
+Passwords must not be stored as plaintext or returned through credential management responses. -## πŸ” Security Behavior +The password verification process operates against a protected password representation using the configured password hashing infrastructure. -Credential changes trigger security actions: +Credential management APIs should expose only the information required for their intended operations, not the underlying secret. -- Changing password revokes sessions -- Reset flows require verification tokens -- Invalid attempts are tracked +This separation also allows password verification and password management to evolve without changing the user identity model. -πŸ‘‰ Credentials are tightly coupled with security. +### Credential Security State -
+A credential is not necessarily usable merely because a matching secret has been supplied. -## πŸ”‘ Reset Flow +Credential validation must also account for relevant lifecycle and security conditions, such as: -Password reset is a multi-step process: +- Whether the credential is active or revoked. +- Whether an applicable expiration condition has been reached. +- Whether the credential belongs to the intended identity. +- Whether the requested operation is permitted. +- Whether the credential is eligible for the authentication mechanism being executed. -1. Begin reset (generate token or code) -2. Validate token -3. Apply new secret +Secret verification and credential usability are related but distinct decisions. -πŸ‘‰ Reset flow is protected against enumeration and abuse. +A correctly supplied password must not make a revoked or otherwise unusable credential valid. -
+## Credential Validation -## 🧠 Mental Model +UltimateAuth separates credential-specific verification from the broader authentication flow. -Users define identity. +For password authentication, credential validation includes evaluating the supplied secret against the stored protected representation and enforcing the relevant credential conditions. -Credentials define authentication. +The authentication server then uses the result within its authentication flow. -## 🎯 Summary +This boundary matters because credential verification is not equivalent to session creation or token issuance. -- Credentials handle authentication secrets -- Password is default but extensible -- Integrated via lifecycle hooks -- Strong security guarantees -- Fully extensible for new credential types +A 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. + +The same architectural distinction applies to other credential mechanisms. + +A 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. + +## Credential Lifecycle + +Credentials are managed resources with their own lifecycle. + +The reference domain provides operations around credential creation, secret changes, revocation, and recovery. + +| Operation | Purpose | +|---|---| +| **Add** | Register a credential for an identity | +| **Change secret** | Replace authentication material through an authorized operation | +| **Revoke** | Make a credential unusable | +| **Begin reset** | Initiate a controlled credential recovery process | +| **Complete reset** | Replace a secret after satisfying recovery requirements | + +These operations have different security requirements. + +Changing 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. + +UltimateAuth treats these distinctions explicitly rather than assuming that every credential mutation has the same security effect. + +## Credential Ownership and Operation Scope + +Credential management operations must be evaluated in the context of the actor, the target identity, and the requested operation. + +UltimateAuth distinguishes between self-service, administrative, and anonymous recovery scenarios. + +### Self-Service Operations + +An authenticated user may manage credentials associated with their own identity, subject to the applicable permissions and validation rules. + +Changing a secret may require proving knowledge of the existing secret or satisfying another supported security requirement. + +### Administrative Operations + +Administrative credential management is performed against an explicitly identified target. + +Administrative authority does not eliminate the requirement to preserve target identity consistency. + +For example, a credential recovery operation must not modify a different user's credential merely because a supplied identifier resolves to another account. + +### Anonymous Recovery + +Anonymous recovery allows an identity to regain access without an existing authenticated session. + +Because the actor is not authenticated, the recovery process must establish sufficient proof before allowing credential modification. + +These operation scopes are security boundaries, not merely different API routes. + +## Credential Recovery + +Credential recovery is one of the most security-sensitive responsibilities of the Credentials domain. + +The password reset process is designed as a controlled, multi-stage operation rather than an unrestricted secret update. + +### Beginning Recovery + +A recovery request identifies the intended account and initiates a proof-based process. + +Depending on the supported recovery configuration, proof may take the form of a token or code delivered through an appropriate notification mechanism. + +The proof must be bound to the intended recovery context and subject to server-controlled validity rules. + +Recovery material must not be exposed to an anonymous caller simply because that caller initiated the request. + +### Completing Recovery + +Completing recovery requires validating the supplied proof and its associated security conditions before applying a new secret. + +The reference recovery model accounts for: + +- Proof validity and expiration. +- Single-use recovery material. +- Consistency between the recovery target and intended identity. +- Rejection of invalid or previously consumed proof. +- Protection against unauthorized credential replacement. +- Security-state updates following successful recovery. + +A completed recovery must not leave the same proof available for another successful reset. + +### Enumeration Resistance + +Anonymous recovery endpoints must avoid unnecessarily revealing whether an identifier belongs to an existing account. + +UltimateAuth separates externally observable recovery responses from the internal decisions required to locate and manage the account. + +This reduces account enumeration opportunities while preserving the ability to enforce recovery rules. + +Recovery behavior should also be combined with appropriate abuse protection and operational monitoring. + +## Credentials and Authentication Sessions + +Changing or recovering a credential may require action against authentication sessions that were established before the change. + +However, **credential lifecycle and session lifecycle are separate responsibilities**. + +UltimateAuth supports security policies that determine how credential changes and resets affect existing sessions. + +The available policy model includes: + +| Policy | Intended behavior | +|---|---| +| `KeepAll` | Preserve existing sessions | +| `KeepCurrent` | Preserve the current session while revoking other applicable sessions | +| `RevokeAll` | Revoke all applicable sessions | + +The appropriate policy depends on the operation and the application's security requirements. + +A password reset performed through anonymous recovery may warrant different treatment from an authenticated password change. + +Enforcement also depends on the authentication mode. + +In 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. + +Credential changes therefore initiate the applicable security response; they do not imply identical immediate token invalidation across every mode. + +See [Authentication Modes](/docs/fundamentals/authentication-modes) for the underlying differences. + +## Integration with the Users Domain + +The Credentials domain does not need to own user records to manage authentication material associated with an identity. + +User creation, deletion, or other lifecycle changes may require corresponding credential operations. + +UltimateAuth supports coordination through integration contracts rather than requiring the Users and Credentials implementations to depend directly on one another. + +For example, an application may coordinate initial credential enrollment with user creation or perform credential cleanup as part of account removal. + +These behaviors belong to explicit integration workflows. + +Creating 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. + +This distinction keeps identity lifecycle and credential lifecycle independently understandable. + +## Extending Credential Capabilities + +Credential mechanisms differ substantially in how they establish trust. + +UltimateAuth's architecture is intended to support additional mechanisms by separating credential-specific verification and management from authentication flow orchestration. + +Such extensions may require their own: + +- Credential representation and metadata. +- Enrollment and verification procedures. +- Security and expiration rules. +- Recovery or revocation behavior. +- Persistence and integration contracts. + +Not every credential mechanism needs to implement password-style secret changes or recovery. + +The goal is to preserve consistent authentication boundaries while allowing each credential mechanism to enforce its own security requirements. + +For most applications using password authentication, the reference implementation provides the recommended starting point. + +Applications with specialized requirements can customize supported services and integrations or provide alternative implementations through the relevant contracts. + +Detailed extension and replacement strategies belong in the dedicated customization documentation. + +## Architectural Perspective + +The Credentials domain establishes an important distinction within UltimateAuth: + +**A user identifies the account. A credential provides authentication evidence. The authentication engine decides how that evidence establishes authentication state.** + +Keeping these responsibilities separate allows the framework to support identity management without tying authentication to one user entity, one secret format, or one credential mechanism. + +The 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. + +## Next Steps + +- [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. + +For the authentication flow that consumes credential verification results, see [Flow-Based Authentication](/docs/fundamentals/flow-based-authentication). \ No newline at end of file diff --git a/docs/content/plugin-domains/index.md b/docs/content/plugin-domains/index.md index 14950f03..d29ba94c 100644 --- a/docs/content/plugin-domains/index.md +++ b/docs/content/plugin-domains/index.md @@ -4,150 +4,119 @@ order: 1 group: plugin-domains --- +# Plugin Domains -# 🧩 Plugin Domains +UltimateAuth separates **authentication infrastructure** from the domain capabilities commonly required by identity and access management systems. -Authentication alone is not enough. +The authentication engine is responsible for establishing and validating authentication state. Managing users, credentials, and authorization policies introduces additional responsibilities that do not inherently belong to that engine. -Real-world systems also require: +UltimateAuth organizes these capabilities into **Plugin Domains**: modular, independently structured domains that extend the framework beyond authentication without making their business logic part of the authentication core. -- User management -- Credential handling -- Authorization rules +Plugin Domains are an architectural concept specific to UltimateAuth. They are not simply runtime plugins or optional features within a monolithic identity system. -πŸ‘‰ UltimateAuth provides these as **plugin domains** +## Authentication Core and Plugin Domains -## 🧠 What Is a Plugin Domain? +The distinction is based on responsibility rather than importance. -A plugin domain is a **modular business layer** built on top of UltimateAuth. +**Authentication Core** provides the infrastructure for authentication flows, authentication modes, sessions, tokens, and runtime security boundaries. -πŸ‘‰ Core handles authentication -πŸ‘‰ Plugin domains handle identity and access logic +**Plugin Domains** provide the identity and access management capabilities built around that infrastructure. -
+The framework currently organizes these responsibilities into three primary domains: -## πŸ— Architecture +| Domain | Responsibility | +|---|---| +| **Users** | User lifecycle, identifiers, profiles, and account management | +| **Credentials** | Credential registration, management, verification, and recovery | +| **Authorization** | Permissions, roles, policies, and access management | -Each plugin domain is composed of multiple layers: +These domains are not universally required for every authentication scenario. An application may already have its own identity infrastructure or may need only a subset of UltimateAuth's domain capabilities. -### πŸ”Ή Bridge Package +However, together they provide the foundation for using UltimateAuth as a broader identity and access management platform rather than only an authentication engine. -Defines the server needed bridge interfaces: +## Architectural Boundaries -πŸ‘‰ This package provides required minimal info for server package. +Each Plugin Domain owns a distinct area of business logic. -### πŸ”Ή Contracts Package +A domain should not need to understand the internal implementation of another domain to perform its responsibilities. -Shared models between: +This separation establishes several architectural properties: -- Server -- Client +- **Independent domain boundaries:** Each domain defines and maintains its own contracts and behavior. +- **Explicit integration:** Cross-domain interactions occur through defined abstractions and integration points. +- **Replaceable infrastructure:** Storage implementations and supporting infrastructure can be substituted without changing the authentication engine. +- **Controlled extensibility:** Applications can customize domain behavior while preserving established framework contracts. -πŸ‘‰ Includes DTOs, requests, responses +Isolation does not mean that domains never interact. Identity management operations frequently involve multiple domains, and those interactions must remain coordinated. -### πŸ”Ή Reference Implementation +UltimateAuth addresses this through explicit integration contracts rather than direct dependencies between domain implementations. -Provides default behavior: +## Domain Package Structure -- Application services -- Store interfaces -- Default implementations +Plugin Domains follow a layered package structure that separates integration contracts, public models, domain behavior, and persistence. -πŸ‘‰ Acts as a production-ready baseline +| Layer | Purpose | +|---|---| +| **Bridge** | Minimal abstractions required for integration with UltimateAuth Server | +| **Contracts** | Shared requests, responses, and data transfer models | +| **Reference Implementation** | Default application services, domain behavior, and storage abstractions | +| **Persistence** | Concrete storage implementations, including InMemory and Entity Framework Core | -### πŸ”Ή Persistence Layer +This structure keeps the authentication server independent of the full internal implementation of each domain. -Provides storage implementations: +It also allows applications to use the provided implementations while retaining control over infrastructure and customization. -- InMemory -- Entity Framework Core +The reference implementations are intended to be practical foundations for real applications, not merely illustrative examples. -πŸ‘‰ Additional providers (Redis, etc.) can be added +## Cross-Domain Integration -
+Some identity operations naturally cross domain boundaries. -## πŸ”„ Extensibility Model +For example, creating a user may need to initiate credential-related behavior. That coordination should not require the Users domain to reference the Credentials domain directly. -Plugin domains are designed to be: +Instead, UltimateAuth uses integration abstractions and lifecycle hooks to connect independently implemented responsibilities. -- Replaceable -- Extendable -- Composable +`IUserLifecycleIntegration` is an example of such an integration point. -πŸ‘‰ You can implement your own persistence -πŸ‘‰ You can extend behavior +This approach allows domain-specific behavior to remain within its owning domain while supporting coordinated application workflows. -
+Integration hooks are explicit extension boundaries, not a mechanism for bypassing domain ownership or security rules. -## ⚠️ Recommended Approach +## Extending Plugin Domains -In most cases: +UltimateAuth provides reference implementations so applications do not need to rebuild common identity management capabilities from scratch. -πŸ‘‰ You should NOT replace a plugin domain entirely +For most applications, the recommended approach is to **start with the provided domain implementations and customize only what is necessary**. -Instead: +Depending on the domain and its supported extension contracts, customization may include: -- Use provided implementations -- Extend via interfaces -- Customize behavior where needed +- Replacing persistence implementations. +- Providing application-specific services. +- Extending supported lifecycle integrations. +- Integrating existing identity or access management infrastructure. -πŸ‘‰ This ensures compatibility with the framework +Replacing an entire domain is possible as an architectural objective, but doing so requires preserving the contracts expected by the rest of the system. -
+Modularity provides flexibility; it does not eliminate the need for compatible behavior. -## πŸ”Œ Domain Isolation +## Choosing the Right Capabilities -Plugin domains are designed to be **fully isolated** from each other. +Plugin Domains are designed to be composed according to application requirements. -πŸ‘‰ A plugin domain does NOT reference another plugin domain -πŸ‘‰ There are no direct dependencies between domains +An application focused on authentication infrastructure may rely primarily on the Core and Server capabilities. -### 🧠 Why? +An application requiring complete identity management can incorporate Users, Credentials, and Authorization together. -This ensures: +The important distinction is that **the authentication engine does not need to become a monolithic identity management system to support either scenario**. -- Loose coupling -- Independent evolution -- Replaceability -- Clear boundaries +This separation allows UltimateAuth to serve both focused authentication use cases and more comprehensive identity platforms while maintaining clear architectural boundaries. -## πŸ”„ Communication via Hooks +## Explore the Domains -Plugin domains communicate **only through integration hooks**. +Continue with the individual domains to understand their responsibilities, public contracts, and extension points. -For example: +- [Users](/docs/plugin-domains/users) β€” User identities, identifiers, and lifecycle management. +- [Credentials](/docs/plugin-domains/credentials) β€” Credential management, verification, and recovery. +- [Authorization](/docs/plugin-domains/authorization) β€” Roles, permissions, policies, and access control. -- User domain triggers β†’ `OnUserCreated` -- Credential domain listens β†’ creates password credential - -πŸ‘‰ This is implemented via abstractions such as: - -- `IUserLifecycleIntegration` -- domain events / integration points - -
- -## 🧠 Mental Model - -If you remember one thing: - -πŸ‘‰ Core = authentication engine -πŸ‘‰ Plugin domains = business logic - -## 🎯 Why This Matters - -This architecture allows UltimateAuth to: - -- Stay modular -- Support multiple domains -- Enable enterprise customization -- Avoid monolithic identity systems - -πŸ‘‰ You don’t build everything from scratch -πŸ‘‰ You assemble what you need - -## ➑️ Next Step - -- Manage users β†’ Users -- Handle credentials β†’ Credentials -- Control access β†’ Authorization +For the underlying authentication architecture, see [Runtime Architecture](/docs/fundamentals/runtime-architecture) and [Authentication Model](/docs/fundamentals/authentication-model). \ No newline at end of file diff --git a/docs/content/plugin-domains/policies.md b/docs/content/plugin-domains/policies.md index b688e5b9..d01771c4 100644 --- a/docs/content/plugin-domains/policies.md +++ b/docs/content/plugin-domains/policies.md @@ -1,134 +1,397 @@ --- -title: Policies +title: Policies & Access Control order: 5 group: plugin-domains --- +# Policies & Access Control -# πŸ›‘ Policies & Access Control +Authorization is not simply a question of whether an identity possesses a permission. -UltimateAuth uses a **policy-driven authorization model**. +A protected operation may involve an authenticated actor, a target identity, tenant boundaries, resource ownership, and security conditions that cannot be represented by a permission alone. -Policies are not simple checks β€” -they are **composable decision units** evaluated at runtime. +UltimateAuth addresses these requirements through a **policy-driven access control pipeline**. -## 🧠 Mental Model +Policies are independent decision units evaluated within a shared access context. The authorization orchestrator coordinates those decisions and determines whether the requested operation can proceed. -Authorization in UltimateAuth is: +This page explains how that process works. -πŸ‘‰ Context-based -πŸ‘‰ Policy-driven -πŸ‘‰ Orchestrated +For actions, permissions, roles, and scope conventions, see [Authorization](/docs/plugin-domains/authorization). -### Flow +## The Access Decision Model -1. Build `AccessContext` -2. Resolve policies -3. Execute authority -4. Allow / Deny / Reauth +UltimateAuth evaluates authorization using three primary elements: -
+| Element | Responsibility | +|---|---| +| **AccessContext** | Describes the actor, target, operation, and relevant security context | +| **Policies** | Evaluate individual authorization requirements | +| **AccessOrchestrator** | Coordinates policy resolution, evaluation, and execution | -## βš™οΈ AccessContext +These elements separate the description of an operation from the rules used to authorize it. -Every authorization decision is based on: +A permission may grant authority to perform an action, but policies determine whether that authority is valid in the current context. -- Actor (who is calling) -- Target (what is being accessed) -- Action (what is being done) -- Tenant -- Claims / permissions +For example, a caller may possess an administrative permission while attempting to access a resource belonging to another tenant. -
+The permission may match, but the operation must still satisfy tenant isolation requirements. -## πŸ”Œ Policy Resolution +## AccessContext -Policies are resolved using: +`AccessContext` represents the security context of an authorization operation. -- Action prefix matching -- Runtime filtering (`AppliesTo`) +It provides the information needed to evaluate access without requiring each policy to reconstruct the request independently. -Example: +Conceptually, the context includes: -- `users.create.admin` -- `users.*` -- `authorization.roles.*` +| Component | Purpose | +|---|---| +| **Actor** | The identity or system principal performing the operation | +| **Target** | The identity or resource affected by the operation | +| **Action** | The protected operation being requested | +| **Tenant context** | The tenant boundaries relevant to the actor and target | +| **Authorization information** | Relevant claims, permissions, and security state | -
+The context is more than a collection of request parameters. -## 🧩 Policy Types +It establishes the relationship between the authenticated actor and the operation being performed. + +### Actor and Target Are Different Concepts + +Consider an administrative operation: + +```text +users.delete.admin +``` + +The actor is the administrator requesting the deletion. + +The target is the user account being deleted. + +These identities must remain distinguishable throughout authorization and execution. + +For a self-service operation: + +```text +users.delete.self +``` + +The actor and target are expected to represent the same identity. + +That relationship must be established from trusted context, not assumed from the action suffix. + +### Trusted Context Construction + +Authorization decisions are only as reliable as the information supplied to them. + +An endpoint may receive a target user ID or tenant ID from the caller, but these values must not automatically become trusted authorization facts. + +The authentication infrastructure establishes the actor's identity. The application or domain operation resolves the intended target. The authorization context brings those elements together. + +Policies then evaluate the resulting relationships. + +This distinction is essential for preventing cross-user and cross-tenant access. + +## Policies as Decision Units + +A policy evaluates a specific security requirement. + +Rather than combining every authorization rule into one large conditional expression, UltimateAuth separates requirements into reusable policies. + +Examples include: + +- Requiring an authenticated actor. +- Requiring an active user. +- Enforcing tenant isolation. +- Verifying self-access relationships. +- Requiring an appropriate permission. +- Restricting an operation to a system context. + +Each policy evaluates its own responsibility within the available access context. + +A policy should not need to understand every other policy to make its decision. + +This allows authorization rules to be composed without losing their individual meaning. + +## Policy Resolution + +Not every policy is relevant to every operation. + +UltimateAuth supports policy resolution based on action matching and runtime applicability. + +### Action-Based Matching + +Policies can be associated with action patterns. + +For example: + +```text +users.create.admin +users.* +authorization.roles.* +``` + +An action-specific policy can target one operation, while a broader pattern can apply to a related group of operations. + +This allows common rules to be shared across a domain without duplicating policy registration for every action. + +### Runtime Applicability + +Action matching alone may not provide enough information to determine whether a policy should execute. + +A policy can also evaluate whether it applies to the current context through `AppliesTo`. + +This enables context-dependent policy selection. + +For example, a policy may apply only to administrative operations that modify account state, rather than every administrative read operation. + +**Policy resolution determines which rules participate in the decision. Policy evaluation determines whether those rules permit the operation.** + +Keeping these stages separate makes authorization behavior easier to reason about. + +## Policy Categories + +UltimateAuth distinguishes between security requirements that must be enforced broadly and policies selected for particular operations. + +### Invariants + +Invariants represent fundamental security rules that must not be bypassed by ordinary permission grants or application-level authorization decisions. + +They are evaluated before the remaining policy stages. + +An invariant is not equivalent to a role permission or an optional business rule. + +Its purpose is to preserve a security condition that must hold regardless of the requested operation's ordinary authorization configuration. ### Global Policies -Always evaluated: +Global policies apply broadly across protected operations. + +They provide common security requirements that should not need to be registered independently for every action. + +Examples of such requirements include authentication and tenant isolation. -- RequireAuthenticated -- DenyCrossTenant +Their applicability may still depend on the operation's context. An intentionally anonymous action, for example, cannot be treated identically to an authenticated administrative operation. ### Runtime Policies -Resolved dynamically: +Runtime policies are selected and evaluated according to the requested action and current access context. -- RequireActiveUser -- MustHavePermission -- RequireSelf +They support requirements such as: -### Invariants +- Permission checks. +- Self-access validation. +- Active-user requirements. +- Operation-specific restrictions. -Executed first: +This separation allows the authorization system to maintain mandatory security boundaries while remaining extensible for individual domains and applications. -- Cannot be bypassed -- Hard security rules +## Policy Evaluation Order -
+UltimateAuth's authorization pipeline follows a defined evaluation order: -## βš–οΈ Policy Evaluation +1. **Invariants** +2. **Global policies** +3. **Runtime policies** -Evaluation order: +This order establishes a clear security hierarchy. -1. Invariants -2. Global policies -3. Runtime policies +A runtime permission grant must not override an invariant that has already rejected the operation. -πŸ‘‰ First deny wins -πŸ‘‰ Allow means β€œno objection” -πŸ‘‰ Reauth can be requested +Likewise, satisfying an action-specific policy does not eliminate the need to satisfy applicable global requirements. -
+### Deny Takes Precedence -## πŸ” Example Policy +The decision model is deny-oriented. -### Deny Admin Self Modification +When a required policy denies an operation, a later policy cannot convert that denial into an authorization grant. -- Blocks admin modifying own account -- Applies only to `.admin` actions -- Ignores read operations +This prevents authorization from depending on the accidental presence of a permissive policy. -### Require Active User +### Allow Is Not a Universal Grant -- Ensures user exists -- Ensures user is active -- Skips anonymous actions +A policy returning an allow result means that its own requirement has been satisfied or that it has no objection to the operation. -
+It does not mean that all other requirements have been satisfied. -## πŸš€ Access Orchestrator +The overall decision is produced by the authorization pipeline, not by one successful policy in isolation. -The orchestrator is the entry point: +### Reauthentication -- Enriches context (claims, permissions) -- Resolves policies -- Executes authority -- Runs command if allowed +Some authorization decisions may require additional authentication rather than producing an ordinary allow or deny outcome. -## 🎯 Key Principles +UltimateAuth's decision model includes the ability to represent a reauthentication requirement. -- Policies are composable -- Authorization is deterministic -- No hidden magic -- Fully extensible +This allows authorization to distinguish between insufficient authority and situations where the actor may need to establish a stronger or more recent authentication state. ---- +The exact handling of such a result depends on the surrounding authentication and application flow. + +## AccessOrchestrator + +`AccessOrchestrator` coordinates the authorization process. + +Its responsibilities include preparing the effective access context, resolving applicable policies, evaluating the required security conditions, and coordinating execution when access is permitted. + +The conceptual pipeline is: + +```text +Requested operation + | + v +Build AccessContext + | + v +Resolve authorization information + | + v +Evaluate invariants + | + v +Evaluate global policies + | + v +Resolve applicable runtime policies + | + v +Evaluate runtime policies + | + +---- Deny ------> Reject operation + | + +---- Reauth ----> Require reauthentication + | + v + Allow + | + v +Execute authorized operation +``` + +This is a conceptual representation of the access decision pipeline, not a substitute for the concrete orchestration API. + +The orchestrator centralizes authorization coordination so that individual application services do not need to reproduce the complete evaluation process. + +## Authorization Before Execution + +Authorization should be enforced before a protected operation performs security-sensitive changes. + +Consider an administrative credential operation. + +The system may need to establish: + +1. Who is performing the operation. +2. Which account is being targeted. +3. Whether the actor possesses the required administrative permission. +4. Whether the target belongs to an allowed tenant. +5. Whether additional operation-specific restrictions apply. + +Only after the applicable requirements succeed should the protected mutation proceed. + +This separation is particularly important for operations that affect credentials, account status, roles, or sessions. + +A rejected authorization decision must not leave behind a partially completed protected operation. + +## Example: Preventing Administrative Self-Modification + +Some administrative operations may intentionally prohibit an administrator from modifying their own account through an administrative route. + +This is different from ordinary self-service account management. + +A policy enforcing this restriction could consider: + +- Whether the requested action has administrative scope. +- Whether the operation modifies security-sensitive account state. +- Whether the actor and target represent the same identity. + +If the policy applies and the actor is also the target, it denies the operation. + +The purpose is to prevent an administrative capability from bypassing the separate rules governing self-service operations. + +Such a policy illustrates why permission checks alone are insufficient. + +An administrator may possess the required action permission while still being prohibited from performing the operation against a particular target. + +## Example: Requiring an Active User + +An active-user policy evaluates whether the relevant identity satisfies the account state requirements for an operation. + +An authenticated principal may exist even after the corresponding user account has been suspended or otherwise made ineligible. + +The authorization system therefore needs a way to enforce account eligibility independently of the original authentication event. + +The policy can evaluate the relevant user state and deny operations that require an active account. + +Anonymous operations may follow different applicability rules because they do not necessarily have an authenticated user identity. + +The exact enforcement behavior also depends on how current user security state is obtained. + +## Policy Decisions and Authentication Modes + +The policy model is conceptually independent of the authentication mode, but the freshness of its input data matters. + +In session-authoritative modes, server-side state may be consulted as part of protected operations. + +In JWT-based modes, some authorization information may originate from claims issued earlier. + +This creates a distinction between: + +- Evaluating a policy against current authoritative data. +- Evaluating a policy against previously issued authorization claims. + +For example, a permission removed after token issuance may still be present in a locally validated JWT until that token expires. + +A policy can evaluate its inputs correctly while those inputs reflect an earlier authorization state. + +Applications must account for this when choosing token lifetimes, permission resolution strategies, and security requirements. + +See [Authentication Modes](/docs/fundamentals/authentication-modes). + +## Application-Level Access Control + +UltimateAuth's built-in authorization rules protect framework operations, but applications also have their own resources and business requirements. + +A business application may need policies for: + +- Editing resources owned by a particular user. +- Restricting operations to a tenant. +- Requiring elevated authority for administrative changes. +- Preventing sensitive changes under particular account conditions. + +These requirements can be modeled as application-specific authorization policies. + +The recommended architectural approach is to keep security decisions at a boundary that cannot be bypassed by alternative execution paths. + +For HTTP endpoints, ASP.NET Core authorization can provide the request-level enforcement boundary. + +For direct application service calls, including in-process operations, authorization must also be enforced where the protected operation actually executes. + +The presence of an `[Authorize]` attribute on a controller does not automatically protect a service invoked directly from another part of the application. + +## Extending the Policy Pipeline + +UltimateAuth's policy model is designed to accommodate application-specific requirements without modifying the authentication engine. + +Custom policies can introduce additional authorization conditions while participating in the established access decision process. + +Such policies should preserve the framework's security boundaries: + +- Do not treat untrusted request values as established identity facts. +- Do not bypass mandatory invariants. +- Do not use broad permission grants to override target or tenant restrictions. +- Keep policy applicability and evaluation behavior explicit. + +Detailed custom policy implementation and registration examples belong in [How-to Guides](/docs/how-to), where they can be presented alongside complete application code. + +## Architectural Perspective + +UltimateAuth authorization is a coordinated evaluation of security requirements. + +`AccessContext` describes the operation. Policy resolution identifies the relevant requirements. Policy evaluation determines whether those requirements are satisfied. `AccessOrchestrator` coordinates the process and its execution boundary. + +The result is an authorization model that is **context-aware, policy-driven, and extensible without making individual permissions responsible for every security decision**. + +## Next Steps -πŸ‘‰ Authorization is not a single check -πŸ‘‰ It is a **pipeline of decisions** +- [Authorization](/docs/plugin-domains/authorization) β€” Actions, permissions, roles, and scope semantics. +- [Users](/docs/plugin-domains/users) β€” Account lifecycle and identity state. +- [Credentials](/docs/plugin-domains/credentials) β€” Credential ownership and security operations. +- [Request Lifecycle](/docs/fundamentals/request-lifecycle) β€” Authorization within protected requests. \ No newline at end of file diff --git a/docs/content/plugin-domains/users-domain.md b/docs/content/plugin-domains/users-domain.md index c7b27935..f0dcbf25 100644 --- a/docs/content/plugin-domains/users-domain.md +++ b/docs/content/plugin-domains/users-domain.md @@ -4,118 +4,254 @@ order: 2 group: plugin-domains --- +# Users Domain -# πŸ‘€ Users Domain +A user is more than a database record, an email address, or a profile. -Users in UltimateAuth are not a single entity. +In UltimateAuth, user identity is modeled through a separation of **authentication identity, account lifecycle, identifiers, and application-specific user information**. -Instead, they are composed of multiple parts that together define identity. +This distinction allows the authentication engine to operate independently of a particular user entity or persistence model, while the Users Plugin Domain provides the capabilities needed to manage identities throughout their lifecycle. -## 🧠 Core Concept +The Users domain builds on the authentication infrastructure provided by UltimateAuth Core and Server. It does not redefine how authentication works; it defines how user identities are represented, maintained, and integrated with that infrastructure. -A user is represented by three main components: +## Understanding User Identity -- Lifecycle (security & state) -- Identifiers (login surface) -- Profile (user data) +UltimateAuth distinguishes between three aspects of a user: -
+| Component | Responsibility | +|---|---| +| **Lifecycle** | Account existence, status, and security-relevant state | +| **Identifiers** | Values through which a user can be identified or discovered | +| **Profile** | Application-specific information associated with the user | -## πŸ” Lifecycle (Security Anchor) +These components represent different responsibilities and should not be treated as interchangeable. -Lifecycle defines: +For example, changing a display name should not affect authentication state. Verifying an email address should not implicitly change account status. Suspending an account should not require deleting its identifiers or profile. -- When a user created -- Whether a user is active, suspended, or deleted -- Security version (used for invalidating sessions/tokens) +Separating these concerns makes identity operations more predictable and allows security-sensitive changes to be handled explicitly. -πŸ‘‰ This is the core of authentication security. +## User Identity in Core and Server -
+UltimateAuth does not require applications to adopt the Users Plugin Domain simply to authenticate users. -## πŸ”‘ Identifiers (Login System) +Core and Server provide the foundational authentication infrastructure, including the contracts and runtime mechanisms needed to associate authentication operations with user identities. -Identifiers represent how a user logs in: +At this level, a user is primarily an **authentication subject**. -- Email -- Username -- Phone +The authentication system needs to establish which identity is involved in an operation, evaluate the relevant security conditions, and maintain the appropriate authentication state. -Each identifier has: +It does not inherently need to know the user's display name, profile fields, or how the application's user records are stored. -- Normalized value -- Primary flag -- Verification state +This separation is intentional: -### ⭐ Login Identifiers +- **Core** defines foundational authentication concepts, contracts, and security semantics. +- **Server** executes authentication flows and enforces the relevant runtime security boundaries. +- **Users** provides higher-level identity management capabilities through a dedicated Plugin Domain. -Not all identifiers are used for login. +An application with an existing identity system can integrate that system with UltimateAuth's authentication infrastructure without necessarily adopting the reference user model. -πŸ‘‰ Only **primary identifiers** are considered login identifiers. +The authentication identity is therefore a framework-level concept, while the complete user management model belongs to the application or the Users domain. -#### βš™οΈ Configurable Behavior +## What the Users Domain Adds -Developers can control: +The Users Plugin Domain provides a structured approach to managing user identities beyond the minimum requirements of authentication. -- Which identifier types are allowed for login -- Whether email/phone must be verified -- Whether multiple identifiers are allowed -- Global uniqueness rules +Its responsibilities include: -#### πŸ”Œ Custom Login Logic +- Creating and managing user accounts. +- Maintaining account lifecycle and status. +- Registering and managing user identifiers. +- Enforcing identifier ownership and verification rules. +- Applying user management validation and access boundaries. +- Coordinating security-relevant changes with authentication infrastructure. +- Exposing user management operations through application services and shared contracts. -UltimateAuth allows custom login identifier resolution. +These capabilities are distinct from credential management. -You can: +The Users domain determines **which identity an identifier belongs to** and maintains the relevant user state. The Credentials domain manages the authentication credentials associated with identities. -- Add custom identifier types -- Override resolution logic -- Implement your own resolver +For example, an email address is a user identifier; a password is a credential. They may participate in the same login flow, but they have different ownership and lifecycle rules. -πŸ‘‰ This means login is fully extensible. +## Account Lifecycle -
+The lifecycle represents the existence and security-relevant state of a user account. -## 🧾 Profile (User Data) +In the reference model, lifecycle information includes account creation, account status, and security state used to coordinate authentication-related changes. -Profile contains non-auth data: +Account status may represent conditions such as active, suspended, or deleted, according to the supported lifecycle model. -- Name -- Bio -- Localization -- Metadata +These states are not merely descriptive profile properties. They can influence whether an account is eligible to participate in authentication and other protected operations. -πŸ‘‰ This is not used for authentication. +### Security State and Session Invalidation -
+User lifecycle changes may require existing authentication state to be invalidated. -## βš™οΈ Application Service +UltimateAuth separates the decision to change a user's security state from the mechanisms used to enforce that change. -User operations are handled by an orchestration layer. +Depending on the configured authentication mode, enforcement may involve server-side session invalidation, token lifecycle rules, or other supported security mechanisms. -It is responsible for: +This distinction matters because not every authentication mode can invalidate an already-issued credential immediately. -- Creating users -- Managing identifiers -- Applying validation rules -- Triggering integrations -- Revoking sessions when needed +For example, a locally validated JWT may remain valid until expiration unless the protected resource performs additional server-side validation. -
+The Users domain participates in security-sensitive account operations, while the authentication infrastructure determines how the resulting security state is enforced. -## πŸ” Mental Model +For more information, see [Authentication Modes](/docs/fundamentals/authentication-modes). -Authentication answers: +## User Identifiers -β†’ Who are you? +An identifier is a value associated with a user that allows the identity to be referenced or resolved. -Users domain answers: +Common examples include: -β†’ What is this user? +- Email addresses. +- Usernames. +- Phone numbers. +- Application-defined identifier types. -## 🎯 Summary +UltimateAuth treats identifiers as independent identity records rather than assuming that every user has exactly one email address or username. -- Users are composed, not singular -- Login is based on identifiers -- Login identifiers are configurable -- System is extensible by design +This supports applications with different identity requirements without changing the underlying authentication model. + +### Identifier Properties + +The reference identifier model distinguishes several important properties: + +| Property | Purpose | +|---|---| +| **Type** | Identifies the kind of identifier, such as email or username | +| **Value** | The identifier value associated with the user | +| **Normalized value** | A representation used for consistent lookup and comparison | +| **Primary state** | Indicates the identifier's designated primary role | +| **Verification state** | Indicates whether ownership has been verified | + +These properties address separate questions. + +An identifier may exist without being verified. A verified identifier does not automatically become primary. A primary identifier does not necessarily satisfy every authentication policy. + +### Login Identifier Resolution + +Identifier resolution determines how an incoming login identifier is associated with a user identity. + +In the reference approach, primary identifiers define the intended login surface, subject to the configured identifier resolution and verification policies. + +Applications can control which identifier types are accepted and how identifiers are resolved. + +This allows authentication to support different identity strategies without embedding email-specific or username-specific assumptions into Core. + +The distinction is important: **an identifier is identity data; whether it is acceptable for login is an authentication policy decision.** + +### Identifier Ownership and Verification + +Identifier verification is a security operation, not a simple update to a Boolean property. + +Marking an identifier as verified must be based on an appropriate ownership proof or an explicitly authorized administrative operation. + +The reference domain therefore distinguishes between: + +- Registering an identifier. +- Establishing ownership of an identifier. +- Verifying that ownership. +- Changing an identifier's role or status. + +An anonymous or self-service request must not be able to claim verified ownership merely by supplying a verification flag. + +Likewise, operations targeting another user's identifiers must respect the applicable authorization and ownership boundaries. + +This prevents identifier management from becoming an alternative route around authentication security. + +## User Profiles + +A profile contains information about the user that is not itself an authentication credential or an identity verification proof. + +Typical profile information may include: + +- Display name and personal information. +- Localization preferences. +- Application metadata. +- Other domain-specific user attributes. + +UltimateAuth deliberately separates profile data from the security-critical identity model. + +Applications should be able to evolve their profile structure without redesigning authentication flows or changing how identifiers are verified. + +A profile attribute may still be relevant to application authorization or business rules, but it should not automatically be treated as proof of identity. + +The reference model provides a starting point; applications remain responsible for deciding which additional user attributes belong in their own domain. + +## Reference Implementation + +The Users Plugin Domain includes a reference implementation that demonstrates and provides a practical user management model. + +It brings together lifecycle, identifiers, and profile management through application services and supporting abstractions. + +Rather than exposing persistence operations as the primary application interface, the reference implementation places user management rules within the application service layer. + +This layer coordinates operations such as: + +- User creation and account management. +- Identifier registration and updates. +- Identifier verification and ownership validation. +- Account status changes. +- Integration and lifecycle notifications. +- Security-related coordination with the authentication infrastructure. + +This is an important architectural boundary. + +A request to verify an identifier, for example, is not equivalent to directly updating a database field. The application service is responsible for enforcing the rules that make the operation valid. + +The reference implementation provides a recommended starting point for these behaviors without requiring every application to use the same persistence technology or user profile structure. + +## Domain Integration + +User management frequently affects other parts of an identity platform. + +Creating a user may be followed by credential enrollment. Changing account security state may require session invalidation. Deleting an account may require coordinated cleanup. + +UltimateAuth supports these scenarios through explicit integration contracts rather than direct dependencies between Plugin Domain implementations. + +For example, `IUserLifecycleIntegration` provides an integration boundary for user lifecycle operations. + +This allows other capabilities to participate in user-related workflows while keeping the Users domain responsible for its own state and rules. + +The distinction between domain ownership and cross-domain coordination is central to UltimateAuth's modular architecture. + +## Customizing User Management + +Applications are not required to accept every detail of the reference implementation. + +Depending on their requirements and the available extension contracts, they may: + +- Provide custom identifier resolution. +- Configure supported identifier and verification policies. +- Replace storage implementations. +- Integrate existing user repositories. +- Extend supported lifecycle and application service integrations. + +For most applications, the recommended approach is to begin with the reference implementation and customize specific behavior rather than replacing the entire domain. + +More advanced scenarios, including custom domain implementations and integration with existing identity systems, will be covered separately in the customization documentation. + +## Choosing Your User Model + +There are two broad integration approaches. + +**Use the reference Users domain** when the application needs a ready-made foundation for account lifecycle, identifiers, verification, and user management. + +**Integrate an existing identity model** when the application already owns these responsibilities and needs UltimateAuth primarily for its authentication infrastructure. + +Neither approach changes the fundamental security requirements. + +Identity resolution must be trustworthy, identifier ownership must be protected, and authentication state must be managed according to the configured security model. + +The difference is which part of the application owns the user management implementation. + +## Next Steps + +The Users domain establishes identity ownership and account lifecycle, but it does not independently provide every capability of an identity platform. + +- [Credentials](/docs/plugin-domains/credentials) β€” Learn how authentication credentials are managed independently of user identifiers. +- [Authorization](/docs/plugin-domains/authorization) β€” Explore how identities are associated with permissions and access policies. +- [Plugin Domains](/docs/plugin-domains) β€” Return to the overall Plugin Domain architecture. + +For the distinction between identity and authentication infrastructure, see [Authentication Model](/docs/fundamentals/authentication-model). \ No newline at end of file diff --git a/docs/content/security/access-token-behavior.md b/docs/content/security/access-token-behavior.md deleted file mode 100644 index 4920b808..00000000 --- a/docs/content/security/access-token-behavior.md +++ /dev/null @@ -1,172 +0,0 @@ ---- -title: Access Token Behavior -order: 2 -group: security ---- - - -# 🎯 Access Token Behavior - -Access tokens in UltimateAuth are intentionally **short-lived and mode-aware**. -They are not the primary source of truth for authentication. - -## 🧠 Core Principle - -In UltimateAuth: - -πŸ‘‰ Access tokens are **transport tokens** -πŸ‘‰ Sessions are the **source of truth** - -## πŸ”‘ Token Types - -UltimateAuth supports two access token types: - -### πŸ”Ή Opaque Tokens - -- Random, non-readable values -- Stored and validated server-side -- Typically used with session-based auth - -### πŸ”Ή JWT Tokens - -- Self-contained tokens -- Contain claims (user, tenant, session, etc.) -- Signed and verifiable without storage - -## βš™οΈ Mode-Dependent Behavior - -Access token behavior depends on auth mode: - -### 🟒 PureOpaque - -- No JWT issued -- Session cookie is the primary mechanism -- Access token may not exist externally - -πŸ‘‰ Validation = session validation - -### 🟑 Hybrid - -- Opaque + JWT together -- Session still authoritative -- JWT used for API access - -πŸ‘‰ Validation = session + token - -### 🟠 SemiHybrid - -- JWT is primary access token -- Session still exists -- Refresh rotation enabled - -πŸ‘‰ Balanced approach - -### πŸ”΅ PureJwt - -- Only JWT + refresh tokens -- No session state required - -πŸ‘‰ Stateless mode - -## ⏱ Lifetime Strategy - -Access tokens are: - -- short-lived -- replaceable -- not trusted long-term - -Typical lifetime: - -```text -5–15 minutes -``` - -## πŸ”„ Refresh Interaction - -Access tokens are never extended directly. - -Instead: - -```text -Access Token β†’ expires β†’ Refresh β†’ new Access Token -``` - -πŸ‘‰ This ensures: - -- forward-only security -- no silent extension -- replay window minimization - -## 🧾 Claims Model - -JWT tokens may include: - -- sub (user id) -- tenant -- sid (session id) -- jti (token id) -- custom claims - -πŸ‘‰ Claims are generated at issuance time -πŸ‘‰ Not dynamically updated afterward - -## ⚠️ Important Implication - -If something changes: - -- user roles -- permissions -- security state - -πŸ‘‰ existing JWTs are NOT updated - -πŸ‘‰ This is why: - -- tokens are short-lived -- refresh is required -- session validation may still apply - -## πŸ›‘ Security Boundaries - -Access tokens are: - -❌ not revocable individually (JWT) -❌ not long-term identity - -βœ” tied to session or refresh flow -βœ” bounded by expiration - -## πŸ”₯ Why This Matters - -UltimateAuth avoids a common mistake: - -πŸ‘‰ treating JWT as the system of record - -Instead: - -πŸ‘‰ JWT is a **snapshot**, not truth - -## ⚠️ Design Tradeoff - -Short-lived tokens mean: - -- more refresh calls -- slightly more backend interaction - -πŸ‘‰ But this enables: - -- safer rotation -- better revocation -- reduced attack window - -## 🧠 Mental Model - -If you remember one thing: - -πŸ‘‰ Access tokens are **temporary access grants** -πŸ‘‰ Not persistent identity - -## ➑️ Next Step - -Continue to **Policy Pipeline Deep Dive** to understand how access decisions are enforced. diff --git a/docs/content/security/authorization-pipeline.md b/docs/content/security/authorization-pipeline.md new file mode 100644 index 00000000..906f726f --- /dev/null +++ b/docs/content/security/authorization-pipeline.md @@ -0,0 +1,591 @@ +--- +title: Authorization Pipeline Internals +order: 4 +group: security +--- + +# Authorization Pipeline Internals + +UltimateAuth authorization is implemented as a coordinated evaluation process rather than a collection of unrelated permission checks. + +The authorization pipeline brings together the operation being requested, the identities involved, the applicable permissions, and the policies responsible for enforcing security requirements. + +At its center are several distinct responsibilities: + +- **Context preparation:** Establish the security information required to evaluate an operation. +- **Permission resolution:** Make effective authorization grants available to the decision process. +- **Policy resolution:** Determine which security rules apply to the requested action. +- **Policy evaluation:** Evaluate the applicable requirements in a defined order. +- **Decision enforcement:** Permit the protected operation to proceed only when the authorization result allows it. + +This page examines how those responsibilities fit together. + +For an introduction to actions, permissions, and roles, see [Authorization](/docs/plugin-domains/authorization). For policy concepts and their intended use, see [Policies & Access Control](/docs/plugin-domains/policies). + +## Pipeline Architecture + +The authorization process can be represented as a sequence of stages: + +```text +Protected Operation + | + v + AccessContext + | + v +Context Enrichment + | + v +Permission Resolution + | + v +Invariant Evaluation + | + v +Global Policy Evaluation + | + v +Runtime Policy Resolution + | + v +Runtime Policy Evaluation + | + v + Final Decision + | + +---- Deny ----------------> Reject + | + +---- Reauthentication ---> Require Reauthentication + | + +---- Allow ---------------> Execute +``` + +This diagram describes the logical responsibilities of the pipeline. + +It should not be interpreted as a guarantee that every stage performs a separate database query or that every authentication mode uses the same mechanism to obtain authorization information. + +The important property is that **authorization is evaluated against a coherent access context before the protected operation is allowed to proceed**. + +## AccessContext as the Evaluation Boundary + +`AccessContext` carries the information required to evaluate an authorization request. + +It connects the authenticated actor, the intended target, the requested action, and the relevant security environment. + +Conceptually, its responsibilities include: + +| Information | Purpose | +|---|---| +| Actor | Identifies the principal performing the operation | +| Target | Identifies the account or resource affected | +| Action | Describes the protected operation | +| Tenant context | Establishes the relevant isolation boundaries | +| Claims | Provides authentication and authorization information | +| Permissions | Represents the effective permission information available for evaluation | + +The context is shared across the authorization process so that individual policies do not need to independently reconstruct the operation. + +### Actor and Target Separation + +Consider the action: + +```text +users.delete.admin +``` + +The actor is the principal requesting the deletion. + +The target is the user account being deleted. + +These identities may be different, and the authorization pipeline must preserve that distinction. + +For a self-service action: + +```text +users.delete.self +``` + +The action indicates a self-service operation, but the action name alone does not prove that actor and target are the same identity. + +That relationship must be established from trusted context and evaluated by the applicable security rules. + +### Context Integrity + +The reliability of the authorization decision depends on the integrity of the context. + +A target identifier or tenant identifier supplied by a client must not automatically be accepted as a trusted authorization fact. + +The pipeline must operate on the authenticated actor and appropriately resolved target information. + +A correctly implemented policy cannot compensate for a context that incorrectly identifies the actor or misrepresents the target. + +## Context Enrichment + +The initial authorization context may not contain all the information required by the applicable policies. + +Context enrichment makes additional authorization information available before evaluation. + +This can include resolving effective permissions and preparing them for efficient policy checks. + +Conceptually: + +```text +Authenticated Actor + | + v +Resolve Authorization Information + | + v +Effective Permissions + | + v +CompiledPermissionSet + | + v +Enriched AccessContext +``` + +The purpose of enrichment is to separate authorization data resolution from the individual rules that consume that data. + +A permission policy should not need to independently load role assignments and rebuild the user's effective permission set every time it evaluates an action. + +### Authorization Data Freshness + +Enrichment does not automatically guarantee that all authorization information is current. + +The source of permission data matters. + +For example, permissions obtained from current server-side state may reflect a recent role change, while permissions derived exclusively from an already-issued JWT may reflect an earlier state. + +The pipeline can evaluate the information it receives consistently, but it cannot make stale claims current without consulting an authoritative source. + +This distinction is particularly relevant to SemiHybrid and PureJwt authentication. + +## Permission Compilation + +UltimateAuth uses `CompiledPermissionSet` to represent permissions in a form suitable for repeated authorization checks. + +Permission compilation separates the representation of stored grants from the process of deciding whether an action is permitted. + +For example: + +```text +Requested action: + users.delete.admin + +Effective grants: + users.read.self + users.delete.admin + credentials.list.self +``` + +A permission policy can evaluate whether the effective grants cover the requested action. + +Conceptually: + +```csharp +permissionSet.IsAllowed(action); +``` + +This illustrates the permission-checking responsibility, rather than a complete public API integration example. + +### Wildcard Permissions + +Compiled permissions may also represent wildcard grants. + +For example: + +```text +users.* +users.profile.* +* +``` + +Wildcard matching must respect the permission model's defined action hierarchy. + +A broad grant should not be inferred merely because several currently visible permissions share a prefix. + +Where permission normalization replaces a complete action group with a wildcard, the normalization must preserve the intended permission semantics. + +### Compilation Is Not Authorization + +A compiled permission set is an efficient representation of grants. + +It does not independently establish that the actor may perform the operation. + +A matching permission can still be insufficient when: + +- The actor is not permitted to access the target. +- The operation crosses a prohibited tenant boundary. +- The account does not satisfy required security conditions. +- An operation-specific policy denies the action. + +Permission matching is one requirement within the broader authorization decision. + +## Policy Registration and Resolution + +Runtime policies must be discoverable for the operations they protect. + +UltimateAuth's authorization architecture includes policy registration and compilation concepts represented by: + +- `AccessPolicyRegistry` +- `CompiledAccessPolicySet` + +These components separate policy configuration from runtime selection. + +Conceptually: + +```text +Registered Policies + | + v +AccessPolicyRegistry + | + v +CompiledAccessPolicySet + | + v +Policies Matching Requested Action +``` + +### Action-Based Selection + +Policies may be associated with action prefixes or patterns. + +For example: + +```text +users.* +users.delete.* +users.delete.admin +``` + +A request for: + +```text +users.delete.admin +``` + +may select policies associated with the relevant action hierarchy. + +This allows broad policies to cover a domain while more specific policies enforce requirements for individual operations. + +### Prefix Matching and Boundaries + +Action matching must respect the intended structure of action identifiers. + +For example: + +```text +users.delete.admin +``` + +belongs to the `users` action domain. + +But an unrelated action whose text merely begins with similar characters should not automatically be treated as belonging to that domain. + +Policy matching must therefore follow the actual action-pattern semantics implemented by the registry. + +### Resolution Versus Evaluation + +Policy resolution answers: + +**Which policies are candidates for this operation?** + +Policy evaluation answers: + +**Do those policies permit this operation in the current context?** + +A policy may match an action pattern but still be inapplicable to a particular runtime context. + +That distinction is handled through policy applicability rules. + +## Runtime Applicability + +A policy can determine whether it applies to the current authorization context. + +UltimateAuth represents this responsibility through `AppliesTo`. + +For example, a policy restricting administrative self-modification may only be relevant when: + +- The requested action has administrative scope. +- The operation changes protected account state. +- The actor and target are the same identity. + +It may not apply to administrative read operations or unrelated domains. + +Conceptually: + +```text +Policy selected by action + | + v +AppliesTo(context) + | + +---- No ----> Skip policy + | + +---- Yes ---> Evaluate policy +``` + +This enables policies to remain focused on specific security requirements without requiring every action to have an entirely separate policy implementation. + +### Applicability Is Not a Security Bypass + +An applicability rule determines when a policy participates in evaluation. + +If a mandatory security requirement can be skipped because its applicability condition is too broad or incorrectly implemented, the resulting authorization boundary may be weaker than intended. + +Policies enforcing fundamental security invariants should not depend on application-controlled applicability rules that can bypass those invariants. + +## Policy Evaluation Stages + +UltimateAuth's policy architecture distinguishes between invariants, global policies, and runtime policies. + +These categories establish the order and scope of security evaluation. + +### Stage 1: Invariants + +Invariants represent mandatory security requirements. + +They are evaluated before ordinary policy stages. + +An invariant denial prevents the requested operation from proceeding, regardless of whether another policy would otherwise permit it. + +Invariants provide the strongest enforcement boundary within the policy pipeline. + +They should not be treated as ordinary permissions that can be overridden by a broader grant. + +### Stage 2: Global Policies + +Global policies enforce broadly applicable security requirements. + +They may examine the operation's context to determine whether their rule is relevant. + +Examples include requirements related to authentication state, account eligibility, and tenant isolation. + +Global policies allow these requirements to be enforced consistently without attaching identical rules to every individual action. + +### Stage 3: Runtime Policies + +Runtime policies are resolved according to the requested action and evaluated against the current context. + +They support requirements such as: + +- Required permissions. +- Self-access restrictions. +- Administrative operation constraints. +- Domain-specific authorization rules. + +Runtime policies extend the decision process without replacing the mandatory security requirements established by earlier stages. + +## IAccessAuthority and Decision Evaluation + +`IAccessAuthority` represents the authorization decision responsibility within the pipeline. + +The authority evaluates the applicable security requirements and produces an authorization result. + +Conceptually, the result can represent: + +| Result | Meaning | +|---|---| +| **Allow** | The applicable requirements permit the operation | +| **Deny** | A required security condition rejects the operation | +| **Reauthentication required** | The operation requires an additional authentication condition before proceeding | + +These outcomes must not be confused with the result of an individual policy. + +### Policy Allow Versus Final Allow + +An individual policy may return an allow result because its own requirement has been satisfied. + +That does not necessarily mean the entire operation is authorized. + +Other applicable policies may still deny the request or require reauthentication. + +**An individual policy's approval is not a universal authorization grant.** + +### Deny Precedence + +A denial from an applicable mandatory policy prevents the operation from being authorized. + +The pipeline must not allow a later permissive policy to override a previously established denial. + +This supports predictable enforcement across multiple independent security rules. + +### Reauthentication Requirements + +Some operations may require stronger or more recent authentication. + +A policy can represent that requirement separately from an ordinary denial. + +The authorization system must distinguish between: + +- An operation that is prohibited. +- An operation that may proceed after additional authentication. +- An operation that is currently authorized. + +The exact precedence and propagation of reauthentication results must follow the implemented decision engine. + +A reauthentication result should not be interpreted as permission to execute the protected operation immediately. + +## Permission Policies + +`MustHavePermissionPolicy` represents the connection between the permission model and the policy pipeline. + +Its responsibility is to evaluate whether the actor has the permission required for the requested action. + +Conceptually: + +```text +AccessContext.Action + | + v +CompiledPermissionSet + | + v +Permission Evaluation + | + v +Policy Decision +``` + +This design separates permission storage and resolution from permission enforcement. + +Roles and assignments determine effective grants. + +The compiled permission set represents those grants. + +The permission policy evaluates the requested action against them. + +The final authorization decision also considers the other applicable security policies. + +### Permissions Are Not a Replacement for Policies + +For example, an actor may possess: + +```text +users.delete.admin +``` + +That permission does not automatically authorize deleting any user in any tenant. + +The operation must still satisfy the relevant target, tenant, and account security requirements. + +This is why the pipeline evaluates multiple requirements rather than relying on a single permission match. + +## Orchestration and Protected Execution + +`AccessOrchestrator` coordinates authorization and the protected operation. + +Its purpose is to establish a consistent enforcement boundary. + +Conceptually: + +```text +Operation Requested + | + v +Prepare AccessContext + | + v +Resolve and Evaluate Policies + | + +---- Not Allowed ---> Stop + | + v +Execute Protected Operation +``` + +The security requirement is straightforward: + +**A protected operation must not execute merely because its caller reached an application service or endpoint.** + +Authorization must succeed at an enforcement boundary that covers the operation. + +### HTTP and In-Process Execution + +HTTP endpoints may enforce authorization through ASP.NET Core authentication and authorization integration. + +However, applications may also execute operations directly through in-process services. + +For example, a Blazor Server application can invoke a server-side service without making a separate HTTP request. + +A controller-level `[Authorize]` attribute does not automatically protect such a direct service invocation. + +Where operations can be reached through multiple execution paths, authorization must be enforced at an appropriate shared boundary. + +This prevents one entry point from bypassing rules enforced by another. + +## Determinism and Security + +A structured policy pipeline makes authorization behavior easier to reason about. + +For the same effective context, policy configuration, and authoritative security state, the decision should follow the same evaluation rules. + +However, authorization can depend on mutable information: + +- Account status. +- Tenant membership. +- Role assignments. +- Effective permissions. +- Session security state. +- Target ownership. + +A policy evaluation that uses stale or inconsistent data may produce a result that differs from one using current authoritative state. + +Deterministic policy evaluation therefore depends on both the evaluation rules and the consistency of the information supplied to them. + +### Time-of-Check and Time-of-Use + +Authorization may be evaluated before a protected operation modifies data. + +If relevant security or ownership information changes between the authorization decision and the mutation, a time-of-check/time-of-use problem may arise. + +Sensitive operations may require transactional consistency, concurrency controls, or revalidation within the protected operation. + +A policy pipeline provides a structured decision process, but it does not automatically make every application-level data mutation atomic. + +## Extensibility Boundaries + +The authorization pipeline is designed to support application-specific policies. + +Custom policies can introduce new requirements without replacing the underlying permission model or authentication engine. + +However, extensibility must preserve the security hierarchy. + +A custom policy should not: + +- Override mandatory invariants. +- Trust caller-supplied identity information without validation. +- Treat an action name as proof of actor-target ownership. +- Bypass tenant isolation through a broad permission. +- Assume that JWT claims always reflect current authorization state. + +Custom policies should evaluate well-defined requirements using trusted context and participate in the established decision model. + +Implementation and registration examples belong in [How-to Guides](/docs/how-to). + +## Architectural Perspective + +UltimateAuth's authorization pipeline separates the responsibilities involved in an access decision. + +`AccessContext` describes the operation. + +Context enrichment and permission compilation prepare the required authorization information. + +Policy resolution identifies the relevant rules. + +`IAccessAuthority` evaluates those rules according to the pipeline's decision semantics. + +`AccessOrchestrator` coordinates authorization with protected execution. + +Together, these components provide a structured foundation for context-aware, policy-driven authorization. + +**The pipeline's security comes not from the number of policies it executes, but from consistently enforcing the applicable requirements before a protected operation is allowed to proceed.** + +## Next Steps + +- [Authorization](/docs/plugin-domains/authorization) β€” Actions, permissions, roles, and scopes. +- [Policies & Access Control](/docs/plugin-domains/policies) β€” Policy concepts and access decision behavior. +- [Session Security Model](/docs/security/session-security-model) β€” Authentication security state and validation boundaries. +- [Token Security](/docs/security/token-security) β€” Credential validity and authorization freshness. \ No newline at end of file diff --git a/docs/content/security/policy-pipeline.md b/docs/content/security/policy-pipeline.md deleted file mode 100644 index f7d28ba3..00000000 --- a/docs/content/security/policy-pipeline.md +++ /dev/null @@ -1,230 +0,0 @@ ---- -title: Policy Pipeline -order: 4 -group: security ---- - - -# 🧠 Policy Pipeline Deep Dive - -UltimateAuth does not rely on simple role checks. - -Authorization is executed through a **multi-stage policy pipeline** that evaluates: - -- invariants (always enforced rules) -- global policies -- runtime (action-based) policies - -## 🧠 Core Principle - -πŸ‘‰ Authorization is not a single check -πŸ‘‰ It is a **decision pipeline** - -## πŸ” High-Level Flow - -```text -AccessContext - ↓ -Enrichment (claims, permissions) - ↓ -Invariants - ↓ -Global Policies - ↓ -Runtime Policies (action-based) - ↓ -Final Decision -``` - -## 🧩 AccessContext - -Every authorization decision is based on an `AccessContext`. - -It contains: - -- actor (who is making the request) -- target (resource / user) -- tenant -- action (string-based, e.g. "users.delete") -- attributes (claims, permissions, etc.) - -πŸ‘‰ Everything in the pipeline reads from this context - -## πŸ”„ Step 1: Context Enrichment - -Before policies run, context is enriched: - -- permissions are loaded -- compiled into `CompiledPermissionSet` -- attached to context - -πŸ‘‰ This allows policies to run without hitting storage repeatedly - -## πŸ›‘ Step 2: Invariants - -Invariants are **always enforced rules**. - -They run first and cannot be bypassed. - -Examples: - -- user must be authenticated -- cross-tenant access is denied -- request must be valid - -πŸ‘‰ If an invariant fails: - -```text -β†’ DENY immediately -``` - -## 🌐 Step 3: Global Policies - -Global policies apply to all requests but are conditional. - -Examples: - -- RequireActiveUserPolicy -- DenyAdminSelfModificationPolicy - -Each policy: - -- decides if it applies (`AppliesTo`) -- evaluates (`Decide`) - -πŸ‘‰ Important: - -- β€œAllow” means *no objection* -- not final approval - -## 🎯 Step 4: Runtime Policies - -Runtime policies are: - -- action-based -- dynamically resolved - -They come from: - -```text -AccessPolicyRegistry β†’ CompiledAccessPolicySet -``` - -Policies are selected by: - -```text -context.Action.StartsWith(prefix) -``` - -Example: - -```text -Action: users.delete.admin - -Matches: -- users.* -- users.delete.* -- users.delete.admin -``` - -## βš–οΈ Step 5: Decision Engine - -All policies are evaluated by: - -πŸ‘‰ `IAccessAuthority` - -Evaluation order: - -1. Invariants -2. Global policies -3. Runtime policies - -### Decision Rules - -- First **deny** β†’ stops execution -- β€œAllow” β†’ continue -- β€œRequiresReauthentication” β†’ tracked - -Final result: - -```text -Allow -Deny(reason) -ReauthenticationRequired -``` - -## πŸ” Permission Integration - -Permissions are not directly checked in services. - -Instead: - -- permissions are compiled -- policies use them - -Example policy: - -```text -MustHavePermissionPolicy -``` - -Checks: - -```text -CompiledPermissionSet.IsAllowed(action) -``` - - -πŸ‘‰ This decouples: - -- permission storage -- authorization logic - -## πŸ”₯ Why This Matters - -This pipeline enables: - -- composable security rules -- consistent enforcement -- separation of concerns -- extensibility - -Compared to typical systems: - -| Feature | Traditional | UltimateAuth | -|---------------------|------------|-------------| -| Inline checks | βœ… | ❌ | -| Central pipeline | ❌ | βœ… | -| Policy composition | ❌ | βœ… | -| Action-based rules | ❌ | βœ… | - -## ⚠️ Design Tradeoff - -This model introduces: - -- more abstraction -- more components - -πŸ‘‰ But gives: - -- predictability -- auditability -- flexibility - -## 🧠 Mental Model - -If you remember one thing: - -πŸ‘‰ Authorization is not β€œif (role == admin)” -πŸ‘‰ It is a **pipeline of decisions** - -## ➑️ Summary - -UltimateAuth authorization: - -- is policy-driven -- runs through a structured pipeline -- separates invariants, global rules, and action rules -- produces deterministic decisions - -πŸ‘‰ This makes it suitable for complex, multi-tenant, security-sensitive systems diff --git a/docs/content/security/refresh-rotation.md b/docs/content/security/refresh-rotation.md index 51c00e3f..8d60b7fa 100644 --- a/docs/content/security/refresh-rotation.md +++ b/docs/content/security/refresh-rotation.md @@ -1,170 +1,470 @@ --- -title: Refresh Rotation +title: Refresh Rotation & Replay Protection order: 3 group: security --- +# Refresh Rotation & Replay Protection -# πŸ”„ Refresh Token Rotation +Refresh credentials allow authentication to continue without requiring the user to repeat the original login process. -Refresh tokens in UltimateAuth are not simple long-lived tokens. +Because a refresh credential may remain useful longer than an access credential, its compromise can have significant security consequences. -They are part of a **rotation-based security system** designed to: +**Refresh rotation** is one mechanism for reducing this risk. It replaces previously issued refresh authentication material as part of a successful continuation operation. -- prevent token replay -- detect token theft -- enforce forward-only session progression +However, secure rotation involves more than generating a new token. It requires consistent state transitions, concurrency control, invalid credential handling, and a clear policy for suspected credential reuse. -## 🧠 Why Rotation? +This page examines those security properties and their limitations. -In traditional systems: +For token formats, access token validation, and general refresh behavior, see [Token Security](/docs/security/token-security). -- refresh tokens are long-lived -- they can be reused multiple times +## Rotation as a Security Protocol -πŸ‘‰ If stolen, an attacker can: +In a rotating refresh strategy, a successfully consumed refresh credential is replaced with new authentication material. -- silently keep refreshing access tokens -- stay undetected +Conceptually: -UltimateAuth solves this with: +```text +Refresh A + | + | Successful continuation + v +Refresh A consumed +Refresh B issued + | + | Successful continuation + v +Refresh B consumed +Refresh C issued +``` + +The security objective is to prevent an older refresh credential from remaining indefinitely reusable after a successful continuation. + +A rotating refresh mechanism must maintain enough authoritative state to distinguish between credentials that are currently eligible for use and credentials that have already been consumed or invalidated. + +This is fundamentally different from validating a self-contained access token using only its signature and expiration. + +### What Rotation Does and Does Not Guarantee + +Rotation can: + +- Limit the continued usefulness of previously consumed credentials. +- Provide a signal when an invalidated credential is presented again. +- Support targeted security responses to suspicious continuation attempts. +- Reduce the opportunities for prolonged reuse of the same refresh credential. + +Rotation does not inherently: + +- Prevent the initial theft of a refresh credential. +- Prove that a reuse attempt was made by an attacker. +- Guarantee that the legitimate client wins a race against an attacker. +- Invalidate every previously issued access token. +- Guarantee immediate containment without an enforced revocation mechanism. + +The strength of the protection depends on how the refresh strategy implements and validates its state transitions. + +## Authentication Mode Considerations + +Refresh behavior is not identical across UltimateAuth's authentication modes. + +| Mode | Refresh and rotation considerations | +|---|---| +| **PureOpaque** | Uses opaque session continuation rather than a separate access/refresh credential pair | +| **Hybrid** | Uses opaque access and refresh credentials with server-authoritative session state | +| **SemiHybrid** | Uses JWT access tokens and server-managed refresh lifecycle state | +| **PureJwt** | Uses locally validated JWT access tokens and may use an optional refresh store | + +The presence of a refresh operation does not necessarily mean that a separate refresh credential is replaced on every invocation. + +Depending on the configured mode and strategy, a continuation operation may rotate credentials, update existing server-managed state, or follow another supported lifecycle behavior. + +**Rotation guarantees must be evaluated against the selected refresh strategy, not assumed from the existence of a refresh endpoint.** + +See [Authentication Modes](/docs/fundamentals/authentication-modes). + +## Refresh Credential State + +A rotating refresh mechanism needs to distinguish credentials that remain eligible for continuation from credentials that are no longer acceptable. + +Conceptually, relevant conditions include: + +| Condition | Meaning | +|---|---| +| **Active** | The credential is eligible for a refresh attempt, subject to all applicable checks | +| **Consumed or replaced** | The credential has already participated in a successful rotation | +| **Revoked** | The credential or associated refresh authority has been explicitly invalidated | +| **Expired** | The credential has exceeded its permitted lifetime | +| **Invalid or unknown** | The presented value cannot be resolved to an acceptable refresh credential | + +These are conceptual security conditions, not necessarily distinct persisted enum values in every implementation. + +A consumed credential and an administratively revoked credential may both be unusable, but the reasons for rejection are different. + +That distinction matters when evaluating whether a failed refresh represents ordinary expiration, an invalid request, or potential credential reuse. + +## The Rotation Transaction + +A successful rotating refresh operation involves a sequence of security-sensitive steps. + +Conceptually: + +1. Resolve the presented refresh credential. +2. Validate its eligibility for continuation. +3. Validate the applicable session, account, and client context. +4. Establish that the credential has not already been consumed. +5. Apply the configured rotation transition. +6. Issue the replacement authentication material. +7. Persist the resulting state consistently. +8. Return the renewed credentials to the client. + +The exact implementation and ordering depend on the selected refresh strategy. -πŸ‘‰ **single-use refresh tokens (rotation)** +The important security requirement is that the transition must not leave multiple conflicting refresh credentials valid because two requests observed the same earlier state. -## πŸ” Rotation Model +### Atomicity -Each refresh token is: +Consider a refresh credential `A`. -- used exactly once -- replaced with a new token -- linked to a chain +Two requests arrive almost simultaneously: ```text -Token A β†’ Token B β†’ Token C β†’ ... +Request 1: Refresh(A) +Request 2: Refresh(A) ``` -When a refresh happens: +An unsafe implementation might allow both requests to read `A` as active before either marks it consumed. -1. Token A is validated -2. Token A is **revoked** -3. Token B is issued -4. Token A is marked as replaced by B +Both requests could then issue different replacement credentials: -## πŸ” Token State +```text + Refresh A + | + +------+------+ + | | + Request 1 Request 2 + | | + v v + Refresh B Refresh C +``` + +If both replacements remain valid, the system has unintentionally created competing continuation paths. + +A single-use rotation strategy must prevent this outcome through an appropriate atomic state transition or concurrency mechanism. -A refresh token can be: +Possible implementation techniques include transactional updates, compare-and-swap operations, or other storage-specific concurrency controls. -- Active β†’ valid and usable -- Revoked β†’ already used or manually revoked -- Expired β†’ lifetime exceeded -- Replaced β†’ already rotated +The precise mechanism is an implementation concern, but the security property is essential: -πŸ‘‰ Only **active tokens** are valid +**Only the transition permitted by the configured rotation semantics may succeed.** -## 🚨 Reuse Detection +## Concurrent Refresh Requests -This is the most critical security feature. +Concurrent refresh requests are not necessarily malicious. -If a refresh token is used **after it has already been rotated**, it means: +They can occur when: -πŸ‘‰ The token was reused -πŸ‘‰ Likely stolen +- Several API requests discover an expired access token at the same time. +- Multiple browser tabs attempt to refresh authentication. +- A client retries a request after a network interruption. +- Background and foreground operations initiate refresh simultaneously. -### What happens? +Without coordination, a legitimate client can submit the same refresh credential more than once. -When reuse is detected: +### Client-Side Coordination -- the system identifies the session chain -- the entire chain can be revoked -- all related sessions become invalid +Applications should avoid issuing unnecessary concurrent refresh requests. -πŸ‘‰ This immediately cuts off both: +A common approach is to coordinate refresh through a single in-progress operation and allow other requests to await its result. + +Conceptually: + +```text +API Request A ──┐ +API Request B ──┼──> One refresh operation +API Request C β”€β”€β”˜ | + v + Renewed credentials + | + +-----------+-----------+ + | | | + v v v + Retry A Retry B Retry C +``` -- attacker -- legitimate user (forcing reauthentication) +Client coordination improves reliability and reduces accidental reuse attempts. -## πŸ”— Chain Awareness +However, it is not a replacement for server-side concurrency enforcement. -Refresh tokens belong to a **session chain**. +Different devices, processes, or compromised clients cannot be assumed to share the same coordination mechanism. -This enables: +### Server-Side Enforcement -- tracking rotation history -- detecting anomalies -- applying revocation at the correct scope +The server remains responsible for enforcing the configured refresh credential lifecycle. -Without chains: +A client-side lock cannot guarantee that an attacker or another client instance will not present the same credential. -❌ You cannot safely detect reuse -❌ You cannot know which tokens belong together +For single-use rotation, the server must ensure that a consumed credential cannot successfully establish another independent continuation path. -## πŸ”„ Rotation Flow +## Refresh Credential Reuse + +Refresh credential reuse occurs when a credential is presented after it is no longer eligible for the operation. + +For a single-use rotating strategy, presenting a previously consumed credential is a particularly important case. ```text -Client β†’ Refresh(Token A) - β†’ Validate A - β†’ Revoke A - β†’ Issue B - β†’ Return new tokens +Initial state: + Refresh A is active + +First request: + Refresh(A) succeeds + A is consumed + B is issued + +Later request: + Refresh(A) is attempted again + A is no longer eligible ``` -## ⚠️ Invalid Scenarios +The later request must not be accepted as a new successful use of `A`. + +### Reuse Is a Security Signal + +A consumed refresh credential may be presented again because: + +- An attacker copied the credential before rotation. +- A legitimate client retried after losing the refresh response. +- Two client operations raced to refresh simultaneously. +- Client credential storage was not updated consistently. -### 1. Expired Token +The server may not have enough information to distinguish these situations conclusively. + +Therefore, **reuse detection identifies a suspicious credential lifecycle event; it does not automatically prove credential theft.** + +### Detection Versus Response + +Detecting invalid reuse and responding to it are separate responsibilities. + +A refresh strategy may reject the request without taking broader action. + +A stricter security policy may invalidate additional refresh state or revoke an associated session or chain. + +The appropriate response depends on the implementation, authentication mode, and supported security policy. + +A documentation claim that reuse always causes chain-wide revocation would be correct only if that behavior is actually enforced by the selected strategy. + +## Replay and Token Theft Scenarios + +Rotation is particularly useful when reasoning about stolen refresh credentials. + +### Scenario 1: The Legitimate Client Refreshes First + +An attacker copies refresh credential `A`, but the legitimate client uses it before the attacker. ```text -Token expired β†’ reject +Legitimate client: + A -> B + +Attacker: + Attempts A + A is already consumed ``` -### 2. Revoked Token +If the rotation state is enforced correctly, the attacker's attempt to use `A` cannot produce another successful continuation. + +The server may also be able to identify the attempt as reuse. + +### Scenario 2: The Attacker Refreshes First + +An attacker copies active refresh credential `A` and successfully uses it before the legitimate client. ```text -Token already used β†’ reuse detected +Attacker: + A -> B + +Legitimate client: + Attempts A + A is already consumed ``` -### 3. Session Mismatch +In this case, the attacker may already possess the replacement credential `B`. + +Rejecting the legitimate client's later use of `A` is necessary, but it does not by itself remove the attacker's newly obtained credential. + +A broader revocation response may be required to contain the compromise. + +This illustrates why rotation and reuse detection are not identical to automatic compromise recovery. + +### Scenario 3: A Refresh Response Is Lost + +The legitimate client sends `A`. + +The server successfully consumes `A` and issues `B`, but the response never reaches the client. + +The client still possesses `A`. + +A retry may now appear to be reuse. + +This creates an important reliability and security tradeoff. + +Strict single-use semantics can reject the retry, while a more tolerant continuation strategy requires carefully defined behavior that does not accidentally create an exploitable replay window. + +The chosen strategy must handle this situation consistently. + +## Session and Chain Association + +In session-managed authentication modes, refresh operations can be associated with session and chain state. + +This allows the authentication infrastructure to evaluate continuation within the relevant security context. + +For example, a refresh operation may need to establish that: + +- The refresh credential belongs to the expected authentication state. +- The associated session remains eligible for continuation. +- The relevant chain has not been revoked. +- The account-level security state permits continuation. +- Any configured device context requirements are satisfied. + +The exact checks depend on the mode and implementation. + +### Chain-Level Containment + +When a refresh compromise is associated with a session chain, chain-level revocation can provide a broader response than invalidating a single refresh credential. + +This may be appropriate when multiple related sessions or continuation credentials could be affected. + +However, a chain is a logical session continuity boundary, not a cryptographically verified physical device. + +Chain-level revocation controls the associated authentication state; it does not permanently prevent the physical device from authenticating again. + +See [Session Security Model](/docs/security/session-security-model). + +## Invalid Refresh Conditions + +Not every failed refresh should be classified as token reuse. + +Different failure conditions have different security meanings. + +| Condition | Expected security interpretation | +|---|---| +| Unknown credential | No acceptable refresh authority was established | +| Expired credential | The permitted continuation lifetime has ended | +| Explicitly revoked credential | The refresh authority has been invalidated | +| Previously consumed credential | Potential reuse under a single-use rotation strategy | +| Session or chain mismatch | Presented credential is inconsistent with expected authentication context | +| Account security invalidation | Continuation is no longer permitted under current account security state | +| Concurrent use | Multiple attempts may compete for the same continuation authority | + +A secure implementation must reject invalid continuation attempts according to the selected strategy. + +The application should not assume that every rejection requires the same logging, revocation, or user-facing response. + +## Rotation and Access Token Validity + +Refresh rotation affects authentication continuation. + +It does not necessarily affect access tokens that were issued earlier. + +For example: ```text -Token does not belong to expected session β†’ reject +JWT Access A issued +Refresh A issued + | + v +Refresh A rotated to Refresh B +JWT Access B issued ``` -## 🧠 Security Guarantees +If `JWT Access A` has not expired, it may remain valid under local JWT validation even after refresh rotation. + +Similarly, revoking `Refresh B` may prevent future refresh operations without immediately invalidating `JWT Access B`. + +This distinction is especially important in SemiHybrid and PureJwt. + +### Revocation Scope + +There are separate questions to answer: + +- Can the old refresh credential be used again? +- Can the replacement refresh credential still be used? +- Is the associated server-managed session valid? +- Are previously issued access tokens still accepted? + +A refresh security operation may affect some of these conditions without affecting all of them. + +The authentication mode determines where current server-side state is consulted. + +## Rotation and Credential Storage + +Refresh rotation depends on maintaining authoritative information about credential eligibility. + +For opaque refresh credentials, an implementation may store a one-way verifier or hash rather than the original credential value. + +This reduces the exposure of usable credentials if the persistence store is disclosed. + +However, secure storage alone does not provide rotation. + +The implementation must also maintain the necessary lifecycle state and enforce transitions correctly. + +### Operational Requirements + +A production refresh mechanism should account for: + +- Secure credential generation. +- Appropriate credential expiration. +- Protected transport and client storage. +- Consistent persistence updates. +- Concurrency control. +- Safe handling of invalid and consumed credentials. +- Revocation behavior appropriate to the authentication mode. + +The exact persistence and concurrency mechanisms are implementation-specific. + +## Security Tradeoffs + +Strict rotation improves control over credential reuse, but it introduces operational complexity. + +| Benefit | Corresponding consideration | +|---|---| +| Consumed credentials cannot remain reusable | Requires authoritative lifecycle state | +| Reuse attempts can become observable | Legitimate retries may resemble attacks | +| Continuation can be invalidated independently | Revocation must be consistently enforced | +| Replacement credentials limit prolonged reuse | Concurrent refresh requests require coordination | +| Compromise responses can target session state | Broader revocation may interrupt legitimate sessions | + +These tradeoffs are not reasons to avoid rotation. + +They are reasons to treat it as a security protocol rather than a simple token replacement operation. + +## Security Design Principles -Rotation ensures: +**Rotation must have defined semantics.** A refresh operation should not be described as single-use unless the configured strategy actually enforces single-use behavior. -- refresh tokens are forward-only -- old tokens cannot be reused safely -- stolen tokens are detectable -- compromise triggers containment +**State transitions must be consistent.** Concurrent requests must not create conflicting valid continuation paths under a single-use strategy. -## πŸ”₯ Why This Matters +**Reuse detection is not theft attribution.** A reused credential is suspicious, but the cause may be malicious or accidental. -Compared to traditional refresh tokens: +**Detection and containment are separate.** Rejecting reuse, revoking refresh state, and revoking a session chain are different security actions. -| Feature | Traditional | UltimateAuth | -|----------------------|------------|-------------| -| Reusable tokens | βœ… | ❌ | -| Reuse detection | ❌ | βœ… | -| Chain awareness | ❌ | βœ… | -| Automatic containment| ❌ | βœ… | +**Client coordination is helpful but insufficient.** The server must enforce credential eligibility independently. -## ⚠️ Design Tradeoff +**Access and refresh validity remain distinct.** Refresh rotation does not automatically invalidate previously issued JWT access tokens. -Rotation requires: +**Mode-specific behavior matters.** The same security guarantee must not be attributed to every authentication mode without considering its actual refresh implementation. -- token storage -- state tracking -- additional validation logic +## Architectural Perspective -πŸ‘‰ UltimateAuth chooses security over simplicity. +Refresh rotation is a mechanism for controlling how authentication authority continues over time. -## 🧠 Mental Model +Its security value comes from enforcing credential lifecycle transitions, preventing invalid continuation, and supporting an appropriate response when a previously consumed credential is presented again. -If you remember one thing: +UltimateAuth's authentication modes provide different continuation models. Rotation behavior must therefore be understood in the context of the selected mode, its server-managed state, and its configured refresh strategy. -πŸ‘‰ A refresh token is not a reusable key -πŸ‘‰ It is a **one-time step in a chain** +**A secure rotating refresh mechanism does not merely issue a new credential. It ensures that the previous credential can no longer establish an unauthorized continuation path.** -## ➑️ Next Step +## Next Steps -Continue to **Access Token Behavior** to understand how short-lived tokens interact with rotation. +- [Token Security](/docs/security/token-security) β€” Access and refresh credential security fundamentals. +- [Session Security Model](/docs/security/session-security-model) β€” Hierarchical validation and revocation boundaries. +- [Session Model & Lifecycle](/docs/sessions-devices/session-model) β€” Session continuity, expiration, and revocation. +- [Authentication Modes](/docs/fundamentals/authentication-modes) β€” Mode-specific authentication and refresh behavior. \ No newline at end of file diff --git a/docs/content/security/session-security-model.md b/docs/content/security/session-security-model.md index e905d138..90e7cb90 100644 --- a/docs/content/security/session-security-model.md +++ b/docs/content/security/session-security-model.md @@ -4,218 +4,460 @@ order: 1 group: security --- +# Session Security Model -# πŸ” Session Security Model +A valid authentication credential is not necessarily sufficient to establish that an authentication session should still be trusted. -UltimateAuth is built around a **hierarchical session model**. +A session may have been revoked, its associated account security state may have changed, or the client may no longer satisfy the conditions under which authentication was established. -This is the foundation of its security design. +UltimateAuth addresses these concerns through a **hierarchical session security model**, with explicit boundaries for session validity, device-associated continuity, and account-level invalidation. -## 🧠 Why a Session Model? +In session-managed authentication modes, the security hierarchy is: -Many systems treat authentication as one of these: +**Root β†’ Chain β†’ Session** -- a cookie -- a bearer token -- a login flag +This page focuses on the security guarantees, validation requirements, and enforcement boundaries of that hierarchy. -That works for simple apps. +For the underlying entities and their lifecycle, see [Session Model & Lifecycle](/docs/sessions-devices/session-model). -It breaks down when you need: +## Security Objectives -- per-device isolation -- targeted revocation -- security state propagation -- reliable reauthentication boundaries +The session security model is designed to support several distinct requirements. -UltimateAuth solves this by modeling authentication as: +| Objective | Security purpose | +|---|---| +| **Session isolation** | Invalidate a particular authentication session without necessarily affecting others | +| **Chain isolation** | Control sessions associated with a particular device or client context | +| **Account-level invalidation** | Invalidate authentication state across the account's session hierarchy | +| **Security state propagation** | Detect changes to account-level authentication security | +| **Context consistency** | Ensure that related session, chain, root, and tenant information remains consistent | +| **Lifecycle enforcement** | Reject authentication state that has expired or been revoked | + +These requirements are related, but they should not be collapsed into a single token expiration check. + +A token can remain structurally valid even when the server-managed authentication state associated with it is no longer acceptable. + +Whether that state is consulted during a protected request depends on the authentication mode. + +## Security Boundaries + +UltimateAuth's session hierarchy separates security responsibilities into three levels. ```text -Root β†’ Chain β†’ Session +Account-level security + | + v + SessionRoot + | + | Account-wide security state + | Security version + | + v + SessionChain + | + | Session continuity + | Device context + | Chain-level revocation + | + v + Session + | + | Individual validity + | Expiration + | Revocation + | + v + Authentication state ``` -
+Each level provides a distinct boundary for evaluating or invalidating authentication state. + +### Root Security Boundary + +The root represents account-level authentication security. + +It provides a common authority for security state that affects sessions associated with the same account. + +This enables account-wide invalidation without requiring the user identity itself to be deleted or recreated. + +### Chain Security Boundary + +A chain groups related authentication sessions and may be associated with a device or client environment. + +It provides a boundary for revoking related sessions together. + +A chain is not proof of physical device identity, and its existence does not automatically mean that the associated device is trusted. + +### Session Security Boundary + +A session represents individual server-managed authentication state. + +It can be independently subject to expiration, revocation, and consistency checks. + +A session is not equivalent to an access token: the token is presented by the client, while the session represents server-managed security state where the selected mode uses it. + +## Layered Session Validation + +In a session-authoritative authentication mode, accepting a presented credential requires more than recognizing its identifier. + +The associated session hierarchy must satisfy the applicable validity and consistency requirements. + +These requirements can be understood in several categories. + +### Session Validity + +The session must satisfy its own security conditions. + +Relevant checks include: + +- The session exists in the authoritative session store. +- The session has not been revoked. +- The session has not exceeded its applicable expiration limits. +- The session is associated with the expected chain. + +A credential referencing a missing, expired, or revoked session must not establish a valid session-authoritative authentication state. + +### Chain Validity + +The associated chain must satisfy the applicable chain security conditions. + +These may include: -## 🧩 The Three Layers +- The chain exists. +- The chain has not been revoked. +- The chain belongs to the expected account and tenant context. +- The session-to-chain relationship is consistent. +- Any applicable chain-level expiration or inactivity conditions are satisfied. + +Chain validation prevents an otherwise recognizable session from bypassing a security restriction applied to its broader session continuity boundary. + +### Root Validity + +The root must satisfy the account-level security requirements. + +Relevant conditions include: + +- The expected root exists. +- The root is associated with the expected identity and tenant context. +- The chain belongs to the expected root. +- The root has not been invalidated. +- Applicable security version requirements are satisfied. + +This prevents a valid-looking session from being accepted when its account-level security authority has changed. + +### Relationship Consistency + +The hierarchy must also be internally consistent. + +A session cannot be considered valid merely because a session record, a chain record, and a root record each exist independently. + +Their ownership and association must match the expected authentication context. + +This is especially important in multi-tenant applications, where a mismatched tenant or account association must not be accepted as a valid authentication relationship. + +**The security decision concerns the entire applicable hierarchy, not an isolated record.** + +## Security Versioning + +Security versioning allows UltimateAuth to represent changes in account-level authentication security. + +A root maintains a security version, and a session can retain the version associated with its creation. + +During validation, the system can compare these values. + +### Matching Security State + +```text +Root.SecurityVersion = 7 +Session.SecurityVersionAtCreation = 7 + +Security version check: PASS +``` + +The session was created under the current security version. + +This does not mean that the session is automatically valid; expiration, revocation, and other applicable checks must still succeed. + +### Changed Security State + +```text +Root.SecurityVersion = 8 +Session.SecurityVersionAtCreation = 7 + +Security version check: FAIL +``` -### πŸ”Ή Root +The session was created under an earlier account-level security state. -A **Root** represents the user-level authentication authority. +When security version validation is enforced, the mismatch invalidates that session. -It is: +### Why Versioning Matters -- tenant-bound -- user-bound -- long-lived -- security-versioned +Without a shared security version, an account-wide security change may require every individual session to be located and updated before the change can be enforced. -The root is the highest-level trust anchor for authentication state. +A security version provides a common condition against which existing session state can be evaluated. -### πŸ“± Chain +This can support responses to events such as: -A **Chain** represents a device-level authentication boundary. +- Credential changes or recovery. +- Administrative security actions. +- Account compromise. +- Account-level session invalidation. -It groups sessions that belong to the same device or client context. +However, the triggering operation and configured session revocation policy determine whether a security version change should occur. -A chain is where UltimateAuth models: +Not every credential change must automatically invalidate every session. -- device continuity -- touch activity -- rotation tracking -- device-level revoke +For example, UltimateAuth supports credential-related session handling policies such as `KeepAll`, `KeepCurrent`, and `RevokeAll`. -### πŸ”‘ Session +A security operation must preserve the intended policy semantics rather than indiscriminately invalidating the entire account hierarchy. -A **Session** is a single authentication instance. +See [Credentials](/docs/plugin-domains/credentials). -It is the most granular identity proof in the system. +## Device Context Validation -A session contains: +Device-associated session management can add security conditions to session validation. -- creation time -- expiration time -- revocation state -- security version snapshot -- chain relationship -- device snapshot -- claims snapshot +A chain may be associated with a device identifier or other client context. +When device binding checks are configured and enforced, the system can compare the expected context with the context presented by a request. -## πŸ”— Relationship Model +For example: ```text -User - └── Root - └── Chain (device) - └── Session (login instance) +Chain.DeviceId = device-A +Request.DeviceId = device-B + +Device context mismatch ``` -πŸ‘‰ Root answers: **what is the current security authority for this user?** -πŸ‘‰ Chain answers: **which device context is this?** -πŸ‘‰ Session answers: **which authentication instance is being used?** +A configured mismatch policy may reject the operation. + +However, this comparison must be interpreted correctly. + +### Identification Is Not Authentication + +A device identifier is an identifying value, not necessarily a secret. + +If an attacker can copy or forge that value, matching the identifier does not prove that the request originated from the legitimate physical device. + +Device context validation can enforce consistency with recorded metadata, but stronger device authentication requires an appropriate proof mechanism. + +### Device Metadata Is Not a Security Guarantee + +Operating system, browser, platform, and IP information may provide useful context. + +They are not universally stable or unique. + +Changes to this information can occur during legitimate usage, and matching values can occur across unrelated clients. + +Applications should distinguish between: + +- Informational device metadata. +- Configured device consistency requirements. +- Cryptographically established device binding. + +The security guarantee must reflect the mechanism actually enforced. + +See [Device Management](/docs/sessions-devices/device-management). + +## Expiration and Inactivity + +Expiration is a security control, not merely a cleanup mechanism. + +UltimateAuth distinguishes between time-based validity and explicit security invalidation. + +Depending on the configured mode and options, relevant conditions may include: + +| Condition | Security meaning | +|---|---| +| Session lifetime | The authentication state has exceeded its permitted duration | +| Idle timeout | The required activity has not occurred within the allowed interval | +| Sliding expiration | Qualifying activity can extend validity within configured limits | +| Absolute lifetime | Authentication state cannot remain valid beyond a fixed boundary | +| Revocation | Authentication state has been explicitly invalidated | + +A session may be expired without being revoked. + +Similarly, a session may be revoked before reaching its natural expiration. + +These conditions should remain distinguishable for correct enforcement and security diagnostics. + +### Activity Tracking Is Not Universal + +Session activity may be recorded through touch operations. + +However, a locally validated JWT does not necessarily cause a server-side session touch on every protected request. + +Therefore, inactivity enforcement depends on where authoritative activity state is maintained and when it is checked. + +An application should not assume that all authentication modes provide identical idle-timeout guarantees. + +## Hierarchical Revocation + +Revocation allows authentication state to be invalidated before its normal expiration. -## πŸ›‘ Security Versioning +UltimateAuth's hierarchy supports different revocation scopes. -One of the most important protections in UltimateAuth is **security versioning**. +### Session-Level Revocation -A root maintains a security version. +Targets an individual session. -Each session stores the security version that existed at creation time. +Other sessions may remain valid if their own security conditions continue to be satisfied. -Validation compares them. +This is appropriate for terminating a particular authentication instance. -If they no longer match: +### Chain-Level Revocation + +Targets the session continuity associated with a chain. + +It can invalidate related sessions without necessarily affecting other chains belonging to the same account. + +This supports targeted containment when a particular client environment is suspected of compromise. + +### Root-Level Revocation + +Targets the account-level session security boundary. + +It can invalidate the applicable session hierarchy, including chains and sessions associated with that root. + +Security versioning can contribute to this mechanism by invalidating sessions created under earlier account-level security state. + +### Revocation Scope and Enforcement ```text -Session β†’ invalid +Session revoke + -> Targeted session + +Chain revoke + -> Sessions associated with the chain + +Root revoke + -> Account-level session hierarchy ``` -This is how UltimateAuth can invalidate existing sessions after events such as: +These are logical revocation scopes. + +The effect on protected requests depends on whether the authentication mode consults the revoked state. + +**Revoking server-side state is not equivalent to cryptographically invalidating an already-issued JWT.** + +## Authentication Mode Enforcement + +The most important limitation of hierarchical session security is that its enforcement depends on the selected authentication mode. + +| Mode | Protected request enforcement | Revocation considerations | +|---|---|---| +| **PureOpaque** | Uses authoritative server-side session validation | Revoked session state can be rejected on subsequent validation | +| **Hybrid** | Uses server-authoritative opaque credential and session validation | Session and chain revocation can be enforced through authoritative validation | +| **SemiHybrid** | JWT access tokens are validated locally for protected requests | Server-side revocation may not invalidate an already-issued JWT until expiration | +| **PureJwt** | JWT access tokens are primarily validated without a server-side session lookup | Immediate session-hierarchy revocation must not be assumed | + +### Server-Authoritative Modes + +PureOpaque and Hybrid can evaluate protected requests against current server-managed session state. + +This allows applicable expiration, revocation, and security version conditions to affect subsequent authentication decisions. + +### Locally Validated JWT Modes + +SemiHybrid and PureJwt can accept a valid JWT without consulting current session hierarchy state on every protected request. + +Consequently, changes to server-side session security may not immediately affect an already-issued access token. + +The duration of this exposure depends on token lifetime and any additional validation or revocation mechanism configured by the application. + +### Security Tradeoff + +This is a deliberate architectural distinction. + +Server-authoritative validation provides stronger immediate control over active authentication state at the cost of server-side validation work. + +Locally validated JWTs reduce that dependency but cannot inherently provide the same immediate revocation behavior. + +See [Authentication Modes](/docs/fundamentals/authentication-modes). + +## Threat Scenarios -- password change -- credential reset -- account recovery -- administrative security action +The session security model helps address several common security scenarios. -## πŸ” Validation Model +### A Session Credential Is Stolen -Session validation is not a single check. +An attacker obtains authentication material associated with one session. -It is a layered verification process. +If the compromise is detected, the affected session can be revoked. -A session is considered valid only if all of these still hold: +In server-authoritative modes, subsequent attempts to use that session can be rejected through session validation. -- the session exists -- the session is active -- the chain exists -- the chain is active -- the chain belongs to the expected tenant -- the chain matches the session -- the root exists -- the root is not revoked -- the root matches the chain -- the root security version matches the session snapshot +If the attacker possesses an independently valid JWT, the result depends on JWT validation and revocation behavior. -## πŸ“± Device Awareness +### A Client Environment Is Compromised -Chains provide device-level isolation. +An attacker may have access to authentication material associated with a particular browser or application installation. -When a device identifier is available, validation can compare: +Chain-level revocation provides a broader response than revoking only one session. -- device stored on chain -- device presented by request +It can invalidate related session state while preserving unrelated chains where appropriate. -If they do not match, UltimateAuth can reject the request depending on configuration. +### An Account Is Compromised -πŸ‘‰ This makes device-bound security enforceable without turning every authentication flow into a custom implementation. +An account-level security event may require invalidating authentication state across multiple devices. -## ⏱ Expiration and Activity +Root-level invalidation provides a common security boundary for the applicable session hierarchy. -UltimateAuth separates: +The operation must still account for the configured authentication mode and credential-related revocation policy. -- session expiration -- chain activity -- root security state +### A Tenant Relationship Is Inconsistent -This is important. +A session, chain, or root references an unexpected tenant or account relationship. -A session may expire because of time. -A chain may become inactive because of idle timeout. -A root may invalidate everything because security state changed. +A session-authoritative validation process must not accept the credential solely because the individual records exist. -These are different failure modes with different meanings. +The relationships must satisfy the expected security context. -## πŸšͺ Revocation Boundaries +### A Device Identifier Is Spoofed -Revocation is also hierarchical. +An attacker presents a device identifier matching a known client. -### Session revoke -Invalidates one authentication instance. +The identifier match alone does not establish device ownership. -### Chain revoke -Invalidates all sessions for one device. +The request must still satisfy the authentication and applicable session validation requirements. -### Root revoke -Invalidates all chains and sessions for the user. +These scenarios illustrate how different controls address different classes of risk. -πŸ‘‰ This gives UltimateAuth targeted containment. +No single session metadata field or policy provides complete protection against every form of credential compromise. -Instead of β€œlog out everywhere or nowhere,” -you can revoke exactly the right scope. +## Security Design Principles -## πŸ”₯ Why This Matters +The session security model follows several important principles. -This model gives you controls that flat token systems usually do not provide: +**Validate relationships, not just identifiers.** Session, chain, root, account, and tenant associations must remain consistent. -- revoke one device without affecting others -- invalidate sessions after security changes -- reason about device trust explicitly -- separate authentication lifetime from token lifetime +**Separate authentication credentials from authoritative state.** A credential can reference security state without being equivalent to that state. -## ⚠️ Design Tradeoff +**Apply revocation at the appropriate scope.** Session, chain, and root invalidation serve different security purposes. -This model is more sophisticated than plain JWT-only authentication. +**Do not equate device recognition with device proof.** Client metadata provides context but not necessarily strong device authentication. -That is intentional. +**Respect authentication mode boundaries.** A security control is only effective where its underlying state is actually checked. -UltimateAuth chooses: +**Keep lifecycle policy explicit.** Credential changes, recovery, expiration, and administrative actions may require different invalidation behavior. -- explicit state -- revocability -- traceable security decisions +## Architectural Perspective -over: +UltimateAuth's hierarchical session model provides multiple security boundaries instead of relying on a single token validity check. -- minimal infrastructure -- purely stateless assumptions +The root represents account-level security authority, the chain organizes related session continuity, and the session represents individual server-managed authentication state. -## 🧠 Mental Model +Together, they support targeted revocation, security versioning, relationship validation, and device-aware session control. -If you remember one thing: +Their practical security guarantees depend on the authentication mode and the validation mechanisms enforced during protected operations. -πŸ‘‰ Authentication in UltimateAuth is not β€œa token” -πŸ‘‰ It is a **security hierarchy with revocation boundaries** +**A secure session is not merely one that has not expired. It is one whose applicable security state, ownership relationships, and validation requirements still hold.** -## ➑️ Next Step +## Next Steps -Continue to **Refresh Token Rotation** to understand how continuation security works after login. +- [Session Model & Lifecycle](/docs/sessions-devices/session-model) β€” Understand the underlying hierarchy and lifecycle. +- [Device Management](/docs/sessions-devices/device-management) β€” Explore device context and chain association. +- [Authentication Modes](/docs/fundamentals/authentication-modes) β€” Compare enforcement across authentication modes. +- [Credentials](/docs/plugin-domains/credentials) β€” Understand credential changes and session revocation policies. \ No newline at end of file diff --git a/docs/content/security/token-security.md b/docs/content/security/token-security.md new file mode 100644 index 00000000..515c8f09 --- /dev/null +++ b/docs/content/security/token-security.md @@ -0,0 +1,560 @@ +--- +title: Token Security +order: 2 +group: security +--- + +# Token Security + +Authentication tokens allow clients to present authentication state to protected applications and services. + +However, not all tokens provide the same security guarantees. + +An opaque credential requires authoritative server-side validation. A JSON Web Token (JWT) can be verified locally using its signature and claims. A refresh credential enables authentication to continue without repeating the original login process. + +UltimateAuth supports these different mechanisms through its authentication modes. + +**Token security depends on the credential's purpose, how it is validated, how long it remains valid, and which security state is consulted during validation.** + +This page explains the security characteristics of access and refresh credentials, including issuance, expiration, rotation, replay risks, and revocation boundaries. + +For the overall authentication architecture, see [Authentication Modes](/docs/fundamentals/authentication-modes). + +## Authentication State and Credentials + +A token is authentication material presented by a client. + +It is not the user identity itself. + +Depending on the authentication mode, the token may reference server-managed authentication state or contain signed claims that can be validated without retrieving a session. + +These approaches establish different trust boundaries. + +### Server-Authoritative Authentication + +In server-authoritative modes, a presented opaque credential is validated against server-managed authentication state. + +The server can evaluate the current validity of the corresponding session and applicable security conditions. + +This allows revocation and other authoritative state changes to affect subsequent protected requests. + +### Locally Validated Authentication + +In JWT-based modes, a protected resource can validate a token using its signature, issuer, audience, expiration, and other applicable token requirements. + +The resource may not need to consult the issuing server or a session store for every request. + +This reduces the dependency on server-side session lookups but changes how quickly revocation and authorization changes can be enforced. + +**Neither model makes the token an identity record. The token is a credential whose validity is determined by the selected authentication mechanism.** + +## Token Types + +UltimateAuth uses two principal access credential formats: opaque credentials and JWTs. + +Refresh credentials serve a separate purpose. + +| Credential | Purpose | Validation model | +|---|---|---| +| **Opaque access credential** | Authenticate protected requests | Authoritative server-side validation | +| **JWT access token** | Authenticate protected requests | Cryptographic and claims validation | +| **Opaque refresh credential** | Continue authentication and obtain renewed credentials | Refresh-specific validation and state checks | + +The distinction between access and refresh credentials is important. + +Access credentials are presented to protected resources. + +Refresh credentials are presented to the authentication system for a continuation operation. They should not be accepted as ordinary access credentials. + +## Opaque Access Credentials + +An opaque credential is a value whose contents do not expose the authenticated identity or authorization claims to the client. + +Conceptually: + +```text +Access credential: +q8Vn2...random-opaque-value...M7x +``` + +The client presents the credential, but cannot determine its authorization meaning by decoding it. + +The server resolves and validates the associated authentication state. + +### Security Properties + +Opaque access credentials provide several useful characteristics: + +- Their meaning is determined by authoritative server-side state. +- They do not need to expose identity claims in a client-readable payload. +- Their validity can be affected by session expiration or revocation. +- They can participate in account-level and chain-level security enforcement. + +However, opaque credentials are still bearer credentials unless an additional proof mechanism is enforced. + +An attacker who obtains a usable opaque credential may be able to present it. + +Opacity alone does not prevent credential theft or replay. + +### Server-Side Validation + +In a session-authoritative mode, validation can enforce the applicable session hierarchy requirements, including expiration, revocation, ownership, and security state consistency. + +See [Session Security Model](/docs/security/session-security-model). + +## JWT Access Tokens + +A JWT is a structured token containing signed claims. + +A typical JWT contains three encoded sections: + +```text +header.payload.signature +``` + +Its payload may describe the authenticated subject, tenant, authorization information, token lifetime, and other relevant context. + +A JWT is not encrypted merely because it is encoded and signed. + +Applications should not place secrets or unnecessarily sensitive information in a JWT payload. + +### Cryptographic Validation + +A protected resource must validate the JWT according to the configured token requirements. + +Relevant checks include: + +- Signature validity. +- Accepted signing algorithm and key. +- Expected issuer. +- Intended audience. +- Expiration and other applicable time constraints. +- Required token claims and application-specific restrictions. + +A successfully decoded JWT is not necessarily a valid JWT. + +Likewise, a correctly signed JWT is not automatically acceptable to every resource. + +The resource must verify that the token was issued for its intended security context. + +### Local Validation + +JWTs can be validated without loading current server-side session state. + +This is useful for distributed APIs and services that need to authenticate requests efficiently. + +However, local validation introduces an important limitation: + +**A token can remain cryptographically valid after the underlying account or authorization state has changed.** + +Whether such changes immediately affect access depends on additional validation and revocation mechanisms. + +## Authentication Mode Behavior + +UltimateAuth selects credential behavior according to the configured authentication mode. + +| Mode | Access mechanism | Continuation mechanism | Protected request authority | +|---|---|---|---| +| **PureOpaque** | Opaque session credential | Session lifecycle | Server-managed session state | +| **Hybrid** | Opaque access credential | Opaque refresh credential | Server-managed session state | +| **SemiHybrid** | JWT access token | Server-managed refresh flow | Locally validated JWT | +| **PureJwt** | JWT access token | Optional refresh mechanism | Locally validated JWT | + +### PureOpaque + +PureOpaque uses an opaque session credential. + +It does not require a separate access-token and refresh-token pair. + +The credential is validated through the authoritative session mechanism. + +Session lifecycle operations govern the continuation and validity of authentication state. + +### Hybrid + +Hybrid uses distinct opaque access and refresh credentials. + +Access requests are validated against authoritative server-managed state. + +Refresh operations use the refresh credential to continue authentication according to the configured lifecycle and rotation behavior. + +**Hybrid does not require JWT access tokens.** + +### SemiHybrid + +SemiHybrid issues JWT access tokens while maintaining server-side session state for relevant lifecycle operations. + +Protected requests can validate JWT access tokens locally. + +Refresh and logout interact with the server-managed authentication lifecycle. + +This means that server-side revocation does not necessarily invalidate an already-issued JWT immediately. + +### PureJwt + +PureJwt uses JWT access tokens without requiring the Root β†’ Chain β†’ Session hierarchy for ordinary protected request validation. + +An optional refresh mechanism may maintain refresh-specific state. + +The existence of a refresh store does not make JWT access validation session-authoritative. + +For more detail, see [Authentication Modes](/docs/fundamentals/authentication-modes). + +## Access Token Lifetime + +Access credentials should have lifetimes appropriate to their authentication mode and security requirements. + +Short-lived JWT access tokens are particularly important because a token may remain usable until expiration even after server-side authorization state changes. + +For example, an application might choose a JWT access lifetime of several minutes. + +However, **a lifetime such as 5–15 minutes is an example of an application security choice, not a universal UltimateAuth default or requirement**. + +The appropriate value depends on factors such as: + +- Sensitivity of protected resources. +- Expected frequency of authorization changes. +- Acceptable revocation delay. +- Availability and security of the refresh mechanism. +- Client behavior and network conditions. + +### Lifetime Versus Revocation + +Expiration limits how long a credential can be accepted. + +Revocation invalidates authentication state before its ordinary expiration. + +These mechanisms are related but not interchangeable. + +In server-authoritative modes, current revocation state can be checked during protected requests. + +In locally validated JWT modes, expiration may remain the primary limit on an already-issued access token unless an additional revocation check is implemented. + +## Refresh Credentials + +Refresh credentials allow clients to continue authentication without repeating the original authentication ceremony. + +They are intended for the authentication system's refresh operation, not for general API access. + +In UltimateAuth, refresh behavior is mode-dependent. + +PureOpaque does not require a separate refresh credential pair, while Hybrid and SemiHybrid use refresh flows. PureJwt can support an optional refresh mechanism. + +### Refresh Security Requirements + +A secure refresh operation must establish that the presented credential is eligible for continued authentication. + +Depending on the mode and configured strategy, this may involve checking: + +- Credential existence and validity. +- Expiration. +- Revocation or consumption state. +- Associated user or session context. +- Applicable client and device context. +- Rotation and reuse conditions. + +A valid refresh credential should not automatically bypass account-level or session-level security requirements that apply to the selected mode. + +### Access and Refresh Separation + +Access and refresh credentials have different responsibilities. + +```text +Authentication + | + v +Access credential + | + | Used for protected requests + v +Access expires + | + v +Refresh operation + | + | Validate refresh credential + | Apply continuation rules + v +Renewed access credential +``` + +The refresh operation does not extend the expiration timestamp of an already-issued JWT. + +It issues new authentication material when continuation is permitted. + +## Refresh Rotation + +Refresh rotation replaces refresh authentication material according to the configured refresh strategy. + +A rotating refresh mechanism can invalidate the previously used refresh credential and issue a replacement. + +Conceptually: + +```text +Refresh credential A + | + | Successful refresh + v +Refresh credential A consumed +Refresh credential B issued +``` + +This reduces the period during which an older refresh credential remains reusable. + +However, **rotation behavior is not identical across every UltimateAuth mode or refresh strategy**. + +Some session continuation operations may update existing server-side state without replacing a separate refresh credential. + +The effective behavior must follow the selected mode and its configured refresh strategy. + +### Rotation Is Not Replay Prevention by Itself + +Rotation limits credential reuse only when the server enforces the relevant consumption or replacement state. + +A secure rotating refresh mechanism must account for situations such as: + +- The same credential being submitted again. +- Concurrent refresh requests. +- A credential being reused after replacement. +- An interrupted response after a successful rotation. +- An attacker and legitimate client attempting to use the same credential. + +The exact outcome depends on the implemented refresh strategy and concurrency guarantees. + +Rotation should not be described as automatically eliminating every replay risk. + +## Refresh Token Reuse + +Refresh token reuse occurs when a credential is presented after it has already been consumed, replaced, or otherwise invalidated. + +For a single-use rotating refresh credential, this is a security-relevant event. + +Conceptually: + +```text +Client refreshes with A + | + v +A replaced by B + | + v +Another request presents A + | + v +Reuse / invalid credential condition +``` + +A reuse attempt may indicate credential theft, but it can also arise from concurrent requests or client synchronization failures. + +The refresh implementation must distinguish these situations according to its supported guarantees and policy. + +### Security Response + +Possible security responses include: + +- Rejecting the invalid refresh attempt. +- Invalidating the associated refresh state. +- Revoking an affected session. +- Revoking a broader session chain where supported and appropriate. + +These are distinct actions. + +The presence of a reuse-detection mechanism does not imply that every reuse attempt automatically revokes the entire device-associated chain. + +Such behavior must be explicitly supported by the configured refresh strategy. + +## Refresh Credential Storage + +Refresh credentials are generally more sensitive to long-term compromise because they may allow new access credentials to be obtained. + +Their storage therefore deserves stronger protection than ordinary client-visible access metadata. + +### Server-Side Storage + +For persisted opaque refresh credentials, storing a one-way verifier or hash instead of the raw credential can reduce the impact of a storage disclosure. + +However, the exact persistence model depends on the selected authentication mode and refresh implementation. + +It should not be assumed that every authentication mode uses the same refresh storage structure. + +### Client-Side Storage + +The appropriate client storage mechanism depends on the application environment. + +For browser applications, HttpOnly cookies can prevent JavaScript from directly reading refresh credentials, although cookie-based refresh flows still require appropriate CSRF and origin protections. + +Native applications may use platform-provided secure storage where suitable. + +Refresh credentials should not be exposed through application logs, diagnostic output, URLs, or unrelated API responses. + +### Transport Security + +Authentication credentials must be protected in transit. + +Production authentication and refresh endpoints should use HTTPS. + +Applications should also avoid sending refresh credentials to services that do not need to receive them. + +## Claims and Authorization Freshness + +JWT claims represent information established when the token was issued. + +They may include standard or application-specific information such as: + +```text +sub Subject identifier +iss Issuer +aud Audience +exp Expiration +iat Issued-at time +jti Token identifier +``` + +Depending on the application, a JWT may also include tenant information, roles, permissions, or session references. + +These claims are not automatically updated when the underlying account changes. + +### Example: Permission Removal + +Consider the following sequence: + +1. A JWT is issued containing an administrative permission. +2. An administrator removes that permission from the user. +3. The client continues presenting the previously issued JWT. + +If the protected resource relies only on the signed claims in that JWT, the removed permission may remain effective until the token expires. + +The signature remains valid because the token has not been modified. + +### Authorization Strategies + +Applications can address this limitation through their security architecture, including: + +- Shorter JWT lifetimes. +- Reissuing claims during refresh. +- Consulting authoritative authorization state for sensitive operations. +- Using server-authoritative authentication modes where immediate state enforcement is required. + +The appropriate approach depends on the security requirements of the application. + +See [Authorization](/docs/plugin-domains/authorization) and [Policies & Access Control](/docs/plugin-domains/policies). + +## Token Revocation + +Revocation behavior differs fundamentally between opaque credentials and locally validated JWTs. + +### Opaque Credential Revocation + +An opaque credential depends on authoritative validation. + +If the corresponding server-managed authentication state is revoked, subsequent validation can reject the credential. + +The precise scope depends on whether the security operation targets a session, chain, or account-level root. + +### JWT Revocation + +A signed JWT cannot be made cryptographically invalid merely by deleting a server-side session record. + +Its signature and expiration remain unchanged. + +To reject an otherwise valid JWT before expiration, the resource must enforce an additional condition, such as an authoritative revocation check or another supported invalidation mechanism. + +Without that additional enforcement, a locally validated JWT may remain acceptable until expiration. + +### Refresh Revocation + +Revoking a refresh credential can prevent further successful refresh operations. + +However, that does not necessarily invalidate access tokens already issued before the revocation. + +**Access revocation and refresh revocation are separate security concerns.** + +## Session Security and Token Security + +Token security and session security are closely related, but they operate at different levels. + +| Concern | Token Security | Session Security | +|---|---|---| +| Credential format | Opaque or JWT | Root, chain, and session state | +| Cryptographic validation | JWT signature and claims | Not a substitute for token signature validation | +| Expiration | Token validity period | Session and chain lifecycle limits | +| Rotation | Replacement of authentication material | Continuity and associated state transitions | +| Revocation | Whether a presented credential remains acceptable | Which authentication state has been invalidated | +| Device context | May be associated with token use | Managed through applicable chain and session context | + +A token may be valid according to its own format but unacceptable under authoritative session security rules. + +Conversely, server-side session state may remain active while a particular access token has expired. + +Both levels must be understood to reason correctly about the selected authentication mode. + +## Common Security Misconceptions + +### A Valid Signature Means Current Authorization + +A valid JWT signature proves that the token has not been modified and was signed by an accepted key. + +It does not prove that the user's permissions or account status have remained unchanged since issuance. + +### A Device Identifier Prevents Token Theft + +A device identifier may support context consistency checks. + +It does not independently prevent a stolen bearer credential from being replayed. + +### Refresh Rotation Makes Token Theft Impossible + +Rotation can reduce reuse opportunities and support detection of invalid reuse. + +It does not prevent a stolen credential from being used before the legitimate client or server detects the compromise. + +### Logout Invalidates Every JWT Immediately + +Logout may revoke server-managed session or refresh state. + +Previously issued JWT access tokens can remain valid until expiration unless protected resources enforce an additional invalidation mechanism. + +### Short-Lived Tokens Eliminate the Need for Session Security + +Short lifetimes reduce the exposure window of a compromised token. + +They do not replace account-level security policies, credential recovery protections, or session revocation controls where those capabilities are required. + +## Security Design Principles + +UltimateAuth's token model is best understood through a few principles. + +**Use credentials only for their intended purpose.** Access and refresh credentials must not be interchangeable. + +**Validate according to the selected mode.** Opaque credentials require authoritative validation; JWTs require correct cryptographic and claims validation. + +**Treat JWT claims as issuance-time information.** Current authorization state may differ from what an older token contains. + +**Keep access credentials appropriately short-lived.** Token lifetime should reflect the application's acceptable exposure window. + +**Enforce refresh continuation rules.** Rotation, expiration, consumption, and reuse behavior must be consistent with the configured strategy. + +**Distinguish token expiration from session revocation.** They invalidate authentication through different mechanisms. + +**Do not promise immediate JWT revocation without enforcement.** Server-side state changes affect locally validated tokens only when the resource checks the relevant state. + +## Architectural Perspective + +UltimateAuth does not impose a single token model on every application. + +PureOpaque and Hybrid emphasize server-authoritative authentication state. + +SemiHybrid and PureJwt support locally validated JWT access, with different approaches to authentication continuation. + +These choices affect performance, storage requirements, revocation behavior, and authorization freshness. + +The purpose of the token security model is to make those tradeoffs explicit. + +**A token establishes access only while its applicable validation requirements remain satisfied. The strength and freshness of those requirements depend on the authentication mode.** + +## Next Steps + +- [Session Security Model](/docs/security/session-security-model) β€” Understand authoritative session validation and hierarchical revocation. +- [Authentication Modes](/docs/fundamentals/authentication-modes) β€” Compare credential behavior across modes. +- [Session Model & Lifecycle](/docs/sessions-devices/session-model) β€” Explore session creation, continuation, and invalidation. +- [Credentials](/docs/plugin-domains/credentials) β€” Understand credential management and recovery security. \ No newline at end of file diff --git a/docs/content/sessions-devices/device-management.md b/docs/content/sessions-devices/device-management.md new file mode 100644 index 00000000..80bc61be --- /dev/null +++ b/docs/content/sessions-devices/device-management.md @@ -0,0 +1,308 @@ +--- +title: Device Management +order: 3 +group: sessions-devices +--- + +# Device Management + +Authentication does not happen in isolation from the device or client environment in which it takes place. + +A user may authenticate from a desktop browser, a mobile application, or several different environments simultaneously. Each environment can have its own authentication state, activity history, and security requirements. + +UltimateAuth supports **device-aware session management** by associating authentication sessions with device context and organizing related sessions through `SessionChain`. + +This provides a foundation for device-level visibility, session continuity, and targeted security operations without treating device identity as a substitute for user authentication. + +## Understanding Device-Aware Authentication + +Traditional token-based authentication can be implemented without explicitly modeling the client environment. + +A token is issued, validated, and eventually expires or is revoked. + +That approach is sufficient for some applications, but it becomes limiting when an application needs to distinguish between different client environments belonging to the same user. + +For example: + +- A user signs in from both a desktop browser and a mobile application. +- An administrator needs to revoke sessions associated with one client environment. +- A session is refreshed repeatedly while remaining associated with the same device context. +- An application needs to apply session limits or track activity across client environments. + +UltimateAuth separates these concerns through its session and chain model. + +The objective is not to prove the physical identity of a device. It is to provide a structured way to associate authentication activity with a recognizable client context. + +## Device Context and SessionChain + +The central concept in UltimateAuth's device-aware model is `SessionChain`. + +A session represents authentication state. + +A `SessionChain` groups related session activity under a persistent chain identity, which can be associated with a device or client environment. + +Conceptually: + +```text +User Identity + | + +-- SessionChain (Desktop Browser) + | | + | +-- Session + | +-- Session / Rotation History + | + +-- SessionChain (Mobile Application) + | + +-- Session + +-- Session / Rotation History +``` + +This diagram illustrates the relationship between identity, chain, and session. It does not imply that every authentication mode retains multiple simultaneous session records or stores a complete rotation history. + +### A Chain Is Not a Physical Device + +A `SessionChain` should not be understood as a physical hardware identifier. + +It represents a logical continuity boundary for related authentication sessions. + +A single physical device may use different browsers, application installations, or isolated client storage environments. + +Conversely, device identifiers supplied by clients are not necessarily reliable proof that requests originate from the same physical device. + +The practical relationship is: + +**A device context can be associated with a SessionChain, but the chain itself is an authentication-session concept.** + +This distinction is important when designing security rules around device recognition and session revocation. + +## Device Information + +Device-aware session management can use information describing the client environment. + +Relevant information may include: + +| Information | Purpose | +|---|---| +| `DeviceId` | Identifies a client-reported or application-managed device context | +| Platform | Distinguishes environments such as web and mobile | +| Operating system | Provides client environment metadata | +| Browser | Describes the browser environment when applicable | +| IP address | Provides network context for security and activity analysis | + +The availability and reliability of this information depend on the client platform and the application's integration. + +Not every client can provide the same metadata. + +### DeviceId and Trust + +A `DeviceId` is useful for identifying a client environment across requests or authentication operations. + +However, an identifier alone is not proof of device ownership. + +A value stored in browser storage, for example, may be copied, cleared, or replaced. An identifier generated by a mobile application may change after application data is removed. + +For that reason, device recognition must not be confused with authentication. + +UltimateAuth's authentication and session validation rules remain responsible for determining whether a request is authorized to use the associated authentication state. + +### Device Fingerprinting + +Platform, operating system, browser, and network information may help describe a client environment. + +These attributes are not inherently unique or stable. + +Multiple devices can share similar attributes, and a single device can present different attributes over time. + +They should therefore be treated as contextual signals rather than a guaranteed device fingerprint. + +Applications requiring stronger device assurance need an appropriate proof or binding mechanism in addition to ordinary metadata. + +## Device Continuity + +Device continuity describes how authentication activity remains associated with an existing client context. + +A session chain provides the logical structure for maintaining that continuity. + +### First Authentication + +When an authentication flow establishes a new session, the session infrastructure determines the appropriate chain association. + +If a new chain is required, the session is associated with that chain. + +The resulting chain can represent the beginning of a device-associated authentication history. + +### Subsequent Authentication + +A later authentication from the same client environment may be associated with an existing chain when the configured matching and security conditions permit it. + +However, recognizing a device identifier does not automatically require the system to reuse a previous chain. + +Chain association must respect the applicable lifecycle, validity, and security rules. + +For example, a revoked chain should not become trusted again merely because a client presents the same device identifier. + +### Device Continuity Is Not Session Continuity + +A client environment can remain recognizable even when its previous authentication session has expired. + +Likewise, a new authentication session can be established without inheriting the validity of an earlier session. + +The chain model allows related activity to be organized while keeping session validity subject to authentication and security rules. + +## Activity Tracking + +Device-associated session management can maintain information about authentication activity. + +Examples include: + +- When a chain was created. +- When related authentication activity was last observed. +- How often relevant session activity was recorded. +- How many rotation operations were associated with the chain. + +Fields such as `LastSeenAt`, `TouchCount`, and `RotationCount` help describe these aspects of the session lifecycle. + +### Touch Operations + +A touch operation records qualifying session activity according to the configured session behavior. + +Depending on the authentication mode and session strategy, this may update activity metadata such as the last-seen timestamp or touch counter. + +Touch behavior should not be interpreted as a guarantee that every authenticated HTTP request updates persistent device activity. + +Different authentication modes have different requirements for consulting and updating server-side session state. + +### Rotation Tracking + +Token or session rotation may replace authentication material while preserving the logical relationship with an existing chain. + +Where rotation tracking is supported, the chain can maintain information about those transitions. + +This makes it possible to distinguish the continuity of a client environment from the lifecycle of an individual credential or session. + +Rotation counts are operational signals. They do not independently establish that a device is legitimate or that suspicious activity has occurred. + +## Logout and Device Revocation + +Logout and chain revocation address different security scopes. + +### Logout + +Logout terminates the authentication state targeted by the logout operation. + +Its precise effect depends on the authentication mode, the credentials involved, and the configured session behavior. + +A logout operation does not necessarily mean that every session associated with the same device context has been revoked. + +It also does not necessarily require deleting the associated chain record. + +### Chain Revocation + +Chain revocation targets the logical group of sessions associated with a chain. + +It provides a broader security operation than terminating a single session. + +This is useful when an application needs to invalidate the authentication continuity associated with a particular client environment. + +For example, if a user believes a mobile application session has been compromised, the application may revoke the corresponding chain rather than revoking unrelated sessions associated with other environments. + +### Revocation Does Not Establish Device Trust + +A retained chain record is not equivalent to a trusted device. + +Likewise, removing or revoking a chain does not physically block a device from attempting to authenticate again. + +Whether a new authentication attempt is permitted depends on the authentication flow and the applicable security policies. + +The chain model provides a boundary for managing authentication state; it is not a permanent device allowlist or hardware security mechanism. + +## Device Binding and Security Boundaries + +Device-aware authentication can associate session state with device context. + +However, the strength of that association depends on what is actually validated. + +There is an important distinction between: + +- **Device metadata:** Information describing a client environment. +- **Device identification:** An identifier used to recognize a client context. +- **Device binding:** A security requirement connecting authentication material to a validated device-related condition. +- **Device authentication:** Proof that a client possesses an appropriate device-bound secret or cryptographic key. + +These concepts provide different levels of assurance. + +An application must not assume that supplying a `DeviceId` alone prevents stolen credentials from being reused elsewhere. + +Security guarantees depend on the binding and validation mechanisms enforced by the configured authentication infrastructure. + +## Device Management Across Authentication Modes + +UltimateAuth supports different authentication modes, and device-aware session behavior must be understood within those modes. + +| Mode | Device and session considerations | +|---|---| +| **PureOpaque** | Server-side session state is authoritative for protected requests, allowing session validity to be evaluated against server-managed state. | +| **Hybrid** | Opaque access and refresh credentials are associated with server-authoritative session state. | +| **SemiHybrid** | Server-side session state participates in lifecycle operations, while issued JWT access tokens may remain valid until expiration. | +| **PureJwt** | Protected requests are primarily validated from JWT contents; server-side device and session enforcement depends on the configured supporting infrastructure. | + +The presence of a device-associated chain does not imply identical enforcement behavior in every mode. + +### Revocation and JWT Validity + +In modes that validate protected requests against authoritative server-side session state, session revocation can affect subsequent requests through that validation. + +In JWT-based modes, a previously issued access token may remain valid until expiration if protected requests do not consult current revocation state. + +Therefore, revoking a device-associated chain must not be described as universally invalidating every previously issued access token immediately. + +See [Authentication Modes](/docs/fundamentals/authentication-modes). + +## Session and Device Limits + +Applications may need to limit how many authentication sessions or client environments a user can maintain. + +UltimateAuth's session configuration provides the architectural place for managing supported session and chain constraints. + +Depending on the configured capabilities, these constraints may concern: + +- The number of chains associated with a user. +- The number of sessions associated with a chain. +- Session expiration and activity behavior. +- The treatment of device context changes. +- Revocation and lifecycle rules. + +The exact available options and enforcement behavior depend on the authentication mode and session configuration. + +Device management should not be understood as a separate enforcement system operating independently of session management. + +## Operational Visibility + +Device-associated session information can help applications present useful security information to users or administrators. + +For example, an account management interface may display recognizable client environments and provide an option to revoke related sessions. + +Such interfaces should distinguish between recorded information and verified facts. + +A browser name or IP address may help a user recognize an activity record, but it does not conclusively identify the person or physical device involved. + +Likewise, activity counters can support monitoring without independently providing anomaly detection. + +UltimateAuth provides session and chain concepts that applications can use for these experiences, while the application's security and user-interface requirements determine how the information should be presented. + +## Architectural Perspective + +Device management in UltimateAuth is built on the relationship between identity, session, and chain. + +**The user identity identifies the account. The session represents authentication state. The SessionChain organizes related session continuity, potentially associated with a device context.** + +This model enables targeted session management and device-aware security operations without making device metadata an independent source of authentication trust. + +It also preserves an important architectural boundary: device recognition, session validity, and authorization remain distinct security concerns. + +## Next Steps + +- [Session Model](/docs/sessions-devices/session-model) β€” Understand sessions, chains, and their lifecycle. +- [Session Security](/docs/sessions-devices/session-security) β€” Explore expiration, rotation, touch, and revocation. +- [Authentication Modes](/docs/fundamentals/authentication-modes) β€” Compare session validation across authentication modes. +- [Request Lifecycle](/docs/fundamentals/request-lifecycle) β€” Understand how protected requests are authenticated and authorized. \ No newline at end of file diff --git a/docs/content/sessions-devices/session-lifecycle.md b/docs/content/sessions-devices/session-lifecycle.md new file mode 100644 index 00000000..65632538 --- /dev/null +++ b/docs/content/sessions-devices/session-lifecycle.md @@ -0,0 +1,424 @@ +--- +title: Session Model & Lifecycle +order: 2 +group: sessions-devices +--- + +# Session Model & Lifecycle + +Authentication is more than issuing a credential and checking its expiration. + +In stateful authentication systems, successful authentication establishes security state that must be managed throughout its lifetime. Sessions may be refreshed, extended, revoked, or invalidated in response to changes in account security. + +UltimateAuth models this state through a structured hierarchy: + +**Root β†’ Chain β†’ Session** + +This hierarchy separates account-level authentication security, device-associated session continuity, and individual authentication sessions. + +The model provides the foundation for session management, security versioning, targeted revocation, and lifecycle enforcement. + +Its runtime behavior depends on the configured authentication mode. + +## Understanding the Session Hierarchy + +UltimateAuth organizes server-managed authentication state into three levels. + +| Level | Represents | Primary responsibility | +|---|---|---| +| **SessionRoot** | Account-level authentication security | Global session state and security versioning | +| **SessionChain** | Related session continuity | Device association, activity, and chain-level control | +| **Session** | Individual authentication state | Session validity, expiration, and revocation | + +Conceptually: + +```text +User Identity + | + └── SessionRoot + | + β”œβ”€β”€ SessionChain (Desktop Browser) + | | + | β”œβ”€β”€ Session + | └── Session + | + └── SessionChain (Mobile Application) + | + └── Session +``` + +This is a logical representation of the hierarchy. It does not imply that every authentication mode stores the same records or maintains multiple simultaneous sessions within each chain. + +Each level addresses a different security concern. + +## SessionRoot: Account-Level Security + +`SessionRoot` represents the account-level security boundary for server-managed authentication sessions. + +It associates the authentication session hierarchy with a user identity and maintains security information relevant across that identity's sessions. + +The root is not the user account itself. + +The Users domain may own the user's identifiers, profile, and lifecycle. The session root belongs to authentication security management. + +### Root Responsibilities + +A session root provides a common boundary for: + +- Associating session chains with an identity. +- Maintaining account-level session security state. +- Coordinating global session invalidation. +- Supporting security version checks. + +The distinction is important because changes to authentication security do not necessarily require changes to the user entity. + +For example, invalidating existing sessions should not require recreating the user or changing the user's identifiers. + +### Security Versioning + +UltimateAuth's session model supports security versioning. + +A root can maintain a security version representing the current account-level authentication security state. + +Sessions can retain the corresponding version from their creation. + +Conceptually: + +```text +Root.SecurityVersion = 4 +Session.SecurityVersionAtCreation = 4 + +Result: Version matches +``` + +After an account-level security invalidation: + +```text +Root.SecurityVersion = 5 +Session.SecurityVersionAtCreation = 4 + +Result: Version mismatch +``` + +When the applicable session validation checks detect a version mismatch, the older session is no longer valid. + +This allows the authentication system to invalidate previously established session state through a shared security boundary. + +The effect on already-issued JWT access tokens still depends on whether protected requests consult current server-side security state. + +## SessionChain: Session Continuity + +`SessionChain` represents a logical grouping of related authentication sessions. + +A chain can be associated with a particular device or client environment, allowing session activity to be managed within a recognizable continuity boundary. + +For example, a user may have separate chains for a desktop browser and a mobile application. + +However, a chain is not a physical device identity or a guarantee that the device has been cryptographically verified. + +### Chain Responsibilities + +A session chain can provide a boundary for: + +- Associating related sessions. +- Maintaining device or client context. +- Recording relevant activity. +- Tracking session rotation. +- Applying chain-level security operations. +- Revoking related sessions together. + +This allows UltimateAuth to distinguish between the lifecycle of an individual session and the continuity of the environment associated with it. + +### Device Association + +A chain may include device-related information such as a device identifier, platform, operating system, or browser. + +These attributes help describe the client environment. + +They must not be treated as independent proof of device ownership. + +See [Device Management](/docs/sessions-devices/device-management) for the device association model and its security boundaries. + +## Session: Individual Authentication State + +A `Session` represents an individual unit of server-managed authentication state. + +It is associated with a chain and participates in the validity rules established by the authentication mode and session configuration. + +A session may contain information relevant to: + +- Its associated identity and chain. +- Creation and expiration. +- Revocation status. +- Security version at creation. +- Authentication lifecycle and validity. + +A session is not necessarily equivalent to an access token. + +In opaque authentication modes, a client credential may reference server-managed session state. + +In JWT-based modes, an issued access token may be validated locally without consulting the session hierarchy on every protected request. + +**The token is authentication material. The session is a security-state concept.** + +Keeping these concepts separate is essential for understanding UltimateAuth's authentication modes. + +## Session Creation + +A session lifecycle begins when an authentication flow successfully establishes the required authentication state. + +For modes using the server-managed session hierarchy, the process conceptually involves: + +1. Resolving the authenticated user identity. +2. Resolving or establishing the corresponding session root. +3. Determining the appropriate session chain. +4. Creating or updating the required session state. +5. Issuing the authentication material required by the selected mode. + +The authentication flow must satisfy its verification and security requirements before establishing a valid authenticated session. + +### Root Resolution + +An existing account-level session root can be reused when appropriate. + +If the required root does not exist, the session infrastructure can establish it as part of session creation. + +### Chain Resolution + +The session infrastructure determines whether authentication should be associated with an existing chain or a new chain. + +A previously observed device identifier does not automatically establish that an existing chain is valid or eligible for reuse. + +The decision must respect the applicable chain lifecycle and security conditions. + +### Session Establishment + +Once the appropriate security context has been established, the authentication system creates or updates the session state required by the authentication mode. + +The resulting client credential is then issued according to that mode. + +## Active Session Usage + +After authentication, the client presents the appropriate authentication material when accessing protected resources. + +What happens next depends on the configured mode. + +In session-authoritative modes, protected requests can be evaluated against current server-managed session state. + +In locally validated JWT modes, protected requests may be authenticated using the token's cryptographic validity and claims without loading the current session record. + +This distinction affects session activity tracking, expiration enforcement, and revocation behavior. + +### Touch Operations + +A touch operation records qualifying session activity. + +Depending on the session strategy, it may update information such as: + +- `LastSeenAt` +- `TouchCount` +- Relevant session activity or expiration state + +Touch behavior is not necessarily performed for every HTTP request. + +Applications should not assume that an authenticated request always results in a persistent session update. + +### Sliding Expiration + +Sliding expiration allows eligible authentication state to remain valid as qualifying activity occurs. + +Unlike a fixed expiration, sliding expiration can extend a session's usable lifetime according to configured rules. + +However, sliding expiration does not necessarily remove absolute lifetime restrictions. + +A session may still become invalid when a maximum permitted lifetime is reached. + +The exact behavior depends on the selected authentication mode and its session configuration. + +## Refresh and Rotation + +Refresh and rotation are related to session lifecycle but are not identical operations. + +**Refresh** is an authentication operation used to continue or renew authentication state. + +**Rotation** replaces authentication material or session-related credentials according to the configured security strategy. + +The relationship between these operations varies by authentication mode. + +### PureOpaque + +PureOpaque uses server-authoritative opaque session authentication. + +It does not require a separate access-token and refresh-token pair. + +Session continuity and activity management are handled through the applicable opaque session lifecycle. + +### Hybrid + +Hybrid uses distinct opaque access and refresh credentials backed by server-authoritative session state. + +Refresh operations can renew authentication material while preserving the appropriate session or chain relationship. + +The exact rotation behavior follows the configured refresh strategy. + +### SemiHybrid + +SemiHybrid uses JWT access tokens while maintaining server-side session state for lifecycle operations such as refresh and logout. + +Refresh may issue new JWT access material and update the relevant server-managed state. + +Already-issued access tokens may remain valid until expiration because protected requests do not necessarily consult that state. + +### PureJwt + +PureJwt primarily relies on stateless JWT validation for protected requests. + +A refresh mechanism may use an independent refresh store, but the server-managed Root β†’ Chain β†’ Session hierarchy must not be assumed to govern every protected request. + +This is an important distinction between the framework's common authentication concepts and the persistence requirements of individual modes. + +See [Authentication Modes](/docs/fundamentals/authentication-modes). + +## Session Expiration + +Session expiration defines when authentication state is no longer eligible for continued use. + +Depending on the authentication mode and configuration, expiration may involve: + +| Expiration mechanism | Meaning | +|---|---| +| **Fixed lifetime** | Authentication state expires after a defined duration | +| **Idle timeout** | Authentication state expires after insufficient qualifying activity | +| **Sliding expiration** | Eligible activity extends the usable lifetime | +| **Absolute expiration** | Authentication state cannot extend beyond a maximum lifetime | + +These mechanisms are not necessarily all enabled or applicable in every authentication mode. + +### Expired Versus Revoked + +Expiration and revocation represent different reasons for invalidation. + +**Expiration** occurs because a configured time-based condition has been reached. + +**Revocation** occurs because authentication state has been explicitly invalidated through a security operation. + +Both can make a session unusable, but they represent different lifecycle events and may be recorded or enforced differently. + +## Session Revocation + +UltimateAuth's hierarchical session model supports security operations at different levels. + +The scope of revocation determines how broadly authentication state is affected. + +### Session-Level Revocation + +Session-level revocation targets an individual session. + +It is appropriate when a specific authentication instance must be invalidated without necessarily affecting other sessions associated with the same user. + +### Chain-Level Revocation + +Chain-level revocation targets the session continuity represented by a chain. + +It allows related sessions to be invalidated together. + +This can be useful when a particular client environment is suspected of compromise or when a user wants to terminate sessions associated with one device context. + +Revoking a chain does not mean that the corresponding physical device is permanently blocked from authenticating again. + +### Root-Level Revocation + +Root-level revocation affects the account-level authentication security boundary. + +It provides a mechanism for invalidating authentication state associated with the user's session hierarchy. + +Security version changes can support this operation by making previously created sessions fail subsequent version validation. + +This is useful for broad security responses, including operations that require invalidating all applicable existing sessions. + +### Revocation Scope + +Conceptually: + +```text +Session revocation + └── One session + +Chain revocation + └── Related sessions in one chain + +Root revocation + └── Account-level session hierarchy +``` + +The effective invalidation of protected requests depends on the authentication mode. + +In particular, server-side revocation does not automatically invalidate a previously issued JWT that is still cryptographically valid and is not checked against current revocation state. + +## Session Security and Credential Changes + +Session lifecycle is closely related to credential security. + +A password change or recovery operation may require existing authentication sessions to be preserved or revoked. + +UltimateAuth supports session handling policies for credential changes and resets, including: + +| Policy | Behavior | +|---|---| +| `KeepAll` | Preserve applicable existing sessions | +| `KeepCurrent` | Preserve the current session while revoking other applicable sessions | +| `RevokeAll` | Revoke all applicable sessions | + +These policies allow applications to choose the security response appropriate to the operation. + +For example, an authenticated password change and an anonymous password recovery operation may require different session handling. + +The Credentials domain initiates the relevant security behavior, while the session infrastructure applies the corresponding lifecycle operations. + +See [Credentials](/docs/plugin-domains/credentials). + +## Session Configuration + +Session behavior is configured through UltimateAuth's session options, including `UAuthSessionOptions`. + +The configuration model governs supported aspects of session lifecycle such as: + +- Session lifetimes. +- Activity and expiration behavior. +- Refresh and rotation strategies. +- Session and chain limits. +- Device-related session constraints. +- Revocation and lifecycle enforcement. + +Not every option applies to every authentication mode. + +For example, a configuration setting that controls server-side session activity may not affect the validation of a stateless JWT access token. + +Applications should configure session behavior with the selected authentication mode and its enforcement boundaries in mind. + +Detailed configuration examples belong in the configuration reference and How-to Guides. + +## Architectural Perspective + +UltimateAuth separates authentication identity, session continuity, and individual authentication state. + +The Root β†’ Chain β†’ Session hierarchy expresses those responsibilities at different security levels: + +- **Root** provides account-level authentication security. +- **Chain** organizes related session continuity. +- **Session** represents individual server-managed authentication state. + +This structure supports targeted session management without requiring all security operations to act on every session belonging to a user. + +At the same time, UltimateAuth does not assume that every authentication mode requires identical server-side state. + +The hierarchy is most directly relevant to session-managed authentication, while JWT-based modes preserve their own validation and lifecycle characteristics. + +**Authentication establishes trust. Session management determines how that trust is maintained, renewed, and invalidated over time.** + +## Next Steps + +- [Device Management](/docs/sessions-devices/device-management) β€” Device context, chain association, and device-aware session management. +- [Session Security](/docs/sessions-devices/session-security) β€” Session expiration, rotation, revocation, and security controls. +- [Authentication Modes](/docs/fundamentals/authentication-modes) β€” Understand how session behavior differs between modes. +- [Request Lifecycle](/docs/fundamentals/request-lifecycle) β€” Learn how protected requests validate authentication state. \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json index 9515fdd8..62941635 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json @@ -1,7 +1,7 @@ { "Slug": "auth-flows/index", "Title": "Auth Flows", - "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./login-flow.md\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./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", "Headings": [ { "Id": "a-flow-is-more-than-an-endpoint", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json index 64fbcd18..f92b4715 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json @@ -1,71 +1,331 @@ { "Slug": "client/authentication", "Title": "Authentication", - "Html": "\n\u003Cp\u003EThis section explains how to use the UltimateAuth client for authentication flows.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EAuthentication in UltimateAuth is \u003Cstrong\u003Eflow-based\u003C/strong\u003E, not endpoint-based.\u003C/p\u003E\n\u003Cp\u003EYou interact with:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003EFlowClient\u003C/code\u003E\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022login\u0022\u003E\uD83D\uDD11 Login\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic-login\u0022\u003EBasic Login\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.LoginAsync(new LoginRequest\n{\n Identifier = \u0026quot;user@ultimateauth.com\u0026quot;,\n Secret = \u0026quot;password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This triggers a full login flow:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESends credentials\u003C/li\u003E\n\u003Cli\u003EHandles redirect\u003C/li\u003E\n\u003Cli\u003EEstablishes session\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022try-login-pre-check\u0022\u003E\u26A1 Try Login (Pre-check)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\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 = \u0026quot;user@mail.com\u0026quot;,\n Secret = \u0026quot;password\u0026quot;\n },\n UAuthSubmitMode.TryOnly\n);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022modes\u0022\u003EModes\u003C/h3\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\u003ETryOnly\u003C/td\u003E\n\u003Ctd\u003EValidate only\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDirectCommit\u003C/td\u003E\n\u003Ctd\u003ESkip validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ETryAndCommit\u003C/td\u003E\n\u003Ctd\u003EValidate then login if success\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Use \u003Ccode\u003EDirectCommit\u003C/code\u003E when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need maximum performance while sacrificing interactive SPA capabilities.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Use \u003Ccode\u003ETryOnly\u003C/code\u003E when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need validation feedback\u003C/li\u003E\n\u003Cli\u003EYou want custom UI flows\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Use \u003Ccode\u003ETryAndCommit\u003C/code\u003E when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need completely interactive SPA experience.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003ETryAndCommit\u003C/code\u003E is the recommended mode for most applications.\u003C/p\u003E\n\u003Cp\u003EIt provides:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EValidation feedback\u003C/li\u003E\n\u003Cli\u003EAutomatic redirect on success\u003C/li\u003E\n\u003Cli\u003ESmooth SPA experience\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh\u0022\u003E\uD83D\uDD04 Refresh\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\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\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022possible-outcomes\u0022\u003EPossible Outcomes\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003ESuccess \u2192 new tokens/session updated\u003C/li\u003E\n\u003Cli\u003ETouched \u2192 session extended\u003C/li\u003E\n\u003Cli\u003ERotated \u2192 refresh token rotated\u003C/li\u003E\n\u003Cli\u003ENoOp \u2192 nothing changed\u003C/li\u003E\n\u003Cli\u003EReauthRequired \u2192 login required\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Refresh behavior depends on auth mode:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPureOpaque \u2192 session touch\u003C/li\u003E\n\u003Cli\u003EHybrid/JWT \u2192 token rotation\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIn default, successful refresh returns success outcome. If you want to learn success detail such as no-op, touched or rotated, open it via server options:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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(o =\u0026gt;\n{\n o.Diagnostics.EnableRefreshDetails = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003E\uD83D\uDEAA Logout\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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\u003E\uD83D\uDC49 This:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnds current session\u003C/li\u003E\n\u003Cli\u003EClears authentication state\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-logout-variants\u0022\u003E\uD83D\uDCF1 Device Logout Variants\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.LogoutMyDeviceAsync(sessionId);\nawait UAuthClient.Flows.LogoutMyOtherDevicesAsync();\nawait UAuthClient.Flows.LogoutAllMyDevicesAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 These operate on \u003Cstrong\u003Esession chains (devices)\u003C/strong\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pkce-flow-public-clients\u0022\u003E\uD83D\uDD10 PKCE Flow (Public Clients)\u003C/h2\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\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-pkce\u0022\u003EComplete 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.CompletePkceLoginAsync(request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EComplete PKCE also has try semantics the same as login flow. UltimateAuth suggests to use \u003Ccode\u003ETryCompletePkceLoginAsync\u003C/code\u003E for complete interactive experience.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003E\uD83D\uDC49 Required for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESPA\u003C/li\u003E\n\u003Cli\u003EBlazor WASM\u003C/li\u003E\n\u003Cli\u003EMobile apps\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-note\u0022\u003E\uD83D\uDEA8 Security Note\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EPublic clients MUST use PKCE\u003C/li\u003E\n\u003Cli\u003EServer clients MAY allow direct login\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EDirect credential posting disabled by default and throws exception when you directly call login. You can enable it via options. You should only use it for debugging and development purposes.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Login.AllowCredentialPost = true;\n});\n---\n\n## \uD83C\uDFAF Summary\n\nAuthentication in UltimateAuth:\n\n- is flow-driven\n- adapts to client type\n- enforces security by design\n\n---\n\n\uD83D\uDC49 Always use \u0060FlowClient\u0060 for authentication operations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\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/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/authentication-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": "overview", - "Text": "\uD83E\uDDE0 Overview", + "Id": "accessing-the-authentication-client", + "Text": "1. Accessing the Authentication Client", "Level": 0 }, { - "Id": "login", - "Text": "\uD83D\uDD11 Login", + "Id": "choosing-the-correct-login-flow", + "Text": "2. Choosing the Correct Login Flow", "Level": 0 }, { - "Id": "basic-login", - "Text": "Basic Login", + "Id": "direct-login", + "Text": "3. Direct Login", + "Level": 0 + }, + { + "Id": "loginrequest-properties", + "Text": "LoginRequest Properties", + "Level": 1 + }, + { + "Id": "redirect-after-login", + "Text": "Redirect After Login", + "Level": 1 + }, + { + "Id": "interactive-login-with-tryloginasync", + "Text": "4. Interactive Login with TryLoginAsync", + "Level": 0 + }, + { + "Id": "submit-modes", + "Text": "Submit Modes", + "Level": 1 + }, + { + "Id": "tryonly-validate-without-login", + "Text": "TryOnly: Validate Without Login", + "Level": 1 + }, + { + "Id": "tryandcommit-interactive-login", + "Text": "TryAndCommit: Interactive Login", + "Level": 1 + }, + { + "Id": "directcommit-single-commit-submission", + "Text": "DirectCommit: Single Commit Submission", + "Level": 1 + }, + { + "Id": "handling-login-results", + "Text": "5. Handling Login Results", + "Level": 0 + }, + { + "Id": "handling-transport-and-protocol-errors", + "Text": "Handling Transport and Protocol Errors", + "Level": 1 + }, + { + "Id": "pkce-authentication", + "Text": "6. PKCE Authentication", + "Level": 0 + }, + { + "Id": "start-pkce", + "Text": "Start PKCE", + "Level": 1 + }, + { + "Id": "configure-pkce", + "Text": "Configure PKCE", + "Level": 1 + }, + { + "Id": "observe-authorization", + "Text": "Observe Authorization", + "Level": 1 + }, + { + "Id": "disable-automatic-redirection", + "Text": "Disable Automatic Redirection", + "Level": 1 + }, + { + "Id": "complete-pkce-login", + "Text": "7. Complete PKCE Login", + "Level": 0 + }, + { + "Id": "interactive-pkce-completion", + "Text": "Interactive PKCE Completion", + "Level": 1 + }, + { + "Id": "try-pkce-without-commit", + "Text": "Try PKCE Without Commit", + "Level": 1 + }, + { + "Id": "retrywithnewpkce", + "Text": "RetryWithNewPkce", + "Level": 1 + }, + { + "Id": "direct-credential-posting-restrictions", + "Text": "8. Direct Credential Posting Restrictions", + "Level": 0 + }, + { + "Id": "trusted-server-hosted-clients", + "Text": "Trusted Server-Hosted Clients", + "Level": 1 + }, + { + "Id": "refresh-authentication", + "Text": "9. Refresh Authentication", + "Level": 0 + }, + { + "Id": "handling-refresh-results", + "Text": "Handling Refresh Results", + "Level": 1 + }, + { + "Id": "refresh-outcomes", + "Text": "Refresh Outcomes", + "Level": 1 + }, + { + "Id": "detailed-refresh-diagnostics", + "Text": "Detailed Refresh Diagnostics", + "Level": 1 + }, + { + "Id": "manual-and-automatic-refresh", + "Text": "Manual and Automatic Refresh", "Level": 1 }, { - "Id": "try-login-pre-check", - "Text": "\u26A1 Try Login (Pre-check)", + "Id": "automatic-refresh-configuration", + "Text": "10. Automatic Refresh Configuration", "Level": 0 }, { - "Id": "modes", - "Text": "Modes", + "Id": "automatic-refresh-is-not-a-security-guarantee", + "Text": "Automatic Refresh Is Not a Security Guarantee", "Level": 1 }, { - "Id": "refresh", - "Text": "\uD83D\uDD04 Refresh", + "Id": "validate-current-authentication", + "Text": "11. Validate Current Authentication", "Level": 0 }, { - "Id": "possible-outcomes", - "Text": "Possible Outcomes", + "Id": "inspect-session-context", + "Text": "Inspect Session Context", + "Level": 1 + }, + { + "Id": "validateasync-versus-ui-authentication-state", + "Text": "ValidateAsync Versus UI Authentication State", "Level": 1 }, { "Id": "logout", - "Text": "\uD83D\uDEAA Logout", + "Text": "12. Logout", "Level": 0 }, { - "Id": "device-logout-variants", - "Text": "\uD83D\uDCF1 Device Logout Variants", + "Id": "logout-button", + "Text": "Logout Button", + "Level": 1 + }, + { + "Id": "logout-a-specific-device", + "Text": "13. Logout a Specific Device", "Level": 0 }, { - "Id": "pkce-flow-public-clients", - "Text": "\uD83D\uDD10 PKCE Flow (Public Clients)", + "Id": "current-device-behavior", + "Text": "Current Device Behavior", + "Level": 1 + }, + { + "Id": "logout-other-devices", + "Text": "14. Logout Other Devices", "Level": 0 }, { - "Id": "start-pkce", - "Text": "Start PKCE", + "Id": "logout-all-devices", + "Text": "15. Logout All Devices", + "Level": 0 + }, + { + "Id": "administrative-device-logout", + "Text": "16. Administrative Device Logout", + "Level": 0 + }, + { + "Id": "logout-one-user-device", + "Text": "Logout One User Device", + "Level": 1 + }, + { + "Id": "logout-other-devices-for-a-user", + "Text": "Logout Other Devices for a User", + "Level": 1 + }, + { + "Id": "logout-all-devices-for-a-user", + "Text": "Logout All Devices for a User", + "Level": 1 + }, + { + "Id": "building-login-forms-with-uauthloginform", + "Text": "17. Building Login Forms with UAuthLoginForm", + "Level": 0 + }, + { + "Id": "basic-usage", + "Text": "Basic Usage", + "Level": 1 + }, + { + "Id": "customizing-the-login-ui", + "Text": "Customizing the Login UI", + "Level": 1 + }, + { + "Id": "submission-modes", + "Text": "Submission Modes", + "Level": 1 + }, + { + "Id": "handling-authentication-results", + "Text": "Handling Authentication Results", + "Level": 1 + }, + { + "Id": "using-uauthloginform-with-pkce", + "Text": "Using UAuthLoginForm with PKCE", + "Level": 1 + }, + { + "Id": "return-url-handling", + "Text": "Return URL Handling", + "Level": 1 + }, + { + "Id": "custom-login-endpoints", + "Text": "Custom Login Endpoints", + "Level": 1 + }, + { + "Id": "submit-the-form-programmatically", + "Text": "Submit the Form Programmatically", + "Level": 1 + }, + { + "Id": "controlling-enter-key-submission", + "Text": "Controlling Enter-Key Submission", + "Level": 1 + }, + { + "Id": "reloading-pkce-context", + "Text": "Reloading PKCE Context", + "Level": 1 + }, + { + "Id": "component-parameters", + "Text": "Component Parameters", + "Level": 1 + }, + { + "Id": "when-to-use-uauthloginform", + "Text": "When to Use UAuthLoginForm", + "Level": 1 + }, + { + "Id": "common-integration-mistakes", + "Text": "18. Common Integration Mistakes", + "Level": 0 + }, + { + "Id": "calling-direct-login-from-a-public-client", + "Text": "Calling Direct Login from a Public Client", + "Level": 1 + }, + { + "Id": "treating-tryonly-as-authentication", + "Text": "Treating TryOnly as Authentication", + "Level": 1 + }, + { + "Id": "assuming-directcommit-skips-server-validation", + "Text": "Assuming DirectCommit Skips Server Validation", + "Level": 1 + }, + { + "Id": "assuming-every-refresh-rotates-tokens", + "Text": "Assuming Every Refresh Rotates Tokens", + "Level": 1 + }, + { + "Id": "using-a-session-id-for-device-logout", + "Text": "Using a Session ID for Device Logout", + "Level": 1 + }, + { + "Id": "assuming-logout-immediately-invalidates-all-jwts", + "Text": "Assuming Logout Immediately Invalidates All JWTs", "Level": 1 }, { - "Id": "complete-pkce", - "Text": "Complete PKCE", + "Id": "using-validateasync-on-every-render", + "Text": "Using ValidateAsync on Every Render", "Level": 1 }, { - "Id": "security-note", - "Text": "\uD83D\uDEA8 Security Note", + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json index 13314c7a..6959e991 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json @@ -1,71 +1,321 @@ { "Slug": "client/authorization", "Title": "Authorization", - "Html": "\n\u003Cp\u003EThis section explains how to manage roles, permissions, and access control using the UltimateAuth client.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EAuthorization in UltimateAuth is policy-driven and role-based.\u003C/p\u003E\n\u003Cp\u003EOn the client, you interact with:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode\u003EUAuthClient.Authorization\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-concepts\u0022\u003E\uD83D\uDD11 Core Concepts\u003C/h2\u003E\n\u003Cp\u003ERoles\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ENamed groups of permissions\u003C/li\u003E\n\u003Cli\u003EAssigned to users\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EPermissions\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EFine-grained access definitions\u003C/li\u003E\n\u003Cli\u003EExample: users.create.anonymous, users.delete.self, authorization.roles.admin\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EPolicies\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERuntime decision rules\u003C/li\u003E\n\u003Cli\u003EEnforced automatically on server\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022query-roles\u0022\u003E\uD83D\uDCCB Query Roles\u003C/h3\u003E\n\u003Cp\u003Evar result = await UAuthClient.Authorization.QueryRolesAsync(new RoleQuery\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-role\u0022\u003E\u2795 Create Role\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.CreateRoleAsync(new CreateRoleRequest\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rename-role\u0022\u003E\u270F\uFE0F Rename Role\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.RenameRoleAsync(new RenameRoleRequest\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022set-permissions\u0022\u003E\uD83E\uDDE9 Set Permissions\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.SetRolePermissionsAsync(new SetRolePermissionsRequest\n{\nRoleId = roleId,\nPermissions = new[]\n{\nPermission.From(\u0026quot;users.read\u0026quot;),\nPermission.From(\u0026quot;users.update\u0026quot;)\n}\n});\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Permissions support:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EExact match \u2192 users.create\u003C/li\u003E\n\u003Cli\u003EPrefix \u2192 users.*\u003C/li\u003E\n\u003Cli\u003EWildcard \u2192 *\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-role\u0022\u003E\u274C Delete Role\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.DeleteRoleAsync(new DeleteRoleRequest\n);\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Automatically removes role assignments from users\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assign-role-to-user\u0022\u003E\uD83D\uDC64 Assign Role to User\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.AssignRoleToUserAsync(new AssignRoleRequest\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remove-role\u0022\u003E\u2796 Remove Role\u003C/h3\u003E\n\u003Cp\u003Eawait UAuthClient.Authorization.RemoveRoleFromUserAsync(new RemoveRoleRequest\n);\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-user-roles\u0022\u003E\uD83D\uDCCB Get User Roles\u003C/h3\u003E\n\u003Cp\u003Evar roles = await UAuthClient.Authorization.GetUserRolesAsync(userKey);\n\uD83D\uDD0D Check Authorization\nvar result = await UAuthClient.Authorization.CheckAsync(new AuthorizationCheckRequest\n);\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Returns:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAllow / Deny\u003C/li\u003E\n\u003Cli\u003EReason (if denied)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-model\u0022\u003E\uD83E\uDDE0 Permission Model\u003C/h2\u003E\n\u003Cp\u003EPermissions are normalized and optimized:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EFull access \u2192 *\u003C/li\u003E\n\u003Cli\u003EGroup access \u2192 users.*\u003C/li\u003E\n\u003Cli\u003ESpecific \u2192 users.create\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Internally compiled for fast evaluation\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-notes\u0022\u003E\uD83D\uDD10 Security Notes\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthorization is enforced server-side\u003C/li\u003E\n\u003Cli\u003EClient only requests actions\u003C/li\u003E\n\u003Cli\u003EPolicies may override permissions\u003C/li\u003E\n\u003Cli\u003ECross-tenant access is denied by default\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003EAuthorization in UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ecombines roles \u002B permissions \u002B policies\u003C/li\u003E\n\u003Cli\u003Eis evaluated through a decision pipeline\u003C/li\u003E\n\u003Cli\u003Esupports fine-grained and scalable access control\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\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=\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-another-users-roles\u0022\u003E8. 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\u003E9. 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\u003E10. 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\u003E11. 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\u003E12. 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\u003E13. 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\u003E14. 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\u003E15. 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\u003E16. 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\u003E17. 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\u003E18. 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\u003E19. 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\u003E20. 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\u003E21. 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\u003E22. 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\u0022\u003E23. 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\u003E24. 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-model\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand session validity and security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { - "Id": "overview", - "Text": "\uD83E\uDDE0 Overview", + "Id": "understanding-authorization", + "Text": "1. Understanding Authorization", "Level": 0 }, { - "Id": "core-concepts", - "Text": "\uD83D\uDD11 Core Concepts", + "Id": "accessing-the-authorization-client", + "Text": "2. Accessing the Authorization Client", + "Level": 0 + }, + { + "Id": "understanding-permissions", + "Text": "3. Understanding Permissions", + "Level": 0 + }, + { + "Id": "permission-patterns", + "Text": "Permission Patterns", + "Level": 1 + }, + { + "Id": "permission-normalization", + "Text": "Permission Normalization", + "Level": 1 + }, + { + "Id": "wildcards-are-powerful", + "Text": "Wildcards Are Powerful", + "Level": 1 + }, + { + "Id": "check-authorization", + "Text": "4. Check Authorization", + "Level": 0 + }, + { + "Id": "authorizationcheckrequest", + "Text": "AuthorizationCheckRequest", + "Level": 1 + }, + { + "Id": "check-a-specific-resource", + "Text": "Check a Specific Resource", + "Level": 1 + }, + { + "Id": "understanding-authorization-results", + "Text": "5. Understanding Authorization Results", + "Level": 0 + }, + { + "Id": "handle-all-decision-outcomes", + "Text": "Handle All Decision Outcomes", + "Level": 1 + }, + { + "Id": "use-authorization-checks-in-blazor", + "Text": "6. Use Authorization Checks in Blazor", + "Level": 0 + }, + { + "Id": "why-client-checks-are-not-security-boundaries", + "Text": "Why Client Checks Are Not Security Boundaries", + "Level": 1 + }, + { + "Id": "get-my-roles", + "Text": "7. Get My Roles", + "Level": 0 + }, + { + "Id": "userrolesgetresult", + "Text": "UserRolesGetResult", + "Level": 1 + }, + { + "Id": "userroleinfo", + "Text": "UserRoleInfo", + "Level": 1 + }, + { + "Id": "pagination", + "Text": "Pagination", + "Level": 1 + }, + { + "Id": "get-another-users-roles", + "Text": "8. Get Another User\u0027s Roles", "Level": 0 }, { "Id": "query-roles", - "Text": "\uD83D\uDCCB Query Roles", + "Text": "9. Query Roles", + "Level": 0 + }, + { + "Id": "rolequery", + "Text": "RoleQuery", + "Level": 1 + }, + { + "Id": "search-roles", + "Text": "Search Roles", + "Level": 1 + }, + { + "Id": "include-deleted-roles", + "Text": "Include Deleted Roles", + "Level": 1 + }, + { + "Id": "understanding-roleinfo", + "Text": "10. Understanding RoleInfo", + "Level": 0 + }, + { + "Id": "create-a-role", + "Text": "11. Create a Role", + "Level": 0 + }, + { + "Id": "create-a-role-with-permissions", + "Text": "Create a Role with Permissions", + "Level": 1 + }, + { + "Id": "create-a-read-only-role", + "Text": "Create a Read-Only Role", + "Level": 1 + }, + { + "Id": "rename-a-role", + "Text": "12. Rename a Role", + "Level": 0 + }, + { + "Id": "set-role-permissions", + "Text": "13. Set Role Permissions", + "Level": 0 + }, + { + "Id": "important-this-is-a-replacement-operation", + "Text": "Important: This Is a Replacement Operation", + "Level": 1 + }, + { + "Id": "safely-add-a-permission-to-an-existing-role", + "Text": "Safely Add a Permission to an Existing Role", "Level": 1 }, { - "Id": "create-role", - "Text": "\u2795 Create Role", + "Id": "remove-a-permission", + "Text": "Remove a Permission", + "Level": 1 + }, + { + "Id": "clear-all-permissions", + "Text": "Clear All Permissions", + "Level": 1 + }, + { + "Id": "assign-a-role-to-a-user", + "Text": "14. Assign a Role to a User", + "Level": 0 + }, + { + "Id": "assign-multiple-roles", + "Text": "Assign Multiple Roles", + "Level": 1 + }, + { + "Id": "remove-a-role-from-a-user", + "Text": "15. Remove a Role from a User", + "Level": 0 + }, + { + "Id": "important-effective-permissions-may-remain", + "Text": "Important: Effective Permissions May Remain", "Level": 1 }, { - "Id": "rename-role", - "Text": "\u270F\uFE0F Rename Role", + "Id": "delete-a-role", + "Text": "16. Delete a Role", + "Level": 0 + }, + { + "Id": "hard-delete", + "Text": "Hard Delete", "Level": 1 }, { - "Id": "set-permissions", - "Text": "\uD83E\uDDE9 Set Permissions", + "Id": "roledeleteresult", + "Text": "RoleDeleteResult", "Level": 1 }, { - "Id": "delete-role", - "Text": "\u274C Delete Role", + "Id": "inspect-the-result", + "Text": "Inspect the Result", "Level": 1 }, { - "Id": "assign-role-to-user", - "Text": "\uD83D\uDC64 Assign Role to User", + "Id": "build-a-role-management-page", + "Text": "17. Build a Role Management Page", + "Level": 0 + }, + { + "Id": "build-a-user-role-assignment-interface", + "Text": "18. Build a User Role Assignment Interface", + "Level": 0 + }, + { + "Id": "load-user-roles", + "Text": "Load User Roles", "Level": 1 }, { - "Id": "remove-role", - "Text": "\u2796 Remove Role", + "Id": "assign-a-selected-role", + "Text": "Assign a Selected Role", "Level": 1 }, { - "Id": "get-user-roles", - "Text": "\uD83D\uDCCB Get User Roles", + "Id": "remove-a-selected-role", + "Text": "Remove a Selected Role", "Level": 1 }, { - "Id": "permission-model", - "Text": "\uD83E\uDDE0 Permission Model", + "Id": "authorization-state-events", + "Text": "19. Authorization State Events", "Level": 0 }, { - "Id": "security-notes", - "Text": "\uD83D\uDD10 Security Notes", + "Id": "authorization-changes-and-active-sessions", + "Text": "20. Authorization Changes and Active Sessions", + "Level": 0 + }, + { + "Id": "roles-are-not-sessions", + "Text": "Roles Are Not Sessions", + "Level": 1 + }, + { + "Id": "multi-tenant-authorization", + "Text": "21. Multi-Tenant Authorization", + "Level": 0 + }, + { + "Id": "handling-authorization-errors", + "Text": "22. Handling Authorization Errors", + "Level": 0 + }, + { + "Id": "handle-role-creation-errors", + "Text": "Handle Role Creation Errors", + "Level": 1 + }, + { + "Id": "handle-authorization-check-failures", + "Text": "Handle Authorization Check Failures", + "Level": 1 + }, + { + "Id": "security-considerations", + "Text": "23. Security Considerations", + "Level": 0 + }, + { + "Id": "server-authorization-is-authoritative", + "Text": "Server Authorization Is Authoritative", + "Level": 1 + }, + { + "Id": "follow-least-privilege", + "Text": "Follow Least Privilege", + "Level": 1 + }, + { + "Id": "protect-administrative-operations", + "Text": "Protect Administrative Operations", + "Level": 1 + }, + { + "Id": "treat-permission-replacement-carefully", + "Text": "Treat Permission Replacement Carefully", + "Level": 1 + }, + { + "Id": "handle-reauthentication-requirements", + "Text": "Handle Reauthentication Requirements", + "Level": 1 + }, + { + "Id": "respect-tenant-boundaries", + "Text": "Respect Tenant Boundaries", + "Level": 1 + }, + { + "Id": "do-not-infer-effective-access-from-one-role", + "Text": "Do Not Infer Effective Access from One Role", + "Level": 1 + }, + { + "Id": "account-for-cached-authorization-state", + "Text": "Account for Cached Authorization State", + "Level": 1 + }, + { + "Id": "putting-everything-together", + "Text": "24. Putting Everything Together", "Level": 0 }, { - "Id": "summary", - "Text": "\uD83C\uDFAF Summary", + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json index 8dd6aac0..e8c5e2c6 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json @@ -1,91 +1,306 @@ { "Slug": "client/credentials", "Title": "Credentials", - "Html": "\n\u003Cp\u003EThis section explains how to manage user credentials (such as passwords) using the UltimateAuth client.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003ECredential operations are handled via:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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\u003EThis includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Epassword management\u003C/li\u003E\n\u003Cli\u003Ecredential reset flows\u003C/li\u003E\n\u003Cli\u003Eadmin credential operations\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-password-self\u0022\u003E\uD83D\uDD10 Change Password (Self)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Credentials.ChangeMyAsync(new ChangeCredentialRequest\n{\n CurrentSecret = \u0026quot;old-password\u0026quot;,\n NewSecret = \u0026quot;new-password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Requires current password\u003Cbr\u003E\n\uD83D\uDC49 Triggers \u003Ccode\u003ECredentialsChangedSelf\u003C/code\u003E event\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022reset-password-self\u0022\u003E\uD83D\uDD01 Reset Password (Self)\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022begin-reset\u0022\u003EBegin Reset\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 begin = await UAuthClient.Credentials.BeginResetMyAsync(\n new BeginResetCredentialRequest\n {\n Identifier = \u0026quot;user@mail.com\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-reset\u0022\u003EComplete Reset\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.Credentials.CompleteResetMyAsync(\n new CompleteResetCredentialRequest\n {\n Token = \u0026quot;reset-token\u0026quot;,\n NewSecret = \u0026quot;new-password\u0026quot;\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Typically used in:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eforgot password flows\u003C/li\u003E\n\u003Cli\u003Eemail-based reset flows\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-credential-self\u0022\u003E\u2795 Add Credential (Self)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Credentials.AddMyAsync(new AddCredentialRequest\n{\n Secret = \u0026quot;password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-credential-self\u0022\u003E\u274C Revoke Credential (Self)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Credentials.RevokeMyAsync(new RevokeCredentialRequest\n{\n CredentialId = credentialId\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Useful for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eremoving login methods\u003C/li\u003E\n\u003Cli\u003Einvalidating compromised credentials\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-change-user-credential\u0022\u003E\uD83D\uDC51 Admin: Change User Credential\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Credentials.ChangeUserAsync(userKey, new ChangeCredentialRequest\n{\n NewSecret = \u0026quot;new-password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Does NOT require current password\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-add-credential\u0022\u003E\uD83D\uDC51 Admin: Add Credential\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Credentials.AddUserAsync(userKey, new AddCredentialRequest\n{\n Secret = \u0026quot;password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-revoke-credential\u0022\u003E\uD83D\uDC51 Admin: Revoke Credential\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Credentials.RevokeUserAsync(userKey, new RevokeCredentialRequest\n{\n CredentialId = credentialId\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-reset-credential\u0022\u003E\uD83D\uDC51 Admin: Reset Credential\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022begin\u0022\u003EBegin\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.Credentials.BeginResetUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete\u0022\u003EComplete\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.Credentials.CompleteResetUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-credential-admin\u0022\u003E\u274C Delete Credential (Admin)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Credentials.DeleteUserAsync(userKey, new DeleteCredentialRequest\n{\n CredentialId = credentialId\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-model\u0022\u003E\uD83E\uDDE0 Credential Model\u003C/h2\u003E\n\u003Cp\u003ECredentials are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Euser-bound\u003C/li\u003E\n\u003Cli\u003Esecurity-version aware\u003C/li\u003E\n\u003Cli\u003Elifecycle-managed\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Changing credentials may:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Einvalidate sessions\u003C/li\u003E\n\u003Cli\u003Etrigger security updates\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-notes\u0022\u003E\uD83D\uDD10 Security Notes\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EPasswords are never sent back to client\u003C/li\u003E\n\u003Cli\u003EAll secrets are hashed server-side\u003C/li\u003E\n\u003Cli\u003EReset flows should be protected (email, OTP, etc.)\u003C/li\u003E\n\u003Cli\u003EAdmin operations are policy-protected\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003ECredential management in UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esupports self-service and admin flows\u003C/li\u003E\n\u003Cli\u003Eintegrates with security lifecycle\u003C/li\u003E\n\u003Cli\u003Eenables safe credential rotation\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-model\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=\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-model\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": "overview", - "Text": "\uD83E\uDDE0 Overview", + "Id": "understanding-credentials", + "Text": "1. Understanding Credentials", "Level": 0 }, { - "Id": "change-password-self", - "Text": "\uD83D\uDD10 Change Password (Self)", + "Id": "accessing-the-credential-client", + "Text": "2. Accessing the Credential Client", "Level": 0 }, { - "Id": "reset-password-self", - "Text": "\uD83D\uDD01 Reset Password (Self)", + "Id": "change-your-password", + "Text": "3. Change Your Password", "Level": 0 }, { - "Id": "begin-reset", - "Text": "Begin Reset", + "Id": "changecredentialrequest", + "Text": "ChangeCredentialRequest", "Level": 1 }, { - "Id": "complete-reset", - "Text": "Complete Reset", + "Id": "credentialchangeresult", + "Text": "CredentialChangeResult", "Level": 1 }, { - "Id": "add-credential-self", - "Text": "\u2795 Add Credential (Self)", + "Id": "client-state-event", + "Text": "Client State Event", + "Level": 1 + }, + { + "Id": "build-a-password-change-form", + "Text": "4. Build a Password Change Form", "Level": 0 }, { - "Id": "revoke-credential-self", - "Text": "\u274C Revoke Credential (Self)", + "Id": "add-a-credential", + "Text": "5. Add a Credential", "Level": 0 }, { - "Id": "admin-change-user-credential", - "Text": "\uD83D\uDC51 Admin: Change User Credential", + "Id": "addcredentialrequest", + "Text": "AddCredentialRequest", + "Level": 1 + }, + { + "Id": "credentialaddresult", + "Text": "CredentialAddResult", + "Level": 1 + }, + { + "Id": "password-change-versus-password-recovery", + "Text": "6. Password Change Versus Password Recovery", "Level": 0 }, { - "Id": "admin-add-credential", - "Text": "\uD83D\uDC51 Admin: Add Credential", + "Id": "begin-password-recovery", + "Text": "7. Begin Password Recovery", "Level": 0 }, { - "Id": "admin-revoke-credential", - "Text": "\uD83D\uDC51 Admin: Revoke Credential", + "Id": "beginresetcredentialrequest", + "Text": "BeginResetCredentialRequest", + "Level": 1 + }, + { + "Id": "reset-code-types", + "Text": "Reset Code Types", + "Level": 1 + }, + { + "Id": "credentialbeginresetresult", + "Text": "CredentialBeginResetResult", + "Level": 1 + }, + { + "Id": "complete-password-recovery", + "Text": "8. Complete Password Recovery", + "Level": 0 + }, + { + "Id": "completeresetcredentialrequest", + "Text": "CompleteResetCredentialRequest", + "Level": 1 + }, + { + "Id": "credentialactionresult", + "Text": "CredentialActionResult", + "Level": 1 + }, + { + "Id": "complete-recovery-using-a-numeric-code", + "Text": "9. Complete Recovery Using a Numeric Code", "Level": 0 }, { - "Id": "admin-reset-credential", - "Text": "\uD83D\uDC51 Admin: Reset Credential", + "Id": "build-a-forgot-password-form", + "Text": "10. Build a Forgot Password Form", "Level": 0 }, { - "Id": "begin", - "Text": "Begin", + "Id": "step-one-request-recovery", + "Text": "Step One: Request Recovery", "Level": 1 }, { - "Id": "complete", - "Text": "Complete", + "Id": "step-two-complete-recovery", + "Text": "Step Two: Complete Recovery", "Level": 1 }, { - "Id": "delete-credential-admin", - "Text": "\u274C Delete Credential (Admin)", + "Id": "configure-reset-notifications", + "Text": "11. Configure Reset Notifications", "Level": 0 }, { - "Id": "credential-model", - "Text": "\uD83E\uDDE0 Credential Model", + "Id": "register-the-notifier", + "Text": "Register the Notifier", + "Level": 1 + }, + { + "Id": "missing-notifier-configuration", + "Text": "Missing Notifier Configuration", + "Level": 1 + }, + { + "Id": "recovery-security-behavior", + "Text": "12. Recovery Security Behavior", "Level": 0 }, { - "Id": "security-notes", - "Text": "\uD83D\uDD10 Security Notes", + "Id": "account-enumeration-resistance", + "Text": "Account Enumeration Resistance", + "Level": 1 + }, + { + "Id": "proof-expiration", + "Text": "Proof Expiration", + "Level": 1 + }, + { + "Id": "single-use-proofs", + "Text": "Single-Use Proofs", + "Level": 1 + }, + { + "Id": "identifier-and-target-binding", + "Text": "Identifier and Target Binding", + "Level": 1 + }, + { + "Id": "session-revocation", + "Text": "Session Revocation", + "Level": 1 + }, + { + "Id": "revoke-your-credential", + "Text": "13. Revoke Your Credential", + "Level": 0 + }, + { + "Id": "state-event", + "Text": "State Event", + "Level": 1 + }, + { + "Id": "administrative-credential-management", + "Text": "14. Administrative Credential Management", "Level": 0 }, { - "Id": "summary", - "Text": "\uD83C\uDFAF Summary", + "Id": "add-a-credential-1", + "Text": "Add a Credential", + "Level": 1 + }, + { + "Id": "change-a-users-password", + "Text": "Change a User\u0027s Password", + "Level": 1 + }, + { + "Id": "revoke-a-users-credential", + "Text": "Revoke a User\u0027s Credential", + "Level": 1 + }, + { + "Id": "begin-administrative-recovery", + "Text": "Begin Administrative Recovery", + "Level": 1 + }, + { + "Id": "complete-administrative-recovery", + "Text": "Complete Administrative Recovery", + "Level": 1 + }, + { + "Id": "delete-a-users-credential", + "Text": "Delete a User\u0027s Credential", + "Level": 1 + }, + { + "Id": "hard-delete", + "Text": "Hard Delete", + "Level": 1 + }, + { + "Id": "credential-changes-and-session-invalidation", + "Text": "15. Credential Changes and Session Invalidation", + "Level": 0 + }, + { + "Id": "credentialsessionbehavior", + "Text": "CredentialSessionBehavior", + "Level": 1 + }, + { + "Id": "configure-the-policy", + "Text": "Configure the Policy", + "Level": 1 + }, + { + "Id": "recovery-and-preservecurrent", + "Text": "Recovery and PreserveCurrent", + "Level": 1 + }, + { + "Id": "authentication-mode-considerations", + "Text": "Authentication Mode Considerations", + "Level": 1 + }, + { + "Id": "handling-credential-results", + "Text": "16. Handling Credential Results", + "Level": 0 + }, + { + "Id": "credential-creation", + "Text": "Credential Creation", + "Level": 1 + }, + { + "Id": "credential-recovery", + "Text": "Credential Recovery", + "Level": 1 + }, + { + "Id": "http-status-helpers", + "Text": "HTTP Status Helpers", + "Level": 1 + }, + { + "Id": "credential-state-events", + "Text": "17. Credential State Events", + "Level": 0 + }, + { + "Id": "security-considerations", + "Text": "18. Security Considerations", + "Level": 0 + }, + { + "Id": "never-expose-stored-secrets", + "Text": "Never Expose Stored Secrets", + "Level": 1 + }, + { + "Id": "use-secure-transport", + "Text": "Use Secure Transport", + "Level": 1 + }, + { + "Id": "protect-recovery-proofs", + "Text": "Protect Recovery Proofs", + "Level": 1 + }, + { + "Id": "keep-self-service-and-administrative-operations-separate", + "Text": "Keep Self-Service and Administrative Operations Separate", + "Level": 1 + }, + { + "Id": "preserve-account-recovery-safety", + "Text": "Preserve Account Recovery Safety", + "Level": 1 + }, + { + "Id": "consider-session-consequences", + "Text": "Consider Session Consequences", + "Level": 1 + }, + { + "Id": "do-not-assume-every-credential-type-is-implemented", + "Text": "Do Not Assume Every Credential Type Is Implemented", + "Level": 1 + }, + { + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json index 00de2972..d1abf661 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json @@ -1,91 +1,286 @@ { "Slug": "client/identifiers", "Title": "Identifiers", - "Html": "\n\u003Cp\u003EThis section explains how UltimateAuth manages user identifiers such as email, username, and phone.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth, identifiers are \u003Cstrong\u003Efirst-class entities\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThey are NOT just fields on the user.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 On the client, you interact 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\u003EUAuthClient.Identifiers...\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-an-identifier\u0022\u003E\uD83D\uDD11 What is an Identifier?\u003C/h2\u003E\n\u003Cp\u003EAn identifier represents a way to identify a user:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEmail\u003C/li\u003E\n\u003Cli\u003EUsername\u003C/li\u003E\n\u003Cli\u003EPhone number\u003C/li\u003E\n\u003Cli\u003ECustom identifiers\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Each identifier has:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EValue (e.g. user@ultimateauth.com)\u003C/li\u003E\n\u003Cli\u003EType (email, username, etc.)\u003C/li\u003E\n\u003Cli\u003EVerification state\u003C/li\u003E\n\u003Cli\u003EPrimary flag\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022primary-identifier\u0022\u003E\u2B50 Primary Identifier\u003C/h2\u003E\n\u003Cp\u003EA user can have multiple identifiers, but only one can be \u003Cstrong\u003Eprimary\u003C/strong\u003E. Setting an identifier to primary automatically unset the current primary identifier if exists.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Identifiers.SetMyPrimaryAsync(new SetPrimaryUserIdentifierRequest\n{\n Id = identifierId\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Primary identifier is typically:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUsed for display\u003C/li\u003E\n\u003Cli\u003EPreferred for communication\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022login-identifiers\u0022\u003E\uD83D\uDD10 Login Identifiers\u003C/h2\u003E\n\u003Cp\u003ENot all identifiers are used for login.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Cstrong\u003ELogin identifiers are a subset of identifiers\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This is configurable:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnable/disable per type\u003C/li\u003E\n\u003Cli\u003ECustom logic can be applied\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-my-identifiers\u0022\u003E\uD83D\uDCCB Get My Identifiers\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\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\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-identifier\u0022\u003E\u2795 Add Identifier\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Identifiers.AddMyAsync(new AddUserIdentifierRequest\n{\n Identifier = \u0026quot;new@ultimateauth.com\u0026quot;,\n Type = UserIdentifierType.Email,\n IsPrimary = true\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-identifier\u0022\u003E\u270F\uFE0F Update Identifier\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUpdateUserIdentifierRequest updateRequest = new()\n{\n Id = item.Id,\n NewValue = item.Value\n};\n\nawait UAuthClient.Identifiers.UpdateMyAsync(updateRequest);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022verify-identifier\u0022\u003E\u2705 Verify Identifier\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Identifiers.VerifyMyAsync(new VerifyUserIdentifierRequest\n{\n Id = identifierId\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-identifier\u0022\u003E\u274C Delete Identifier\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Identifiers.DeleteMyAsync(new DeleteUserIdentifierRequest\n{\n Id = identifierId\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-operations\u0022\u003E\uD83D\uDC64 Admin Operations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-user-identifiers\u0022\u003EGet User 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\u003Eawait UAuthClient.Identifiers.GetUserAsync(userKey);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-identifier-to-user\u0022\u003EAdd Identifier to 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\u003Eawait UAuthClient.Identifiers.AddUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-identifier-1\u0022\u003EUpdate 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\u003Eawait UAuthClient.Identifiers.UpdateUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-identifier-1\u0022\u003EDelete 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\u003Eawait UAuthClient.Identifiers.DeleteUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022state-events\u0022\u003E\uD83D\uDD04 State Events\u003C/h2\u003E\n\u003Cp\u003EIdentifier changes trigger events:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EIdentifiersChanged\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Useful for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUI updates\u003C/li\u003E\n\u003Cli\u003ECache invalidation\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003E\uD83D\uDD10 Security Considerations\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EIdentifiers may require verification\u003C/li\u003E\n\u003Cli\u003ELogin identifiers can be restricted\u003C/li\u003E\n\u003Cli\u003EPrimary identifier can be controlled\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth identifiers:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eare independent entities\u003C/li\u003E\n\u003Cli\u003Esupport multiple types\u003C/li\u003E\n\u003Cli\u003Eseparate login vs non-login identifiers\u003C/li\u003E\n\u003Cli\u003Eare fully manageable via client\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/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\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-model\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand authentication security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { - "Id": "overview", - "Text": "\uD83E\uDDE0 Overview", + "Id": "understanding-user-identifiers", + "Text": "1. Understanding User Identifiers", "Level": 0 }, { - "Id": "what-is-an-identifier", - "Text": "\uD83D\uDD11 What is an Identifier?", + "Id": "accessing-the-identifier-client", + "Text": "2. Accessing the Identifier Client", "Level": 0 }, { - "Id": "primary-identifier", - "Text": "\u2B50 Primary Identifier", + "Id": "get-my-identifiers", + "Text": "3. Get My Identifiers", "Level": 0 }, { - "Id": "login-identifiers", - "Text": "\uD83D\uDD10 Login Identifiers", + "Id": "useridentifierinfo", + "Text": "UserIdentifierInfo", + "Level": 1 + }, + { + "Id": "filter-identifiers-in-the-ui", + "Text": "Filter Identifiers in the UI", + "Level": 1 + }, + { + "Id": "pagination", + "Text": "4. Pagination", "Level": 0 }, { - "Id": "get-my-identifiers", - "Text": "\uD83D\uDCCB Get My Identifiers", + "Id": "add-an-identifier", + "Text": "5. Add an Identifier", "Level": 0 }, { - "Id": "add-identifier", - "Text": "\u2795 Add Identifier", + "Id": "add-an-email-address", + "Text": "Add an Email Address", + "Level": 1 + }, + { + "Id": "add-a-phone-number", + "Text": "Add a Phone Number", + "Level": 1 + }, + { + "Id": "add-a-username", + "Text": "Add a Username", + "Level": 1 + }, + { + "Id": "add-a-custom-identifier", + "Text": "Add a Custom Identifier", + "Level": 1 + }, + { + "Id": "add-as-primary", + "Text": "Add as Primary", + "Level": 1 + }, + { + "Id": "identifier-uniqueness-and-normalization", + "Text": "6. Identifier Uniqueness and Normalization", "Level": 0 }, { - "Id": "update-identifier", - "Text": "\u270F\uFE0F Update Identifier", + "Id": "use-server-results-as-authoritative", + "Text": "Use Server Results as Authoritative", + "Level": 1 + }, + { + "Id": "check-identifier-availability", + "Text": "7. Check Identifier Availability", "Level": 0 }, { - "Id": "verify-identifier", - "Text": "\u2705 Verify Identifier", + "Id": "check-a-username", + "Text": "Check a Username", + "Level": 1 + }, + { + "Id": "check-an-email-address", + "Text": "Check an Email Address", + "Level": 1 + }, + { + "Id": "availability-result", + "Text": "Availability Result", + "Level": 1 + }, + { + "Id": "display-validation-feedback", + "Text": "Display Validation Feedback", + "Level": 1 + }, + { + "Id": "update-an-identifier", + "Text": "8. Update an Identifier", "Level": 0 }, { - "Id": "delete-identifier", - "Text": "\u274C Delete Identifier", + "Id": "verification-after-an-update", + "Text": "Verification After an Update", + "Level": 1 + }, + { + "Id": "understanding-primary-identifiers", + "Text": "9. Understanding Primary Identifiers", "Level": 0 }, { - "Id": "admin-operations", - "Text": "\uD83D\uDC64 Admin Operations", + "Id": "set-a-primary-identifier", + "Text": "Set a Primary Identifier", + "Level": 1 + }, + { + "Id": "unset-a-primary-identifier", + "Text": "Unset a Primary Identifier", + "Level": 1 + }, + { + "Id": "display-primary-identifiers", + "Text": "Display Primary Identifiers", + "Level": 1 + }, + { + "Id": "verify-identifier-ownership", + "Text": "10. Verify Identifier Ownership", "Level": 0 }, { - "Id": "get-user-identifiers", - "Text": "Get User Identifiers", + "Id": "verify-an-identifier", + "Text": "Verify an Identifier", "Level": 1 }, { - "Id": "add-identifier-to-user", - "Text": "Add Identifier to User", + "Id": "verification-flow", + "Text": "Verification Flow", "Level": 1 }, { - "Id": "update-identifier-1", - "Text": "Update Identifier", + "Id": "server-side-verification-with-iuseridentifierverifier", + "Text": "11. Server-Side Verification with IUserIdentifierVerifier", + "Level": 0 + }, + { + "Id": "register-your-verifier", + "Text": "Register Your Verifier", "Level": 1 }, { - "Id": "delete-identifier-1", - "Text": "Delete Identifier", + "Id": "delete-an-identifier", + "Text": "12. Delete an Identifier", + "Level": 0 + }, + { + "Id": "hard-delete", + "Text": "Hard Delete", "Level": 1 }, { - "Id": "state-events", - "Text": "\uD83D\uDD04 State Events", + "Id": "deleting-primary-identifiers", + "Text": "Deleting Primary Identifiers", + "Level": 1 + }, + { + "Id": "deleting-login-identifiers", + "Text": "Deleting Login Identifiers", + "Level": 1 + }, + { + "Id": "build-an-identifier-management-page", + "Text": "13. Build an Identifier Management Page", + "Level": 0 + }, + { + "Id": "administrative-identifier-management", + "Text": "14. Administrative Identifier Management", + "Level": 0 + }, + { + "Id": "get-a-users-identifiers", + "Text": "Get a User\u0027s Identifiers", + "Level": 1 + }, + { + "Id": "add-an-identifier-1", + "Text": "Add an Identifier", + "Level": 1 + }, + { + "Id": "update-an-identifier-1", + "Text": "Update an Identifier", + "Level": 1 + }, + { + "Id": "set-primary", + "Text": "Set Primary", + "Level": 1 + }, + { + "Id": "unset-primary", + "Text": "Unset Primary", + "Level": 1 + }, + { + "Id": "verify-an-identifier-as-administrator", + "Text": "Verify an Identifier as Administrator", + "Level": 1 + }, + { + "Id": "delete-an-identifier-1", + "Text": "Delete an Identifier", + "Level": 1 + }, + { + "Id": "handling-identifier-operation-results", + "Text": "15. Handling Identifier Operation Results", "Level": 0 }, + { + "Id": "handle-success-and-failure", + "Text": "Handle Success and Failure", + "Level": 1 + }, + { + "Id": "identifier-state-events", + "Text": "16. Identifier State Events", + "Level": 0 + }, + { + "Id": "refresh-identifier-data-after-changes", + "Text": "Refresh Identifier Data After Changes", + "Level": 1 + }, { "Id": "security-considerations", - "Text": "\uD83D\uDD10 Security Considerations", + "Text": "17. Security Considerations", "Level": 0 }, { - "Id": "summary", - "Text": "\uD83C\uDFAF Summary", + "Id": "verification-requires-proof", + "Text": "Verification Requires Proof", + "Level": 1 + }, + { + "Id": "primary-does-not-mean-verified", + "Text": "Primary Does Not Mean Verified", + "Level": 1 + }, + { + "Id": "login-eligibility-is-separate", + "Text": "Login Eligibility Is Separate", + "Level": 1 + }, + { + "Id": "tenant-boundaries-matter", + "Text": "Tenant Boundaries Matter", + "Level": 1 + }, + { + "Id": "availability-checks-are-not-reservations", + "Text": "Availability Checks Are Not Reservations", + "Level": 1 + }, + { + "Id": "administrative-verification-is-privileged", + "Text": "Administrative Verification Is Privileged", + "Level": 1 + }, + { + "Id": "identifier-changes-may-affect-authentication", + "Text": "Identifier Changes May Affect Authentication", + "Level": 1 + }, + { + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/index.json index 876b19e7..35d18cac 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/index.json @@ -1,46 +1,96 @@ { "Slug": "client/index", - "Title": "Client \u0026 API", - "Html": "\n\u003Cp\u003EUltimateAuth Client is a \u003Cstrong\u003Ehigh-level SDK\u003C/strong\u003E designed to simplify authentication flows.\u003C/p\u003E\n\u003Cp\u003EIt is NOT just an HTTP wrapper.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-makes-the-client-different\u0022\u003E\uD83E\uDDE0 What Makes the Client Different?\u003C/h2\u003E\n\u003Cp\u003EThe client:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EHandles full authentication flows (login, PKCE, refresh)\u003C/li\u003E\n\u003Cli\u003EManages redirects automatically\u003C/li\u003E\n\u003Cli\u003EPublishes state events\u003C/li\u003E\n\u003Cli\u003EProvides structured results\u003C/li\u003E\n\u003Cli\u003EWorks across multiple client types (SPA, server, hybrid)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You SHOULD use the client instead of calling endpoints manually.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-architecture\u0022\u003E\uD83E\uDDF1 Client Architecture\u003C/h2\u003E\n\u003Cp\u003EThe client is split into multiple specialized services:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EService\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\u003EFlowClient\u003C/td\u003E\n\u003Ctd\u003ELogin, logout, refresh, PKCE\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESessionClient\u003C/td\u003E\n\u003Ctd\u003ESession \u0026amp; device management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUserClient\u003C/td\u003E\n\u003Ctd\u003EUser profile \u0026amp; lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIdentifierClient\u003C/td\u003E\n\u003Ctd\u003EEmail / username / phone management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredentialClient\u003C/td\u003E\n\u003Ctd\u003EPassword management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthorizationClient\u003C/td\u003E\n\u003Ctd\u003ERoles \u0026amp; permissions\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-entry-point\u0022\u003E\uD83E\uDDE9 Client Entry Point\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth exposes a single entry point:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003EUAuthClient\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\u003E[Inject] IUAuthClient UAuthClient { get; set; } = null!;\n\nawait UAuthClient.Flows.LoginAsync(...);\nawait UAuthClient.Users.GetMeAsync();\nawait UAuthClient.Sessions.GetMyChainsAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-concept-flow-based-design\u0022\u003E\uD83D\uDD11 Core Concept: Flow-Based Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is \u003Cstrong\u003Eflow-oriented\u003C/strong\u003E, not endpoint-oriented.\u003C/p\u003E\n\u003Cp\u003EInstead of calling endpoints:\u003C/p\u003E\n\u003Cp\u003E\u274C POST /auth/login\u003Cbr\u003E\n\u2714 flowClient.LoginAsync()\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022example\u0022\u003E\u26A1 Example\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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(new LoginRequest\n{\n Identifier = \u0026quot;user@ultimateauth.com\u0026quot;,\n Secret = \u0026quot;password\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This automatically:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBuilds request payload\u003C/li\u003E\n\u003Cli\u003EHandles redirect\u003C/li\u003E\n\u003Cli\u003EIntegrates with configured endpoints\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022state-events\u0022\u003E\uD83D\uDD04 State Events\u003C/h2\u003E\n\u003Cp\u003EClient automatically publishes events:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESessionRevoked\u003C/li\u003E\n\u003Cli\u003EProfileChanged\u003C/li\u003E\n\u003Cli\u003EAuthorizationChanged\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-to-use-this-section\u0022\u003E\uD83E\uDDED How to Use This Section\u003C/h2\u003E\n\u003Cp\u003EFollow these guides:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eauthentication.md \u2192 login, refresh, logout\u003C/li\u003E\n\u003Cli\u003Esession-management.md \u2192 sessions \u0026amp; devices\u003C/li\u003E\n\u003Cli\u003Euser-management.md \u2192 user operations\u003C/li\u003E\n\u003Cli\u003Eidentifiers.md \u2192 login identifiers\u003C/li\u003E\n\u003Cli\u003Eauthorization.md \u2192 roles \u0026amp; permissions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth Client:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eabstracts complexity\u003C/li\u003E\n\u003Cli\u003Eenforces correct flows\u003C/li\u003E\n\u003Cli\u003Ereduces security mistakes\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Think of it as:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003E\u201CAuthentication runtime for your frontend / app\u201D\u003C/strong\u003E\u003C/p\u003E\n", + "Title": "Client SDK Overview", + "Html": "\n\u003Cp\u003EThe UltimateAuth Client SDK provides a unified way to integrate authentication, session management, user operations, credentials, and authorization into .NET applications.\u003C/p\u003E\n\u003Cp\u003EInstead of manually constructing requests for every authentication endpoint, applications can use dedicated client services that understand UltimateAuth\u0027s API contracts and authentication workflows.\u003C/p\u003E\n\u003Cp\u003EThe SDK is designed for applications that need to interact with an UltimateAuth server, including Blazor WebAssembly, Blazor Server, and other supported .NET client environments.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThis section is the practical developer guide to using UltimateAuth from application code.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-the-client-sdk-provides\u0022\u003EWhat the Client SDK Provides\u003C/h2\u003E\n\u003Cp\u003EThe Client SDK is more than a collection of HTTP methods.\u003C/p\u003E\n\u003Cp\u003EIt brings together several responsibilities:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthentication workflows\u003C/strong\u003E \u2014 Start login, refresh, logout, and other supported authentication operations.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession management\u003C/strong\u003E \u2014 Interact with available session and device management capabilities.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUser management\u003C/strong\u003E \u2014 Retrieve and manage user information through the appropriate API contracts.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ECredential operations\u003C/strong\u003E \u2014 Manage credentials and initiate supported recovery workflows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthorization management\u003C/strong\u003E \u2014 Work with roles, permissions, and other exposed authorization operations.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EStructured results\u003C/strong\u003E \u2014 Receive operation results through the SDK\u0027s response contracts.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClient state integration\u003C/strong\u003E \u2014 Coordinate supported authentication state changes and notifications.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe SDK helps applications interact with UltimateAuth consistently while keeping the server responsible for authentication and authorization enforcement.\u003C/p\u003E\n\u003Cp\u003EIt does not replace server-side security checks.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-client-architecture\u0022\u003EThe Client Architecture\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates client responsibilities into specialized services.\u003C/p\u003E\n\u003Cp\u003EThe main client entry point is \u003Ccode\u003EUAuthClient\u003C/code\u003E, which provides access to the SDK\u0027s functional areas.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EApplication\n |\n v\nUAuthClient\n |\n \u002B-- Flows\n | Login, logout, refresh, PKCE\n |\n \u002B-- Sessions\n | Sessions and device-associated chains\n |\n \u002B-- Users\n | User information and lifecycle\n |\n \u002B-- Identifiers\n | User identifiers and verification\n |\n \u002B-- Credentials\n | Credential management and recovery\n |\n \u002B-- Authorization\n Roles, permissions, and access management\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese represent functional responsibilities. The precise available members and operations depend on the installed packages and supported SDK contracts.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-specialized-clients\u0022\u003EWhy Specialized Clients?\u003C/h3\u003E\n\u003Cp\u003EAuthentication-related operations have different requirements.\u003C/p\u003E\n\u003Cp\u003EA login flow may establish authentication state and involve redirects. A user management operation may retrieve or update account information. A credential recovery operation may require additional verification and security handling.\u003C/p\u003E\n\u003Cp\u003EKeeping these responsibilities separate provides a clearer programming model and makes individual APIs easier to discover.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022getting-started-with-uauthclient\u0022\u003EGetting Started with UAuthClient\u003C/h2\u003E\n\u003Cp\u003EIn a supported dependency injection environment, application components can consume the configured UltimateAuth client.\u003C/p\u003E\n\u003Cp\u003EFor example, a Blazor component can receive the client through dependency injection:\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@inject UAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOnce configured, the application uses the appropriate client service for the operation it needs.\u003C/p\u003E\n\u003Cp\u003EThe following illustrates the intended programming 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\u003Evar user = await UAuthClient.Users.GetMeAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe SDK handles the corresponding client operation and returns its structured result.\u003C/p\u003E\n\u003Cp\u003EBefore using the client, the application must register the appropriate UltimateAuth services and configure its connection to the authentication server.\u003C/p\u003E\n\u003Cp\u003EFor a complete setup, follow the Getting Started guide for your application environment.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choose-your-application-environment\u0022\u003EChoose Your Application Environment\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports different application architectures, but their authentication and transport requirements are not identical.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-webassembly\u0022\u003EBlazor WebAssembly\u003C/h3\u003E\n\u003Cp\u003EBlazor WebAssembly executes application code in the browser.\u003C/p\u003E\n\u003Cp\u003EThe client communicates with the authentication server and protected APIs through browser-compatible HTTP requests.\u003C/p\u003E\n\u003Cp\u003EBrowser security rules, credential handling, CORS configuration, and authentication state synchronization are relevant to this environment.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server\u0022\u003EBlazor Server\u003C/h3\u003E\n\u003Cp\u003EBlazor Server executes application code on the server while maintaining an interactive connection to the browser.\u003C/p\u003E\n\u003Cp\u003EAuthentication state, cookie behavior, and server-side execution require different considerations from a browser-hosted WebAssembly application.\u003C/p\u003E\n\u003Cp\u003EServer-side code may also invoke application services directly rather than making an HTTP request.\u003C/p\u003E\n\u003Cp\u003ESuch operations still require appropriate server-side authorization enforcement.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022interactive-web-applications\u0022\u003EInteractive Web Applications\u003C/h3\u003E\n\u003Cp\u003EApplications combining server rendering and WebAssembly interactivity may have multiple execution environments.\u003C/p\u003E\n\u003Cp\u003EDevelopers should distinguish between operations performed on the server and operations performed in the browser.\u003C/p\u003E\n\u003Cp\u003EShared UI components do not automatically imply identical transport or credential handling.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022other.net-clients\u0022\u003EOther .NET Clients\u003C/h3\u003E\n\u003Cp\u003EOther supported .NET application environments can use the relevant client SDK capabilities according to their platform requirements.\u003C/p\u003E\n\u003Cp\u003ECredential storage, HTTP transport, redirect handling, and authentication state integration may differ from browser applications.\u003C/p\u003E\n\u003Cp\u003EThe SDK\u0027s shared contracts help maintain consistency, while platform-specific integration handles the environment\u0027s requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022working-with-authentication-state\u0022\u003EWorking with Authentication State\u003C/h2\u003E\n\u003Cp\u003EAuthentication is not simply a method call that returns a token.\u003C/p\u003E\n\u003Cp\u003EThe client may need to coordinate several related concerns:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECompleting the selected authentication flow.\u003C/li\u003E\n\u003Cli\u003EHandling the resulting authentication state.\u003C/li\u003E\n\u003Cli\u003ERenewing authentication when continuation is supported.\u003C/li\u003E\n\u003Cli\u003EReacting to logout or session invalidation.\u003C/li\u003E\n\u003Cli\u003EUpdating application components when relevant state changes.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth provides client-side abstractions and notifications for supported operations.\u003C/p\u003E\n\u003Cp\u003EFor example, user or credential changes may publish SDK events after a successful operation.\u003C/p\u003E\n\u003Cp\u003EApplications can use the available state integration mechanisms to update their UI without treating every API response as a complete authentication state transition.\u003C/p\u003E\n\u003Cp\u003EThe exact behavior depends on the client environment and configured integration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-sdk-results\u0022\u003EUnderstanding SDK Results\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth client operations use structured response contracts.\u003C/p\u003E\n\u003Cp\u003EApplications should inspect the result of an operation rather than assuming that completing an asynchronous call means the requested action succeeded.\u003C/p\u003E\n\u003Cp\u003EA typical application workflow 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\u003ECall SDK operation\n |\n v\nReceive structured result\n |\n \u002B-- Success\n | |\n | v\n | Continue application workflow\n |\n \u002B-- Failure\n |\n v\n Handle error\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDifferent operations may return different result types.\u003C/p\u003E\n\u003Cp\u003EThe relevant guide for each client service explains its request and response contracts, including how to handle failures.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-events-and-ui-updates\u0022\u003EClient Events and UI Updates\u003C/h2\u003E\n\u003Cp\u003ESome SDK operations can publish notifications when a successful operation changes application-relevant state.\u003C/p\u003E\n\u003Cp\u003EFor example, credential or user management operations may notify subscribers that related information has changed.\u003C/p\u003E\n\u003Cp\u003EThese notifications help decouple an operation from the UI components that react to it.\u003C/p\u003E\n\u003Cp\u003EA component can respond by refreshing its data rather than requiring the caller to manually update every affected view.\u003C/p\u003E\n\u003Cp\u003EHowever, client events are not a security authority.\u003C/p\u003E\n\u003Cp\u003EAn event does not replace server-side validation, and the absence of an event does not prove that the underlying security state has remained unchanged.\u003C/p\u003E\n\u003Cp\u003EApplications should continue to treat the server as authoritative wherever the selected authentication mode requires it.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022calling-protected-resource-apis\u0022\u003ECalling Protected Resource APIs\u003C/h2\u003E\n\u003Cp\u003EAuthentication is often only the first step.\u003C/p\u003E\n\u003Cp\u003EApplications also need to call protected business APIs after the user has authenticated.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports integration with protected resource requests, allowing the client application to use the authentication mechanisms appropriate to its configured environment.\u003C/p\u003E\n\u003Cp\u003EA protected resource request involves two distinct responsibilities:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EThe client sends the request with the appropriate authentication material.\u003C/li\u003E\n\u003Cli\u003EThe receiving server validates authentication and enforces authorization.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe Client SDK can help with the client-side responsibility.\u003C/p\u003E\n\u003Cp\u003EIt cannot grant access to an operation that the receiving server does not authorize.\u003C/p\u003E\n\u003Cp\u003EWhen building applications with separate resource APIs, developers must configure the relevant API integration and server-side authentication enforcement.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-sdk-guides\u0022\u003EClient SDK Guides\u003C/h2\u003E\n\u003Cp\u003EThe following guides cover the major client responsibilities with practical C# examples.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EGuide\u003C/th\u003E\n\u003Cth\u003EWhat you will learn\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAuthentication\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin, logout, refresh, PKCE, and authentication state handling\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESession Management\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERetrieve, inspect, and manage sessions and device-associated chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EUser Management\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERetrieve user information and perform supported user lifecycle operations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EIdentifiers\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EManage email addresses, usernames, phone numbers, and verification workflows\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAdd, change, revoke, and recover credentials\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAuthorization\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EWork with roles, permissions, and exposed authorization management operations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EResource API Requests\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECall protected application APIs using the appropriate authentication integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EEach guide focuses on a specific developer task and includes the relevant SDK methods, request models, response handling, and implementation considerations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-learning-path\u0022\u003ERecommended Learning Path\u003C/h3\u003E\n\u003Cp\u003EIf you are integrating UltimateAuth for the first time, start with \u003Cstrong\u003EAuthentication\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EOnce login and authentication state handling work correctly, continue with \u003Cstrong\u003EResource API Requests\u003C/strong\u003E to connect authentication to your application\u0027s protected functionality.\u003C/p\u003E\n\u003Cp\u003EAfter that, use the domain-specific guides for the features your application needs.\u003C/p\u003E\n\u003Cp\u003EFor example, an account settings page may require the User Management, Identifiers, and Credentials guides, while an administration panel may additionally require Authorization and Session Management.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-sdk-and-server-responsibilities\u0022\u003EClient SDK and Server Responsibilities\u003C/h2\u003E\n\u003Cp\u003EThe Client SDK simplifies integration, but it does not independently enforce the security of server-side operations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EClient responsibility\u003C/th\u003E\n\u003Cth\u003EServer responsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESubmit authentication requests\u003C/td\u003E\n\u003Ctd\u003EValidate authentication requirements\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EManage supported client state\u003C/td\u003E\n\u003Ctd\u003EEstablish and validate authoritative session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESend protected resource requests\u003C/td\u003E\n\u003Ctd\u003EAuthenticate and authorize protected operations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EHandle structured responses\u003C/td\u003E\n\u003Ctd\u003EEnforce domain rules and security policies\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReact to state notifications\u003C/td\u003E\n\u003Ctd\u003EApply revocation and account security controls\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis separation is particularly important for applications that share UI components across different hosting models.\u003C/p\u003E\n\u003Cp\u003EA shared component can use the same SDK abstractions, while the hosting environment and server determine how authentication material is transported and validated.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-to-use-the-client-sdk\u0022\u003EWhen to Use the Client SDK\u003C/h2\u003E\n\u003Cp\u003EUse the Client SDK when building an application that interacts with UltimateAuth\u0027s exposed authentication and management APIs.\u003C/p\u003E\n\u003Cp\u003EIt is generally preferable to manually reproducing the corresponding HTTP contracts because it provides a consistent integration surface and reduces duplicated request-handling code.\u003C/p\u003E\n\u003Cp\u003EDirect endpoint integration may still be appropriate for clients that cannot use the .NET SDK or for specialized interoperability requirements.\u003C/p\u003E\n\u003Cp\u003ERegardless of the client implementation, the same server-side authentication and authorization requirements apply.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cp\u003EContinue with the Client SDK guides to implement real application workflows.\u003C/p\u003E\n\u003Cp\u003EStart with \u003Cstrong\u003EAuthentication\u003C/strong\u003E to configure and use login, refresh, and logout operations.\u003C/p\u003E\n\u003Cp\u003EThen explore session management, user operations, credentials, authorization, and protected resource API integration according to your application\u0027s requirements.\u003C/p\u003E\n\u003Cp\u003EFor the concepts behind these operations, refer to \u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E and \u003Ca href=\u0022/docs/plugin-domains\u0022\u003EPlugin Domains\u003C/a\u003E.\u003C/p\u003E\n", "Headings": [ { - "Id": "what-makes-the-client-different", - "Text": "\uD83E\uDDE0 What Makes the Client Different?", + "Id": "what-the-client-sdk-provides", + "Text": "What the Client SDK Provides", "Level": 0 }, { - "Id": "client-architecture", - "Text": "\uD83E\uDDF1 Client Architecture", + "Id": "the-client-architecture", + "Text": "The Client Architecture", "Level": 0 }, { - "Id": "client-entry-point", - "Text": "\uD83E\uDDE9 Client Entry Point", + "Id": "why-specialized-clients", + "Text": "Why Specialized Clients?", + "Level": 1 + }, + { + "Id": "getting-started-with-uauthclient", + "Text": "Getting Started with UAuthClient", + "Level": 0 + }, + { + "Id": "choose-your-application-environment", + "Text": "Choose Your Application Environment", + "Level": 0 + }, + { + "Id": "blazor-webassembly", + "Text": "Blazor WebAssembly", + "Level": 1 + }, + { + "Id": "blazor-server", + "Text": "Blazor Server", + "Level": 1 + }, + { + "Id": "interactive-web-applications", + "Text": "Interactive Web Applications", + "Level": 1 + }, + { + "Id": "other.net-clients", + "Text": "Other .NET Clients", + "Level": 1 + }, + { + "Id": "working-with-authentication-state", + "Text": "Working with Authentication State", + "Level": 0 + }, + { + "Id": "understanding-sdk-results", + "Text": "Understanding SDK Results", + "Level": 0 + }, + { + "Id": "client-events-and-ui-updates", + "Text": "Client Events and UI Updates", "Level": 0 }, { - "Id": "core-concept-flow-based-design", - "Text": "\uD83D\uDD11 Core Concept: Flow-Based Design", + "Id": "calling-protected-resource-apis", + "Text": "Calling Protected Resource APIs", "Level": 0 }, { - "Id": "example", - "Text": "\u26A1 Example", + "Id": "client-sdk-guides", + "Text": "Client SDK Guides", "Level": 0 }, { - "Id": "state-events", - "Text": "\uD83D\uDD04 State Events", + "Id": "recommended-learning-path", + "Text": "Recommended Learning Path", + "Level": 1 + }, + { + "Id": "client-sdk-and-server-responsibilities", + "Text": "Client SDK and Server Responsibilities", "Level": 0 }, { - "Id": "how-to-use-this-section", - "Text": "\uD83E\uDDED How to Use This Section", + "Id": "when-to-use-the-client-sdk", + "Text": "When to Use the Client SDK", "Level": 0 }, { - "Id": "summary", - "Text": "\uD83C\uDFAF Summary", + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/session-management.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/session-management.json index 1b24e140..4c834885 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/session-management.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/session-management.json @@ -1,86 +1,196 @@ { "Slug": "client/session-management", "Title": "Session Management", - "Html": "\n\u003Cp\u003EThis section explains how to manage sessions and devices using the UltimateAuth client.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth, sessions are \u003Cstrong\u003Enot just tokens\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThey are structured as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoot \u2192 user-level security\u003C/li\u003E\n\u003Cli\u003EChain \u2192 device (browser / mobile / app)\u003C/li\u003E\n\u003Cli\u003ESession \u2192 individual authentication instance\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 On the client, you interact 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\u003E[Inject] IUAuthClient UAuthClient { get; set; } = null!;\n\nawait UAuthClient.Sessions...\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-active-sessions-devices\u0022\u003E\uD83D\uDCCB Get Active Sessions (Devices)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\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\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Returns:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EActive devices\u003C/li\u003E\n\u003Cli\u003ESession chains\u003C/li\u003E\n\u003Cli\u003EMetadata (device, timestamps)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-session-detail\u0022\u003E\uD83D\uDD0D Get Session Detail\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar detail = await UAuthClient.Sessions.GetMyChainDetailAsync(chainId);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Use this to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInspect a specific device\u003C/li\u003E\n\u003Cli\u003EView session history\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-vs-revoke-important\u0022\u003E\uD83D\uDEAA Logout vs Revoke (Important)\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003ELogout\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.LogoutAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnds \u003Cstrong\u003Ecurrent session\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003EUser can login again normally\u003C/li\u003E\n\u003Cli\u003EDoes NOT affect other devices\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-session-control\u0022\u003ERevoke (Session Control)\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.Sessions.RevokeMyChainAsync(chainId);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalidates entire \u003Cstrong\u003Edevice chain\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003EAll sessions under that device are revoked\u003C/li\u003E\n\u003Cli\u003ECannot be restored\u003C/li\u003E\n\u003Cli\u003ENew login creates a new chain\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Key difference:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogout = end current session\u003C/li\u003E\n\u003Cli\u003ERevoke = destroy device identity\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor standard auth process, use \u003Ccode\u003EUAuthClient.Flows.LogoutMyDeviceAsync(chainId)\u003C/code\u003E instead of \u003Ccode\u003EUAuthClient.Sessions.RevokeMyChainAsync(chainId)\u003C/code\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-other-devices\u0022\u003E\uD83D\uDCF1 Revoke Other Devices\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Sessions.RevokeMyOtherChainsAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EKeeps current device active\u003C/li\u003E\n\u003Cli\u003ELogs out all other devices\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-all-sessions\u0022\u003E\uD83D\uDCA5 Revoke All Sessions\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Sessions.RevokeAllMyChainsAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogs out ALL devices (including current)\u003C/li\u003E\n\u003Cli\u003EForces full reauthentication everywhere\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-session-management\u0022\u003E\uD83D\uDC64 Admin Session Management\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-user-devices\u0022\u003EGet User 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\u003Eawait UAuthClient.Sessions.GetUserChainsAsync(userKey);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-specific-session\u0022\u003ERevoke Specific Session\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.Sessions.RevokeUserSessionAsync(userKey, sessionId);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-device-chain\u0022\u003ERevoke 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\u003Eawait UAuthClient.Sessions.RevokeUserChainAsync(userKey, chainId);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-all-user-sessions\u0022\u003ERevoke All User Sessions\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.Sessions.RevokeAllUserChainsAsync(userKey);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-model\u0022\u003E\uD83E\uDDE0 Device Model\u003C/h2\u003E\n\u003Cp\u003EEach chain represents a \u003Cstrong\u003Edevice identity\u003C/strong\u003E:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBrowser instance\u003C/li\u003E\n\u003Cli\u003EMobile device\u003C/li\u003E\n\u003Cli\u003EApplication instance\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Sessions are grouped under chains.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-implications\u0022\u003E\uD83D\uDD10 Security Implications\u003C/h2\u003E\n\u003Cp\u003ESession operations are security-critical:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERevoke is irreversible\u003C/li\u003E\n\u003Cli\u003EDevice isolation is enforced\u003C/li\u003E\n\u003Cli\u003ECross-device attacks are contained\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003ESession management in UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eis device-aware\u003C/li\u003E\n\u003Cli\u003Eseparates logout vs revoke\u003C/li\u003E\n\u003Cli\u003Egives full control over user sessions\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-model\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\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-model\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-model\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Revocation and session security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { - "Id": "overview", - "Text": "\uD83E\uDDE0 Overview", + "Id": "understanding-the-session-hierarchy", + "Text": "1. Understanding the Session Hierarchy", "Level": 0 }, { - "Id": "get-active-sessions-devices", - "Text": "\uD83D\uDCCB Get Active Sessions (Devices)", + "Id": "accessing-the-session-client", + "Text": "2. Accessing the Session Client", "Level": 0 }, { - "Id": "get-session-detail", - "Text": "\uD83D\uDD0D Get Session Detail", + "Id": "list-my-devices-and-session-chains", + "Text": "3. List My Devices and Session Chains", "Level": 0 }, { - "Id": "logout-vs-revoke-important", - "Text": "\uD83D\uDEAA Logout vs Revoke (Important)", + "Id": "sessionchainsummary", + "Text": "SessionChainSummary", + "Level": 1 + }, + { + "Id": "identify-the-current-device", + "Text": "Identify the Current Device", + "Level": 1 + }, + { + "Id": "pagination", + "Text": "4. Pagination", "Level": 0 }, { - "Id": "logout", - "Text": "Logout", + "Id": "navigating-pages", + "Text": "Navigating Pages", "Level": 1 }, { - "Id": "revoke-session-control", - "Text": "Revoke (Session Control)", + "Id": "inspect-a-session-chain", + "Text": "5. Inspect a Session Chain", + "Level": 0 + }, + { + "Id": "sessionchaindetail", + "Text": "SessionChainDetail", "Level": 1 }, { - "Id": "revoke-other-devices", - "Text": "\uD83D\uDCF1 Revoke Other Devices", + "Id": "inspect-session-history", + "Text": "Inspect Session History", + "Level": 1 + }, + { + "Id": "understanding-chain-states", + "Text": "6. Understanding Chain States", + "Level": 0 + }, + { + "Id": "revoke-one-of-my-devices", + "Text": "7. Revoke One of My Devices", "Level": 0 }, { - "Id": "revoke-all-sessions", - "Text": "\uD83D\uDCA5 Revoke All Sessions", + "Id": "detect-revocation-of-the-current-chain", + "Text": "Detect Revocation of the Current Chain", + "Level": 1 + }, + { + "Id": "revoke-all-other-devices", + "Text": "8. Revoke All Other Devices", + "Level": 0 + }, + { + "Id": "revoke-all-my-devices", + "Text": "9. Revoke All My Devices", + "Level": 0 + }, + { + "Id": "security-considerations", + "Text": "Security Considerations", + "Level": 1 + }, + { + "Id": "logout-versus-session-revocation", + "Text": "10. Logout Versus Session Revocation", + "Level": 0 + }, + { + "Id": "which-api-should-you-use", + "Text": "Which API Should You Use?", + "Level": 1 + }, + { + "Id": "logout-a-device-through-flows", + "Text": "Logout a Device Through Flows", + "Level": 1 + }, + { + "Id": "build-a-device-management-page", + "Text": "11. Build a Device Management Page", "Level": 0 }, { - "Id": "admin-session-management", - "Text": "\uD83D\uDC64 Admin Session Management", + "Id": "administrative-session-management", + "Text": "12. Administrative Session Management", "Level": 0 }, { - "Id": "get-user-devices", - "Text": "Get User Devices", + "Id": "list-a-users-devices", + "Text": "List a User\u0027s Devices", + "Level": 1 + }, + { + "Id": "inspect-a-users-chain", + "Text": "Inspect a User\u0027s Chain", + "Level": 1 + }, + { + "Id": "revoke-an-individual-session", + "Text": "Revoke an Individual Session", "Level": 1 }, { - "Id": "revoke-specific-session", - "Text": "Revoke Specific Session", + "Id": "revoke-a-users-device-chain", + "Text": "Revoke a User\u0027s Device Chain", "Level": 1 }, { - "Id": "revoke-device-chain", - "Text": "Revoke Device (Chain)", + "Id": "revoke-all-user-chains", + "Text": "Revoke All User Chains", "Level": 1 }, { - "Id": "revoke-all-user-sessions", - "Text": "Revoke All User Sessions", + "Id": "revoke-the-users-session-root", + "Text": "Revoke the User\u0027s Session Root", "Level": 1 }, { - "Id": "device-model", - "Text": "\uD83E\uDDE0 Device Model", + "Id": "choosing-the-correct-revocation-scope", + "Text": "13. Choosing the Correct Revocation Scope", "Level": 0 }, { - "Id": "security-implications", - "Text": "\uD83D\uDD10 Security Implications", + "Id": "handling-session-operation-results", + "Text": "14. Handling Session Operation Results", "Level": 0 }, { - "Id": "summary", - "Text": "\uD83C\uDFAF Summary", + "Id": "administrative-authorization-failures", + "Text": "Administrative Authorization Failures", + "Level": 1 + }, + { + "Id": "session-state-events", + "Text": "15. Session State Events", + "Level": 0 + }, + { + "Id": "security-and-operational-considerations", + "Text": "16. Security and Operational Considerations", + "Level": 0 + }, + { + "Id": "device-metadata-is-descriptive", + "Text": "Device Metadata Is Descriptive", + "Level": 1 + }, + { + "Id": "session-chains-are-not-permanent-device-identities", + "Text": "Session Chains Are Not Permanent Device Identities", + "Level": 1 + }, + { + "Id": "revocation-is-server-controlled", + "Text": "Revocation Is Server-Controlled", + "Level": 1 + }, + { + "Id": "authentication-modes-matter", + "Text": "Authentication Modes Matter", + "Level": 1 + }, + { + "Id": "protect-administrative-operations", + "Text": "Protect Administrative Operations", + "Level": 1 + }, + { + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/user-management.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/user-management.json index b20bb2df..de32652a 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/user-management.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/user-management.json @@ -1,86 +1,311 @@ { "Slug": "client/user-management", "Title": "User Management", - "Html": "\n\u003Cp\u003EThis section explains how to manage users using the UltimateAuth client.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022overview\u0022\u003E\uD83E\uDDE0 Overview\u003C/h2\u003E\n\u003Cp\u003EUser operations are handled via:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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\u003EThis includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EProfile management\u003C/li\u003E\n\u003Cli\u003EUser lifecycle\u003C/li\u003E\n\u003Cli\u003EAdmin operations\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-current-user\u0022\u003E\uD83D\uDE4B Get Current User\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar me = await UAuthClient.Users.GetMeAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Returns:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUser profile\u003C/li\u003E\n\u003Cli\u003EStatus\u003C/li\u003E\n\u003Cli\u003EBasic identity data\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-profile\u0022\u003E\u270F\uFE0F Update Profile\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.UpdateMeAsync(new UpdateProfileRequest\n{\n DisplayName = \u0026quot;John Doe\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Triggers:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EProfile update\u003C/li\u003E\n\u003Cli\u003EState event (ProfileChanged)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-current-user\u0022\u003E\u274C Delete Current User\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.DeleteMeAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDeletes user (based on configured mode)\u003C/li\u003E\n\u003Cli\u003EEnds session\u003C/li\u003E\n\u003Cli\u003ETriggers state update\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-query-users\u0022\u003E\uD83D\uDC51 Admin: Query Users\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\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(new UserQuery\n{\n Search = \u0026quot;john\u0026quot;,\n PageNumber = 1,\n PageSize = 10\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Supports:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esearch\u003C/li\u003E\n\u003Cli\u003Epagination\u003C/li\u003E\n\u003Cli\u003Efiltering (status, etc.)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-user\u0022\u003E\u2795 Create User\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.CreateAsync(new CreateUserRequest\n{\n UserName = \u0026quot;john\u0026quot;,\n Email = \u0026quot;john@mail.com\u0026quot;,\n Password = \u0026quot;123456\u0026quot;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin-create\u0022\u003E\uD83D\uDEE0 Admin Create\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.CreateAsAdminAsync(request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-status\u0022\u003E\uD83D\uDD04 Change Status\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self\u0022\u003ESelf\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.Users.ChangeMyStatusAsync(new ChangeUserStatusSelfRequest\n{\n Status = UserStatus.SelfSuspended\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022admin\u0022\u003EAdmin\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.Users.ChangeUserStatusAsync(userKey, new ChangeUserStatusAdminRequest\n{\n Status = UserStatus.Suspended\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-user-admin\u0022\u003E\u274C Delete User (Admin)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.DeleteUserAsync(userKey, new DeleteUserRequest\n{\n Mode = DeleteMode.Soft\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022get-user\u0022\u003E\uD83D\uDD0D Get User\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar user = await UAuthClient.Users.GetUserAsync(userKey);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022update-user-admin\u0022\u003E\u270F\uFE0F Update User (Admin)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.UpdateUserAsync(userKey, request);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifecycle-model\u0022\u003E\uD83E\uDDE0 Lifecycle Model\u003C/h2\u003E\n\u003Cp\u003EUsers have a lifecycle:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EActive\u003C/li\u003E\n\u003Cli\u003ESuspended\u003C/li\u003E\n\u003Cli\u003EDisabled\u003C/li\u003E\n\u003Cli\u003EDeleted (soft/hard)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Status impacts:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Elogin ability\u003C/li\u003E\n\u003Cli\u003Esession validity\u003C/li\u003E\n\u003Cli\u003Eauthorization\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-notes\u0022\u003E\uD83D\uDD10 Security Notes\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EStatus changes may invalidate sessions\u003C/li\u003E\n\u003Cli\u003EDelete may trigger cleanup across domains\u003C/li\u003E\n\u003Cli\u003EAdmin actions are policy-protected\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cp\u003EUser management in UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eis lifecycle-aware\u003C/li\u003E\n\u003Cli\u003Esupports self \u002B admin flows\u003C/li\u003E\n\u003Cli\u003Eintegrates with session \u0026amp; security model\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/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=\u0022get-a-specific-user\u0022\u003E13. 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\u003E14. 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\u003E15. 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\u003E16. 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\u003E17. 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\u003E18. 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\u003E19. 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\u003E20. 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\u003E21. 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\u003E22. 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\u003E23. 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\u003E24. 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", "Headings": [ { - "Id": "overview", - "Text": "\uD83E\uDDE0 Overview", + "Id": "accessing-the-user-client", + "Text": "1. Accessing the User Client", "Level": 0 }, { - "Id": "get-current-user", - "Text": "\uD83D\uDE4B Get Current User", + "Id": "get-the-current-user", + "Text": "2. Get the Current User", "Level": 0 }, { - "Id": "update-profile", - "Text": "\u270F\uFE0F Update Profile", + "Id": "userview", + "Text": "UserView", + "Level": 1 + }, + { + "Id": "update-the-current-users-profile", + "Text": "3. Update the Current User\u0027s Profile", + "Level": 0 + }, + { + "Id": "important-profile-updates-replace-editable-fields", + "Text": "Important: Profile Updates Replace Editable Fields", + "Level": 1 + }, + { + "Id": "recommended-read-modify-and-submit", + "Text": "Recommended: Read, Modify, and Submit", + "Level": 1 + }, + { + "Id": "profile-state-events", + "Text": "Profile State Events", + "Level": 1 + }, + { + "Id": "understanding-multiple-profiles", + "Text": "4. Understanding Multiple Profiles", + "Level": 0 + }, + { + "Id": "default-profile", + "Text": "Default Profile", + "Level": 1 + }, + { + "Id": "profile-keys", + "Text": "Profile Keys", + "Level": 1 + }, + { + "Id": "retrieve-a-specific-profile", + "Text": "5. Retrieve a Specific Profile", "Level": 0 }, { - "Id": "delete-current-user", - "Text": "\u274C Delete Current User", + "Id": "create-an-additional-profile", + "Text": "6. Create an Additional Profile", "Level": 0 }, { - "Id": "admin-query-users", - "Text": "\uD83D\uDC51 Admin: Query Users", + "Id": "clone-an-existing-profile", + "Text": "Clone an Existing Profile", + "Level": 1 + }, + { + "Id": "profile-creation-events", + "Text": "Profile Creation Events", + "Level": 1 + }, + { + "Id": "update-a-specific-profile", + "Text": "7. Update a Specific Profile", "Level": 0 }, { - "Id": "create-user", - "Text": "\u2795 Create User", + "Id": "delete-a-profile", + "Text": "8. Delete a Profile", "Level": 0 }, { - "Id": "admin-create", - "Text": "\uD83D\uDEE0 Admin Create", + "Id": "create-a-user", + "Text": "9. Create a User", + "Level": 0 + }, + { + "Id": "createuserrequest", + "Text": "CreateUserRequest", + "Level": 1 + }, + { + "Id": "usercreateresult", + "Text": "UserCreateResult", + "Level": 1 + }, + { + "Id": "administrative-user-creation", + "Text": "10. Administrative User Creation", "Level": 0 }, { - "Id": "change-status", - "Text": "\uD83D\uDD04 Change Status", + "Id": "query-users", + "Text": "11. Query Users", "Level": 0 }, { - "Id": "self", - "Text": "Self", + "Id": "userquery-filters", + "Text": "UserQuery Filters", "Level": 1 }, { - "Id": "admin", - "Text": "Admin", + "Id": "search-behavior", + "Text": "Search Behavior", + "Level": 1 + }, + { + "Id": "filter-by-status", + "Text": "Filter by Status", + "Level": 1 + }, + { + "Id": "include-deleted-users", + "Text": "Include Deleted Users", + "Level": 1 + }, + { + "Id": "usersummary-and-pagination", + "Text": "12. UserSummary and Pagination", + "Level": 0 + }, + { + "Id": "navigating-pages", + "Text": "Navigating Pages", "Level": 1 }, { - "Id": "delete-user-admin", - "Text": "\u274C Delete User (Admin)", + "Id": "get-a-specific-user", + "Text": "13. Get a Specific User", "Level": 0 }, { - "Id": "get-user", - "Text": "\uD83D\uDD0D Get User", + "Id": "retrieve-a-specific-user-profile", + "Text": "Retrieve a Specific User Profile", + "Level": 1 + }, + { + "Id": "update-another-users-profile", + "Text": "14. Update Another User\u0027s Profile", "Level": 0 }, { - "Id": "update-user-admin", - "Text": "\u270F\uFE0F Update User (Admin)", + "Id": "update-a-named-profile", + "Text": "Update a Named Profile", + "Level": 1 + }, + { + "Id": "manage-another-users-profiles", + "Text": "15. Manage Another User\u0027s Profiles", "Level": 0 }, { - "Id": "lifecycle-model", - "Text": "\uD83E\uDDE0 Lifecycle Model", + "Id": "create-a-profile", + "Text": "Create a Profile", + "Level": 1 + }, + { + "Id": "delete-a-profile-1", + "Text": "Delete a Profile", + "Level": 1 + }, + { + "Id": "understanding-user-status", + "Text": "16. Understanding User Status", "Level": 0 }, { - "Id": "security-notes", - "Text": "\uD83D\uDD10 Security Notes", + "Id": "self-service-and-administrative-statuses", + "Text": "Self-Service and Administrative Statuses", + "Level": 1 + }, + { + "Id": "change-your-own-status", + "Text": "17. Change Your Own Status", "Level": 0 }, { - "Id": "summary", - "Text": "\uD83C\uDFAF Summary", + "Id": "userstatuschangeresult", + "Text": "UserStatusChangeResult", + "Level": 1 + }, + { + "Id": "state-event", + "Text": "State Event", + "Level": 1 + }, + { + "Id": "change-another-users-status", + "Text": "18. Change Another User\u0027s Status", + "Level": 0 + }, + { + "Id": "example-disable-an-account", + "Text": "Example: Disable an Account", + "Level": 1 + }, + { + "Id": "example-restore-an-active-status", + "Text": "Example: Restore an Active Status", + "Level": 1 + }, + { + "Id": "delete-the-current-user", + "Text": "19. Delete the Current User", + "Level": 0 + }, + { + "Id": "important-considerations", + "Text": "Important Considerations", + "Level": 1 + }, + { + "Id": "administrative-user-deletion", + "Text": "20. Administrative User Deletion", + "Level": 0 + }, + { + "Id": "soft-delete", + "Text": "Soft Delete", + "Level": 1 + }, + { + "Id": "hard-delete", + "Text": "Hard Delete", + "Level": 1 + }, + { + "Id": "deletemode", + "Text": "DeleteMode", + "Level": 1 + }, + { + "Id": "userdeleteresult", + "Text": "UserDeleteResult", + "Level": 1 + }, + { + "Id": "build-a-user-administration-table", + "Text": "21. Build a User Administration Table", + "Level": 0 + }, + { + "Id": "handling-user-operation-results", + "Text": "22. Handling User Operation Results", + "Level": 0 + }, + { + "Id": "transport-level-versus-operation-level-success", + "Text": "Transport-Level Versus Operation-Level Success", + "Level": 1 + }, + { + "Id": "http-status-helpers", + "Text": "HTTP Status Helpers", + "Level": 1 + }, + { + "Id": "client-state-events", + "Text": "23. Client State Events", + "Level": 0 + }, + { + "Id": "security-and-lifecycle-considerations", + "Text": "24. Security and Lifecycle Considerations", + "Level": 0 + }, + { + "Id": "self-service-and-administrative-boundaries", + "Text": "Self-Service and Administrative Boundaries", + "Level": 1 + }, + { + "Id": "status-changes-and-existing-sessions", + "Text": "Status Changes and Existing Sessions", + "Level": 1 + }, + { + "Id": "profile-data-is-not-authorization-data", + "Text": "Profile Data Is Not Authorization Data", + "Level": 1 + }, + { + "Id": "profile-replacement-can-cause-data-loss", + "Text": "Profile Replacement Can Cause Data Loss", + "Level": 1 + }, + { + "Id": "deletion-is-a-lifecycle-operation", + "Text": "Deletion Is a Lifecycle Operation", + "Level": 1 + }, + { + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/advanced-configuration.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/advanced-configuration.json index a098e38f..8576cd4b 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/advanced-configuration.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/advanced-configuration.json @@ -1,71 +1,376 @@ { "Slug": "configuration/advanced-configuration", "Title": "Advanced Configuration", - "Html": "\n\u003Cp\u003EUltimateAuth is designed to be flexible \u2014 but not fragile.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You can customize almost every part of the system\u003Cbr\u003E\n\uD83D\uDC49 Without breaking its security guarantees\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022philosophy\u0022\u003E\u26A0\uFE0F Philosophy\u003C/h2\u003E\n\u003Cp\u003ECustomization in UltimateAuth follows one rule:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You can extend behavior\u003Cbr\u003E\n\uD83D\uDC49 You should not bypass security\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extension-points\u0022\u003E\uD83E\uDDE9 Extension Points\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth exposes multiple extension points:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EResolvers\u003C/li\u003E\n\u003Cli\u003EValidators\u003C/li\u003E\n\u003Cli\u003EAuthorities\u003C/li\u003E\n\u003Cli\u003EOrchestrators\u003C/li\u003E\n\u003Cli\u003EStores\u003C/li\u003E\n\u003Cli\u003EEvents\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You don\u2019t replace the system\u003Cbr\u003E\n\uD83D\uDC49 You plug into it\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022replacing-services\u0022\u003E\uD83D\uDD0C Replacing Services\u003C/h2\u003E\n\u003Cp\u003EAll core services can be overridden using DI:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddScoped\u0026lt;ISessionValidator, CustomSessionValidator\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This allows deep customization\u003Cbr\u003E\n\uD83D\uDC49 While preserving the pipeline\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorities-decisions\u0022\u003E\uD83E\uDDE0 Authorities \u0026amp; Decisions\u003C/h2\u003E\n\u003Cp\u003EAuthorities are responsible for decisions:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELoginAuthority\u003C/li\u003E\n\u003Cli\u003EAccessAuthority\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Cp\u003EYou can override 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\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddScoped\u0026lt;ILoginAuthority, CustomLoginAuthority\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This changes decision logic\u003Cbr\u003E\n\uD83D\uDC49 Without touching flows\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022orchestrators\u0022\u003E\uD83D\uDD04 Orchestrators\u003C/h2\u003E\n\u003Cp\u003EOrchestrators coordinate execution:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EValidate\u003C/li\u003E\n\u003Cli\u003EAuthorize\u003C/li\u003E\n\u003Cli\u003EExecute command\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 They enforce invariants\u003Cbr\u003E\n\uD83D\uDC49 Do not bypass them, unless you exact know what you are doing\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022store-customization\u0022\u003E\uD83D\uDDC4 Store Customization\u003C/h2\u003E\n\u003Cp\u003EYou can provide custom stores:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession store\u003C/li\u003E\n\u003Cli\u003ERefresh token store\u003C/li\u003E\n\u003Cli\u003EUser store\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Supports EF Core, in-memory, or custom implementations\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022events\u0022\u003E\uD83D\uDCE1 Events\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides event hooks:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogin\u003C/li\u003E\n\u003Cli\u003ELogout\u003C/li\u003E\n\u003Cli\u003ERefresh\u003C/li\u003E\n\u003Cli\u003ERevoke\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Events.OnUserLoggedIn = ctx =\u0026gt;\n{\n // custom logic\n return Task.CompletedTask;\n};\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Use events for side-effects\u003Cbr\u003E\n\uD83D\uDC49 Not for core logic\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-configuration-overrides\u0022\u003E\u2699\uFE0F Mode Configuration Overrides\u003C/h2\u003E\n\u003Cp\u003EYou can customize behavior per 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\u003Eo.ModeConfigurations[UAuthMode.Hybrid] = options =\u0026gt;\n{\n options.Token.AccessTokenLifetime = TimeSpan.FromMinutes(5);\n};\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Runs after defaults\u003Cbr\u003E\n\uD83D\uDC49 Allows fine-grained control\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-resolvers\u0022\u003E\uD83D\uDD10 Custom Resolvers\u003C/h2\u003E\n\u003Cp\u003EYou can override how data is resolved:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ETenant resolver\u003C/li\u003E\n\u003Cli\u003ESession resolver\u003C/li\u003E\n\u003Cli\u003EDevice resolver\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Enables full control over request interpretation\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022safety-boundaries\u0022\u003E\uD83D\uDEE1 Safety Boundaries\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth enforces:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvariants\u003C/li\u003E\n\u003Cli\u003EValidation\u003C/li\u003E\n\u003Cli\u003EFail-fast behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Unsafe overrides will fail early\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Extend the system\u003Cbr\u003E\n\uD83D\uDC49 Don\u2019t fight the system\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EEverything is replaceable via DI\u003C/li\u003E\n\u003Cli\u003EAuthorities control decisions\u003C/li\u003E\n\u003Cli\u003EOrchestrators enforce flow\u003C/li\u003E\n\u003Cli\u003EEvents are for side-effects\u003C/li\u003E\n\u003Cli\u003ESecurity boundaries are protected\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EReturn to \u003Cstrong\u003EAuth Flows\u003C/strong\u003E or explore \u003Cstrong\u003EPlugin Domains\u003C/strong\u003E\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth supports advanced customization of authentication behavior through effective server options, authentication mode resolution, dependency injection, custom infrastructure, and lifecycle events.\u003C/p\u003E\n\u003Cp\u003EThese mechanisms serve different purposes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EOptions\u003C/strong\u003E define configurable behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EEffective options\u003C/strong\u003E adapt that behavior to the selected authentication mode.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EResolvers\u003C/strong\u003E determine information required by authentication flows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthorities and validators\u003C/strong\u003E participate in security decisions.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EStores\u003C/strong\u003E persist authentication and application state.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EEvents\u003C/strong\u003E allow applications to react to completed operations.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EMost applications should begin with the standard Server and Client options. Advanced customization becomes useful when an application needs specialized authentication behavior, custom infrastructure, or integration with existing systems.\u003C/p\u003E\n\u003Cp\u003EThis guide explains the supported extension mechanisms, their execution order, and the security boundaries developers must preserve.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-versus-extensibility\u0022\u003E1. Configuration Versus Extensibility\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between configuring existing behavior and replacing an implementation.\u003C/p\u003E\n\u003Cp\u003EFor example, changing a session lifetime is 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.Session.Lifetime = TimeSpan.FromHours(8);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EProviding a custom clock is an infrastructure extension:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddSingleton\u0026lt;IClock, ApplicationClock\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationClock\u003C/code\u003E is an implementation supplied by your application.\u003C/p\u003E\n\u003Cp\u003EThese approaches are not interchangeable.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ERequirement\u003C/th\u003E\n\u003Cth\u003ERecommended mechanism\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EChange session lifetime\u003C/td\u003E\n\u003Ctd\u003EServer options\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EChange token lifetime for a specific mode\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EConfigureMode()\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESelect authentication mode differently\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIEffectiveAuthModeResolver\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EResolve tenants from a custom source\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EITenantIdResolver\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECustomize login decisions\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EILoginAuthority\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESupply signing keys\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIJwtSigningKeyProvider\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPersist sessions in another backend\u003C/td\u003E\n\u003Ctd\u003EStore implementation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReact to successful login\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthEvents\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAdd application-specific access rules\u003C/td\u003E\n\u003Ctd\u003EPolicies\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EPrefer the most specific supported mechanism rather than replacing an entire authentication component.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-effective-server-options\u0022\u003E2. Understanding Effective Server Options\u003C/h2\u003E\n\u003Cp\u003EThe server does not necessarily use the original \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E instance unchanged for every authentication flow.\u003C/p\u003E\n\u003Cp\u003EInstead, UltimateAuth can resolve an effective configuration based on the client profile and authentication mode.\u003C/p\u003E\n\u003Cp\u003EThe relevant 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\u003EIEffectiveServerOptionsProvider\nEffectiveUAuthServerOptions\nIEffectiveAuthModeResolver\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configured-options\u0022\u003EConfigured Options\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E represents the application\u0027s configured server settings.\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 options.Token.Issuer = \u0026quot;MyApplication\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese settings form the base configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022effective-options\u0022\u003EEffective Options\u003C/h3\u003E\n\u003Cp\u003EThe provider produces an \u003Ccode\u003EEffectiveUAuthServerOptions\u003C/code\u003E instance containing:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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 class EffectiveUAuthServerOptions\n{\n public UAuthMode Mode { get; init; }\n public UAuthServerOptions Options { get; init; } = default!;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EMode\u003C/code\u003E identifies the resolved authentication mode.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EOptions\u003C/code\u003E contains the effective server configuration for that mode.\u003C/p\u003E\n\u003Cp\u003EThe effective configuration is derived from the original settings rather than replacing the application\u0027s registered options globally.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022effective-configuration-resolution\u0022\u003E3. Effective Configuration Resolution\u003C/h2\u003E\n\u003Cp\u003EThe current \u003Ccode\u003EEffectiveServerOptionsProvider\u003C/code\u003E follows this sequence:\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\u003EConfigured UAuthServerOptions\n |\n v\n Resolve mode\n |\n v\n Clone base options\n |\n v\n Apply mode defaults\n |\n v\n Apply ConfigureMode override\n |\n v\n EffectiveUAuthServerOptions\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn simplified form:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar original = baseOptions.Value;\n\nvar mode = modeResolver.Resolve(clientProfile);\n\nvar options = original.Clone();\n\nConfigureDefaults.ApplyModeDefaults(mode, options);\n\nif (original.ModeConfigurations.TryGetValue(mode, out var configure))\n{\n configure(options);\n}\n\nreturn new EffectiveUAuthServerOptions\n{\n Mode = mode,\n Options = options\n};\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis execution order is important.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EMode defaults are applied after the original configuration is cloned.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EConsequently, certain values configured globally may be replaced by mode-specific defaults.\u003C/p\u003E\n\u003Cp\u003EExplicit mode overrides are applied last.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-changes-between-requests\u0022\u003EWhat Changes Between Requests?\u003C/h3\u003E\n\u003Cp\u003EDifferent client profiles can resolve to different authentication modes.\u003C/p\u003E\n\u003Cp\u003EThe provider therefore can produce different effective configurations for different client profiles.\u003C/p\u003E\n\u003Cp\u003EHowever, this does not mean every setting is independently recalculated from arbitrary request data.\u003C/p\u003E\n\u003Cp\u003EIn the current implementation, mode selection is based on the supplied \u003Ccode\u003EUAuthClientProfile\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe provider also accepts tenant context, but the default effective-options construction does not apply an additional tenant-specific configuration override.\u003C/p\u003E\n\u003Cp\u003EApplications should not assume that per-tenant options customization exists automatically.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022default-authentication-mode-resolution\u0022\u003E4. Default Authentication Mode Resolution\u003C/h2\u003E\n\u003Cp\u003EThe default \u003Ccode\u003EEffectiveAuthModeResolver\u003C/code\u003E maps client profiles to authentication modes.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EClient profile\u003C/th\u003E\n\u003Cth\u003EResolved mode\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EWebServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorWasm\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMaui\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EApi\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureJwt\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOther / unknown\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese mappings describe the current default resolver.\u003C/p\u003E\n\u003Cp\u003EThey do not imply that every resolved mode is fully supported in the current release.\u003C/p\u003E\n\u003Cp\u003EIn particular, the current server options validator rejects explicit activation of \u003Ccode\u003ESemiHybrid\u003C/code\u003E and \u003Ccode\u003EPureJwt\u003C/code\u003E through \u003Ccode\u003EAllowedModes\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EApplications should verify that their client profiles resolve to a supported authentication mode before deploying.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022allowedmodes-is-not-a-mode-selector\u0022\u003EAllowedModes Is Not a Mode Selector\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\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\u003EThis restricts which modes are allowed.\u003C/p\u003E\n\u003Cp\u003EIt does not instruct the resolver to choose \u003Ccode\u003EHybrid\u003C/code\u003E for every client.\u003C/p\u003E\n\u003Cp\u003EMode selection and mode restrictions are separate responsibilities.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-authentication-modes\u0022\u003E5. Configure Authentication Modes\u003C/h2\u003E\n\u003Cp\u003EUse the public \u003Ccode\u003EConfigureMode()\u003C/code\u003E extension to customize settings for a particular authentication 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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.ConfigureMode(UAuthMode.Hybrid, mode =\u0026gt;\n {\n mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(5);\n });\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis configuration changes the effective access token lifetime for \u003Ccode\u003EHybrid\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIt does not change the effective \u003Ccode\u003EPureOpaque\u003C/code\u003E configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-configuremode-matters\u0022\u003EWhy ConfigureMode Matters\u003C/h3\u003E\n\u003Cp\u003EConsider this 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.Token.AccessTokenLifetime = TimeSpan.FromMinutes(30);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe current \u003Ccode\u003EHybrid\u003C/code\u003E mode defaults explicitly set:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EToken.AccessTokenLifetime = TimeSpan.FromMinutes(10);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EBecause mode defaults are applied after the base options are cloned, the effective \u003Ccode\u003EHybrid\u003C/code\u003E access token lifetime becomes ten minutes rather than thirty.\u003C/p\u003E\n\u003Cp\u003ETo override that behavior, use:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.ConfigureMode(UAuthMode.Hybrid, mode =\u0026gt;\n {\n mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(30);\n });\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe explicit mode override is applied after the mode defaults.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-multiple-modes\u0022\u003EConfigure Multiple 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.ConfigureMode(UAuthMode.Hybrid, mode =\u0026gt;\n {\n mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(10);\n });\n\n options.ConfigureMode(UAuthMode.PureOpaque, mode =\u0026gt;\n {\n mode.Session.IdleTimeout = TimeSpan.FromDays(3);\n });\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach override is associated with its specified mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-limitations\u0022\u003EImportant Limitations\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EConfigureMode()\u003C/code\u003E configures the effective options for a mode.\u003C/p\u003E\n\u003Cp\u003EIt does not:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnable an unsupported authentication mode.\u003C/li\u003E\n\u003Cli\u003EBypass the server\u0027s allowed-mode restrictions.\u003C/li\u003E\n\u003Cli\u003EReplace the authentication mode resolver.\u003C/li\u003E\n\u003Cli\u003EAutomatically create a tenant-specific configuration.\u003C/li\u003E\n\u003Cli\u003EGuarantee that every combination of effective settings is safe.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe current implementation applies mode overrides during effective-options resolution.\u003C/p\u003E\n\u003Cp\u003EDo not assume that every mode-specific combination receives the same startup validation as the original \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E instance.\u003C/p\u003E\n\u003Cp\u003ETest mode overrides carefully, especially when changing token issuance, session lifetimes, or credential delivery.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-defaults\u0022\u003E6. Mode Defaults\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides mode-specific defaults for authentication behavior.\u003C/p\u003E\n\u003Cp\u003EThe current implementation includes templates for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ESemiHybrid\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EPureJwt\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese templates configure relevant token, session, cookie, and response behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque\u0022\u003EPureOpaque\u003C/h3\u003E\n\u003Cp\u003EThe \u003Ccode\u003EPureOpaque\u003C/code\u003E defaults 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\u003EToken.IssueJwt = false;\nToken.IssueOpaque = false;\nToken.IssueRefresh = false;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn this mode, the session identifier is the primary opaque authentication credential.\u003C/p\u003E\n\u003Cp\u003EThe defaults also configure a longer idle timeout and session touch interval where no explicit values were supplied.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003EHybrid\u003C/h3\u003E\n\u003Cp\u003EThe \u003Ccode\u003EHybrid\u003C/code\u003E defaults 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\u003EToken.IssueJwt = true;\nToken.IssueOpaque = true;\nToken.AccessTokenLifetime = TimeSpan.FromMinutes(10);\nToken.RefreshTokenLifetime = TimeSpan.FromDays(7);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EHybrid also configures refresh-token delivery through a cookie.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022unsupported-modes\u0022\u003EUnsupported Modes\u003C/h3\u003E\n\u003Cp\u003EAlthough default templates exist for \u003Ccode\u003ESemiHybrid\u003C/code\u003E and \u003Ccode\u003EPureJwt\u003C/code\u003E, their presence does not mean those modes are ready for production use.\u003C/p\u003E\n\u003Cp\u003EThe server\u0027s current validator explicitly rejects these modes when they are enabled through \u003Ccode\u003EAllowedModes\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EOnly use modes supported by the current release.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-effective-options\u0022\u003E7. Inspect Effective Options\u003C/h2\u003E\n\u003Cp\u003EAdvanced integrations can use \u003Ccode\u003EIEffectiveServerOptionsProvider\u003C/code\u003E to inspect configured and effective server options.\u003C/p\u003E\n\u003Cp\u003EThe interface 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\u003Epublic interface IEffectiveServerOptionsProvider\n{\n UAuthServerOptions GetOriginal(HttpContext context);\n EffectiveUAuthServerOptions GetEffective(HttpContext context, UAuthClientProfile clientProfile);\n EffectiveUAuthServerOptions GetEffective(TenantKey tenant, UAuthClientProfile clientProfile);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-the-original-configuration\u0022\u003EInspect the Original 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\u003Evar original = provider.GetOriginal(httpContext);\nvar configuredLifetime = original.Token.AccessTokenLifetime;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-the-effective-configuration\u0022\u003EInspect the Effective 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\u003Evar effective = provider.GetEffective(httpContext, UAuthClientProfile.BlazorWasm);\nvar mode = effective.Mode;\nvar lifetime = effective.Options.Token.AccessTokenLifetime;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is useful when diagnosing unexpected differences between configured and runtime behavior.\u003C/p\u003E\n\u003Cp\u003EFor example, a globally configured access token lifetime may differ from the effective value because a mode template overrides it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-not-mutate-the-original-options\u0022\u003EDo Not Mutate the Original Options\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EGetOriginal()\u003C/code\u003E returns the original options instance.\u003C/p\u003E\n\u003Cp\u003ETreat it as read-only application configuration.\u003C/p\u003E\n\u003Cp\u003ERuntime customization should use the supported configuration mechanisms rather than mutating registered options during requests.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-authentication-mode-resolution\u0022\u003E8. Custom Authentication Mode Resolution\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth 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\u003EIEffectiveAuthModeResolver\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe interface 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 interface IEffectiveAuthModeResolver\n{\n UAuthMode Resolve(UAuthClientProfile clientProfile);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe default implementation selects the mode using the client profile.\u003C/p\u003E\n\u003Cp\u003EAn application can provide its own resolver when the default mapping does not meet its requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-resolver-example\u0022\u003ECustom Resolver Example\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 class ApplicationAuthModeResolver : IEffectiveAuthModeResolver\n{\n public UAuthMode Resolve(UAuthClientProfile clientProfile)\n {\n return clientProfile switch\n {\n UAuthClientProfile.BlazorServer =\u0026gt; UAuthMode.PureOpaque,\n UAuthClientProfile.BlazorWasm =\u0026gt; UAuthMode.Hybrid,\n UAuthClientProfile.Maui =\u0026gt; UAuthMode.Hybrid,\n\n _ =\u0026gt; UAuthMode.Hybrid\n };\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERegister the 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.AddSingleton\u0026lt;IEffectiveAuthModeResolver, ApplicationAuthModeResolver\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server registers its default resolver using \u003Ccode\u003ETryAddSingleton\u003C/code\u003E, so an existing registration can take precedence.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003ESecurity Considerations\u003C/h3\u003E\n\u003Cp\u003EThe custom resolver must return a mode compatible with the server\u0027s supported modes and security requirements.\u003C/p\u003E\n\u003Cp\u003EDo not choose authentication modes based solely on untrusted client-provided values without appropriate validation.\u003C/p\u003E\n\u003Cp\u003EAlso note that the current resolver interface receives a client profile, not an arbitrary HTTP request context.\u003C/p\u003E\n\u003Cp\u003EFor more complex context-dependent selection, additional application integration may be necessary.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022dependency-injection-extension-points\u0022\u003E9. Dependency Injection Extension Points\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth registers many internal services through dependency injection.\u003C/p\u003E\n\u003Cp\u003ESeveral default registrations use \u003Ccode\u003ETryAddScoped\u003C/code\u003E or \u003Ccode\u003ETryAddSingleton\u003C/code\u003E, allowing applications to supply an implementation before the default registration is added.\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.AddScoped\u0026lt;ISessionValidator, ApplicationSessionValidator\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe application-defined validator is registered before the UltimateAuth server\u0027s default registration.\u003C/p\u003E\n\u003Cp\u003EBecause the server uses \u003Ccode\u003ETryAddScoped\u003C/code\u003E for this service, it does not add its default implementation when an existing registration is present.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022registration-order-matters\u0022\u003ERegistration Order Matters\u003C/h3\u003E\n\u003Cp\u003EFor services registered using \u003Ccode\u003ETryAdd...\u003C/code\u003E, prefer:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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;ISessionValidator, ApplicationSessionValidator\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDo not assume that registering a replacement after the server will always produce the same behavior.\u003C/p\u003E\n\u003Cp\u003EThe result depends on the service\u0027s registration pattern and how it is resolved.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022not-every-service-is-replaceable-in-the-same-way\u0022\u003ENot Every Service Is Replaceable in the Same Way\u003C/h3\u003E\n\u003Cp\u003ESome UltimateAuth services use ordinary \u003Ccode\u003EAddSingleton\u003C/code\u003E or \u003Ccode\u003EAddScoped\u003C/code\u003E registrations.\u003C/p\u003E\n\u003Cp\u003EOthers use \u003Ccode\u003ETryAdd...\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ESome are registered as collections.\u003C/p\u003E\n\u003Cp\u003EThese patterns have different override semantics.\u003C/p\u003E\n\u003Cp\u003ETherefore, do not assume that every internal service can be replaced safely or that every replacement preserves the intended security behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-login-authority\u0022\u003E10. Custom Login Authority\u003C/h2\u003E\n\u003Cp\u003EThe login authority participates in deciding whether a login attempt is permitted.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth 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\u003EILoginAuthority\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe interface 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 interface ILoginAuthority\n{\n LoginDecision Decide(LoginDecisionContext context);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn application can implement its own login decision logic.\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\u003Epublic sealed class ApplicationLoginAuthority : ILoginAuthority\n{\n public LoginDecision Decide(LoginDecisionContext context)\n {\n // Evaluate application-specific login rules.\n // Return a valid LoginDecision.\n throw new NotImplementedException();\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe example illustrates the extension contract; it is not a usable implementation.\u003C/p\u003E\n\u003Cp\u003ERegister the 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;ILoginAuthority, ApplicationLoginAuthority\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-security-boundary\u0022\u003EImportant Security Boundary\u003C/h3\u003E\n\u003Cp\u003EReplacing the login authority changes a security decision component.\u003C/p\u003E\n\u003Cp\u003EThe replacement must preserve required authentication checks and correctly handle all decision outcomes.\u003C/p\u003E\n\u003Cp\u003EDo not replace the authority merely to add logging, notifications, or metrics.\u003C/p\u003E\n\u003Cp\u003EUse lifecycle events for those purposes.\u003C/p\u003E\n\u003Cp\u003EIf the application only needs additional access rules, consider the policy system before replacing a core authority.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorities-policies-and-invariants\u0022\u003E11. Authorities, Policies, and Invariants\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates different responsibilities within its security architecture.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorities\u0022\u003EAuthorities\u003C/h3\u003E\n\u003Cp\u003EAuthorities evaluate security decisions.\u003C/p\u003E\n\u003Cp\u003EExamples 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\u003EIAuthAuthority\nIAccessAuthority\nILoginAuthority\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies\u0022\u003EPolicies\u003C/h3\u003E\n\u003Cp\u003EPolicies define application-specific rules used by the authorization system.\u003C/p\u003E\n\u003Cp\u003EThey should be the preferred extension mechanism when the requirement is to add or refine access rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022invariants\u0022\u003EInvariants\u003C/h3\u003E\n\u003Cp\u003EInvariants enforce foundational security requirements.\u003C/p\u003E\n\u003Cp\u003EThe server registers invariants including:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EDeviceRequiredInvariant\nExpiredSessionInvariant\nInvalidOrRevokedSessionInvariant\nTenantResolvedInvariant\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese are registered as a collection of \u003Ccode\u003EIAuthorityInvariant\u003C/code\u003E implementations.\u003C/p\u003E\n\u003Cp\u003EUnlike ordinary application policies, security invariants protect fundamental authentication requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not remove or bypass invariants to make a custom authentication flow succeed.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EFor example, an expired or revoked session should not become valid merely because a custom access policy grants permission.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-tenant-resolution\u0022\u003E12. Custom Tenant Resolution\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports route, header, and host-based tenant resolution.\u003C/p\u003E\n\u003Cp\u003EThese mechanisms are controlled through server options.\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.MultiTenant.EnableHeader = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor applications with a different tenant identification strategy, UltimateAuth 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\u003EITenantIdResolver\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server\u0027s default registration uses \u003Ccode\u003ETryAddSingleton\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA custom resolver can therefore be registered before 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\u003Ebuilder.Services.AddSingleton\u0026lt;ITenantIdResolver, ApplicationTenantIdResolver\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationTenantIdResolver\u003C/code\u003E represents an application-defined implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-to-use-a-custom-resolver\u0022\u003EWhen to Use a Custom Resolver\u003C/h3\u003E\n\u003Cp\u003EA custom tenant resolver may be appropriate when tenants are identified through:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn application-specific host naming convention.\u003C/li\u003E\n\u003Cli\u003EA trusted gateway-provided tenant identifier.\u003C/li\u003E\n\u003Cli\u003EA custom routing structure.\u003C/li\u003E\n\u003Cli\u003EAn existing tenant management system.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations-1\u0022\u003ESecurity Considerations\u003C/h3\u003E\n\u003Cp\u003ETenant identification is not the same as tenant authorization.\u003C/p\u003E\n\u003Cp\u003EThe server must still verify that the authenticated user or application is allowed to access the resolved tenant.\u003C/p\u003E\n\u003Cp\u003ENever trust arbitrary tenant identifiers supplied by clients without applying the required validation and isolation rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-session-and-device-resolution\u0022\u003E13. Custom Session and Device Resolution\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates session identifier resolution from session validation.\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\u003EISessionIdResolver\nISessionValidator\nIDeviceResolver\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese interfaces have different responsibilities.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EComponent\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\u003EISessionIdResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDetermine which session identifier is supplied\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EISessionValidator\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EValidate the session\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIDeviceResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve device information\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe server supports multiple session identifier sources through internal resolvers, including cookies, bearer credentials, headers, and query parameters.\u003C/p\u003E\n\u003Cp\u003EThe active resolution behavior is controlled by the configured session resolution options.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-session-validator\u0022\u003ECustom Session Validator\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;ISessionValidator, ApplicationSessionValidator\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA custom validator must preserve the relevant expiration, revocation, and security-state checks.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-device-resolver\u0022\u003ECustom Device Resolver\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;IDeviceResolver, ApplicationDeviceResolver\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDevice resolution may affect session identification and security decisions.\u003C/p\u003E\n\u003Cp\u003ECustom implementations should produce consistent, trustworthy device information.\u003C/p\u003E\n\u003Cp\u003EAvoid replacing these services unless the application\u0027s requirements cannot be met through standard options.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-stores\u0022\u003E14. Custom Stores\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates domain behavior from persistence through store interfaces.\u003C/p\u003E\n\u003Cp\u003EExamples 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\u003EISessionStore\nIRefreshTokenStore\nIRoleStore\nIUserRoleStore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ESome stores are resolved through corresponding factory interfaces.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides persistence implementations in separate packages, including in-memory and Entity Framework Core implementations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-a-store-implementation\u0022\u003EChoosing a Store Implementation\u003C/h3\u003E\n\u003Cp\u003EUse in-memory implementations for suitable development, testing, or demonstration scenarios.\u003C/p\u003E\n\u003Cp\u003EUse durable persistence for production authentication state that must survive process restarts or be shared across application instances.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-store-requirements\u0022\u003ECustom Store Requirements\u003C/h3\u003E\n\u003Cp\u003EA custom store must preserve the semantics expected by the authentication services.\u003C/p\u003E\n\u003Cp\u003EDepending on the store, these may include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECorrect session expiration handling.\u003C/li\u003E\n\u003Cli\u003EReliable revocation state.\u003C/li\u003E\n\u003Cli\u003ERefresh token consumption and replay protection.\u003C/li\u003E\n\u003Cli\u003EConsistent user and tenant scoping.\u003C/li\u003E\n\u003Cli\u003EAppropriate concurrency behavior.\u003C/li\u003E\n\u003Cli\u003EAtomic state transitions where required.\u003C/li\u003E\n\u003Cli\u003ECorrect deletion and lifecycle behavior.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, refresh token rotation may depend on preventing two concurrent requests from successfully consuming the same token.\u003C/p\u003E\n\u003Cp\u003EA store implementation that does not enforce the required concurrency semantics can undermine the security of the refresh flow.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022store-registration\u0022\u003EStore Registration\u003C/h3\u003E\n\u003Cp\u003ECustom store registration depends on the specific store interface and its factory.\u003C/p\u003E\n\u003Cp\u003EDo not assume that replacing one store interface is sufficient when the runtime resolves stores through a factory.\u003C/p\u003E\n\u003Cp\u003EUse the relevant domain package\u0027s documented registration pattern.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-lifecycle-events\u0022\u003E15. Authentication Lifecycle Events\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides application-wide authentication event hooks 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\u003EUAuthEvents\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese hooks allow applications to react to authentication lifecycle events without replacing the authentication flow.\u003C/p\u003E\n\u003Cp\u003EThe current event options expose:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EOnAnyEvent\nOnUserLoggedIn\nOnUserLoggedOut\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022login-event\u0022\u003ELogin Event\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.Events.OnUserLoggedIn = context =\u0026gt;\n {\n // Record a successful login.\n return Task.CompletedTask;\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-event\u0022\u003ELogout Event\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.Events.OnUserLoggedOut = context =\u0026gt;\n {\n // Record a logout event.\n return Task.CompletedTask;\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022global-event-hook\u0022\u003EGlobal Event Hook\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.Events.OnAnyEvent = context =\u0026gt;\n {\n // Process an authentication event.\n return Task.CompletedTask;\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe global hook receives an \u003Ccode\u003EIAuthEventContext\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022appropriate-event-uses\u0022\u003EAppropriate Event Uses\u003C/h3\u003E\n\u003Cp\u003ELifecycle events are suitable for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAudit logging.\u003C/li\u003E\n\u003Cli\u003EApplication metrics.\u003C/li\u003E\n\u003Cli\u003EMonitoring.\u003C/li\u003E\n\u003Cli\u003ENotifications.\u003C/li\u003E\n\u003Cli\u003ELightweight post-operation integration.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThey should not be used to implement critical authentication checks.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022events-are-not-decision-hooks\u0022\u003EEvents Are Not Decision Hooks\u003C/h3\u003E\n\u003Cp\u003EThe documented event delegates run after their corresponding successful operations.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EOnUserLoggedIn\u003C/code\u003E is not the place to decide whether the login should have been permitted.\u003C/p\u003E\n\u003Cp\u003EUse an appropriate authority or policy for security decisions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022avoid-complex-work-inside-events\u0022\u003EAvoid Complex Work Inside Events\u003C/h3\u003E\n\u003Cp\u003ELong-running or failure-prone external operations should generally be delegated to a reliable application service or message-processing mechanism.\u003C/p\u003E\n\u003Cp\u003EDo not make critical authentication behavior depend on an unreliable external notification system.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-jwt-signing-keys\u0022\u003E16. Custom JWT Signing Keys\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth 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\u003EIJwtSigningKeyProvider\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server registers a development signing key provider as its default implementation.\u003C/p\u003E\n\u003Cp\u003EProduction applications should supply an appropriate signing key strategy.\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.AddSingleton\u0026lt;IJwtSigningKeyProvider, ApplicationJwtSigningKeyProvider\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationJwtSigningKeyProvider\u003C/code\u003E is an implementation supplied by the application.\u003C/p\u003E\n\u003Cp\u003EA production implementation should use appropriately protected signing keys and support the application\u0027s operational requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022signing-key-responsibilities\u0022\u003ESigning Key Responsibilities\u003C/h3\u003E\n\u003Cp\u003EConsider:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EKey confidentiality.\u003C/li\u003E\n\u003Cli\u003EKey rotation.\u003C/li\u003E\n\u003Cli\u003EKey identification.\u003C/li\u003E\n\u003Cli\u003EConsistent validation across resource servers.\u003C/li\u003E\n\u003Cli\u003ESecure key storage.\u003C/li\u003E\n\u003Cli\u003ERecovery and deployment procedures.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EDo not use development signing keys as a production security strategy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoint-customization\u0022\u003E17. Endpoint Customization\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides 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:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EIEndpointRouteBuilder\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.OnConfigureEndpoints = endpoints =\u0026gt;\n {\n // Apply supported application-specific\n // endpoint configuration here.\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe hook is invoked after UltimateAuth registers its default endpoints.\u003C/p\u003E\n\u003Cp\u003EIt can support advanced endpoint integration, but it should not be used to remove, remap, or weaken the security requirements of UltimateAuth endpoints.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022prefer-endpoint-options-first\u0022\u003EPrefer Endpoint Options First\u003C/h3\u003E\n\u003Cp\u003EFor ordinary endpoint configuration, use:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Authentication = true;\n options.Endpoints.Credentials = true;\n options.Endpoints.Authorization = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe advanced endpoint hook is intended for cases that cannot be addressed through the supported endpoint options.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-sources-and-precedence\u0022\u003E18. Configuration Sources and Precedence\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth integrates with the standard ASP.NET Core configuration system.\u003C/p\u003E\n\u003Cp\u003EFor the server, the 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:Server\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor the client:\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 Resource APIs:\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\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022registration-order\u0022\u003ERegistration Order\u003C/h3\u003E\n\u003Cp\u003EThe standard server registration applies:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EOption type defaults.\u003C/li\u003E\n\u003Cli\u003EThe \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E configuration delegate.\u003C/li\u003E\n\u003Cli\u003EValues bound from \u003Ccode\u003EUltimateAuth:Server\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EPost-configuration processing.\u003C/li\u003E\n\u003Cli\u003ERegistered options validation.\u003C/li\u003E\n\u003C/ol\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.Login.MaxFailedAttempts = 5;\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 configured value is \u003Ccode\u003E3\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe configuration binding occurs after the registration delegate.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022environment-variables\u0022\u003EEnvironment Variables\u003C/h3\u003E\n\u003Cp\u003EASP.NET Core supports hierarchical configuration keys through double underscores.\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 corresponds 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\u003EEnvironment-specific configuration can therefore override values without requiring changes to application code.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022environment-specific-files\u0022\u003EEnvironment-Specific Files\u003C/h3\u003E\n\u003Cp\u003EStandard ASP.NET Core applications commonly use:\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\u003Eappsettings.json\nappsettings.Development.json\nappsettings.Staging.json\nappsettings.Production.json\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe active environment determines which environment-specific file is loaded.\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\u003EASPNETCORE_ENVIRONMENT=Production\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe final value depends on the configured ASP.NET Core configuration providers and their precedence.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-binding-does-not-replace-mode-resolution\u0022\u003EConfiguration Binding Does Not Replace Mode Resolution\u003C/h3\u003E\n\u003Cp\u003EA value supplied through \u003Ccode\u003Eappsettings.json\u003C/code\u003E becomes part of the configured options.\u003C/p\u003E\n\u003Cp\u003EIt may still be replaced later when UltimateAuth applies mode defaults.\u003C/p\u003E\n\u003Cp\u003EFor settings explicitly controlled by mode templates, use \u003Ccode\u003EConfigureMode()\u003C/code\u003E when the desired behavior must apply to a specific mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022startup-validation-and-runtime-safety\u0022\u003E19. Startup Validation and Runtime Safety\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth validates server options through registered \u003Ccode\u003EIValidateOptions\u0026lt;UAuthServerOptions\u0026gt;\u003C/code\u003E implementations.\u003C/p\u003E\n\u003Cp\u003EThe server uses:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EValidateOnStart()\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows many invalid configurations to fail during application startup.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalid endpoint base paths.\u003C/li\u003E\n\u003Cli\u003EInvalid or unsupported allowed modes.\u003C/li\u003E\n\u003Cli\u003EInvalid session lifetimes.\u003C/li\u003E\n\u003Cli\u003EIncompatible token and session lifetime constraints.\u003C/li\u003E\n\u003Cli\u003EInvalid login settings.\u003C/li\u003E\n\u003Cli\u003EInvalid tenant configuration.\u003C/li\u003E\n\u003Cli\u003EInvalid pagination settings.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validation-has-boundaries\u0022\u003EValidation Has Boundaries\u003C/h3\u003E\n\u003Cp\u003EStartup validation applies to the configured options according to the registered validators.\u003C/p\u003E\n\u003Cp\u003EIt does not automatically prove that every custom dependency injection replacement is safe.\u003C/p\u003E\n\u003Cp\u003EIt also does not guarantee that every mode-specific override has been validated against all possible security invariants.\u003C/p\u003E\n\u003Cp\u003ECustom implementations must preserve the relevant contracts and security requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-validation-strategy\u0022\u003ERecommended Validation Strategy\u003C/h3\u003E\n\u003Cp\u003EFor advanced configurations:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EVerify that the application starts successfully.\u003C/li\u003E\n\u003Cli\u003ETest each enabled authentication mode.\u003C/li\u003E\n\u003Cli\u003ETest login, refresh, validation, and logout.\u003C/li\u003E\n\u003Cli\u003ETest session expiration and revocation.\u003C/li\u003E\n\u003Cli\u003ETest tenant isolation.\u003C/li\u003E\n\u003Cli\u003ETest authorization denial paths.\u003C/li\u003E\n\u003Cli\u003ETest concurrent refresh requests where applicable.\u003C/li\u003E\n\u003Cli\u003EVerify that effective options contain the intended values.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThese checks are especially important when customizing authorities, validators, stores, or authentication mode behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-configuration-mistakes\u0022\u003E20. Common Configuration Mistakes\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuring-core-options-separately-from-server-options\u0022\u003EConfiguring Core Options Separately from Server Options\u003C/h3\u003E\n\u003Cp\u003EWhen using \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E, configure Core-defined behavior through \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EDo not expect a separate \u003Ccode\u003EUAuthOptions\u003C/code\u003E configuration to override the server\u0027s composed options.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-global-values-always-survive-mode-defaults\u0022\u003EAssuming Global Values Always Survive Mode Defaults\u003C/h3\u003E\n\u003Cp\u003EMode templates can overwrite values such as access token lifetime.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003EConfigureMode()\u003C/code\u003E for explicit mode-specific customization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022treating-allowedmodes-as-mode-selection\u0022\u003ETreating AllowedModes as Mode Selection\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EAllowedModes\u003C/code\u003E restricts permitted modes.\u003C/p\u003E\n\u003Cp\u003EIt does not replace \u003Ccode\u003EIEffectiveAuthModeResolver\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022enabling-an-unsupported-mode\u0022\u003EEnabling an Unsupported Mode\u003C/h3\u003E\n\u003Cp\u003EAn enum value or default template does not establish that a mode is ready for production.\u003C/p\u003E\n\u003Cp\u003EUse only modes supported by the current implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022replacing-security-components-unnecessarily\u0022\u003EReplacing Security Components Unnecessarily\u003C/h3\u003E\n\u003Cp\u003EReplacing a validator or authority can change fundamental authentication behavior.\u003C/p\u003E\n\u003Cp\u003EPrefer options and policies when they satisfy the requirement.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-every-di-service-is-replaceable\u0022\u003EAssuming Every DI Service Is Replaceable\u003C/h3\u003E\n\u003Cp\u003EDifferent services use different registration patterns.\u003C/p\u003E\n\u003Cp\u003EReview the actual registration behavior before replacing a service.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-events-for-authorization-decisions\u0022\u003EUsing Events for Authorization Decisions\u003C/h3\u003E\n\u003Cp\u003ELifecycle events are for post-operation reactions.\u003C/p\u003E\n\u003Cp\u003EUse authorities and policies for decisions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022ignoring-store-concurrency\u0022\u003EIgnoring Store Concurrency\u003C/h3\u003E\n\u003Cp\u003EAuthentication stores must preserve the required consistency and atomicity semantics.\u003C/p\u003E\n\u003Cp\u003EThis is particularly important for refresh token rotation and revocation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-runtime-overrides-receive-full-startup-validation\u0022\u003EAssuming Runtime Overrides Receive Full Startup Validation\u003C/h3\u003E\n\u003Cp\u003EMode-specific overrides are applied during effective-options construction.\u003C/p\u003E\n\u003Cp\u003EValidate their results through tests and appropriate application safeguards.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022advanced-configuration-reference\u0022\u003E21. Advanced Configuration Reference\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EMechanism\u003C/th\u003E\n\u003Cth\u003EAPI / Type\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\u003EServer configuration\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBase server settings\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMode customization\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EConfigureMode()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EApply mode-specific overrides\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EEffective configuration\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIEffectiveServerOptionsProvider\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve effective server settings\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EEffective result\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EEffectiveUAuthServerOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolved mode and options\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMode selection\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIEffectiveAuthModeResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve mode from client profile\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ELogin decisions\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EILoginAuthority\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEvaluate login decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthentication authority\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIAuthAuthority\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication security decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAccess authority\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIAccessAuthority\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAccess decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ETenant identification\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EITenantIdResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve tenant identifiers\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession validation\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EISessionValidator\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EValidate sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession identification\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EISessionIdResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve session identifiers\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDevice identification\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIDeviceResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve device information\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESigning keys\u003C/td\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\u003Ctr\u003E\n\u003Ctd\u003ELifecycle events\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthEvents\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EObserve completed authentication operations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EEndpoint customization\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnConfigureEndpoints\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAdvanced endpoint configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession persistence\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EISessionStore\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession storage\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERefresh persistence\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIRefreshTokenStore\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERefresh token storage\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=\u0022recommended-extension-strategy\u0022\u003E22. Recommended Extension Strategy\u003C/h2\u003E\n\u003Cp\u003EWhen an application requires customization, follow this order:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse existing options.\u003C/strong\u003E Prefer supported configuration properties.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse mode overrides.\u003C/strong\u003E Apply \u003Ccode\u003EConfigureMode()\u003C/code\u003E when behavior must differ by authentication mode.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse application policies.\u003C/strong\u003E Extend authorization rules without replacing the authorization pipeline.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse lifecycle events.\u003C/strong\u003E Implement logging, monitoring, and notifications without changing authentication decisions.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse focused resolvers or providers.\u003C/strong\u003E Customize tenant identification, signing keys, or other specific infrastructure concerns.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EReplace authorities, validators, or stores only when necessary.\u003C/strong\u003E Preserve all relevant security invariants and verify the implementation through tests.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe objective is not to replace as much of UltimateAuth as possible.\u003C/p\u003E\n\u003Cp\u003EIt is to customize the smallest component necessary while retaining the guarantees of the surrounding 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/configuration/configuration\u0022\u003EConfiguration Overview\u003C/a\u003E \u2014 Understand registration, configuration sources, and architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E \u2014 Configure authentication and server security behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E \u2014 Configure SDK flows and client behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Understand supported authentication modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Use the client authorization API.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Manage sessions and revocation.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { - "Id": "philosophy", - "Text": "\u26A0\uFE0F Philosophy", + "Id": "configuration-versus-extensibility", + "Text": "1. Configuration Versus Extensibility", "Level": 0 }, { - "Id": "extension-points", - "Text": "\uD83E\uDDE9 Extension Points", + "Id": "understanding-effective-server-options", + "Text": "2. Understanding Effective Server Options", "Level": 0 }, { - "Id": "replacing-services", - "Text": "\uD83D\uDD0C Replacing Services", + "Id": "configured-options", + "Text": "Configured Options", + "Level": 1 + }, + { + "Id": "effective-options", + "Text": "Effective Options", + "Level": 1 + }, + { + "Id": "effective-configuration-resolution", + "Text": "3. Effective Configuration Resolution", + "Level": 0 + }, + { + "Id": "what-changes-between-requests", + "Text": "What Changes Between Requests?", + "Level": 1 + }, + { + "Id": "default-authentication-mode-resolution", + "Text": "4. Default Authentication Mode Resolution", + "Level": 0 + }, + { + "Id": "allowedmodes-is-not-a-mode-selector", + "Text": "AllowedModes Is Not a Mode Selector", + "Level": 1 + }, + { + "Id": "configure-authentication-modes", + "Text": "5. Configure Authentication Modes", + "Level": 0 + }, + { + "Id": "why-configuremode-matters", + "Text": "Why ConfigureMode Matters", + "Level": 1 + }, + { + "Id": "configure-multiple-modes", + "Text": "Configure Multiple Modes", + "Level": 1 + }, + { + "Id": "important-limitations", + "Text": "Important Limitations", + "Level": 1 + }, + { + "Id": "mode-defaults", + "Text": "6. Mode Defaults", + "Level": 0 + }, + { + "Id": "pureopaque", + "Text": "PureOpaque", + "Level": 1 + }, + { + "Id": "hybrid", + "Text": "Hybrid", + "Level": 1 + }, + { + "Id": "unsupported-modes", + "Text": "Unsupported Modes", + "Level": 1 + }, + { + "Id": "inspect-effective-options", + "Text": "7. Inspect Effective Options", + "Level": 0 + }, + { + "Id": "inspect-the-original-configuration", + "Text": "Inspect the Original Configuration", + "Level": 1 + }, + { + "Id": "inspect-the-effective-configuration", + "Text": "Inspect the Effective Configuration", + "Level": 1 + }, + { + "Id": "do-not-mutate-the-original-options", + "Text": "Do Not Mutate the Original Options", + "Level": 1 + }, + { + "Id": "custom-authentication-mode-resolution", + "Text": "8. Custom Authentication Mode Resolution", + "Level": 0 + }, + { + "Id": "custom-resolver-example", + "Text": "Custom Resolver Example", + "Level": 1 + }, + { + "Id": "security-considerations", + "Text": "Security Considerations", + "Level": 1 + }, + { + "Id": "dependency-injection-extension-points", + "Text": "9. Dependency Injection Extension Points", + "Level": 0 + }, + { + "Id": "registration-order-matters", + "Text": "Registration Order Matters", + "Level": 1 + }, + { + "Id": "not-every-service-is-replaceable-in-the-same-way", + "Text": "Not Every Service Is Replaceable in the Same Way", + "Level": 1 + }, + { + "Id": "custom-login-authority", + "Text": "10. Custom Login Authority", + "Level": 0 + }, + { + "Id": "important-security-boundary", + "Text": "Important Security Boundary", + "Level": 1 + }, + { + "Id": "authorities-policies-and-invariants", + "Text": "11. Authorities, Policies, and Invariants", + "Level": 0 + }, + { + "Id": "authorities", + "Text": "Authorities", + "Level": 1 + }, + { + "Id": "policies", + "Text": "Policies", + "Level": 1 + }, + { + "Id": "invariants", + "Text": "Invariants", + "Level": 1 + }, + { + "Id": "custom-tenant-resolution", + "Text": "12. Custom Tenant Resolution", "Level": 0 }, { - "Id": "authorities-decisions", - "Text": "\uD83E\uDDE0 Authorities \u0026 Decisions", + "Id": "when-to-use-a-custom-resolver", + "Text": "When to Use a Custom Resolver", + "Level": 1 + }, + { + "Id": "security-considerations-1", + "Text": "Security Considerations", + "Level": 1 + }, + { + "Id": "custom-session-and-device-resolution", + "Text": "13. Custom Session and Device Resolution", "Level": 0 }, { - "Id": "orchestrators", - "Text": "\uD83D\uDD04 Orchestrators", + "Id": "custom-session-validator", + "Text": "Custom Session Validator", + "Level": 1 + }, + { + "Id": "custom-device-resolver", + "Text": "Custom Device Resolver", + "Level": 1 + }, + { + "Id": "custom-stores", + "Text": "14. Custom Stores", "Level": 0 }, { - "Id": "store-customization", - "Text": "\uD83D\uDDC4 Store Customization", + "Id": "choosing-a-store-implementation", + "Text": "Choosing a Store Implementation", + "Level": 1 + }, + { + "Id": "custom-store-requirements", + "Text": "Custom Store Requirements", + "Level": 1 + }, + { + "Id": "store-registration", + "Text": "Store Registration", + "Level": 1 + }, + { + "Id": "authentication-lifecycle-events", + "Text": "15. Authentication Lifecycle Events", "Level": 0 }, { - "Id": "events", - "Text": "\uD83D\uDCE1 Events", + "Id": "login-event", + "Text": "Login Event", + "Level": 1 + }, + { + "Id": "logout-event", + "Text": "Logout Event", + "Level": 1 + }, + { + "Id": "global-event-hook", + "Text": "Global Event Hook", + "Level": 1 + }, + { + "Id": "appropriate-event-uses", + "Text": "Appropriate Event Uses", + "Level": 1 + }, + { + "Id": "events-are-not-decision-hooks", + "Text": "Events Are Not Decision Hooks", + "Level": 1 + }, + { + "Id": "avoid-complex-work-inside-events", + "Text": "Avoid Complex Work Inside Events", + "Level": 1 + }, + { + "Id": "custom-jwt-signing-keys", + "Text": "16. Custom JWT Signing Keys", + "Level": 0 + }, + { + "Id": "signing-key-responsibilities", + "Text": "Signing Key Responsibilities", + "Level": 1 + }, + { + "Id": "endpoint-customization", + "Text": "17. Endpoint Customization", "Level": 0 }, { - "Id": "mode-configuration-overrides", - "Text": "\u2699\uFE0F Mode Configuration Overrides", + "Id": "prefer-endpoint-options-first", + "Text": "Prefer Endpoint Options First", + "Level": 1 + }, + { + "Id": "configuration-sources-and-precedence", + "Text": "18. Configuration Sources and Precedence", "Level": 0 }, { - "Id": "custom-resolvers", - "Text": "\uD83D\uDD10 Custom Resolvers", + "Id": "registration-order", + "Text": "Registration Order", + "Level": 1 + }, + { + "Id": "environment-variables", + "Text": "Environment Variables", + "Level": 1 + }, + { + "Id": "environment-specific-files", + "Text": "Environment-Specific Files", + "Level": 1 + }, + { + "Id": "configuration-binding-does-not-replace-mode-resolution", + "Text": "Configuration Binding Does Not Replace Mode Resolution", + "Level": 1 + }, + { + "Id": "startup-validation-and-runtime-safety", + "Text": "19. Startup Validation and Runtime Safety", "Level": 0 }, { - "Id": "safety-boundaries", - "Text": "\uD83D\uDEE1 Safety Boundaries", + "Id": "validation-has-boundaries", + "Text": "Validation Has Boundaries", + "Level": 1 + }, + { + "Id": "recommended-validation-strategy", + "Text": "Recommended Validation Strategy", + "Level": 1 + }, + { + "Id": "common-configuration-mistakes", + "Text": "20. Common Configuration Mistakes", "Level": 0 }, { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", + "Id": "configuring-core-options-separately-from-server-options", + "Text": "Configuring Core Options Separately from Server Options", + "Level": 1 + }, + { + "Id": "assuming-global-values-always-survive-mode-defaults", + "Text": "Assuming Global Values Always Survive Mode Defaults", + "Level": 1 + }, + { + "Id": "treating-allowedmodes-as-mode-selection", + "Text": "Treating AllowedModes as Mode Selection", + "Level": 1 + }, + { + "Id": "enabling-an-unsupported-mode", + "Text": "Enabling an Unsupported Mode", + "Level": 1 + }, + { + "Id": "replacing-security-components-unnecessarily", + "Text": "Replacing Security Components Unnecessarily", + "Level": 1 + }, + { + "Id": "assuming-every-di-service-is-replaceable", + "Text": "Assuming Every DI Service Is Replaceable", + "Level": 1 + }, + { + "Id": "using-events-for-authorization-decisions", + "Text": "Using Events for Authorization Decisions", + "Level": 1 + }, + { + "Id": "ignoring-store-concurrency", + "Text": "Ignoring Store Concurrency", + "Level": 1 + }, + { + "Id": "assuming-runtime-overrides-receive-full-startup-validation", + "Text": "Assuming Runtime Overrides Receive Full Startup Validation", + "Level": 1 + }, + { + "Id": "advanced-configuration-reference", + "Text": "21. Advanced Configuration Reference", "Level": 0 }, { - "Id": "key-takeaways", - "Text": "\uD83D\uDCCC Key Takeaways", + "Id": "recommended-extension-strategy", + "Text": "22. Recommended Extension Strategy", "Level": 0 }, { - "Id": "next-step", - "Text": "\u27A1\uFE0F Next Step", + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json index c21a436a..e0768834 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json @@ -1,56 +1,216 @@ { "Slug": "configuration/index", "Title": "Configuration", - "Html": "\n\u003Cp\u003EUltimateAuth is designed to be flexible.\u003C/p\u003E\n\u003Cp\u003EBut flexibility without structure leads to chaos.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Configuration in UltimateAuth is structured, layered, and safe by default.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-you-configure\u0022\u003E\uD83E\uDDE0 What You Configure\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth, you don\u2019t just configure values.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You configure behavior.\u003C/p\u003E\n\u003Cp\u003EThis includes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EHow sessions are created and managed\u003C/li\u003E\n\u003Cli\u003EHow tokens are issued and refreshed\u003C/li\u003E\n\u003Cli\u003EHow tenants are resolved\u003C/li\u003E\n\u003Cli\u003EHow clients interact with the system\u003C/li\u003E\n\u003Cli\u003EWhich features are enabled or restricted\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-layers\u0022\u003E\uD83E\uDDE9 Configuration Layers\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates configuration into three layers:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022core\u0022\u003E\uD83D\uDD39 Core\u003C/h3\u003E\n\u003Cp\u003EDefines authentication behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession lifecycle\u003C/li\u003E\n\u003Cli\u003EToken policies\u003C/li\u003E\n\u003Cli\u003EPKCE flows\u003C/li\u003E\n\u003Cli\u003EMulti-tenancy\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server\u0022\u003E\uD83D\uDD39 Server\u003C/h3\u003E\n\u003Cp\u003EDefines runtime behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAllowed authentication modes\u003C/li\u003E\n\u003Cli\u003EEndpoint exposure\u003C/li\u003E\n\u003Cli\u003ECookie and transport behavior\u003C/li\u003E\n\u003Cli\u003EHub deployment\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client\u0022\u003E\uD83D\uDD39 Client\u003C/h3\u003E\n\u003Cp\u003EDefines client-side behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient profile\u003C/li\u003E\n\u003Cli\u003EPKCE configuration\u003C/li\u003E\n\u003Cli\u003EAuto-refresh and re-authentication\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 These layers are independent but work together.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-sources\u0022\u003E\u2699\uFE0F Configuration Sources\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports two configuration styles:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022code-based-program.cs\u0022\u003ECode-based (Program.cs)\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(o =\u0026gt;\n{\n o.Login.MaxFailedAttempts = 5;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-based-appsettings.json\u0022\u003EConfiguration-based (appsettings.json)\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\u003E{\n \u0026quot;UltimateAuth\u0026quot;: {\n \u0026quot;Server\u0026quot;: {\n \u0026quot;Login\u0026quot;: {\n \u0026quot;MaxFailedAttempts\u0026quot;: 5\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 appsettings.json overrides Program.cs\u003C/p\u003E\n\u003Cp\u003EThis allows:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnvironment-based configuration\u003C/li\u003E\n\u003Cli\u003ECentralized management\u003C/li\u003E\n\u003Cli\u003EProduction-safe overrides\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022safety-by-design\u0022\u003E\uD83D\uDEE1 Safety by Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth does not allow unsafe configurations silently.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalid combinations fail at startup\u003C/li\u003E\n\u003Cli\u003EUnsupported modes are rejected\u003C/li\u003E\n\u003Cli\u003ESecurity invariants are enforced\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Flexibility is allowed\n\uD83D\uDC49 Unsafe behavior is not\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022whats-next\u0022\u003E\uD83C\uDFAF What\u2019s Next?\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EUnderstand configuration layers \u2192 Configuration Overview\u003C/li\u003E\n\u003Cli\u003ELearn Core behavior \u2192 Core Options\u003C/li\u003E\n\u003Cli\u003ECustomize server \u2192 Server Options\u003C/li\u003E\n\u003Cli\u003EControl clients \u2192 Client Options\u003C/li\u003E\n\u003Cli\u003EGo deeper \u2192 Advanced Configuration\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/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", "Headings": [ { - "Id": "what-you-configure", - "Text": "\uD83E\uDDE0 What You Configure", + "Id": "configuration-architecture", + "Text": "1. Configuration Architecture", "Level": 0 }, { - "Id": "configuration-layers", - "Text": "\uD83E\uDDE9 Configuration Layers", + "Id": "configure-the-server", + "Text": "2. Configure the Server", "Level": 0 }, { - "Id": "core", - "Text": "\uD83D\uDD39 Core", + "Id": "example-authentication-and-session-settings", + "Text": "Example: Authentication and Session Settings", "Level": 1 }, { - "Id": "server", - "Text": "\uD83D\uDD39 Server", + "Id": "core-options-are-composed-into-server-options", + "Text": "3. Core Options Are Composed into Server Options", + "Level": 0 + }, + { + "Id": "recommended", + "Text": "Recommended", + "Level": 1 + }, + { + "Id": "not-recommended-for-server-configuration", + "Text": "Not Recommended for Server Configuration", "Level": 1 }, { - "Id": "client", - "Text": "\uD83D\uDD39 Client", + "Id": "server-configuration-groups", + "Text": "4. Server Configuration Groups", + "Level": 0 + }, + { + "Id": "configure-the-client", + "Text": "5. Configure the Client", + "Level": 0 + }, + { + "Id": "client-configuration-groups", + "Text": "Client Configuration Groups", "Level": 1 }, + { + "Id": "server-and-client-configuration-are-independent", + "Text": "6. Server and Client Configuration Are Independent", + "Level": 0 + }, { "Id": "configuration-sources", - "Text": "\u2699\uFE0F Configuration Sources", + "Text": "7. Configuration Sources", + "Level": 0 + }, + { + "Id": "code-based-configuration", + "Text": "Code-Based Configuration", + "Level": 1 + }, + { + "Id": "configuration-based-setup", + "Text": "Configuration-Based Setup", + "Level": 1 + }, + { + "Id": "using-both-sources", + "Text": "Using Both Sources", + "Level": 1 + }, + { + "Id": "configuration-precedence", + "Text": "8. Configuration Precedence", + "Level": 0 + }, + { + "Id": "important-distinction", + "Text": "Important Distinction", + "Level": 1 + }, + { + "Id": "environment-variable-example", + "Text": "Environment Variable Example", + "Level": 1 + }, + { + "Id": "authentication-mode-configuration", + "Text": "9. Authentication Mode Configuration", + "Level": 0 + }, + { + "Id": "restrict-allowed-modes", + "Text": "Restrict Allowed Modes", + "Level": 1 + }, + { + "Id": "unsupported-modes", + "Text": "Unsupported Modes", + "Level": 1 + }, + { + "Id": "configure-credential-session-policies", + "Text": "10. Configure Credential Session Policies", + "Level": 0 + }, + { + "Id": "configure-endpoint-exposure", + "Text": "11. Configure Endpoint Exposure", "Level": 0 }, { - "Id": "code-based-program.cs", - "Text": "Code-based (Program.cs)", + "Id": "multi-tenant-configuration", + "Text": "12. Multi-Tenant Configuration", + "Level": 0 + }, + { + "Id": "resource-api-configuration", + "Text": "13. Resource API Configuration", + "Level": 0 + }, + { + "Id": "important-resource-api-cors-configuration", + "Text": "Important: Resource API CORS Configuration", + "Level": 1 + }, + { + "Id": "validation-and-safe-configuration", + "Text": "14. Validation and Safe Configuration", + "Level": 0 + }, + { + "Id": "example-invalid-session-lifetime", + "Text": "Example: Invalid Session Lifetime", + "Level": 1 + }, + { + "Id": "example-invalid-endpoint-path", + "Text": "Example: Invalid Endpoint Path", + "Level": 1 + }, + { + "Id": "validation-timing", + "Text": "Validation Timing", + "Level": 1 + }, + { + "Id": "extensibility-through-dependency-injection", + "Text": "15. Extensibility Through Dependency Injection", + "Level": 0 + }, + { + "Id": "configuration-versus-extension", + "Text": "Configuration Versus Extension", + "Level": 1 + }, + { + "Id": "endpoint-customization", + "Text": "16. Endpoint Customization", + "Level": 0 + }, + { + "Id": "recommended-configuration-strategy", + "Text": "17. Recommended Configuration Strategy", + "Level": 0 + }, + { + "Id": "keep-server-security-settings-on-the-server", + "Text": "Keep Server Security Settings on the Server", + "Level": 1 + }, + { + "Id": "keep-client-behavior-in-client-options", + "Text": "Keep Client Behavior in Client Options", + "Level": 1 + }, + { + "Id": "use-configuration-files-for-environment-differences", + "Text": "Use Configuration Files for Environment Differences", + "Level": 1 + }, + { + "Id": "prefer-strongly-typed-configuration", + "Text": "Prefer Strongly Typed Configuration", + "Level": 1 + }, + { + "Id": "preserve-secure-defaults", + "Text": "Preserve Secure Defaults", + "Level": 1 + }, + { + "Id": "avoid-duplicate-core-configuration", + "Text": "Avoid Duplicate Core Configuration", "Level": 1 }, { - "Id": "configuration-based-appsettings.json", - "Text": "Configuration-based (appsettings.json)", + "Id": "validate-production-configuration", + "Text": "Validate Production Configuration", "Level": 1 }, { - "Id": "safety-by-design", - "Text": "\uD83D\uDEE1 Safety by Design", + "Id": "configuration-reference", + "Text": "18. Configuration Reference", "Level": 0 }, { - "Id": "whats-next", - "Text": "\uD83C\uDFAF What\u2019s Next?", + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json index c9ec03da..92b5f3a0 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json @@ -105,179 +105,158 @@ "GroupOrder": 3 }, { - "Title": "Session Lifecycle", - "Slug": "auth-flows/session-lifecycle", - "Order": 5, - "Group": "auth-flows", - "GroupOrder": 3 - }, - { - "Title": "Token Behavior", - "Slug": "auth-flows/token-behavior", - "Order": 6, - "Group": "auth-flows", - "GroupOrder": 3 + "Title": "Session Model \u0026 Lifecycle", + "Slug": "sessions-devices/session-lifecycle", + "Order": 2, + "Group": "sessions-devices", + "GroupOrder": 4 }, { "Title": "Device Management", - "Slug": "auth-flows/device-management", - "Order": 7, - "Group": "auth-flows", - "GroupOrder": 3 + "Slug": "sessions-devices/device-management", + "Order": 3, + "Group": "sessions-devices", + "GroupOrder": 4 }, { "Title": "Plugin Domains", "Slug": "plugin-domains/index", "Order": 1, "Group": "plugin-domains", - "GroupOrder": 4 + "GroupOrder": 5 }, { "Title": "Users", "Slug": "plugin-domains/users-domain", "Order": 2, "Group": "plugin-domains", - "GroupOrder": 4 + "GroupOrder": 5 }, { "Title": "Credentials", "Slug": "plugin-domains/credential-domain", "Order": 3, "Group": "plugin-domains", - "GroupOrder": 4 + "GroupOrder": 5 }, { "Title": "Authorization", "Slug": "plugin-domains/authorization-domain", "Order": 4, "Group": "plugin-domains", - "GroupOrder": 4 + "GroupOrder": 5 }, { - "Title": "Policies", + "Title": "Policies \u0026 Access Control", "Slug": "plugin-domains/policies", "Order": 5, "Group": "plugin-domains", - "GroupOrder": 4 + "GroupOrder": 5 }, { - "Title": "Client \u0026 API", + "Title": "Client SDK Overview", "Slug": "client/index", "Order": 1, "Group": "client", - "GroupOrder": 5 + "GroupOrder": 6 }, { "Title": "Authentication", "Slug": "client/authentication", "Order": 2, "Group": "client", - "GroupOrder": 5 + "GroupOrder": 6 }, { "Title": "Session Management", "Slug": "client/session-management", "Order": 3, "Group": "client", - "GroupOrder": 5 + "GroupOrder": 6 }, { "Title": "User Management", "Slug": "client/user-management", "Order": 4, "Group": "client", - "GroupOrder": 5 + "GroupOrder": 6 }, { "Title": "Identifiers", "Slug": "client/identifiers", "Order": 5, "Group": "client", - "GroupOrder": 5 + "GroupOrder": 6 }, { "Title": "Credentials", "Slug": "client/credentials", "Order": 6, "Group": "client", - "GroupOrder": 5 + "GroupOrder": 6 }, { "Title": "Authorization", "Slug": "client/authorization", "Order": 7, "Group": "client", - "GroupOrder": 5 + "GroupOrder": 6 }, { "Title": "Configuration", "Slug": "configuration/index", "Order": 1, "Group": "configuration", - "GroupOrder": 6 - }, - { - "Title": "Configuration Overview", - "Slug": "configuration/configuration-overview", - "Order": 2, - "Group": "configuration", - "GroupOrder": 6 + "GroupOrder": 7 }, { "Title": "Server Options", "Slug": "configuration/server-options", "Order": 3, "Group": "configuration", - "GroupOrder": 6 + "GroupOrder": 7 }, { - "Title": "Client Options", - "Slug": "configuration/client-options", + "Title": "Advanced Configuration", + "Slug": "configuration/advanced-configuration", "Order": 4, "Group": "configuration", - "GroupOrder": 6 - }, - { - "Title": "Configuration Sources", - "Slug": "configuration/configuration-sources", - "Order": 5, - "Group": "configuration", - "GroupOrder": 6 + "GroupOrder": 7 }, { - "Title": "Advanced Configuration", - "Slug": "configuration/advanced-configuration", - "Order": 6, + "Title": "Client Options", + "Slug": "configuration/client-options", + "Order": 4, "Group": "configuration", - "GroupOrder": 6 + "GroupOrder": 7 }, { "Title": "Session Security Model", "Slug": "security/session-security-model", "Order": 1, "Group": "security", - "GroupOrder": 7 + "GroupOrder": 8 }, { - "Title": "Access Token Behavior", - "Slug": "security/access-token-behavior", + "Title": "Token Security", + "Slug": "security/token-security", "Order": 2, "Group": "security", - "GroupOrder": 7 + "GroupOrder": 8 }, { - "Title": "Refresh Rotation", + "Title": "Refresh Rotation \u0026 Replay Protection", "Slug": "security/refresh-rotation", "Order": 3, "Group": "security", - "GroupOrder": 7 + "GroupOrder": 8 }, { - "Title": "Policy Pipeline", - "Slug": "security/policy-pipeline", + "Title": "Authorization Pipeline Internals", + "Slug": "security/authorization-pipeline", "Order": 4, "Group": "security", - "GroupOrder": 7 + "GroupOrder": 8 }, { "Title": "\uD83D\uDD10 UltimateAuth Docs", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json index d70edb96..75b878b9 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json @@ -1,7 +1,7 @@ { "Slug": "getting-started/index", "Title": "Getting Started", - "Html": "\n\u003Cp\u003EWelcome to \u003Cstrong\u003EUltimateAuth\u003C/strong\u003E, a unified authentication and authorization framework for modern .NET applications.\u003C/p\u003E\n\u003Cp\u003EAuthentication should not become a different system every time your application gains a new client, tenant, device, or security requirement. UltimateAuth brings these concerns into \u003Cstrong\u003Eone coherent model\u003C/strong\u003E\u2014without pretending that every application needs the same authentication strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOne framework. Multiple authentication models. A consistent security lifecycle.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-ultimateauth\u0022\u003EWhat is UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is a modular, flow-driven framework for building and managing authentication in .NET. It connects concepts that are often implemented separately:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ESessions, cookies and tokens\u003C/strong\u003E \u2014 stateful session management alongside access and refresh token workflows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthentication and authorization\u003C/strong\u003E \u2014 identity establishment and access decisions within a consistent architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUsers and credentials\u003C/strong\u003E \u2014 identity, identifiers, credentials, and their security state.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevices and session continuity\u003C/strong\u003E \u2014 device-aware session chains, refresh, logout, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClients and delivery mechanisms\u003C/strong\u003E \u2014 browser, API, Blazor, and mobile scenarios with client-aware behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ETenants and isolation\u003C/strong\u003E \u2014 tenant-aware request context and authentication boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESecurity policies and execution flows\u003C/strong\u003E \u2014 explicit decisions, validation, and controlled state transitions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe goal is not to hide these concepts. It is to make them \u003Cstrong\u003Ework together predictably\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022unified-does-not-mean-one-size-fits-all\u0022\u003EUnified Does Not Mean One-Size-Fits-All\u003C/h2\u003E\n\u003Cp\u003EA server-rendered web application, a browser-based SPA, a mobile app, and a backend API do not share the same trust boundaries or credential transport requirements.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides a common foundation while allowing behavior to be shaped by the \u003Cstrong\u003Eeffective authentication mode\u003C/strong\u003E, \u003Cstrong\u003Eclient profile\u003C/strong\u003E, \u003Cstrong\u003Etenant\u003C/strong\u003E, and \u003Cstrong\u003Erequest context\u003C/strong\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcern\u003C/th\u003E\n\u003Cth\u003EWhat UltimateAuth brings together\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\u003EAuthentication models\u003C/td\u003E\n\u003Ctd\u003ESession-oriented and token-oriented workflows\u003C/td\u003E\n\u003Ctd\u003EDifferent clients can participate in one security architecture\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession lifecycle\u003C/td\u003E\n\u003Ctd\u003ERoot \u2192 Chain \u2192 Session\u003C/td\u003E\n\u003Ctd\u003EManage authentication at identity, device, and session scope\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient environments\u003C/td\u003E\n\u003Ctd\u003EASP.NET Core, Blazor, APIs, and .NET mobile clients\u003C/td\u003E\n\u003Ctd\u003EAvoid rebuilding the core authentication model for each frontend\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMulti-tenancy\u003C/td\u003E\n\u003Ctd\u003ETenant resolution and tenant-bound context\u003C/td\u003E\n\u003Ctd\u003EKeep authentication operations within the intended tenant boundary\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredentials and identities\u003C/td\u003E\n\u003Ctd\u003EUser identifiers, credential providers, and security state\u003C/td\u003E\n\u003Ctd\u003ESeparate \u003Cem\u003Ewho the user is\u003C/em\u003E from \u003Cem\u003Ehow they prove it\u003C/em\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESecurity enforcement\u003C/td\u003E\n\u003Ctd\u003EContext, validation, authority, and orchestration\u003C/td\u003E\n\u003Ctd\u003EMake decisions explicit rather than scattering checks across endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential delivery\u003C/td\u003E\n\u003Ctd\u003EMode- and client-aware response policies\u003C/td\u003E\n\u003Ctd\u003EHandle cookies, tokens, and redirects according to the scenario\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession termination\u003C/td\u003E\n\u003Ctd\u003ELogout, targeted revocation, and device management\u003C/td\u003E\n\u003Ctd\u003ERespond proportionately to ordinary sign-out and security incidents\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EUnified\u003C/strong\u003E is therefore about a shared security model\u2014not forcing all applications to use identical cookies, tokens, or endpoints.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-is-more-than-issuing-a-token\u0022\u003EAuthentication Is More Than Issuing a Token\u003C/h2\u003E\n\u003Cp\u003EIn many applications, authentication is described as a short sequence: validate credentials, generate a token, return it to the client.\u003C/p\u003E\n\u003Cp\u003EThat is only part of the story.\u003C/p\u003E\n\u003Cp\u003EA production authentication system must also answer questions such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhich tenant does this request belong to?\u003C/li\u003E\n\u003Cli\u003EWhich user and credential are being evaluated?\u003C/li\u003E\n\u003Cli\u003EIs an existing session still valid?\u003C/li\u003E\n\u003Cli\u003EIs the request coming from a known device?\u003C/li\u003E\n\u003Cli\u003ECan access be renewed, restricted, or terminated?\u003C/li\u003E\n\u003Cli\u003EWhat should happen when a credential changes or a device is no longer trusted?\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth treats authentication as a \u003Cstrong\u003Econtrolled lifecycle\u003C/strong\u003E. Login, refresh, and logout are operations within that lifecycle\u2014not unrelated endpoints that happen to exchange tokens.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessions-are-first-class\u0022\u003ESessions Are First-Class\u003C/h2\u003E\n\u003Cp\u003EWhere session-backed modes apply, UltimateAuth models authentication through a hierarchy:\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 Global session-security boundary\n \u2514\u2500\u2500 Chain Device-associated lifecycle\n \u2514\u2500\u2500 Session Individual authentication instance\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis structure gives the system a vocabulary for different security actions. A session can be invalidated, a device-associated chain can be terminated, and a broader security boundary can be revoked.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA token is not the entire authentication model.\u003C/strong\u003E It is a credential used within that model, subject to the applicable mode and validation rules.\u003C/p\u003E\n\u003Cp\u003ENot every authentication mode has identical persistence or session behavior. The session hierarchy describes the stateful lifecycle where that model is in use.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-driven-by-design\u0022\u003EFlow-Driven by Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth organizes authentication operations around explicit flows. A flow brings together the request context, operation-specific validation, security decisions, permitted state transitions, and response 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\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\u003ERequest\n \u2192 Tenant, session, and user resolution\n \u2192 AuthFlowContext\n \u2192 Flow-specific validation\n \u2192 Security decision and orchestration\n \u2192 State transition (when permitted)\n \u2192 Result and client-appropriate response\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is not a claim that pipelines or orchestration are new inventions. The value is in applying them \u003Cstrong\u003Econsistently across authentication operations\u003C/strong\u003E, so the rules are easier to reason about, test, and extend.\u003C/p\u003E\n\u003Cp\u003EFor the implementation model, see \u003Ca href=\u0022../auth-flows/auth-flows.md\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022tenant-aware-from-the-request-boundary\u0022\u003ETenant-Aware from the Request Boundary\u003C/h2\u003E\n\u003Cp\u003EMulti-tenancy is not simply a \u003Ccode\u003ETenantId\u003C/code\u003E added to a user table. Authentication must understand \u003Cem\u003Ewhich tenant\u0027s security context\u003C/em\u003E a request is operating within.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth resolves tenant context as part of the ASP.NET Core request pipeline and carries it into the authentication flow. The flow context then combines that boundary with client settings, device information, and applicable session validation.\u003C/p\u003E\n\u003Cp\u003EThis helps teams design multi-tenant applications without treating tenant isolation as an afterthought. \u003Cstrong\u003ETenant resolution and configuration still need to be correctly integrated by the host application.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-for-different-clients-not-just-different-screens\u0022\u003EBuilt for Different Clients, Not Just Different Screens\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is designed for applications that may span several environments:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EASP.NET Core web applications\u003C/strong\u003E with server-managed authentication concerns.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EBlazor Server and Blazor WebAssembly\u003C/strong\u003E with different hosting and transport constraints.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E.NET MAUI\u003C/strong\u003E and other native clients with device-oriented authentication needs.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EHTTP APIs\u003C/strong\u003E serving browsers, applications, and other services.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EClient profiles and effective server options help adapt authentication behavior to the requesting client. Response policies determine how applicable credentials are delivered and whether a redirect belongs in the interaction.\u003C/p\u003E\n\u003Cp\u003EThe result is a common backend security vocabulary across multiple application types\u2014not a promise that every client uses the same transport or requires zero configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-is-a-lifecycle-too\u0022\u003ESecurity Is a Lifecycle, Too\u003C/h2\u003E\n\u003Cp\u003ESecurity is not finished when login succeeds. UltimateAuth\u0027s architecture accounts for what happens \u003Cem\u003Eafter\u003C/em\u003E access is established:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ESituation\u003C/th\u003E\n\u003Cth\u003ESecurity question\u003C/th\u003E\n\u003Cth\u003EUltimateAuth concept\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs in\u003C/td\u003E\n\u003Ctd\u003EShould this attempt establish authentication?\u003C/td\u003E\n\u003Ctd\u003ELogin flow and security decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA session needs renewal\u003C/td\u003E\n\u003Ctd\u003ECan continuity be preserved safely?\u003C/td\u003E\n\u003Ctd\u003ERefresh flow and session lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs out\u003C/td\u003E\n\u003Ctd\u003EWhich authentication scope should end?\u003C/td\u003E\n\u003Ctd\u003ELogout\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA device is lost\u003C/td\u003E\n\u003Ctd\u003EHow can that device\u0027s access be terminated?\u003C/td\u003E\n\u003Ctd\u003EChain-level management and revocation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA credential is compromised\u003C/td\u003E\n\u003Ctd\u003EHow should existing authentication be affected?\u003C/td\u003E\n\u003Ctd\u003ECredential security state and revocation policies\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAn administrator manages access\u003C/td\u003E\n\u003Ctd\u003EIs the operation permitted for this actor and target?\u003C/td\u003E\n\u003Ctd\u003EAuthorization and access context\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA key distinction is \u003Cstrong\u003Elogout versus revoke\u003C/strong\u003E. Ordinary logout ends authentication according to its scope; revocation is an irreversible invalidation of the targeted security state. Signing in again may establish \u003Cem\u003Enew\u003C/em\u003E authentication, but it does not restore what was revoked.\u003C/p\u003E\n\u003Cp\u003EExplore the details in \u003Ca href=\u0022../auth-flows/login-flow.md\u0022\u003ELogin Flow\u003C/a\u003E, \u003Ca href=\u0022../auth-flows/refresh-flow.md\u0022\u003ERefresh Flow\u003C/a\u003E, and \u003Ca href=\u0022../auth-flows/logout-flow.md\u0022\u003ELogout Flow\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022modular-and-extensible\u0022\u003EModular and Extensible\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is intended to serve both straightforward applications and systems with more demanding security requirements. Its architecture separates responsibilities such as core contracts, server execution, client integration, user management, credentials, and authorization.\u003C/p\u003E\n\u003Cp\u003EThis separation makes it possible to understand and evolve individual concerns without reducing authentication to a collection of disconnected custom handlers.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EExtensible does not mean every possible flow is already implemented.\u003C/strong\u003E Available modes, integrations, and capabilities should be evaluated against the version you are using; the documentation for each feature describes its current behavior and limitations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-should-you-choose-ultimateauth\u0022\u003EWhen Should You Choose UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is especially relevant when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need quickly-installed, consistent, secure and easy-to-use auth in your .NET application.\u003C/li\u003E\n\u003Cli\u003EYour .NET application has\u2014or will have\u2014\u003Cstrong\u003Emore than one client type\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003EYou need \u003Cstrong\u003Edevice-aware session management\u003C/strong\u003E, not only token generation.\u003C/li\u003E\n\u003Cli\u003EYou are designing a \u003Cstrong\u003Emulti-tenant\u003C/strong\u003E system with clear authentication boundaries.\u003C/li\u003E\n\u003Cli\u003EYou want \u003Cstrong\u003Eauthentication and authorization\u003C/strong\u003E to follow a consistent architectural model.\u003C/li\u003E\n\u003Cli\u003EYou need control over \u003Cstrong\u003Erefresh, logout, revocation, and credential lifecycle\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003EYou value a \u003Cstrong\u003Emodular, self-hosted, open-source\u003C/strong\u003E foundation you can understand and extend.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAlso for a small application with minimal authentication needs, UltimateAuth brings the simplest possible solution.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022open-source-first\u0022\u003EOpen Source First\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is built with a simple commitment: \u003Cstrong\u003Eself-hosted authentication should remain accessible\u003C/strong\u003E. The framework is open source, and self-hosting is intended to remain free. A hosted offering may provide an operational alternative, not a separate tier of authentication features.\u003C/p\u003E\n\u003Cp\u003EThe ambition is to build a framework that developers can adopt, inspect, contribute to, and trust\u2014not merely a service they must depend on.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022where-to-go-next\u0022\u003EWhere to Go Next\u003C/h2\u003E\n\u003Cp\u003EYou do not need to understand every internal component before getting started.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EContinue to Quick Start\u003C/strong\u003E to set up your first UltimateAuth application. Then explore \u003Ca href=\u0022../auth-flows/auth-flows.md\u0022\u003EAuth Flows\u003C/a\u003E to understand how the pieces work together.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe idea to remember:\u003C/strong\u003E UltimateAuth unifies authentication concepts without erasing their differences. One framework, a consistent lifecycle, and room for the security model your application actually needs.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EWelcome to \u003Cstrong\u003EUltimateAuth\u003C/strong\u003E, a unified authentication and authorization framework for modern .NET applications.\u003C/p\u003E\n\u003Cp\u003EAuthentication should not become a different system every time your application gains a new client, tenant, device, or security requirement. UltimateAuth brings these concerns into \u003Cstrong\u003Eone coherent model\u003C/strong\u003E\u2014without pretending that every application needs the same authentication strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOne framework. Multiple authentication models. A consistent security lifecycle.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-ultimateauth\u0022\u003EWhat is UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is a modular, flow-driven framework for building and managing authentication in .NET. It connects concepts that are often implemented separately:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ESessions, cookies and tokens\u003C/strong\u003E \u2014 stateful session management alongside access and refresh token workflows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthentication and authorization\u003C/strong\u003E \u2014 identity establishment and access decisions within a consistent architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUsers and credentials\u003C/strong\u003E \u2014 identity, identifiers, credentials, and their security state.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevices and session continuity\u003C/strong\u003E \u2014 device-aware session chains, refresh, logout, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClients and delivery mechanisms\u003C/strong\u003E \u2014 browser, API, Blazor, and mobile scenarios with client-aware behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ETenants and isolation\u003C/strong\u003E \u2014 tenant-aware request context and authentication boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESecurity policies and execution flows\u003C/strong\u003E \u2014 explicit decisions, validation, and controlled state transitions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe goal is not to hide these concepts. It is to make them \u003Cstrong\u003Ework together predictably\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022unified-does-not-mean-one-size-fits-all\u0022\u003EUnified Does Not Mean One-Size-Fits-All\u003C/h2\u003E\n\u003Cp\u003EA server-rendered web application, a browser-based SPA, a mobile app, and a backend API do not share the same trust boundaries or credential transport requirements.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides a common foundation while allowing behavior to be shaped by the \u003Cstrong\u003Eeffective authentication mode\u003C/strong\u003E, \u003Cstrong\u003Eclient profile\u003C/strong\u003E, \u003Cstrong\u003Etenant\u003C/strong\u003E, and \u003Cstrong\u003Erequest context\u003C/strong\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcern\u003C/th\u003E\n\u003Cth\u003EWhat UltimateAuth brings together\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\u003EAuthentication models\u003C/td\u003E\n\u003Ctd\u003ESession-oriented and token-oriented workflows\u003C/td\u003E\n\u003Ctd\u003EDifferent clients can participate in one security architecture\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession lifecycle\u003C/td\u003E\n\u003Ctd\u003ERoot \u2192 Chain \u2192 Session\u003C/td\u003E\n\u003Ctd\u003EManage authentication at identity, device, and session scope\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient environments\u003C/td\u003E\n\u003Ctd\u003EASP.NET Core, Blazor, APIs, and .NET mobile clients\u003C/td\u003E\n\u003Ctd\u003EAvoid rebuilding the core authentication model for each frontend\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMulti-tenancy\u003C/td\u003E\n\u003Ctd\u003ETenant resolution and tenant-bound context\u003C/td\u003E\n\u003Ctd\u003EKeep authentication operations within the intended tenant boundary\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredentials and identities\u003C/td\u003E\n\u003Ctd\u003EUser identifiers, credential providers, and security state\u003C/td\u003E\n\u003Ctd\u003ESeparate \u003Cem\u003Ewho the user is\u003C/em\u003E from \u003Cem\u003Ehow they prove it\u003C/em\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESecurity enforcement\u003C/td\u003E\n\u003Ctd\u003EContext, validation, authority, and orchestration\u003C/td\u003E\n\u003Ctd\u003EMake decisions explicit rather than scattering checks across endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential delivery\u003C/td\u003E\n\u003Ctd\u003EMode- and client-aware response policies\u003C/td\u003E\n\u003Ctd\u003EHandle cookies, tokens, and redirects according to the scenario\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession termination\u003C/td\u003E\n\u003Ctd\u003ELogout, targeted revocation, and device management\u003C/td\u003E\n\u003Ctd\u003ERespond proportionately to ordinary sign-out and security incidents\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EUnified\u003C/strong\u003E is therefore about a shared security model\u2014not forcing all applications to use identical cookies, tokens, or endpoints.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-is-more-than-issuing-a-token\u0022\u003EAuthentication Is More Than Issuing a Token\u003C/h2\u003E\n\u003Cp\u003EIn many applications, authentication is described as a short sequence: validate credentials, generate a token, return it to the client.\u003C/p\u003E\n\u003Cp\u003EThat is only part of the story.\u003C/p\u003E\n\u003Cp\u003EA production authentication system must also answer questions such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhich tenant does this request belong to?\u003C/li\u003E\n\u003Cli\u003EWhich user and credential are being evaluated?\u003C/li\u003E\n\u003Cli\u003EIs an existing session still valid?\u003C/li\u003E\n\u003Cli\u003EIs the request coming from a known device?\u003C/li\u003E\n\u003Cli\u003ECan access be renewed, restricted, or terminated?\u003C/li\u003E\n\u003Cli\u003EWhat should happen when a credential changes or a device is no longer trusted?\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth treats authentication as a \u003Cstrong\u003Econtrolled lifecycle\u003C/strong\u003E. Login, refresh, and logout are operations within that lifecycle\u2014not unrelated endpoints that happen to exchange tokens.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessions-are-first-class\u0022\u003ESessions Are First-Class\u003C/h2\u003E\n\u003Cp\u003EWhere session-backed modes apply, UltimateAuth models authentication through a hierarchy:\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 Global session-security boundary\n \u2514\u2500\u2500 Chain Device-associated lifecycle\n \u2514\u2500\u2500 Session Individual authentication instance\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis structure gives the system a vocabulary for different security actions. A session can be invalidated, a device-associated chain can be terminated, and a broader security boundary can be revoked.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA token is not the entire authentication model.\u003C/strong\u003E It is a credential used within that model, subject to the applicable mode and validation rules.\u003C/p\u003E\n\u003Cp\u003ENot every authentication mode has identical persistence or session behavior. The session hierarchy describes the stateful lifecycle where that model is in use.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-driven-by-design\u0022\u003EFlow-Driven by Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth organizes authentication operations around explicit flows. A flow brings together the request context, operation-specific validation, security decisions, permitted state transitions, and response 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\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\u003ERequest\n \u2192 Tenant, session, and user resolution\n \u2192 AuthFlowContext\n \u2192 Flow-specific validation\n \u2192 Security decision and orchestration\n \u2192 State transition (when permitted)\n \u2192 Result and client-appropriate response\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is not a claim that pipelines or orchestration are new inventions. The value is in applying them \u003Cstrong\u003Econsistently across authentication operations\u003C/strong\u003E, so the rules are easier to reason about, test, and extend.\u003C/p\u003E\n\u003Cp\u003EFor the implementation model, see \u003Ca href=\u0022/docs/auth-flows/index\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022tenant-aware-from-the-request-boundary\u0022\u003ETenant-Aware from the Request Boundary\u003C/h2\u003E\n\u003Cp\u003EMulti-tenancy is not simply a \u003Ccode\u003ETenantId\u003C/code\u003E added to a user table. Authentication must understand \u003Cem\u003Ewhich tenant\u0027s security context\u003C/em\u003E a request is operating within.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth resolves tenant context as part of the ASP.NET Core request pipeline and carries it into the authentication flow. The flow context then combines that boundary with client settings, device information, and applicable session validation.\u003C/p\u003E\n\u003Cp\u003EThis helps teams design multi-tenant applications without treating tenant isolation as an afterthought. \u003Cstrong\u003ETenant resolution and configuration still need to be correctly integrated by the host application.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-for-different-clients-not-just-different-screens\u0022\u003EBuilt for Different Clients, Not Just Different Screens\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is designed for applications that may span several environments:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EASP.NET Core web applications\u003C/strong\u003E with server-managed authentication concerns.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EBlazor Server and Blazor WebAssembly\u003C/strong\u003E with different hosting and transport constraints.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E.NET MAUI\u003C/strong\u003E and other native clients with device-oriented authentication needs.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EHTTP APIs\u003C/strong\u003E serving browsers, applications, and other services.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EClient profiles and effective server options help adapt authentication behavior to the requesting client. Response policies determine how applicable credentials are delivered and whether a redirect belongs in the interaction.\u003C/p\u003E\n\u003Cp\u003EThe result is a common backend security vocabulary across multiple application types\u2014not a promise that every client uses the same transport or requires zero configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-is-a-lifecycle-too\u0022\u003ESecurity Is a Lifecycle, Too\u003C/h2\u003E\n\u003Cp\u003ESecurity is not finished when login succeeds. UltimateAuth\u0027s architecture accounts for what happens \u003Cem\u003Eafter\u003C/em\u003E access is established:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ESituation\u003C/th\u003E\n\u003Cth\u003ESecurity question\u003C/th\u003E\n\u003Cth\u003EUltimateAuth concept\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs in\u003C/td\u003E\n\u003Ctd\u003EShould this attempt establish authentication?\u003C/td\u003E\n\u003Ctd\u003ELogin flow and security decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA session needs renewal\u003C/td\u003E\n\u003Ctd\u003ECan continuity be preserved safely?\u003C/td\u003E\n\u003Ctd\u003ERefresh flow and session lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs out\u003C/td\u003E\n\u003Ctd\u003EWhich authentication scope should end?\u003C/td\u003E\n\u003Ctd\u003ELogout\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA device is lost\u003C/td\u003E\n\u003Ctd\u003EHow can that device\u0027s access be terminated?\u003C/td\u003E\n\u003Ctd\u003EChain-level management and revocation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA credential is compromised\u003C/td\u003E\n\u003Ctd\u003EHow should existing authentication be affected?\u003C/td\u003E\n\u003Ctd\u003ECredential security state and revocation policies\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAn administrator manages access\u003C/td\u003E\n\u003Ctd\u003EIs the operation permitted for this actor and target?\u003C/td\u003E\n\u003Ctd\u003EAuthorization and access context\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA key distinction is \u003Cstrong\u003Elogout versus revoke\u003C/strong\u003E. Ordinary logout ends authentication according to its scope; revocation is an irreversible invalidation of the targeted security state. Signing in again may establish \u003Cem\u003Enew\u003C/em\u003E authentication, but it does not restore what was revoked.\u003C/p\u003E\n\u003Cp\u003EExplore the details in \u003Ca href=\u0022/docs/auth-flows/login\u0022\u003ELogin Flow\u003C/a\u003E, \u003Ca href=\u0022/docs/auth-flows/refresh\u0022\u003ERefresh Flow\u003C/a\u003E, and \u003Ca href=\u0022/docs/auth-flows/logout\u0022\u003ELogout Flow\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022modular-and-extensible\u0022\u003EModular and Extensible\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is intended to serve both straightforward applications and systems with more demanding security requirements. Its architecture separates responsibilities such as core contracts, server execution, client integration, user management, credentials, and authorization.\u003C/p\u003E\n\u003Cp\u003EThis separation makes it possible to understand and evolve individual concerns without reducing authentication to a collection of disconnected custom handlers.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EExtensible does not mean every possible flow is already implemented.\u003C/strong\u003E Available modes, integrations, and capabilities should be evaluated against the version you are using; the documentation for each feature describes its current behavior and limitations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-should-you-choose-ultimateauth\u0022\u003EWhen Should You Choose UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is especially relevant when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need quickly-installed, consistent, secure and easy-to-use auth in your .NET application.\u003C/li\u003E\n\u003Cli\u003EYour .NET application has\u2014or will have\u2014\u003Cstrong\u003Emore than one client type\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003EYou need \u003Cstrong\u003Edevice-aware session management\u003C/strong\u003E, not only token generation.\u003C/li\u003E\n\u003Cli\u003EYou are designing a \u003Cstrong\u003Emulti-tenant\u003C/strong\u003E system with clear authentication boundaries.\u003C/li\u003E\n\u003Cli\u003EYou want \u003Cstrong\u003Eauthentication and authorization\u003C/strong\u003E to follow a consistent architectural model.\u003C/li\u003E\n\u003Cli\u003EYou need control over \u003Cstrong\u003Erefresh, logout, revocation, and credential lifecycle\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003EYou value a \u003Cstrong\u003Emodular, self-hosted, open-source\u003C/strong\u003E foundation you can understand and extend.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAlso for a small application with minimal authentication needs, UltimateAuth brings the simplest possible solution.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022open-source-first\u0022\u003EOpen Source First\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is built with a simple commitment: \u003Cstrong\u003Eself-hosted authentication should remain accessible\u003C/strong\u003E. The framework is open source, and self-hosting is intended to remain free. A hosted offering may provide an operational alternative, not a separate tier of authentication features.\u003C/p\u003E\n\u003Cp\u003EThe ambition is to build a framework that developers can adopt, inspect, contribute to, and trust\u2014not merely a service they must depend on.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022where-to-go-next\u0022\u003EWhere to Go Next\u003C/h2\u003E\n\u003Cp\u003EYou do not need to understand every internal component before getting started.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EContinue to Quick Start\u003C/strong\u003E to set up your first UltimateAuth application. Then explore \u003Ca href=\u0022/docs/auth-flows/index\u0022\u003EAuth Flows\u003C/a\u003E to understand how the pieces work together.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe idea to remember:\u003C/strong\u003E UltimateAuth unifies authentication concepts without erasing their differences. One framework, a consistent lifecycle, and room for the security model your application actually needs.\u003C/p\u003E\n", "Headings": [ { "Id": "what-is-ultimateauth", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization-domain.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization-domain.json index df32dffb..2752df50 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization-domain.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization-domain.json @@ -1,76 +1,156 @@ { "Slug": "plugin-domains/authorization-domain", "Title": "Authorization", - "Html": "\n\u003Cp\u003EUltimateAuth provides a flexible and extensible authorization system based on:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoles\u003C/li\u003E\n\u003Cli\u003EPermissions\u003C/li\u003E\n\u003Cli\u003EPolicies\u003C/li\u003E\n\u003Cli\u003EAccess orchestration\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-concepts\u0022\u003E\uD83E\uDDE9 Core Concepts\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permissions\u0022\u003E\uD83D\uDD11 Permissions\u003C/h3\u003E\n\u003Cp\u003EIn UltimateAuth, permissions are not just arbitrary strings.\u003C/p\u003E\n\u003Cp\u003EThey follow a \u003Cstrong\u003Estructured action model\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch4 id=\u0022permission-structure\u0022\u003E\uD83E\uDDE9 Permission Structure\u003C/h4\u003E\n\u003Cp\u003EPermissions are built using a consistent format:\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\u003Eresource.operation.scope\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eor\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\u003Eresource.subresource.operation.scope\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch4 id=\u0022examples\u0022\u003E\u2705 Examples\u003C/h4\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.create.admin\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.profile.update.self\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Esessions.revokechain.admin\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Ecredentials.change.self\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This structure is not accidental \u2014\u003Cbr\u003E\nit is \u003Cstrong\u003Edesigned for consistency, readability, and policy evaluation\u003C/strong\u003E.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-in-action-catalog\u0022\u003E\u2699\uFE0F Built-in Action Catalog\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth provides a predefined action catalog.\u003C/p\u003E\n\u003Cp\u003EExamples:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003Eflows.logout.self\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Esessions.listchains.admin\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.delete.self\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Ecredentials.revoke.admin\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eauthorization.roles.assign.admin\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This ensures:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ENo magic strings\u003C/li\u003E\n\u003Cli\u003EDiscoverable permissions\u003C/li\u003E\n\u003Cli\u003EConsistent naming across the system\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022scope-semantics\u0022\u003E\uD83E\uDDE0 Scope Semantics\u003C/h3\u003E\n\u003Cp\u003EThe last part of the permission defines \u003Cstrong\u003Escope\u003C/strong\u003E:\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\u003EMeaning\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eself\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser acts on own resources\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eadmin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser acts on other users\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eanonymous\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo authentication required\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022wildcards-grouping\u0022\u003E\uD83C\uDF32 Wildcards \u0026amp; Grouping\u003C/h3\u003E\n\u003Cp\u003EPermissions support hierarchical matching:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.*\u003C/code\u003E \u2192 all user actions\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.profile.*\u003C/code\u003E \u2192 all profile operations\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003E*\u003C/code\u003E \u2192 full access\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022normalization\u0022\u003E\u26A1 Normalization\u003C/h3\u003E\n\u003Cp\u003EPermissions are automatically normalized:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EFull coverage \u2192 replaced with \u003Ccode\u003E*\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EFull group \u2192 replaced with \u003Ccode\u003Eprefix.*\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022role\u0022\u003ERole\u003C/h3\u003E\n\u003Cp\u003EA role is a collection of permissions.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoles are tenant-scoped\u003C/li\u003E\n\u003Cli\u003ERoles can be dynamically updated\u003C/li\u003E\n\u003Cli\u003EPermissions are normalized internally\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022userrole\u0022\u003EUserRole\u003C/h3\u003E\n\u003Cp\u003EUsers are assigned roles:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMany-to-many relationship\u003C/li\u003E\n\u003Cli\u003EAssignment is timestamped\u003C/li\u003E\n\u003Cli\u003ERole resolution is runtime-based\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-resolution\u0022\u003E\uD83D\uDD04 Permission Resolution\u003C/h2\u003E\n\u003Cp\u003EPermissions are evaluated using:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EExact match\u003C/li\u003E\n\u003Cli\u003EPrefix match\u003C/li\u003E\n\u003Cli\u003EWildcard match\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ECompiledPermissionSet optimizes runtime checks.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022claims-integration\u0022\u003E\uD83E\uDDE0 Claims Integration\u003C/h2\u003E\n\u003Cp\u003EAuthorization integrates with authentication via claims:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERoles \u2192 \u003Ccode\u003EClaimTypes.Role\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EPermissions \u2192 \u003Ccode\u003Epermission\u003C/code\u003E claim\u003C/li\u003E\n\u003Cli\u003ETenant \u2192 \u003Ccode\u003Etenant\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis allows:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EToken-based authorization\u003C/li\u003E\n\u003Cli\u003EStateless permission checks (for JWT modes)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-flow\u0022\u003E\u2699\uFE0F Authorization Flow\u003C/h2\u003E\n\u003Cp\u003EAuthorization is executed through:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 AccessOrchestrator\u003C/p\u003E\n\u003Cp\u003ESteps:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EBuild AccessContext\u003C/li\u003E\n\u003Cli\u003EExecute policies\u003C/li\u003E\n\u003Cli\u003EAllow or deny operation\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies\u0022\u003E\uD83D\uDEE1 Policies\u003C/h2\u003E\n\u003Cp\u003EPolicies are the core of authorization logic.\u003C/p\u003E\n\u003Cp\u003EDefault policies include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequireAuthenticated\u003C/li\u003E\n\u003Cli\u003EDenyCrossTenant\u003C/li\u003E\n\u003Cli\u003ERequireActiveUser\u003C/li\u003E\n\u003Cli\u003ERequireSelf\u003C/li\u003E\n\u003Cli\u003ERequireSystem\u003C/li\u003E\n\u003Cli\u003EMustHavePermission\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022plugin-integration\u0022\u003E\uD83D\uDD0C Plugin Integration\u003C/h2\u003E\n\u003Cp\u003EAuthorization is a plugin domain.\u003C/p\u003E\n\u003Cp\u003EIt:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDoes NOT depend on other domains\u003C/li\u003E\n\u003Cli\u003EUses contracts only\u003C/li\u003E\n\u003Cli\u003EIntegrates via policies and claims\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83C\uDFAF Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthorization is policy-driven\u003C/li\u003E\n\u003Cli\u003ERoles are permission containers\u003C/li\u003E\n\u003Cli\u003EPermissions support wildcard \u0026amp; prefix\u003C/li\u003E\n\u003Cli\u003EPolicies enforce rules\u003C/li\u003E\n\u003Cli\u003EFully extensible and replaceable\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EAuthentication establishes an identity. Authorization determines what that identity is allowed to do.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth treats authorization as a separate responsibility, built around \u003Cstrong\u003Estructured actions, permissions, roles, and policies\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003ERather than relying exclusively on role names or scattered permission checks, UltimateAuth provides a consistent model for describing protected operations and evaluating access within their security context.\u003C/p\u003E\n\u003Cp\u003EThe Authorization Plugin Domain extends this model with permission management, role assignments, and supporting infrastructure.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-authorization\u0022\u003EUnderstanding Authorization\u003C/h2\u003E\n\u003Cp\u003EA successful authentication does not automatically grant access to protected operations.\u003C/p\u003E\n\u003Cp\u003EAn authenticated identity may be permitted to read its own profile while being prohibited from modifying another user\u0027s account. An administrator may have elevated permissions within one tenant without having authority over another tenant.\u003C/p\u003E\n\u003Cp\u003EThese decisions require more than knowing whether the caller is authenticated.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth separates authorization into several responsibilities:\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\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAction\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifies the protected operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPermission\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERepresents authority to perform an action\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERole\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EGroups permissions for assignment\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPolicy\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEvaluates the conditions required to allow access\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAccessContext\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECarries the identity, target, tenant, and operation context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAccessOrchestrator\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECoordinates policy evaluation and the access decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese concepts work together but are not interchangeable.\u003C/p\u003E\n\u003Cp\u003EA permission can indicate that an identity is authorized to perform a class of operations. A policy may still deny a particular request because its target, tenant, or security context is invalid.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthorization is therefore a decision process, not simply a permission lookup.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022structured-actions\u0022\u003EStructured Actions\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth identifies protected operations through a structured action model.\u003C/p\u003E\n\u003Cp\u003EActions use a hierarchical naming convention:\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\u003Eresource.operation.scope\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor operations involving a more specific resource:\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\u003Eresource.subresource.operation.scope\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EExamples 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\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.create.admin\nusers.delete.self\nusers.profile.update.self\ncredentials.change.self\nsessions.revokechain.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe structure makes the intended operation and its access boundary explicit.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ESegment\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\u003EResource\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eusers\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EThe primary protected domain\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESubresource\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eprofile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAn optional, more specific resource\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOperation\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eupdate\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EThe requested behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EScope\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eself\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EThe intended access boundary\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis convention supports consistent naming, grouping, permission matching, and policy evaluation.\u003C/p\u003E\n\u003Cp\u003EIt also helps developers understand an operation without needing to inspect its endpoint implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022uauthactions\u0022\u003EUAuthActions\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthActions\u003C/code\u003E is UltimateAuth\u0027s built-in action catalog.\u003C/p\u003E\n\u003Cp\u003EIt provides predefined identifiers for framework operations so that services, endpoints, and authorization logic can refer to the same action consistently.\u003C/p\u003E\n\u003Cp\u003EExamples of built-in actions 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\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\u003Eflows.logout.self\nsessions.listchains.admin\nusers.delete.self\ncredentials.revoke.admin\nauthorization.roles.assign.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUsing a shared action catalog avoids duplicating string literals throughout the framework and reduces the risk of inconsistent permission names.\u003C/p\u003E\n\u003Cp\u003EActions are identifiers, not authorization decisions.\u003C/p\u003E\n\u003Cp\u003EReferencing an action does not automatically authorize a request. The action must be evaluated against the applicable authorization rules.\u003C/p\u003E\n\u003Cp\u003EApplications can also define actions for their own protected resources using the same naming convention.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-scope\u0022\u003EUnderstanding Scope\u003C/h2\u003E\n\u003Cp\u003EThe final segment of an action describes its intended security scope.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth distinguishes three principal scopes:\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\u003EMeaning\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eself\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAn operation targeting the caller\u0027s own identity or resources\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eadmin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAn operation performed with elevated authority over another target\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eanonymous\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAn operation that can be initiated without an authenticated identity\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe scope is part of the action\u0027s identity.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self-and-admin-are-different-permissions\u0022\u003ESelf and Admin Are Different Permissions\u003C/h3\u003E\n\u003Cp\u003EConsider:\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.delete.self\nusers.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe first represents the authority to delete one\u0027s own account.\u003C/p\u003E\n\u003Cp\u003EThe second represents the authority to perform an administrative deletion.\u003C/p\u003E\n\u003Cp\u003EGranting \u003Ccode\u003Eusers.delete.self\u003C/code\u003E must not implicitly grant \u003Ccode\u003Eusers.delete.admin\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ELikewise, an authenticated user must not gain administrative access simply by supplying another user\u0027s identifier to a self-service operation.\u003C/p\u003E\n\u003Cp\u003EThe application must establish the actual relationship between the actor and the target.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe requested scope is not proof that the caller satisfies that scope.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA self operation requires a valid self relationship. An administrative operation requires the corresponding authority and must satisfy the applicable target and tenant policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022anonymous-does-not-mean-unrestricted\u0022\u003EAnonymous Does Not Mean Unrestricted\u003C/h3\u003E\n\u003Cp\u003EAn anonymous action is designed to be initiated without an authenticated session.\u003C/p\u003E\n\u003Cp\u003EExamples include certain login and credential recovery operations.\u003C/p\u003E\n\u003Cp\u003EHowever, anonymous availability does not remove the need for validation, proof requirements, abuse protection, or operation-specific security policies.\u003C/p\u003E\n\u003Cp\u003EAn anonymous credential reset request, for example, must not be allowed to replace a password without completing the required recovery proof.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permissions\u0022\u003EPermissions\u003C/h2\u003E\n\u003Cp\u003EA permission represents authority associated with a protected action.\u003C/p\u003E\n\u003Cp\u003EIn UltimateAuth, permission identifiers follow the same structured action convention.\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.profile.update.self\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA user granted this permission may be eligible to update their own profile, provided the other required policies succeed.\u003C/p\u003E\n\u003Cp\u003EPermissions do not replace those policies.\u003C/p\u003E\n\u003Cp\u003EA permission match answers whether the relevant authority is present. It does not independently establish that the target belongs to the caller, that the tenant boundary is valid, or that the account satisfies the required security conditions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hierarchical-permissions\u0022\u003EHierarchical Permissions\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth supports hierarchical permission matching.\u003C/p\u003E\n\u003Cp\u003EExamples:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPermission\u003C/th\u003E\n\u003Cth\u003EIntended coverage\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eusers.profile.update.self\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOne specific action\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eusers.profile.*\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EActions under the profile group\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eusers.*\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EActions under the users resource\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003E*\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAll actions covered by the permission matcher\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows permissions to be granted at different levels of granularity.\u003C/p\u003E\n\u003Cp\u003EAn application can assign a narrowly scoped permission to ordinary users while granting broader permission groups to trusted administrative roles.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-normalization\u0022\u003EPermission Normalization\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth provides permission normalization to reduce redundant representations.\u003C/p\u003E\n\u003Cp\u003EWhen a permission set fully covers a known group of actions, the corresponding permissions can be represented by a wildcard for that group.\u003C/p\u003E\n\u003Cp\u003EFor example, complete coverage of a defined group may be normalized 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\u003Eusers.profile.*\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ENormalization is intended to preserve the effective permission coverage, not expand it.\u003C/p\u003E\n\u003Cp\u003EA wildcard must not be introduced merely because several permissions share a prefix. The relevant group must actually be fully covered according to the known permission catalog.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022compiled-permission-sets\u0022\u003ECompiled Permission Sets\u003C/h3\u003E\n\u003Cp\u003EPermission resolution may involve exact matches, hierarchical groups, and wildcards.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003ECompiledPermissionSet\u003C/code\u003E provides an optimized representation for evaluating these permissions at runtime.\u003C/p\u003E\n\u003Cp\u003EThis allows the authorization system to avoid repeatedly performing the same permission-processing work during access checks.\u003C/p\u003E\n\u003Cp\u003EThe optimization does not change the authorization model: permission matching remains one part of the overall access decision.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022roles-and-role-assignments\u0022\u003ERoles and Role Assignments\u003C/h2\u003E\n\u003Cp\u003ERoles provide a convenient way to manage collections of permissions.\u003C/p\u003E\n\u003Cp\u003EInstead of assigning every permission directly to every user, an application can define roles corresponding to its operational responsibilities.\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\u003ESupportAgent\nAccountManager\nTenantAdministrator\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach role can contain the permissions required for that responsibility.\u003C/p\u003E\n\u003Cp\u003EA role is not inherently a security bypass or a substitute for policy evaluation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022role-management\u0022\u003ERole Management\u003C/h3\u003E\n\u003Cp\u003EThe reference authorization model supports role management and user-role assignments.\u003C/p\u003E\n\u003Cp\u003EIts responsibilities include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDefining roles and their permissions.\u003C/li\u003E\n\u003Cli\u003EAssigning roles to users.\u003C/li\u003E\n\u003Cli\u003EManaging role membership.\u003C/li\u003E\n\u003Cli\u003EResolving effective permissions.\u003C/li\u003E\n\u003Cli\u003EMaintaining the relevant tenant boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ERoles and assignments are designed to support tenant-aware authorization.\u003C/p\u003E\n\u003Cp\u003EAn administrative role in one tenant must not automatically imply authority in another tenant.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022roles-versus-permissions\u0022\u003ERoles Versus Permissions\u003C/h3\u003E\n\u003Cp\u003ERoles describe how authority is organized and assigned.\u003C/p\u003E\n\u003Cp\u003EPermissions describe which operations that authority covers.\u003C/p\u003E\n\u003Cp\u003EPolicies determine whether the current operation is permitted under the relevant conditions.\u003C/p\u003E\n\u003Cp\u003EKeeping these concepts separate allows applications to change role structures without redesigning their protected operations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-policies-1\u0022\u003EAuthorization Policies\u003C/h2\u003E\n\u003Cp\u003EPolicies are the enforcement layer of UltimateAuth\u0027s authorization model.\u003C/p\u003E\n\u003Cp\u003EA policy evaluates a security requirement against the current access context.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth includes policy concepts such as:\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\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequireAuthenticated\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequires an authenticated actor\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequireActiveUser\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequires the relevant active-user condition\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequireSelf\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEnforces the self-access relationship\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequireSystem\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERestricts operations to an appropriate system context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDenyCrossTenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEnforces tenant isolation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMustHavePermission\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequires the applicable permission\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese policies address different questions.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EMustHavePermission\u003C/code\u003E may confirm that the actor has \u003Ccode\u003Eusers.delete.admin\u003C/code\u003E, while \u003Ccode\u003EDenyCrossTenant\u003C/code\u003E may independently prevent the operation from targeting a user in an unauthorized tenant.\u003C/p\u003E\n\u003Cp\u003EA successful permission match must not bypass another mandatory security policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies-are-composable\u0022\u003EPolicies Are Composable\u003C/h3\u003E\n\u003Cp\u003EA protected operation may require multiple conditions.\u003C/p\u003E\n\u003Cp\u003EAn administrative account operation might require:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EAn authenticated actor.\u003C/li\u003E\n\u003Cli\u003EA valid and active identity.\u003C/li\u003E\n\u003Cli\u003EPermission for the requested administrative action.\u003C/li\u003E\n\u003Cli\u003EA valid target identity.\u003C/li\u003E\n\u003Cli\u003ECompliance with tenant isolation rules.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EAuthorization succeeds only when the required conditions are satisfied.\u003C/p\u003E\n\u003Cp\u003EThis approach allows UltimateAuth to separate reusable security requirements from the business operation being protected.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accesscontext-and-accessorchestrator\u0022\u003EAccessContext and AccessOrchestrator\u003C/h2\u003E\n\u003Cp\u003EAuthorization needs a trustworthy representation of the operation being evaluated.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E carries the relevant information for the decision, including the actor, target, tenant context, and requested operation.\u003C/p\u003E\n\u003Cp\u003EThe \u003Ccode\u003EAccessOrchestrator\u003C/code\u003E coordinates the applicable policy evaluations and produces the authorization decision.\u003C/p\u003E\n\u003Cp\u003EConceptually, the process 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\u003EAuthenticated identity\n |\n v\nRequested action\n |\n v\nBuild AccessContext\n |\n v\nEvaluate required policies\n |\n \u002B---- Denied ---\u0026gt; Reject operation\n |\n v\n Allowed\n |\n v\nExecute protected operation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe context must be constructed from trusted authentication state and validated operation data.\u003C/p\u003E\n\u003Cp\u003EA caller-provided user ID, tenant ID, or scope string must not automatically be accepted as authoritative.\u003C/p\u003E\n\u003Cp\u003EThis is particularly important for administrative operations and multi-tenant applications.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-and-authentication-are-separate\u0022\u003EAuthorization and Authentication Are Separate\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlowContext\u003C/code\u003E and \u003Ccode\u003EAccessContext\u003C/code\u003E serve different purposes.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlowContext\u003C/code\u003E belongs to authentication flow execution.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E belongs to authorization evaluation.\u003C/p\u003E\n\u003Cp\u003EA successfully completed authentication flow may establish the identity required for an access context, but it does not replace authorization evaluation.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/flow-based-authentication\u0022\u003EFlow-Based Authentication\u003C/a\u003E and \u003Ca href=\u0022/docs/fundamentals/request-lifecycle\u0022\u003ERequest Lifecycle\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022claims-and-permission-resolution\u0022\u003EClaims and Permission Resolution\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth can integrate authorization information with authentication claims.\u003C/p\u003E\n\u003Cp\u003ERelevant claims may 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\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\u003EClaimTypes.Role\npermission\ntenant\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EClaims allow authorization information to participate in token-based and ASP.NET Core authorization scenarios.\u003C/p\u003E\n\u003Cp\u003EHowever, claims are representations of authorization information, not necessarily its authoritative source.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-based-authorization\u0022\u003ESession-Based Authorization\u003C/h3\u003E\n\u003Cp\u003EIn session-authoritative authentication modes, authorization can rely on server-side state and the relevant runtime resolution mechanisms.\u003C/p\u003E\n\u003Cp\u003EChanges to roles, permissions, or account security state can be reflected according to the session validation and authorization configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022jwt-based-authorization\u0022\u003EJWT-Based Authorization\u003C/h3\u003E\n\u003Cp\u003EIn JWT-based modes, roles or permissions may be included in issued tokens.\u003C/p\u003E\n\u003Cp\u003EA protected resource can evaluate those claims without contacting the authentication server on every request.\u003C/p\u003E\n\u003Cp\u003EThis introduces an important tradeoff.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA permission removed after token issuance may remain represented in an already-issued JWT until that token expires.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EApplications requiring immediate permission revocation must account for this behavior through their token lifetimes, refresh strategy, or additional server-side validation.\u003C/p\u003E\n\u003Cp\u003EThe authorization model remains consistent, but the freshness of authorization information depends on how it is resolved.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022protecting-application-endpoints\u0022\u003EProtecting Application Endpoints\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s authorization architecture is not limited to its built-in identity management endpoints.\u003C/p\u003E\n\u003Cp\u003EApplications can protect their own business resources using ASP.NET Core authorization and, where integrated, UltimateAuth\u0027s action and policy model.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022requiring-authentication\u0022\u003ERequiring Authentication\u003C/h3\u003E\n\u003Cp\u003EFor a controller-based resource API, standard ASP.NET Core authorization attributes can require an authenticated caller.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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[ApiController]\n[Route(\u0026quot;api/products\u0026quot;)]\n[Authorize]\npublic sealed class ProductsController : ControllerBase\n{\n [HttpGet]\n public IActionResult GetProducts()\n {\n return Ok();\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen the application is configured with the appropriate UltimateAuth authentication integration, \u003Ccode\u003E[Authorize]\u003C/code\u003E requires a valid authenticated identity.\u003C/p\u003E\n\u003Cp\u003EThis is the simplest protection boundary.\u003C/p\u003E\n\u003Cp\u003EIt does not, by itself, require a particular UltimateAuth action permission.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022requiring-a-named-policy\u0022\u003ERequiring a Named Policy\u003C/h3\u003E\n\u003Cp\u003EASP.NET Core also supports policy-based endpoint authorization.\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\u003E[Authorize(Policy = \u0026quot;Products.Read\u0026quot;)]\n[HttpGet]\npublic IActionResult GetProducts()\n{\n return Ok();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe named policy must be registered in the application\u0027s authorization configuration.\u003C/p\u003E\n\u003Cp\u003EA standard ASP.NET Core policy can require a particular permission claim:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddAuthorization(options =\u0026gt;\n{\n options.AddPolicy(\u0026quot;Products.Read\u0026quot;, policy =\u0026gt;\n {\n policy.RequireAuthenticatedUser();\n policy.RequireClaim(\n \u0026quot;permission\u0026quot;,\n \u0026quot;products.read.self\u0026quot;);\n });\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example uses ASP.NET Core\u0027s built-in claim authorization.\u003C/p\u003E\n\u003Cp\u003EIt checks for the specified claim in the authenticated principal. It does not automatically execute UltimateAuth\u0027s \u003Ccode\u003EAccessOrchestrator\u003C/code\u003E or its domain policies.\u003C/p\u003E\n\u003Cp\u003EIt is suitable when the application\u0027s security model permits authorization based on the available claims.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-ultimateauth-actions-for-application-resources\u0022\u003EUsing UltimateAuth Actions for Application Resources\u003C/h3\u003E\n\u003Cp\u003EApplications can define their own action identifiers using the same convention as UltimateAuth\u0027s built-in catalog.\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\u003Epublic static class ProductActions\n{\n public const string ReadSelf = \u0026quot;products.read.self\u0026quot;;\n public const string CreateAdmin = \u0026quot;products.create.admin\u0026quot;;\n public const string DeleteAdmin = \u0026quot;products.delete.admin\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese constants establish consistent names for application permissions.\u003C/p\u003E\n\u003Cp\u003EThey can be used by permission management, policy registration, and endpoint authorization integrations.\u003C/p\u003E\n\u003Cp\u003EDefining the constants alone does not register them with the authorization system or enforce them at an endpoint.\u003C/p\u003E\n\u003Cp\u003EFor operations requiring actor-target validation, tenant isolation, or other UltimateAuth domain policies, the application must use the appropriate UltimateAuth authorization integration rather than relying solely on a permission claim.\u003C/p\u003E\n\u003Cp\u003EThe exact registration and invocation APIs depend on the configured authorization packages and hosting model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-in-different-hosting-models\u0022\u003EAuthorization in Different Hosting Models\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports different hosting architectures, and the enforcement boundary matters.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-apis\u0022\u003EResource APIs\u003C/h3\u003E\n\u003Cp\u003EFor HTTP resource APIs, authentication and authorization are normally enforced through the ASP.NET Core request pipeline.\u003C/p\u003E\n\u003Cp\u003EControllers and endpoints can use standard authorization attributes and policies.\u003C/p\u003E\n\u003Cp\u003EThe UltimateAuth resource API integration establishes the authentication context required for protected requests.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022integrated-server-applications\u0022\u003EIntegrated Server Applications\u003C/h3\u003E\n\u003Cp\u003EIn an integrated server application, business operations may execute directly through application services without making an HTTP request.\u003C/p\u003E\n\u003Cp\u003ESuch calls do not automatically pass through ASP.NET Core endpoint authorization attributes.\u003C/p\u003E\n\u003Cp\u003EIf an operation bypasses the HTTP endpoint, authorization must be enforced at an appropriate service or application boundary.\u003C/p\u003E\n\u003Cp\u003EThis is particularly relevant for Blazor Server applications that invoke services directly.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EProtecting an HTTP controller does not automatically protect every direct call to the underlying service.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAuthorization must remain effective at the actual execution boundary.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extending-authorization\u0022\u003EExtending Authorization\u003C/h2\u003E\n\u003Cp\u003EApplications may need policies beyond the framework\u0027s built-in requirements.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EResource ownership rules.\u003C/li\u003E\n\u003Cli\u003EOrganization-specific access restrictions.\u003C/li\u003E\n\u003Cli\u003EContext-dependent administrative authority.\u003C/li\u003E\n\u003Cli\u003EBusiness workflow conditions.\u003C/li\u003E\n\u003Cli\u003EAdditional tenant or resource boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth\u0027s policy-driven architecture is intended to support such requirements without embedding them directly into the authentication engine.\u003C/p\u003E\n\u003Cp\u003EThe reference Authorization domain provides a foundation for roles, permissions, and their management.\u003C/p\u003E\n\u003Cp\u003EApplications can build on that foundation while keeping application-specific authorization rules close to the resources and operations they protect.\u003C/p\u003E\n\u003Cp\u003EDetailed customization, custom policy implementation, and advanced permission registration are covered separately.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s authorization model is built around a distinction:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EActions describe operations. Permissions represent authority. Policies enforce security requirements.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ERoles make permissions manageable, while \u003Ccode\u003EAccessContext\u003C/code\u003E and \u003Ccode\u003EAccessOrchestrator\u003C/code\u003E provide the context and coordination needed for authorization decisions.\u003C/p\u003E\n\u003Cp\u003EThis allows applications to use consistent authorization semantics across authentication modes, hosting models, and business domains without reducing security to a collection of role checks.\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 Identity ownership, account lifecycle, and user management.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Authentication evidence and credential security.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains\u0022\u003EPlugin Domains\u003C/a\u003E \u2014 The broader Plugin Domain architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/request-lifecycle\u0022\u003ERequest Lifecycle\u003C/a\u003E \u2014 How authentication and authorization participate in protected requests.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { - "Id": "core-concepts", - "Text": "\uD83E\uDDE9 Core Concepts", + "Id": "understanding-authorization", + "Text": "Understanding Authorization", "Level": 0 }, + { + "Id": "structured-actions", + "Text": "Structured Actions", + "Level": 0 + }, + { + "Id": "uauthactions", + "Text": "UAuthActions", + "Level": 1 + }, + { + "Id": "understanding-scope", + "Text": "Understanding Scope", + "Level": 0 + }, + { + "Id": "self-and-admin-are-different-permissions", + "Text": "Self and Admin Are Different Permissions", + "Level": 1 + }, + { + "Id": "anonymous-does-not-mean-unrestricted", + "Text": "Anonymous Does Not Mean Unrestricted", + "Level": 1 + }, { "Id": "permissions", - "Text": "\uD83D\uDD11 Permissions", + "Text": "Permissions", + "Level": 0 + }, + { + "Id": "hierarchical-permissions", + "Text": "Hierarchical Permissions", "Level": 1 }, { - "Id": "built-in-action-catalog", - "Text": "\u2699\uFE0F Built-in Action Catalog", + "Id": "permission-normalization", + "Text": "Permission Normalization", "Level": 1 }, { - "Id": "scope-semantics", - "Text": "\uD83E\uDDE0 Scope Semantics", + "Id": "compiled-permission-sets", + "Text": "Compiled Permission Sets", "Level": 1 }, { - "Id": "wildcards-grouping", - "Text": "\uD83C\uDF32 Wildcards \u0026 Grouping", + "Id": "roles-and-role-assignments", + "Text": "Roles and Role Assignments", + "Level": 0 + }, + { + "Id": "role-management", + "Text": "Role Management", "Level": 1 }, { - "Id": "normalization", - "Text": "\u26A1 Normalization", + "Id": "roles-versus-permissions", + "Text": "Roles Versus Permissions", "Level": 1 }, { - "Id": "role", - "Text": "Role", + "Id": "authorization-policies-1", + "Text": "Authorization Policies", + "Level": 0 + }, + { + "Id": "policies-are-composable", + "Text": "Policies Are Composable", "Level": 1 }, { - "Id": "userrole", - "Text": "UserRole", + "Id": "accesscontext-and-accessorchestrator", + "Text": "AccessContext and AccessOrchestrator", + "Level": 0 + }, + { + "Id": "authorization-and-authentication-are-separate", + "Text": "Authorization and Authentication Are Separate", "Level": 1 }, { - "Id": "permission-resolution", - "Text": "\uD83D\uDD04 Permission Resolution", + "Id": "claims-and-permission-resolution", + "Text": "Claims and Permission Resolution", "Level": 0 }, { - "Id": "claims-integration", - "Text": "\uD83E\uDDE0 Claims Integration", + "Id": "session-based-authorization", + "Text": "Session-Based Authorization", + "Level": 1 + }, + { + "Id": "jwt-based-authorization", + "Text": "JWT-Based Authorization", + "Level": 1 + }, + { + "Id": "protecting-application-endpoints", + "Text": "Protecting Application Endpoints", "Level": 0 }, { - "Id": "authorization-flow", - "Text": "\u2699\uFE0F Authorization Flow", + "Id": "requiring-authentication", + "Text": "Requiring Authentication", + "Level": 1 + }, + { + "Id": "requiring-a-named-policy", + "Text": "Requiring a Named Policy", + "Level": 1 + }, + { + "Id": "using-ultimateauth-actions-for-application-resources", + "Text": "Using UltimateAuth Actions for Application Resources", + "Level": 1 + }, + { + "Id": "authorization-in-different-hosting-models", + "Text": "Authorization in Different Hosting Models", "Level": 0 }, { - "Id": "policies", - "Text": "\uD83D\uDEE1 Policies", + "Id": "resource-apis", + "Text": "Resource APIs", + "Level": 1 + }, + { + "Id": "integrated-server-applications", + "Text": "Integrated Server Applications", + "Level": 1 + }, + { + "Id": "extending-authorization", + "Text": "Extending Authorization", "Level": 0 }, { - "Id": "plugin-integration", - "Text": "\uD83D\uDD0C Plugin Integration", + "Id": "architectural-perspective", + "Text": "Architectural Perspective", "Level": 0 }, { - "Id": "key-takeaways", - "Text": "\uD83C\uDFAF Key Takeaways", + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credential-domain.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credential-domain.json index 7852b23f..1aa72c47 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credential-domain.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credential-domain.json @@ -1,56 +1,111 @@ { "Slug": "plugin-domains/credential-domain", "Title": "Credentials", - "Html": "\n\u003Cp\u003ECredentials in UltimateAuth define how a user proves their identity.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-concept\u0022\u003E\uD83E\uDDE0 Core Concept\u003C/h2\u003E\n\u003Cp\u003EAuthentication is not tied to users directly.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 It is performed through credentials.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-a-credential\u0022\u003E\uD83D\uDD11 What is a Credential?\u003C/h2\u003E\n\u003Cp\u003EA credential represents a secret or factor used for authentication.\u003C/p\u003E\n\u003Cp\u003EExamples:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPassword\u003C/li\u003E\n\u003Cli\u003EOTP (future)\u003C/li\u003E\n\u003Cli\u003EExternal providers (future)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022password-credential\u0022\u003E\uD83D\uDD12 Password Credential\u003C/h2\u003E\n\u003Cp\u003EThe default credential type is password.\u003C/p\u003E\n\u003Cp\u003EA password credential contains:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EHashed secret (never raw password)\u003C/li\u003E\n\u003Cli\u003ESecurity state (active, revoked, expired)\u003C/li\u003E\n\u003Cli\u003EMetadata (last used, source, etc.)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Credentials are always stored securely and validated through hashing.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-validation\u0022\u003E\u2699\uFE0F Credential Validation\u003C/h2\u003E\n\u003Cp\u003ECredential validation is handled by a validator:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EVerifies secret using hashing\u003C/li\u003E\n\u003Cli\u003EChecks credential usability (revoked, expired, etc.)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Validation is isolated from business logic.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022integration-with-users\u0022\u003E\uD83D\uDD17 Integration with Users\u003C/h2\u003E\n\u003Cp\u003ECredentials are NOT created directly inside user logic.\u003C/p\u003E\n\u003Cp\u003EInstead:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 They are integrated via lifecycle hooks\u003C/p\u003E\n\u003Cp\u003EExample:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhen a user is created \u2192 password credential may be created\u003C/li\u003E\n\u003Cli\u003EWhen a user is deleted \u2192 credentials are removed\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This keeps domains decoupled.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-lifecycle\u0022\u003E\uD83D\uDD04 Credential Lifecycle\u003C/h2\u003E\n\u003Cp\u003ECredentials support:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECreation\u003C/li\u003E\n\u003Cli\u003ESecret change\u003C/li\u003E\n\u003Cli\u003ERevocation\u003C/li\u003E\n\u003Cli\u003EExpiration\u003C/li\u003E\n\u003Cli\u003EDeletion\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-behavior\u0022\u003E\uD83D\uDD01 Security Behavior\u003C/h2\u003E\n\u003Cp\u003ECredential changes trigger security actions:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EChanging password revokes sessions\u003C/li\u003E\n\u003Cli\u003EReset flows require verification tokens\u003C/li\u003E\n\u003Cli\u003EInvalid attempts are tracked\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Credentials are tightly coupled with security.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022reset-flow\u0022\u003E\uD83D\uDD11 Reset Flow\u003C/h2\u003E\n\u003Cp\u003EPassword reset is a multi-step process:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EBegin reset (generate token or code)\u003C/li\u003E\n\u003Cli\u003EValidate token\u003C/li\u003E\n\u003Cli\u003EApply new secret\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003E\uD83D\uDC49 Reset flow is protected against enumeration and abuse.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EUsers define identity.\u003C/p\u003E\n\u003Cp\u003ECredentials define authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredentials handle authentication secrets\u003C/li\u003E\n\u003Cli\u003EPassword is default but extensible\u003C/li\u003E\n\u003Cli\u003EIntegrated via lifecycle hooks\u003C/li\u003E\n\u003Cli\u003EStrong security guarantees\u003C/li\u003E\n\u003Cli\u003EFully extensible for new credential types\u003C/li\u003E\n\u003C/ul\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\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/authentication-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", "Headings": [ { - "Id": "core-concept", - "Text": "\uD83E\uDDE0 Core Concept", + "Id": "understanding-credentials", + "Text": "Understanding Credentials", "Level": 0 }, { - "Id": "what-is-a-credential", - "Text": "\uD83D\uDD11 What is a Credential?", + "Id": "credentials-and-user-identity", + "Text": "Credentials and User Identity", "Level": 0 }, { - "Id": "password-credential", - "Text": "\uD83D\uDD12 Password Credential", + "Id": "responsibilities-across-the-framework", + "Text": "Responsibilities Across the Framework", "Level": 0 }, { - "Id": "credential-validation", - "Text": "\u2699\uFE0F Credential Validation", + "Id": "password-credentials", + "Text": "Password Credentials", "Level": 0 }, { - "Id": "integration-with-users", - "Text": "\uD83D\uDD17 Integration with Users", + "Id": "secret-protection", + "Text": "Secret Protection", + "Level": 1 + }, + { + "Id": "credential-security-state", + "Text": "Credential Security State", + "Level": 1 + }, + { + "Id": "credential-validation", + "Text": "Credential Validation", "Level": 0 }, { "Id": "credential-lifecycle", - "Text": "\uD83D\uDD04 Credential Lifecycle", + "Text": "Credential Lifecycle", + "Level": 0 + }, + { + "Id": "credential-ownership-and-operation-scope", + "Text": "Credential Ownership and Operation Scope", + "Level": 0 + }, + { + "Id": "self-service-operations", + "Text": "Self-Service Operations", + "Level": 1 + }, + { + "Id": "administrative-operations", + "Text": "Administrative Operations", + "Level": 1 + }, + { + "Id": "anonymous-recovery", + "Text": "Anonymous Recovery", + "Level": 1 + }, + { + "Id": "credential-recovery", + "Text": "Credential Recovery", + "Level": 0 + }, + { + "Id": "beginning-recovery", + "Text": "Beginning Recovery", + "Level": 1 + }, + { + "Id": "completing-recovery", + "Text": "Completing Recovery", + "Level": 1 + }, + { + "Id": "enumeration-resistance", + "Text": "Enumeration Resistance", + "Level": 1 + }, + { + "Id": "credentials-and-authentication-sessions", + "Text": "Credentials and Authentication Sessions", "Level": 0 }, { - "Id": "security-behavior", - "Text": "\uD83D\uDD01 Security Behavior", + "Id": "integration-with-the-users-domain", + "Text": "Integration with the Users Domain", "Level": 0 }, { - "Id": "reset-flow", - "Text": "\uD83D\uDD11 Reset Flow", + "Id": "extending-credential-capabilities", + "Text": "Extending Credential Capabilities", "Level": 0 }, { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", + "Id": "architectural-perspective", + "Text": "Architectural Perspective", "Level": 0 }, { - "Id": "summary", - "Text": "\uD83C\uDFAF Summary", + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/index.json index 4ed257bd..89c89e36 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/index.json @@ -1,76 +1,41 @@ { "Slug": "plugin-domains/index", "Title": "Plugin Domains", - "Html": "\n\u003Cp\u003EAuthentication alone is not enough.\u003C/p\u003E\n\u003Cp\u003EReal-world systems also require:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUser management\u003C/li\u003E\n\u003Cli\u003ECredential handling\u003C/li\u003E\n\u003Cli\u003EAuthorization rules\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 UltimateAuth provides these as \u003Cstrong\u003Eplugin domains\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-a-plugin-domain\u0022\u003E\uD83E\uDDE0 What Is a Plugin Domain?\u003C/h2\u003E\n\u003Cp\u003EA plugin domain is a \u003Cstrong\u003Emodular business layer\u003C/strong\u003E built on top of UltimateAuth.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Core handles authentication\u003Cbr /\u003E\n\uD83D\uDC49 Plugin domains handle identity and access logic\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architecture\u0022\u003E\uD83C\uDFD7 Architecture\u003C/h2\u003E\n\u003Cp\u003EEach plugin domain is composed of multiple layers:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022bridge-package\u0022\u003E\uD83D\uDD39 Bridge Package\u003C/h3\u003E\n\u003Cp\u003EDefines the server needed bridge interfaces:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This package provides required minimal info for server package.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022contracts-package\u0022\u003E\uD83D\uDD39 Contracts Package\u003C/h3\u003E\n\u003Cp\u003EShared models between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EServer\u003C/li\u003E\n\u003Cli\u003EClient\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Includes DTOs, requests, responses\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reference-implementation\u0022\u003E\uD83D\uDD39 Reference Implementation\u003C/h3\u003E\n\u003Cp\u003EProvides default behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EApplication services\u003C/li\u003E\n\u003Cli\u003EStore interfaces\u003C/li\u003E\n\u003Cli\u003EDefault implementations\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Acts as a production-ready baseline\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022persistence-layer\u0022\u003E\uD83D\uDD39 Persistence Layer\u003C/h3\u003E\n\u003Cp\u003EProvides storage implementations:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInMemory\u003C/li\u003E\n\u003Cli\u003EEntity Framework Core\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Additional providers (Redis, etc.) can be added\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extensibility-model\u0022\u003E\uD83D\uDD04 Extensibility Model\u003C/h2\u003E\n\u003Cp\u003EPlugin domains are designed to be:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EReplaceable\u003C/li\u003E\n\u003Cli\u003EExtendable\u003C/li\u003E\n\u003Cli\u003EComposable\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You can implement your own persistence\u003Cbr /\u003E\n\uD83D\uDC49 You can extend behavior\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-approach\u0022\u003E\u26A0\uFE0F Recommended Approach\u003C/h2\u003E\n\u003Cp\u003EIn most cases:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 You should NOT replace a plugin domain entirely\u003C/p\u003E\n\u003Cp\u003EInstead:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUse provided implementations\u003C/li\u003E\n\u003Cli\u003EExtend via interfaces\u003C/li\u003E\n\u003Cli\u003ECustomize behavior where needed\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This ensures compatibility with the framework\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022domain-isolation\u0022\u003E\uD83D\uDD0C Domain Isolation\u003C/h2\u003E\n\u003Cp\u003EPlugin domains are designed to be \u003Cstrong\u003Efully isolated\u003C/strong\u003E from each other.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 A plugin domain does NOT reference another plugin domain\u003Cbr /\u003E\n\uD83D\uDC49 There are no direct dependencies between domains\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why\u0022\u003E\uD83E\uDDE0 Why?\u003C/h3\u003E\n\u003Cp\u003EThis ensures:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELoose coupling\u003C/li\u003E\n\u003Cli\u003EIndependent evolution\u003C/li\u003E\n\u003Cli\u003EReplaceability\u003C/li\u003E\n\u003Cli\u003EClear boundaries\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022communication-via-hooks\u0022\u003E\uD83D\uDD04 Communication via Hooks\u003C/h2\u003E\n\u003Cp\u003EPlugin domains communicate \u003Cstrong\u003Eonly through integration hooks\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUser domain triggers \u2192 \u003Ccode\u003EOnUserCreated\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003ECredential domain listens \u2192 creates password credential\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This is implemented via abstractions such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EIUserLifecycleIntegration\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003Edomain events / integration points\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Core = authentication engine\u003Cbr /\u003E\n\uD83D\uDC49 Plugin domains = business logic\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-matters\u0022\u003E\uD83C\uDFAF Why This Matters\u003C/h2\u003E\n\u003Cp\u003EThis architecture allows UltimateAuth to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EStay modular\u003C/li\u003E\n\u003Cli\u003ESupport multiple domains\u003C/li\u003E\n\u003Cli\u003EEnable enterprise customization\u003C/li\u003E\n\u003Cli\u003EAvoid monolithic identity systems\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You don\u2019t build everything from scratch\u003Cbr /\u003E\n\uD83D\uDC49 You assemble what you need\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EManage users \u2192 Users\u003C/li\u003E\n\u003Cli\u003EHandle credentials \u2192 Credentials\u003C/li\u003E\n\u003Cli\u003EControl access \u2192 Authorization\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth separates \u003Cstrong\u003Eauthentication infrastructure\u003C/strong\u003E from the domain capabilities commonly required by identity and access management systems.\u003C/p\u003E\n\u003Cp\u003EThe authentication engine is responsible for establishing and validating authentication state. Managing users, credentials, and authorization policies introduces additional responsibilities that do not inherently belong to that engine.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth organizes these capabilities into \u003Cstrong\u003EPlugin Domains\u003C/strong\u003E: modular, independently structured domains that extend the framework beyond authentication without making their business logic part of the authentication core.\u003C/p\u003E\n\u003Cp\u003EPlugin Domains are an architectural concept specific to UltimateAuth. They are not simply runtime plugins or optional features within a monolithic identity system.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-core-and-plugin-domains\u0022\u003EAuthentication Core and Plugin Domains\u003C/h2\u003E\n\u003Cp\u003EThe distinction is based on responsibility rather than importance.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication Core\u003C/strong\u003E provides the infrastructure for authentication flows, authentication modes, sessions, tokens, and runtime security boundaries.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EPlugin Domains\u003C/strong\u003E provide the identity and access management capabilities built around that infrastructure.\u003C/p\u003E\n\u003Cp\u003EThe framework currently organizes these responsibilities into three primary domains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EDomain\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\u003EUsers\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUser lifecycle, identifiers, profiles, and account management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential registration, management, verification, and recovery\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAuthorization\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EPermissions, roles, policies, and access management\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese domains are not universally required for every authentication scenario. An application may already have its own identity infrastructure or may need only a subset of UltimateAuth\u0027s domain capabilities.\u003C/p\u003E\n\u003Cp\u003EHowever, together they provide the foundation for using UltimateAuth as a broader identity and access management platform rather than only an authentication engine.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-boundaries\u0022\u003EArchitectural Boundaries\u003C/h2\u003E\n\u003Cp\u003EEach Plugin Domain owns a distinct area of business logic.\u003C/p\u003E\n\u003Cp\u003EA domain should not need to understand the internal implementation of another domain to perform its responsibilities.\u003C/p\u003E\n\u003Cp\u003EThis separation establishes several architectural properties:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EIndependent domain boundaries:\u003C/strong\u003E Each domain defines and maintains its own contracts and behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EExplicit integration:\u003C/strong\u003E Cross-domain interactions occur through defined abstractions and integration points.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EReplaceable infrastructure:\u003C/strong\u003E Storage implementations and supporting infrastructure can be substituted without changing the authentication engine.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EControlled extensibility:\u003C/strong\u003E Applications can customize domain behavior while preserving established framework contracts.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIsolation does not mean that domains never interact. Identity management operations frequently involve multiple domains, and those interactions must remain coordinated.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth addresses this through explicit integration contracts rather than direct dependencies between domain implementations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022domain-package-structure\u0022\u003EDomain Package Structure\u003C/h2\u003E\n\u003Cp\u003EPlugin Domains follow a layered package structure that separates integration contracts, public models, domain behavior, and persistence.\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\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EBridge\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EMinimal abstractions required for integration with UltimateAuth Server\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EContracts\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EShared requests, responses, and data transfer models\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EReference Implementation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EDefault application services, domain behavior, and storage abstractions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPersistence\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EConcrete storage implementations, including InMemory and Entity Framework Core\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis structure keeps the authentication server independent of the full internal implementation of each domain.\u003C/p\u003E\n\u003Cp\u003EIt also allows applications to use the provided implementations while retaining control over infrastructure and customization.\u003C/p\u003E\n\u003Cp\u003EThe reference implementations are intended to be practical foundations for real applications, not merely illustrative examples.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022cross-domain-integration\u0022\u003ECross-Domain Integration\u003C/h2\u003E\n\u003Cp\u003ESome identity operations naturally cross domain boundaries.\u003C/p\u003E\n\u003Cp\u003EFor example, creating a user may need to initiate credential-related behavior. That coordination should not require the Users domain to reference the Credentials domain directly.\u003C/p\u003E\n\u003Cp\u003EInstead, UltimateAuth uses integration abstractions and lifecycle hooks to connect independently implemented responsibilities.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EIUserLifecycleIntegration\u003C/code\u003E is an example of such an integration point.\u003C/p\u003E\n\u003Cp\u003EThis approach allows domain-specific behavior to remain within its owning domain while supporting coordinated application workflows.\u003C/p\u003E\n\u003Cp\u003EIntegration hooks are explicit extension boundaries, not a mechanism for bypassing domain ownership or security rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extending-plugin-domains\u0022\u003EExtending Plugin Domains\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides reference implementations so applications do not need to rebuild common identity management capabilities from scratch.\u003C/p\u003E\n\u003Cp\u003EFor most applications, the recommended approach is to \u003Cstrong\u003Estart with the provided domain implementations and customize only what is necessary\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EDepending on the domain and its supported extension contracts, customization may include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EReplacing persistence implementations.\u003C/li\u003E\n\u003Cli\u003EProviding application-specific services.\u003C/li\u003E\n\u003Cli\u003EExtending supported lifecycle integrations.\u003C/li\u003E\n\u003Cli\u003EIntegrating existing identity or access management infrastructure.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EReplacing an entire domain is possible as an architectural objective, but doing so requires preserving the contracts expected by the rest of the system.\u003C/p\u003E\n\u003Cp\u003EModularity provides flexibility; it does not eliminate the need for compatible behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-the-right-capabilities\u0022\u003EChoosing the Right Capabilities\u003C/h2\u003E\n\u003Cp\u003EPlugin Domains are designed to be composed according to application requirements.\u003C/p\u003E\n\u003Cp\u003EAn application focused on authentication infrastructure may rely primarily on the Core and Server capabilities.\u003C/p\u003E\n\u003Cp\u003EAn application requiring complete identity management can incorporate Users, Credentials, and Authorization together.\u003C/p\u003E\n\u003Cp\u003EThe important distinction is that \u003Cstrong\u003Ethe authentication engine does not need to become a monolithic identity management system to support either scenario\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThis separation allows UltimateAuth to serve both focused authentication use cases and more comprehensive identity platforms while maintaining clear architectural boundaries.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022explore-the-domains\u0022\u003EExplore the Domains\u003C/h2\u003E\n\u003Cp\u003EContinue with the individual domains to understand their responsibilities, public contracts, and extension points.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/users\u0022\u003EUsers\u003C/a\u003E \u2014 User identities, identifiers, and lifecycle management.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Credential management, verification, and recovery.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Roles, permissions, policies, and access control.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor the underlying authentication architecture, see \u003Ca href=\u0022/docs/fundamentals/runtime-architecture\u0022\u003ERuntime Architecture\u003C/a\u003E and \u003Ca href=\u0022/docs/fundamentals/authentication-model\u0022\u003EAuthentication Model\u003C/a\u003E.\u003C/p\u003E\n", "Headings": [ { - "Id": "what-is-a-plugin-domain", - "Text": "\uD83E\uDDE0 What Is a Plugin Domain?", + "Id": "authentication-core-and-plugin-domains", + "Text": "Authentication Core and Plugin Domains", "Level": 0 }, { - "Id": "architecture", - "Text": "\uD83C\uDFD7 Architecture", + "Id": "architectural-boundaries", + "Text": "Architectural Boundaries", "Level": 0 }, { - "Id": "bridge-package", - "Text": "\uD83D\uDD39 Bridge Package", - "Level": 1 - }, - { - "Id": "contracts-package", - "Text": "\uD83D\uDD39 Contracts Package", - "Level": 1 - }, - { - "Id": "reference-implementation", - "Text": "\uD83D\uDD39 Reference Implementation", - "Level": 1 - }, - { - "Id": "persistence-layer", - "Text": "\uD83D\uDD39 Persistence Layer", - "Level": 1 - }, - { - "Id": "extensibility-model", - "Text": "\uD83D\uDD04 Extensibility Model", + "Id": "domain-package-structure", + "Text": "Domain Package Structure", "Level": 0 }, { - "Id": "recommended-approach", - "Text": "\u26A0\uFE0F Recommended Approach", - "Level": 0 - }, - { - "Id": "domain-isolation", - "Text": "\uD83D\uDD0C Domain Isolation", - "Level": 0 - }, - { - "Id": "why", - "Text": "\uD83E\uDDE0 Why?", - "Level": 1 - }, - { - "Id": "communication-via-hooks", - "Text": "\uD83D\uDD04 Communication via Hooks", + "Id": "cross-domain-integration", + "Text": "Cross-Domain Integration", "Level": 0 }, { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", + "Id": "extending-plugin-domains", + "Text": "Extending Plugin Domains", "Level": 0 }, { - "Id": "why-this-matters", - "Text": "\uD83C\uDFAF Why This Matters", + "Id": "choosing-the-right-capabilities", + "Text": "Choosing the Right Capabilities", "Level": 0 }, { - "Id": "next-step", - "Text": "\u27A1\uFE0F Next Step", + "Id": "explore-the-domains", + "Text": "Explore the Domains", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/policies.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/policies.json index 75b0e118..157dd0cb 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/policies.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/policies.json @@ -1,33 +1,58 @@ { "Slug": "plugin-domains/policies", - "Title": "Policies", - "Html": "\n\u003Cp\u003EUltimateAuth uses a \u003Cstrong\u003Epolicy-driven authorization model\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EPolicies are not simple checks \u2014\u003Cbr /\u003E\nthey are \u003Cstrong\u003Ecomposable decision units\u003C/strong\u003E evaluated at runtime.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EAuthorization in UltimateAuth is:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Context-based\u003Cbr /\u003E\n\uD83D\uDC49 Policy-driven\u003Cbr /\u003E\n\uD83D\uDC49 Orchestrated\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow\u0022\u003EFlow\u003C/h3\u003E\n\u003Col\u003E\n\u003Cli\u003EBuild \u003Ccode\u003EAccessContext\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EResolve policies\u003C/li\u003E\n\u003Cli\u003EExecute authority\u003C/li\u003E\n\u003Cli\u003EAllow / Deny / Reauth\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accesscontext\u0022\u003E\u2699\uFE0F AccessContext\u003C/h2\u003E\n\u003Cp\u003EEvery authorization decision is based on:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EActor (who is calling)\u003C/li\u003E\n\u003Cli\u003ETarget (what is being accessed)\u003C/li\u003E\n\u003Cli\u003EAction (what is being done)\u003C/li\u003E\n\u003Cli\u003ETenant\u003C/li\u003E\n\u003Cli\u003EClaims / permissions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-resolution\u0022\u003E\uD83D\uDD0C Policy Resolution\u003C/h2\u003E\n\u003Cp\u003EPolicies are resolved using:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAction prefix matching\u003C/li\u003E\n\u003Cli\u003ERuntime filtering (\u003Ccode\u003EAppliesTo\u003C/code\u003E)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EExample:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.create.admin\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eusers.*\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eauthorization.roles.*\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-types\u0022\u003E\uD83E\uDDE9 Policy Types\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022global-policies\u0022\u003EGlobal Policies\u003C/h3\u003E\n\u003Cp\u003EAlways evaluated:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequireAuthenticated\u003C/li\u003E\n\u003Cli\u003EDenyCrossTenant\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-policies\u0022\u003ERuntime Policies\u003C/h3\u003E\n\u003Cp\u003EResolved dynamically:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequireActiveUser\u003C/li\u003E\n\u003Cli\u003EMustHavePermission\u003C/li\u003E\n\u003Cli\u003ERequireSelf\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022invariants\u0022\u003EInvariants\u003C/h3\u003E\n\u003Cp\u003EExecuted first:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECannot be bypassed\u003C/li\u003E\n\u003Cli\u003EHard security rules\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-evaluation\u0022\u003E\u2696\uFE0F Policy Evaluation\u003C/h2\u003E\n\u003Cp\u003EEvaluation order:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EInvariants\u003C/li\u003E\n\u003Cli\u003EGlobal policies\u003C/li\u003E\n\u003Cli\u003ERuntime policies\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003E\uD83D\uDC49 First deny wins\u003Cbr /\u003E\n\uD83D\uDC49 Allow means \u201Cno objection\u201D\u003Cbr /\u003E\n\uD83D\uDC49 Reauth can be requested\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-policy\u0022\u003E\uD83D\uDD10 Example Policy\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deny-admin-self-modification\u0022\u003EDeny Admin Self Modification\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EBlocks admin modifying own account\u003C/li\u003E\n\u003Cli\u003EApplies only to \u003Ccode\u003E.admin\u003C/code\u003E actions\u003C/li\u003E\n\u003Cli\u003EIgnores read operations\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022require-active-user\u0022\u003ERequire Active User\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnsures user exists\u003C/li\u003E\n\u003Cli\u003EEnsures user is active\u003C/li\u003E\n\u003Cli\u003ESkips anonymous actions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022access-orchestrator\u0022\u003E\uD83D\uDE80 Access Orchestrator\u003C/h2\u003E\n\u003Cp\u003EThe orchestrator is the entry point:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnriches context (claims, permissions)\u003C/li\u003E\n\u003Cli\u003EResolves policies\u003C/li\u003E\n\u003Cli\u003EExecutes authority\u003C/li\u003E\n\u003Cli\u003ERuns command if allowed\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-principles\u0022\u003E\uD83C\uDFAF Key Principles\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EPolicies are composable\u003C/li\u003E\n\u003Cli\u003EAuthorization is deterministic\u003C/li\u003E\n\u003Cli\u003ENo hidden magic\u003C/li\u003E\n\u003Cli\u003EFully extensible\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr /\u003E\n\u003Cp\u003E\uD83D\uDC49 Authorization is not a single check\u003Cbr /\u003E\n\uD83D\uDC49 It is a \u003Cstrong\u003Epipeline of decisions\u003C/strong\u003E\u003C/p\u003E\n", + "Title": "Policies \u0026 Access Control", + "Html": "\n\u003Cp\u003EAuthorization is not simply a question of whether an identity possesses a permission.\u003C/p\u003E\n\u003Cp\u003EA protected operation may involve an authenticated actor, a target identity, tenant boundaries, resource ownership, and security conditions that cannot be represented by a permission alone.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth addresses these requirements through a \u003Cstrong\u003Epolicy-driven access control pipeline\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EPolicies are independent decision units evaluated within a shared access context. The authorization orchestrator coordinates those decisions and determines whether the requested operation can proceed.\u003C/p\u003E\n\u003Cp\u003EThis page explains how that process works.\u003C/p\u003E\n\u003Cp\u003EFor actions, permissions, roles, and scope conventions, see \u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-access-decision-model\u0022\u003EThe Access Decision Model\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth evaluates authorization using three primary elements:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EElement\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\u003EAccessContext\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EDescribes the actor, target, operation, and relevant security context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPolicies\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEvaluate individual authorization requirements\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAccessOrchestrator\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECoordinates policy resolution, evaluation, and execution\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese elements separate the description of an operation from the rules used to authorize it.\u003C/p\u003E\n\u003Cp\u003EA permission may grant authority to perform an action, but policies determine whether that authority is valid in the current context.\u003C/p\u003E\n\u003Cp\u003EFor example, a caller may possess an administrative permission while attempting to access a resource belonging to another tenant.\u003C/p\u003E\n\u003Cp\u003EThe permission may match, but the operation must still satisfy tenant isolation requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accesscontext\u0022\u003EAccessContext\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E represents the security context of an authorization operation.\u003C/p\u003E\n\u003Cp\u003EIt provides the information needed to evaluate access without requiring each policy to reconstruct the request independently.\u003C/p\u003E\n\u003Cp\u003EConceptually, the context includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EComponent\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\u003EActor\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe identity or system principal performing the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ETarget\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe identity or resource affected by the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAction\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe protected operation being requested\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ETenant context\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe tenant boundaries relevant to the actor and target\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAuthorization information\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERelevant claims, permissions, and security state\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe context is more than a collection of request parameters.\u003C/p\u003E\n\u003Cp\u003EIt establishes the relationship between the authenticated actor and the operation being performed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022actor-and-target-are-different-concepts\u0022\u003EActor and Target Are Different Concepts\u003C/h3\u003E\n\u003Cp\u003EConsider an administrative operation:\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.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actor is the administrator requesting the deletion.\u003C/p\u003E\n\u003Cp\u003EThe target is the user account being deleted.\u003C/p\u003E\n\u003Cp\u003EThese identities must remain distinguishable throughout authorization and execution.\u003C/p\u003E\n\u003Cp\u003EFor a self-service operation:\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.delete.self\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actor and target are expected to represent the same identity.\u003C/p\u003E\n\u003Cp\u003EThat relationship must be established from trusted context, not assumed from the action suffix.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022trusted-context-construction\u0022\u003ETrusted Context Construction\u003C/h3\u003E\n\u003Cp\u003EAuthorization decisions are only as reliable as the information supplied to them.\u003C/p\u003E\n\u003Cp\u003EAn endpoint may receive a target user ID or tenant ID from the caller, but these values must not automatically become trusted authorization facts.\u003C/p\u003E\n\u003Cp\u003EThe authentication infrastructure establishes the actor\u0027s identity. The application or domain operation resolves the intended target. The authorization context brings those elements together.\u003C/p\u003E\n\u003Cp\u003EPolicies then evaluate the resulting relationships.\u003C/p\u003E\n\u003Cp\u003EThis distinction is essential for preventing cross-user and cross-tenant access.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies-as-decision-units\u0022\u003EPolicies as Decision Units\u003C/h2\u003E\n\u003Cp\u003EA policy evaluates a specific security requirement.\u003C/p\u003E\n\u003Cp\u003ERather than combining every authorization rule into one large conditional expression, UltimateAuth separates requirements into reusable policies.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequiring an authenticated actor.\u003C/li\u003E\n\u003Cli\u003ERequiring an active user.\u003C/li\u003E\n\u003Cli\u003EEnforcing tenant isolation.\u003C/li\u003E\n\u003Cli\u003EVerifying self-access relationships.\u003C/li\u003E\n\u003Cli\u003ERequiring an appropriate permission.\u003C/li\u003E\n\u003Cli\u003ERestricting an operation to a system context.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EEach policy evaluates its own responsibility within the available access context.\u003C/p\u003E\n\u003Cp\u003EA policy should not need to understand every other policy to make its decision.\u003C/p\u003E\n\u003Cp\u003EThis allows authorization rules to be composed without losing their individual meaning.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-resolution\u0022\u003EPolicy Resolution\u003C/h2\u003E\n\u003Cp\u003ENot every policy is relevant to every operation.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports policy resolution based on action matching and runtime applicability.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022action-based-matching\u0022\u003EAction-Based Matching\u003C/h3\u003E\n\u003Cp\u003EPolicies can be associated with action patterns.\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.create.admin\nusers.*\nauthorization.roles.*\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn action-specific policy can target one operation, while a broader pattern can apply to a related group of operations.\u003C/p\u003E\n\u003Cp\u003EThis allows common rules to be shared across a domain without duplicating policy registration for every action.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-applicability\u0022\u003ERuntime Applicability\u003C/h3\u003E\n\u003Cp\u003EAction matching alone may not provide enough information to determine whether a policy should execute.\u003C/p\u003E\n\u003Cp\u003EA policy can also evaluate whether it applies to the current context through \u003Ccode\u003EAppliesTo\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis enables context-dependent policy selection.\u003C/p\u003E\n\u003Cp\u003EFor example, a policy may apply only to administrative operations that modify account state, rather than every administrative read operation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EPolicy resolution determines which rules participate in the decision. Policy evaluation determines whether those rules permit the operation.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EKeeping these stages separate makes authorization behavior easier to reason about.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-categories\u0022\u003EPolicy Categories\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between security requirements that must be enforced broadly and policies selected for particular operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022invariants\u0022\u003EInvariants\u003C/h3\u003E\n\u003Cp\u003EInvariants represent fundamental security rules that must not be bypassed by ordinary permission grants or application-level authorization decisions.\u003C/p\u003E\n\u003Cp\u003EThey are evaluated before the remaining policy stages.\u003C/p\u003E\n\u003Cp\u003EAn invariant is not equivalent to a role permission or an optional business rule.\u003C/p\u003E\n\u003Cp\u003EIts purpose is to preserve a security condition that must hold regardless of the requested operation\u0027s ordinary authorization configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022global-policies\u0022\u003EGlobal Policies\u003C/h3\u003E\n\u003Cp\u003EGlobal policies apply broadly across protected operations.\u003C/p\u003E\n\u003Cp\u003EThey provide common security requirements that should not need to be registered independently for every action.\u003C/p\u003E\n\u003Cp\u003EExamples of such requirements include authentication and tenant isolation.\u003C/p\u003E\n\u003Cp\u003ETheir applicability may still depend on the operation\u0027s context. An intentionally anonymous action, for example, cannot be treated identically to an authenticated administrative operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-policies\u0022\u003ERuntime Policies\u003C/h3\u003E\n\u003Cp\u003ERuntime policies are selected and evaluated according to the requested action and current access context.\u003C/p\u003E\n\u003Cp\u003EThey support requirements such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPermission checks.\u003C/li\u003E\n\u003Cli\u003ESelf-access validation.\u003C/li\u003E\n\u003Cli\u003EActive-user requirements.\u003C/li\u003E\n\u003Cli\u003EOperation-specific restrictions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis separation allows the authorization system to maintain mandatory security boundaries while remaining extensible for individual domains and applications.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-evaluation-order\u0022\u003EPolicy Evaluation Order\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s authorization pipeline follows a defined evaluation order:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003E\u003Cstrong\u003EInvariants\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EGlobal policies\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERuntime policies\u003C/strong\u003E\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThis order establishes a clear security hierarchy.\u003C/p\u003E\n\u003Cp\u003EA runtime permission grant must not override an invariant that has already rejected the operation.\u003C/p\u003E\n\u003Cp\u003ELikewise, satisfying an action-specific policy does not eliminate the need to satisfy applicable global requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deny-takes-precedence\u0022\u003EDeny Takes Precedence\u003C/h3\u003E\n\u003Cp\u003EThe decision model is deny-oriented.\u003C/p\u003E\n\u003Cp\u003EWhen a required policy denies an operation, a later policy cannot convert that denial into an authorization grant.\u003C/p\u003E\n\u003Cp\u003EThis prevents authorization from depending on the accidental presence of a permissive policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022allow-is-not-a-universal-grant\u0022\u003EAllow Is Not a Universal Grant\u003C/h3\u003E\n\u003Cp\u003EA policy returning an allow result means that its own requirement has been satisfied or that it has no objection to the operation.\u003C/p\u003E\n\u003Cp\u003EIt does not mean that all other requirements have been satisfied.\u003C/p\u003E\n\u003Cp\u003EThe overall decision is produced by the authorization pipeline, not by one successful policy in isolation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reauthentication\u0022\u003EReauthentication\u003C/h3\u003E\n\u003Cp\u003ESome authorization decisions may require additional authentication rather than producing an ordinary allow or deny outcome.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s decision model includes the ability to represent a reauthentication requirement.\u003C/p\u003E\n\u003Cp\u003EThis allows authorization to distinguish between insufficient authority and situations where the actor may need to establish a stronger or more recent authentication state.\u003C/p\u003E\n\u003Cp\u003EThe exact handling of such a result depends on the surrounding authentication and application flow.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessorchestrator\u0022\u003EAccessOrchestrator\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessOrchestrator\u003C/code\u003E coordinates the authorization process.\u003C/p\u003E\n\u003Cp\u003EIts responsibilities include preparing the effective access context, resolving applicable policies, evaluating the required security conditions, and coordinating execution when access is permitted.\u003C/p\u003E\n\u003Cp\u003EThe conceptual pipeline 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\u003ERequested operation\n |\n v\nBuild AccessContext\n |\n v\nResolve authorization information\n |\n v\nEvaluate invariants\n |\n v\nEvaluate global policies\n |\n v\nResolve applicable runtime policies\n |\n v\nEvaluate runtime policies\n |\n \u002B---- Deny ------\u0026gt; Reject operation\n |\n \u002B---- Reauth ----\u0026gt; Require reauthentication\n |\n v\n Allow\n |\n v\nExecute authorized operation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is a conceptual representation of the access decision pipeline, not a substitute for the concrete orchestration API.\u003C/p\u003E\n\u003Cp\u003EThe orchestrator centralizes authorization coordination so that individual application services do not need to reproduce the complete evaluation process.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-before-execution\u0022\u003EAuthorization Before Execution\u003C/h2\u003E\n\u003Cp\u003EAuthorization should be enforced before a protected operation performs security-sensitive changes.\u003C/p\u003E\n\u003Cp\u003EConsider an administrative credential operation.\u003C/p\u003E\n\u003Cp\u003EThe system may need to establish:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EWho is performing the operation.\u003C/li\u003E\n\u003Cli\u003EWhich account is being targeted.\u003C/li\u003E\n\u003Cli\u003EWhether the actor possesses the required administrative permission.\u003C/li\u003E\n\u003Cli\u003EWhether the target belongs to an allowed tenant.\u003C/li\u003E\n\u003Cli\u003EWhether additional operation-specific restrictions apply.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EOnly after the applicable requirements succeed should the protected mutation proceed.\u003C/p\u003E\n\u003Cp\u003EThis separation is particularly important for operations that affect credentials, account status, roles, or sessions.\u003C/p\u003E\n\u003Cp\u003EA rejected authorization decision must not leave behind a partially completed protected operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-preventing-administrative-self-modification\u0022\u003EExample: Preventing Administrative Self-Modification\u003C/h2\u003E\n\u003Cp\u003ESome administrative operations may intentionally prohibit an administrator from modifying their own account through an administrative route.\u003C/p\u003E\n\u003Cp\u003EThis is different from ordinary self-service account management.\u003C/p\u003E\n\u003Cp\u003EA policy enforcing this restriction could consider:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhether the requested action has administrative scope.\u003C/li\u003E\n\u003Cli\u003EWhether the operation modifies security-sensitive account state.\u003C/li\u003E\n\u003Cli\u003EWhether the actor and target represent the same identity.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIf the policy applies and the actor is also the target, it denies the operation.\u003C/p\u003E\n\u003Cp\u003EThe purpose is to prevent an administrative capability from bypassing the separate rules governing self-service operations.\u003C/p\u003E\n\u003Cp\u003ESuch a policy illustrates why permission checks alone are insufficient.\u003C/p\u003E\n\u003Cp\u003EAn administrator may possess the required action permission while still being prohibited from performing the operation against a particular target.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-requiring-an-active-user\u0022\u003EExample: Requiring an Active User\u003C/h2\u003E\n\u003Cp\u003EAn active-user policy evaluates whether the relevant identity satisfies the account state requirements for an operation.\u003C/p\u003E\n\u003Cp\u003EAn authenticated principal may exist even after the corresponding user account has been suspended or otherwise made ineligible.\u003C/p\u003E\n\u003Cp\u003EThe authorization system therefore needs a way to enforce account eligibility independently of the original authentication event.\u003C/p\u003E\n\u003Cp\u003EThe policy can evaluate the relevant user state and deny operations that require an active account.\u003C/p\u003E\n\u003Cp\u003EAnonymous operations may follow different applicability rules because they do not necessarily have an authenticated user identity.\u003C/p\u003E\n\u003Cp\u003EThe exact enforcement behavior also depends on how current user security state is obtained.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-decisions-and-authentication-modes\u0022\u003EPolicy Decisions and Authentication Modes\u003C/h2\u003E\n\u003Cp\u003EThe policy model is conceptually independent of the authentication mode, but the freshness of its input data matters.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, server-side state may be consulted as part of protected operations.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, some authorization information may originate from claims issued earlier.\u003C/p\u003E\n\u003Cp\u003EThis creates a distinction between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEvaluating a policy against current authoritative data.\u003C/li\u003E\n\u003Cli\u003EEvaluating a policy against previously issued authorization claims.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, a permission removed after token issuance may still be present in a locally validated JWT until that token expires.\u003C/p\u003E\n\u003Cp\u003EA policy can evaluate its inputs correctly while those inputs reflect an earlier authorization state.\u003C/p\u003E\n\u003Cp\u003EApplications must account for this when choosing token lifetimes, permission resolution strategies, and security requirements.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022application-level-access-control\u0022\u003EApplication-Level Access Control\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s built-in authorization rules protect framework operations, but applications also have their own resources and business requirements.\u003C/p\u003E\n\u003Cp\u003EA business application may need policies for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEditing resources owned by a particular user.\u003C/li\u003E\n\u003Cli\u003ERestricting operations to a tenant.\u003C/li\u003E\n\u003Cli\u003ERequiring elevated authority for administrative changes.\u003C/li\u003E\n\u003Cli\u003EPreventing sensitive changes under particular account conditions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese requirements can be modeled as application-specific authorization policies.\u003C/p\u003E\n\u003Cp\u003EThe recommended architectural approach is to keep security decisions at a boundary that cannot be bypassed by alternative execution paths.\u003C/p\u003E\n\u003Cp\u003EFor HTTP endpoints, ASP.NET Core authorization can provide the request-level enforcement boundary.\u003C/p\u003E\n\u003Cp\u003EFor direct application service calls, including in-process operations, authorization must also be enforced where the protected operation actually executes.\u003C/p\u003E\n\u003Cp\u003EThe presence of an \u003Ccode\u003E[Authorize]\u003C/code\u003E attribute on a controller does not automatically protect a service invoked directly from another part of the application.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extending-the-policy-pipeline\u0022\u003EExtending the Policy Pipeline\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s policy model is designed to accommodate application-specific requirements without modifying the authentication engine.\u003C/p\u003E\n\u003Cp\u003ECustom policies can introduce additional authorization conditions while participating in the established access decision process.\u003C/p\u003E\n\u003Cp\u003ESuch policies should preserve the framework\u0027s security boundaries:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDo not treat untrusted request values as established identity facts.\u003C/li\u003E\n\u003Cli\u003EDo not bypass mandatory invariants.\u003C/li\u003E\n\u003Cli\u003EDo not use broad permission grants to override target or tenant restrictions.\u003C/li\u003E\n\u003Cli\u003EKeep policy applicability and evaluation behavior explicit.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EDetailed custom policy implementation and registration examples belong in \u003Ca href=\u0022/docs/how-to\u0022\u003EHow-to Guides\u003C/a\u003E, where they can be presented alongside complete application code.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth authorization is a coordinated evaluation of security requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E describes the operation. Policy resolution identifies the relevant requirements. Policy evaluation determines whether those requirements are satisfied. \u003Ccode\u003EAccessOrchestrator\u003C/code\u003E coordinates the process and its execution boundary.\u003C/p\u003E\n\u003Cp\u003EThe result is an authorization model that is \u003Cstrong\u003Econtext-aware, policy-driven, and extensible without making individual permissions responsible for every security decision\u003C/strong\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/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Actions, permissions, roles, and scope semantics.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/users\u0022\u003EUsers\u003C/a\u003E \u2014 Account lifecycle and identity state.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Credential ownership and security operations.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/request-lifecycle\u0022\u003ERequest Lifecycle\u003C/a\u003E \u2014 Authorization within protected requests.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", + "Id": "the-access-decision-model", + "Text": "The Access Decision Model", "Level": 0 }, { - "Id": "flow", - "Text": "Flow", + "Id": "accesscontext", + "Text": "AccessContext", + "Level": 0 + }, + { + "Id": "actor-and-target-are-different-concepts", + "Text": "Actor and Target Are Different Concepts", "Level": 1 }, { - "Id": "accesscontext", - "Text": "\u2699\uFE0F AccessContext", + "Id": "trusted-context-construction", + "Text": "Trusted Context Construction", + "Level": 1 + }, + { + "Id": "policies-as-decision-units", + "Text": "Policies as Decision Units", "Level": 0 }, { "Id": "policy-resolution", - "Text": "\uD83D\uDD0C Policy Resolution", + "Text": "Policy Resolution", "Level": 0 }, { - "Id": "policy-types", - "Text": "\uD83E\uDDE9 Policy Types", + "Id": "action-based-matching", + "Text": "Action-Based Matching", + "Level": 1 + }, + { + "Id": "runtime-applicability", + "Text": "Runtime Applicability", + "Level": 1 + }, + { + "Id": "policy-categories", + "Text": "Policy Categories", "Level": 0 }, + { + "Id": "invariants", + "Text": "Invariants", + "Level": 1 + }, { "Id": "global-policies", "Text": "Global Policies", @@ -39,38 +64,68 @@ "Level": 1 }, { - "Id": "invariants", - "Text": "Invariants", + "Id": "policy-evaluation-order", + "Text": "Policy Evaluation Order", + "Level": 0 + }, + { + "Id": "deny-takes-precedence", + "Text": "Deny Takes Precedence", + "Level": 1 + }, + { + "Id": "allow-is-not-a-universal-grant", + "Text": "Allow Is Not a Universal Grant", "Level": 1 }, { - "Id": "policy-evaluation", - "Text": "\u2696\uFE0F Policy Evaluation", + "Id": "reauthentication", + "Text": "Reauthentication", + "Level": 1 + }, + { + "Id": "accessorchestrator", + "Text": "AccessOrchestrator", "Level": 0 }, { - "Id": "example-policy", - "Text": "\uD83D\uDD10 Example Policy", + "Id": "authorization-before-execution", + "Text": "Authorization Before Execution", "Level": 0 }, { - "Id": "deny-admin-self-modification", - "Text": "Deny Admin Self Modification", - "Level": 1 + "Id": "example-preventing-administrative-self-modification", + "Text": "Example: Preventing Administrative Self-Modification", + "Level": 0 }, { - "Id": "require-active-user", - "Text": "Require Active User", - "Level": 1 + "Id": "example-requiring-an-active-user", + "Text": "Example: Requiring an Active User", + "Level": 0 + }, + { + "Id": "policy-decisions-and-authentication-modes", + "Text": "Policy Decisions and Authentication Modes", + "Level": 0 + }, + { + "Id": "application-level-access-control", + "Text": "Application-Level Access Control", + "Level": 0 + }, + { + "Id": "extending-the-policy-pipeline", + "Text": "Extending the Policy Pipeline", + "Level": 0 }, { - "Id": "access-orchestrator", - "Text": "\uD83D\uDE80 Access Orchestrator", + "Id": "architectural-perspective", + "Text": "Architectural Perspective", "Level": 0 }, { - "Id": "key-principles", - "Text": "\uD83C\uDFAF Key Principles", + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/users-domain.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/users-domain.json index 03aa54a2..5a6f45dc 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/users-domain.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/users-domain.json @@ -1,46 +1,81 @@ { "Slug": "plugin-domains/users-domain", "Title": "Users", - "Html": "\n\u003Cp\u003EUsers in UltimateAuth are not a single entity.\u003C/p\u003E\n\u003Cp\u003EInstead, they are composed of multiple parts that together define identity.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-concept\u0022\u003E\uD83E\uDDE0 Core Concept\u003C/h2\u003E\n\u003Cp\u003EA user is represented by three main components:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELifecycle (security \u0026amp; state)\u003C/li\u003E\n\u003Cli\u003EIdentifiers (login surface)\u003C/li\u003E\n\u003Cli\u003EProfile (user data)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifecycle-security-anchor\u0022\u003E\uD83D\uDD10 Lifecycle (Security Anchor)\u003C/h2\u003E\n\u003Cp\u003ELifecycle defines:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhen a user created\u003C/li\u003E\n\u003Cli\u003EWhether a user is active, suspended, or deleted\u003C/li\u003E\n\u003Cli\u003ESecurity version (used for invalidating sessions/tokens)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This is the core of authentication security.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifiers-login-system\u0022\u003E\uD83D\uDD11 Identifiers (Login System)\u003C/h2\u003E\n\u003Cp\u003EIdentifiers represent how a user logs in:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEmail\u003C/li\u003E\n\u003Cli\u003EUsername\u003C/li\u003E\n\u003Cli\u003EPhone\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EEach identifier has:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ENormalized value\u003C/li\u003E\n\u003Cli\u003EPrimary flag\u003C/li\u003E\n\u003Cli\u003EVerification state\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022login-identifiers\u0022\u003E\u2B50 Login Identifiers\u003C/h3\u003E\n\u003Cp\u003ENot all identifiers are used for login.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Only \u003Cstrong\u003Eprimary identifiers\u003C/strong\u003E are considered login identifiers.\u003C/p\u003E\n\u003Ch4 id=\u0022configurable-behavior\u0022\u003E\u2699\uFE0F Configurable Behavior\u003C/h4\u003E\n\u003Cp\u003EDevelopers can control:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhich identifier types are allowed for login\u003C/li\u003E\n\u003Cli\u003EWhether email/phone must be verified\u003C/li\u003E\n\u003Cli\u003EWhether multiple identifiers are allowed\u003C/li\u003E\n\u003Cli\u003EGlobal uniqueness rules\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch4 id=\u0022custom-login-logic\u0022\u003E\uD83D\uDD0C Custom Login Logic\u003C/h4\u003E\n\u003Cp\u003EUltimateAuth allows custom login identifier resolution.\u003C/p\u003E\n\u003Cp\u003EYou can:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAdd custom identifier types\u003C/li\u003E\n\u003Cli\u003EOverride resolution logic\u003C/li\u003E\n\u003Cli\u003EImplement your own resolver\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This means login is fully extensible.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-user-data\u0022\u003E\uD83E\uDDFE Profile (User Data)\u003C/h2\u003E\n\u003Cp\u003EProfile contains non-auth data:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EName\u003C/li\u003E\n\u003Cli\u003EBio\u003C/li\u003E\n\u003Cli\u003ELocalization\u003C/li\u003E\n\u003Cli\u003EMetadata\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This is not used for authentication.\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022application-service\u0022\u003E\u2699\uFE0F Application Service\u003C/h2\u003E\n\u003Cp\u003EUser operations are handled by an orchestration layer.\u003C/p\u003E\n\u003Cp\u003EIt is responsible for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECreating users\u003C/li\u003E\n\u003Cli\u003EManaging identifiers\u003C/li\u003E\n\u003Cli\u003EApplying validation rules\u003C/li\u003E\n\u003Cli\u003ETriggering integrations\u003C/li\u003E\n\u003Cli\u003ERevoking sessions when needed\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83D\uDD01 Mental Model\u003C/h2\u003E\n\u003Cp\u003EAuthentication answers:\u003C/p\u003E\n\u003Cp\u003E\u2192 Who are you?\u003C/p\u003E\n\u003Cp\u003EUsers domain answers:\u003C/p\u003E\n\u003Cp\u003E\u2192 What is this user?\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003E\uD83C\uDFAF Summary\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EUsers are composed, not singular\u003C/li\u003E\n\u003Cli\u003ELogin is based on identifiers\u003C/li\u003E\n\u003Cli\u003ELogin identifiers are configurable\u003C/li\u003E\n\u003Cli\u003ESystem is extensible by design\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EA user is more than a database record, an email address, or a profile.\u003C/p\u003E\n\u003Cp\u003EIn UltimateAuth, user identity is modeled through a separation of \u003Cstrong\u003Eauthentication identity, account lifecycle, identifiers, and application-specific user information\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThis distinction allows the authentication engine to operate independently of a particular user entity or persistence model, while the Users Plugin Domain provides the capabilities needed to manage identities throughout their lifecycle.\u003C/p\u003E\n\u003Cp\u003EThe Users domain builds on the authentication infrastructure provided by UltimateAuth Core and Server. It does not redefine how authentication works; it defines how user identities are represented, maintained, and integrated with that infrastructure.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-user-identity\u0022\u003EUnderstanding User Identity\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between three aspects of a user:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EComponent\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\u003ELifecycle\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAccount existence, status, and security-relevant state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EIdentifiers\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EValues through which a user can be identified or discovered\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EProfile\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EApplication-specific information associated with the user\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese components represent different responsibilities and should not be treated as interchangeable.\u003C/p\u003E\n\u003Cp\u003EFor example, changing a display name should not affect authentication state. Verifying an email address should not implicitly change account status. Suspending an account should not require deleting its identifiers or profile.\u003C/p\u003E\n\u003Cp\u003ESeparating these concerns makes identity operations more predictable and allows security-sensitive changes to be handled explicitly.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022user-identity-in-core-and-server\u0022\u003EUser Identity in Core and Server\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth does not require applications to adopt the Users Plugin Domain simply to authenticate users.\u003C/p\u003E\n\u003Cp\u003ECore and Server provide the foundational authentication infrastructure, including the contracts and runtime mechanisms needed to associate authentication operations with user identities.\u003C/p\u003E\n\u003Cp\u003EAt this level, a user is primarily an \u003Cstrong\u003Eauthentication subject\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe authentication system needs to establish which identity is involved in an operation, evaluate the relevant security conditions, and maintain the appropriate authentication state.\u003C/p\u003E\n\u003Cp\u003EIt does not inherently need to know the user\u0027s display name, profile fields, or how the application\u0027s user records are stored.\u003C/p\u003E\n\u003Cp\u003EThis separation is intentional:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ECore\u003C/strong\u003E defines foundational authentication concepts, contracts, and security semantics.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EServer\u003C/strong\u003E executes authentication flows and enforces the relevant runtime security boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUsers\u003C/strong\u003E provides higher-level identity management capabilities through a dedicated Plugin Domain.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAn application with an existing identity system can integrate that system with UltimateAuth\u0027s authentication infrastructure without necessarily adopting the reference user model.\u003C/p\u003E\n\u003Cp\u003EThe authentication identity is therefore a framework-level concept, while the complete user management model belongs to the application or the Users domain.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-the-users-domain-adds\u0022\u003EWhat the Users Domain Adds\u003C/h2\u003E\n\u003Cp\u003EThe Users Plugin Domain provides a structured approach to managing user identities beyond the minimum requirements of authentication.\u003C/p\u003E\n\u003Cp\u003EIts responsibilities include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECreating and managing user accounts.\u003C/li\u003E\n\u003Cli\u003EMaintaining account lifecycle and status.\u003C/li\u003E\n\u003Cli\u003ERegistering and managing user identifiers.\u003C/li\u003E\n\u003Cli\u003EEnforcing identifier ownership and verification rules.\u003C/li\u003E\n\u003Cli\u003EApplying user management validation and access boundaries.\u003C/li\u003E\n\u003Cli\u003ECoordinating security-relevant changes with authentication infrastructure.\u003C/li\u003E\n\u003Cli\u003EExposing user management operations through application services and shared contracts.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese capabilities are distinct from credential management.\u003C/p\u003E\n\u003Cp\u003EThe Users domain determines \u003Cstrong\u003Ewhich identity an identifier belongs to\u003C/strong\u003E and maintains the relevant user state. The Credentials domain manages the authentication credentials associated with identities.\u003C/p\u003E\n\u003Cp\u003EFor example, an email address is a user identifier; a password is a credential. They may participate in the same login flow, but they have different ownership and lifecycle rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022account-lifecycle\u0022\u003EAccount Lifecycle\u003C/h2\u003E\n\u003Cp\u003EThe lifecycle represents the existence and security-relevant state of a user account.\u003C/p\u003E\n\u003Cp\u003EIn the reference model, lifecycle information includes account creation, account status, and security state used to coordinate authentication-related changes.\u003C/p\u003E\n\u003Cp\u003EAccount status may represent conditions such as active, suspended, or deleted, according to the supported lifecycle model.\u003C/p\u003E\n\u003Cp\u003EThese states are not merely descriptive profile properties. They can influence whether an account is eligible to participate in authentication and other protected operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-state-and-session-invalidation\u0022\u003ESecurity State and Session Invalidation\u003C/h3\u003E\n\u003Cp\u003EUser lifecycle changes may require existing authentication state to be invalidated.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth separates the decision to change a user\u0027s security state from the mechanisms used to enforce that change.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured authentication mode, enforcement may involve server-side session invalidation, token lifecycle rules, or other supported security mechanisms.\u003C/p\u003E\n\u003Cp\u003EThis distinction matters because not every authentication mode can invalidate an already-issued credential immediately.\u003C/p\u003E\n\u003Cp\u003EFor example, a locally validated JWT may remain valid until expiration unless the protected resource performs additional server-side validation.\u003C/p\u003E\n\u003Cp\u003EThe Users domain participates in security-sensitive account operations, while the authentication infrastructure determines how the resulting security state is enforced.\u003C/p\u003E\n\u003Cp\u003EFor more information, 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=\u0022user-identifiers\u0022\u003EUser Identifiers\u003C/h2\u003E\n\u003Cp\u003EAn identifier is a value associated with a user that allows the identity to be referenced or resolved.\u003C/p\u003E\n\u003Cp\u003ECommon examples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEmail addresses.\u003C/li\u003E\n\u003Cli\u003EUsernames.\u003C/li\u003E\n\u003Cli\u003EPhone numbers.\u003C/li\u003E\n\u003Cli\u003EApplication-defined identifier types.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth treats identifiers as independent identity records rather than assuming that every user has exactly one email address or username.\u003C/p\u003E\n\u003Cp\u003EThis supports applications with different identity requirements without changing the underlying authentication model.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-properties\u0022\u003EIdentifier Properties\u003C/h3\u003E\n\u003Cp\u003EThe reference identifier model distinguishes several important 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\u003Cstrong\u003EType\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifies the kind of identifier, such as email or username\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EValue\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe identifier value associated with the user\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ENormalized value\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EA representation used for consistent lookup and comparison\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPrimary state\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EIndicates the identifier\u0027s designated primary role\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EVerification state\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EIndicates whether ownership has been verified\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese properties address separate questions.\u003C/p\u003E\n\u003Cp\u003EAn identifier may exist without being verified. A verified identifier does not automatically become primary. A primary identifier does not necessarily satisfy every authentication policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022login-identifier-resolution\u0022\u003ELogin Identifier Resolution\u003C/h3\u003E\n\u003Cp\u003EIdentifier resolution determines how an incoming login identifier is associated with a user identity.\u003C/p\u003E\n\u003Cp\u003EIn the reference approach, primary identifiers define the intended login surface, subject to the configured identifier resolution and verification policies.\u003C/p\u003E\n\u003Cp\u003EApplications can control which identifier types are accepted and how identifiers are resolved.\u003C/p\u003E\n\u003Cp\u003EThis allows authentication to support different identity strategies without embedding email-specific or username-specific assumptions into Core.\u003C/p\u003E\n\u003Cp\u003EThe distinction is important: \u003Cstrong\u003Ean identifier is identity data; whether it is acceptable for login is an authentication policy decision.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-ownership-and-verification\u0022\u003EIdentifier Ownership and Verification\u003C/h3\u003E\n\u003Cp\u003EIdentifier verification is a security operation, not a simple update to a Boolean property.\u003C/p\u003E\n\u003Cp\u003EMarking an identifier as verified must be based on an appropriate ownership proof or an explicitly authorized administrative operation.\u003C/p\u003E\n\u003Cp\u003EThe reference domain therefore distinguishes between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERegistering an identifier.\u003C/li\u003E\n\u003Cli\u003EEstablishing ownership of an identifier.\u003C/li\u003E\n\u003Cli\u003EVerifying that ownership.\u003C/li\u003E\n\u003Cli\u003EChanging an identifier\u0027s role or status.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAn anonymous or self-service request must not be able to claim verified ownership merely by supplying a verification flag.\u003C/p\u003E\n\u003Cp\u003ELikewise, operations targeting another user\u0027s identifiers must respect the applicable authorization and ownership boundaries.\u003C/p\u003E\n\u003Cp\u003EThis prevents identifier management from becoming an alternative route around authentication security.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022user-profiles\u0022\u003EUser Profiles\u003C/h2\u003E\n\u003Cp\u003EA profile contains information about the user that is not itself an authentication credential or an identity verification proof.\u003C/p\u003E\n\u003Cp\u003ETypical profile information may include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDisplay name and personal information.\u003C/li\u003E\n\u003Cli\u003ELocalization preferences.\u003C/li\u003E\n\u003Cli\u003EApplication metadata.\u003C/li\u003E\n\u003Cli\u003EOther domain-specific user attributes.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth deliberately separates profile data from the security-critical identity model.\u003C/p\u003E\n\u003Cp\u003EApplications should be able to evolve their profile structure without redesigning authentication flows or changing how identifiers are verified.\u003C/p\u003E\n\u003Cp\u003EA profile attribute may still be relevant to application authorization or business rules, but it should not automatically be treated as proof of identity.\u003C/p\u003E\n\u003Cp\u003EThe reference model provides a starting point; applications remain responsible for deciding which additional user attributes belong in their own domain.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022reference-implementation\u0022\u003EReference Implementation\u003C/h2\u003E\n\u003Cp\u003EThe Users Plugin Domain includes a reference implementation that demonstrates and provides a practical user management model.\u003C/p\u003E\n\u003Cp\u003EIt brings together lifecycle, identifiers, and profile management through application services and supporting abstractions.\u003C/p\u003E\n\u003Cp\u003ERather than exposing persistence operations as the primary application interface, the reference implementation places user management rules within the application service layer.\u003C/p\u003E\n\u003Cp\u003EThis layer coordinates operations such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUser creation and account management.\u003C/li\u003E\n\u003Cli\u003EIdentifier registration and updates.\u003C/li\u003E\n\u003Cli\u003EIdentifier verification and ownership validation.\u003C/li\u003E\n\u003Cli\u003EAccount status changes.\u003C/li\u003E\n\u003Cli\u003EIntegration and lifecycle notifications.\u003C/li\u003E\n\u003Cli\u003ESecurity-related coordination with the authentication infrastructure.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis is an important architectural boundary.\u003C/p\u003E\n\u003Cp\u003EA request to verify an identifier, for example, is not equivalent to directly updating a database field. The application service is responsible for enforcing the rules that make the operation valid.\u003C/p\u003E\n\u003Cp\u003EThe reference implementation provides a recommended starting point for these behaviors without requiring every application to use the same persistence technology or user profile structure.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022domain-integration\u0022\u003EDomain Integration\u003C/h2\u003E\n\u003Cp\u003EUser management frequently affects other parts of an identity platform.\u003C/p\u003E\n\u003Cp\u003ECreating a user may be followed by credential enrollment. Changing account security state may require session invalidation. Deleting an account may require coordinated cleanup.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports these scenarios through explicit integration contracts rather than direct dependencies between Plugin Domain implementations.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EIUserLifecycleIntegration\u003C/code\u003E provides an integration boundary for user lifecycle operations.\u003C/p\u003E\n\u003Cp\u003EThis allows other capabilities to participate in user-related workflows while keeping the Users domain responsible for its own state and rules.\u003C/p\u003E\n\u003Cp\u003EThe distinction between domain ownership and cross-domain coordination is central to UltimateAuth\u0027s modular architecture.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022customizing-user-management\u0022\u003ECustomizing User Management\u003C/h2\u003E\n\u003Cp\u003EApplications are not required to accept every detail of the reference implementation.\u003C/p\u003E\n\u003Cp\u003EDepending on their requirements and the available extension contracts, they may:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EProvide custom identifier resolution.\u003C/li\u003E\n\u003Cli\u003EConfigure supported identifier and verification policies.\u003C/li\u003E\n\u003Cli\u003EReplace storage implementations.\u003C/li\u003E\n\u003Cli\u003EIntegrate existing user repositories.\u003C/li\u003E\n\u003Cli\u003EExtend supported lifecycle and application service integrations.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor most applications, the recommended approach is to begin with the reference implementation and customize specific behavior rather than replacing the entire domain.\u003C/p\u003E\n\u003Cp\u003EMore advanced scenarios, including custom domain implementations and integration with existing identity systems, will be covered separately in the customization documentation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-your-user-model\u0022\u003EChoosing Your User Model\u003C/h2\u003E\n\u003Cp\u003EThere are two broad integration approaches.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EUse the reference Users domain\u003C/strong\u003E when the application needs a ready-made foundation for account lifecycle, identifiers, verification, and user management.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EIntegrate an existing identity model\u003C/strong\u003E when the application already owns these responsibilities and needs UltimateAuth primarily for its authentication infrastructure.\u003C/p\u003E\n\u003Cp\u003ENeither approach changes the fundamental security requirements.\u003C/p\u003E\n\u003Cp\u003EIdentity resolution must be trustworthy, identifier ownership must be protected, and authentication state must be managed according to the configured security model.\u003C/p\u003E\n\u003Cp\u003EThe difference is which part of the application owns the user management implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cp\u003EThe Users domain establishes identity ownership and account lifecycle, but it does not independently provide every capability of an identity platform.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Learn how authentication credentials are managed independently of user identifiers.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Explore how identities are associated with permissions and access policies.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains\u0022\u003EPlugin Domains\u003C/a\u003E \u2014 Return to the overall Plugin Domain architecture.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor the distinction between identity and authentication infrastructure, see \u003Ca href=\u0022/docs/fundamentals/authentication-model\u0022\u003EAuthentication Model\u003C/a\u003E.\u003C/p\u003E\n", "Headings": [ { - "Id": "core-concept", - "Text": "\uD83E\uDDE0 Core Concept", + "Id": "understanding-user-identity", + "Text": "Understanding User Identity", "Level": 0 }, { - "Id": "lifecycle-security-anchor", - "Text": "\uD83D\uDD10 Lifecycle (Security Anchor)", + "Id": "user-identity-in-core-and-server", + "Text": "User Identity in Core and Server", "Level": 0 }, { - "Id": "identifiers-login-system", - "Text": "\uD83D\uDD11 Identifiers (Login System)", + "Id": "what-the-users-domain-adds", + "Text": "What the Users Domain Adds", "Level": 0 }, { - "Id": "login-identifiers", - "Text": "\u2B50 Login Identifiers", + "Id": "account-lifecycle", + "Text": "Account Lifecycle", + "Level": 0 + }, + { + "Id": "security-state-and-session-invalidation", + "Text": "Security State and Session Invalidation", "Level": 1 }, { - "Id": "profile-user-data", - "Text": "\uD83E\uDDFE Profile (User Data)", + "Id": "user-identifiers", + "Text": "User Identifiers", + "Level": 0 + }, + { + "Id": "identifier-properties", + "Text": "Identifier Properties", + "Level": 1 + }, + { + "Id": "login-identifier-resolution", + "Text": "Login Identifier Resolution", + "Level": 1 + }, + { + "Id": "identifier-ownership-and-verification", + "Text": "Identifier Ownership and Verification", + "Level": 1 + }, + { + "Id": "user-profiles", + "Text": "User Profiles", + "Level": 0 + }, + { + "Id": "reference-implementation", + "Text": "Reference Implementation", + "Level": 0 + }, + { + "Id": "domain-integration", + "Text": "Domain Integration", "Level": 0 }, { - "Id": "application-service", - "Text": "\u2699\uFE0F Application Service", + "Id": "customizing-user-management", + "Text": "Customizing User Management", "Level": 0 }, { - "Id": "mental-model", - "Text": "\uD83D\uDD01 Mental Model", + "Id": "choosing-your-user-model", + "Text": "Choosing Your User Model", "Level": 0 }, { - "Id": "summary", - "Text": "\uD83C\uDFAF Summary", + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/authorization-pipeline.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/authorization-pipeline.json new file mode 100644 index 00000000..bd52bb12 --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/authorization-pipeline.json @@ -0,0 +1,167 @@ +{ + "Slug": "security/authorization-pipeline", + "Title": "Authorization Pipeline Internals", + "Html": "\n\u003Cp\u003EUltimateAuth authorization is implemented as a coordinated evaluation process rather than a collection of unrelated permission checks.\u003C/p\u003E\n\u003Cp\u003EThe authorization pipeline brings together the operation being requested, the identities involved, the applicable permissions, and the policies responsible for enforcing security requirements.\u003C/p\u003E\n\u003Cp\u003EAt its center are several distinct responsibilities:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EContext preparation:\u003C/strong\u003E Establish the security information required to evaluate an operation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EPermission resolution:\u003C/strong\u003E Make effective authorization grants available to the decision process.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EPolicy resolution:\u003C/strong\u003E Determine which security rules apply to the requested action.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EPolicy evaluation:\u003C/strong\u003E Evaluate the applicable requirements in a defined order.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDecision enforcement:\u003C/strong\u003E Permit the protected operation to proceed only when the authorization result allows it.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis page examines how those responsibilities fit together.\u003C/p\u003E\n\u003Cp\u003EFor an introduction to actions, permissions, and roles, see \u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E. For policy concepts and their intended use, see \u003Ca href=\u0022/docs/plugin-domains/policies\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pipeline-architecture\u0022\u003EPipeline Architecture\u003C/h2\u003E\n\u003Cp\u003EThe authorization process can be represented as a sequence of stages:\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\u003EProtected Operation\n |\n v\n AccessContext\n |\n v\nContext Enrichment\n |\n v\nPermission Resolution\n |\n v\nInvariant Evaluation\n |\n v\nGlobal Policy Evaluation\n |\n v\nRuntime Policy Resolution\n |\n v\nRuntime Policy Evaluation\n |\n v\n Final Decision\n |\n \u002B---- Deny ----------------\u0026gt; Reject\n |\n \u002B---- Reauthentication ---\u0026gt; Require Reauthentication\n |\n \u002B---- Allow ---------------\u0026gt; Execute\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis diagram describes the logical responsibilities of the pipeline.\u003C/p\u003E\n\u003Cp\u003EIt should not be interpreted as a guarantee that every stage performs a separate database query or that every authentication mode uses the same mechanism to obtain authorization information.\u003C/p\u003E\n\u003Cp\u003EThe important property is that \u003Cstrong\u003Eauthorization is evaluated against a coherent access context before the protected operation is allowed to proceed\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accesscontext-as-the-evaluation-boundary\u0022\u003EAccessContext as the Evaluation Boundary\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E carries the information required to evaluate an authorization request.\u003C/p\u003E\n\u003Cp\u003EIt connects the authenticated actor, the intended target, the requested action, and the relevant security environment.\u003C/p\u003E\n\u003Cp\u003EConceptually, its responsibilities include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EInformation\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\u003EActor\u003C/td\u003E\n\u003Ctd\u003EIdentifies the principal performing the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ETarget\u003C/td\u003E\n\u003Ctd\u003EIdentifies the account or resource affected\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAction\u003C/td\u003E\n\u003Ctd\u003EDescribes the protected operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ETenant context\u003C/td\u003E\n\u003Ctd\u003EEstablishes the relevant isolation boundaries\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClaims\u003C/td\u003E\n\u003Ctd\u003EProvides authentication and authorization information\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPermissions\u003C/td\u003E\n\u003Ctd\u003ERepresents the effective permission information available for evaluation\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe context is shared across the authorization process so that individual policies do not need to independently reconstruct the operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022actor-and-target-separation\u0022\u003EActor and Target Separation\u003C/h3\u003E\n\u003Cp\u003EConsider 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\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.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actor is the principal requesting the deletion.\u003C/p\u003E\n\u003Cp\u003EThe target is the user account being deleted.\u003C/p\u003E\n\u003Cp\u003EThese identities may be different, and the authorization pipeline must preserve that distinction.\u003C/p\u003E\n\u003Cp\u003EFor a self-service 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\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.delete.self\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe action indicates a self-service operation, but the action name alone does not prove that actor and target are the same identity.\u003C/p\u003E\n\u003Cp\u003EThat relationship must be established from trusted context and evaluated by the applicable security rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022context-integrity\u0022\u003EContext Integrity\u003C/h3\u003E\n\u003Cp\u003EThe reliability of the authorization decision depends on the integrity of the context.\u003C/p\u003E\n\u003Cp\u003EA target identifier or tenant identifier supplied by a client must not automatically be accepted as a trusted authorization fact.\u003C/p\u003E\n\u003Cp\u003EThe pipeline must operate on the authenticated actor and appropriately resolved target information.\u003C/p\u003E\n\u003Cp\u003EA correctly implemented policy cannot compensate for a context that incorrectly identifies the actor or misrepresents the target.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022context-enrichment\u0022\u003EContext Enrichment\u003C/h2\u003E\n\u003Cp\u003EThe initial authorization context may not contain all the information required by the applicable policies.\u003C/p\u003E\n\u003Cp\u003EContext enrichment makes additional authorization information available before evaluation.\u003C/p\u003E\n\u003Cp\u003EThis can include resolving effective permissions and preparing them for efficient policy checks.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EAuthenticated Actor\n |\n v\nResolve Authorization Information\n |\n v\nEffective Permissions\n |\n v\nCompiledPermissionSet\n |\n v\nEnriched AccessContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe purpose of enrichment is to separate authorization data resolution from the individual rules that consume that data.\u003C/p\u003E\n\u003Cp\u003EA permission policy should not need to independently load role assignments and rebuild the user\u0027s effective permission set every time it evaluates an action.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-data-freshness\u0022\u003EAuthorization Data Freshness\u003C/h3\u003E\n\u003Cp\u003EEnrichment does not automatically guarantee that all authorization information is current.\u003C/p\u003E\n\u003Cp\u003EThe source of permission data matters.\u003C/p\u003E\n\u003Cp\u003EFor example, permissions obtained from current server-side state may reflect a recent role change, while permissions derived exclusively from an already-issued JWT may reflect an earlier state.\u003C/p\u003E\n\u003Cp\u003EThe pipeline can evaluate the information it receives consistently, but it cannot make stale claims current without consulting an authoritative source.\u003C/p\u003E\n\u003Cp\u003EThis distinction is particularly relevant to SemiHybrid and PureJwt authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-compilation\u0022\u003EPermission Compilation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses \u003Ccode\u003ECompiledPermissionSet\u003C/code\u003E to represent permissions in a form suitable for repeated authorization checks.\u003C/p\u003E\n\u003Cp\u003EPermission compilation separates the representation of stored grants from the process of deciding whether an action is permitted.\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\u003ERequested action:\n users.delete.admin\n\nEffective grants:\n users.read.self\n users.delete.admin\n credentials.list.self\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA permission policy can evaluate whether the effective grants cover the requested action.\u003C/p\u003E\n\u003Cp\u003EConceptually:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EpermissionSet.IsAllowed(action);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis illustrates the permission-checking responsibility, rather than a complete public API integration example.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022wildcard-permissions\u0022\u003EWildcard Permissions\u003C/h3\u003E\n\u003Cp\u003ECompiled permissions may also represent wildcard grants.\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.*\nusers.profile.*\n*\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWildcard matching must respect the permission model\u0027s defined action hierarchy.\u003C/p\u003E\n\u003Cp\u003EA broad grant should not be inferred merely because several currently visible permissions share a prefix.\u003C/p\u003E\n\u003Cp\u003EWhere permission normalization replaces a complete action group with a wildcard, the normalization must preserve the intended permission semantics.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022compilation-is-not-authorization\u0022\u003ECompilation Is Not Authorization\u003C/h3\u003E\n\u003Cp\u003EA compiled permission set is an efficient representation of grants.\u003C/p\u003E\n\u003Cp\u003EIt does not independently establish that the actor may perform the operation.\u003C/p\u003E\n\u003Cp\u003EA matching permission can still be insufficient when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe actor is not permitted to access the target.\u003C/li\u003E\n\u003Cli\u003EThe operation crosses a prohibited tenant boundary.\u003C/li\u003E\n\u003Cli\u003EThe account does not satisfy required security conditions.\u003C/li\u003E\n\u003Cli\u003EAn operation-specific policy denies the action.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EPermission matching is one requirement within the broader authorization decision.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-registration-and-resolution\u0022\u003EPolicy Registration and Resolution\u003C/h2\u003E\n\u003Cp\u003ERuntime policies must be discoverable for the operations they protect.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s authorization architecture includes policy registration and compilation concepts represented by:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EAccessPolicyRegistry\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ECompiledAccessPolicySet\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese components separate policy configuration from runtime selection.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003ERegistered Policies\n |\n v\nAccessPolicyRegistry\n |\n v\nCompiledAccessPolicySet\n |\n v\nPolicies Matching Requested Action\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022action-based-selection\u0022\u003EAction-Based Selection\u003C/h3\u003E\n\u003Cp\u003EPolicies may be associated with action prefixes or patterns.\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.*\nusers.delete.*\nusers.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA request for:\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.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Emay select policies associated with the relevant action hierarchy.\u003C/p\u003E\n\u003Cp\u003EThis allows broad policies to cover a domain while more specific policies enforce requirements for individual operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022prefix-matching-and-boundaries\u0022\u003EPrefix Matching and Boundaries\u003C/h3\u003E\n\u003Cp\u003EAction matching must respect the intended structure of action 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\u003Eusers.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Ebelongs to the \u003Ccode\u003Eusers\u003C/code\u003E action domain.\u003C/p\u003E\n\u003Cp\u003EBut an unrelated action whose text merely begins with similar characters should not automatically be treated as belonging to that domain.\u003C/p\u003E\n\u003Cp\u003EPolicy matching must therefore follow the actual action-pattern semantics implemented by the registry.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resolution-versus-evaluation\u0022\u003EResolution Versus Evaluation\u003C/h3\u003E\n\u003Cp\u003EPolicy resolution answers:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EWhich policies are candidates for this operation?\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EPolicy evaluation answers:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo those policies permit this operation in the current context?\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA policy may match an action pattern but still be inapplicable to a particular runtime context.\u003C/p\u003E\n\u003Cp\u003EThat distinction is handled through policy applicability rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-applicability\u0022\u003ERuntime Applicability\u003C/h2\u003E\n\u003Cp\u003EA policy can determine whether it applies to the current authorization context.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth represents this responsibility through \u003Ccode\u003EAppliesTo\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example, a policy restricting administrative self-modification may only be relevant when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe requested action has administrative scope.\u003C/li\u003E\n\u003Cli\u003EThe operation changes protected account state.\u003C/li\u003E\n\u003Cli\u003EThe actor and target are the same identity.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIt may not apply to administrative read operations or unrelated domains.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EPolicy selected by action\n |\n v\nAppliesTo(context)\n |\n \u002B---- No ----\u0026gt; Skip policy\n |\n \u002B---- Yes ---\u0026gt; Evaluate policy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis enables policies to remain focused on specific security requirements without requiring every action to have an entirely separate policy implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022applicability-is-not-a-security-bypass\u0022\u003EApplicability Is Not a Security Bypass\u003C/h3\u003E\n\u003Cp\u003EAn applicability rule determines when a policy participates in evaluation.\u003C/p\u003E\n\u003Cp\u003EIf a mandatory security requirement can be skipped because its applicability condition is too broad or incorrectly implemented, the resulting authorization boundary may be weaker than intended.\u003C/p\u003E\n\u003Cp\u003EPolicies enforcing fundamental security invariants should not depend on application-controlled applicability rules that can bypass those invariants.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-evaluation-stages\u0022\u003EPolicy Evaluation Stages\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s policy architecture distinguishes between invariants, global policies, and runtime policies.\u003C/p\u003E\n\u003Cp\u003EThese categories establish the order and scope of security evaluation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022stage-1-invariants\u0022\u003EStage 1: Invariants\u003C/h3\u003E\n\u003Cp\u003EInvariants represent mandatory security requirements.\u003C/p\u003E\n\u003Cp\u003EThey are evaluated before ordinary policy stages.\u003C/p\u003E\n\u003Cp\u003EAn invariant denial prevents the requested operation from proceeding, regardless of whether another policy would otherwise permit it.\u003C/p\u003E\n\u003Cp\u003EInvariants provide the strongest enforcement boundary within the policy pipeline.\u003C/p\u003E\n\u003Cp\u003EThey should not be treated as ordinary permissions that can be overridden by a broader grant.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022stage-2-global-policies\u0022\u003EStage 2: Global Policies\u003C/h3\u003E\n\u003Cp\u003EGlobal policies enforce broadly applicable security requirements.\u003C/p\u003E\n\u003Cp\u003EThey may examine the operation\u0027s context to determine whether their rule is relevant.\u003C/p\u003E\n\u003Cp\u003EExamples include requirements related to authentication state, account eligibility, and tenant isolation.\u003C/p\u003E\n\u003Cp\u003EGlobal policies allow these requirements to be enforced consistently without attaching identical rules to every individual action.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022stage-3-runtime-policies\u0022\u003EStage 3: Runtime Policies\u003C/h3\u003E\n\u003Cp\u003ERuntime policies are resolved according to the requested action and evaluated against the current context.\u003C/p\u003E\n\u003Cp\u003EThey support requirements such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequired permissions.\u003C/li\u003E\n\u003Cli\u003ESelf-access restrictions.\u003C/li\u003E\n\u003Cli\u003EAdministrative operation constraints.\u003C/li\u003E\n\u003Cli\u003EDomain-specific authorization rules.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ERuntime policies extend the decision process without replacing the mandatory security requirements established by earlier stages.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022iaccessauthority-and-decision-evaluation\u0022\u003EIAccessAuthority and Decision Evaluation\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EIAccessAuthority\u003C/code\u003E represents the authorization decision responsibility within the pipeline.\u003C/p\u003E\n\u003Cp\u003EThe authority evaluates the applicable security requirements and produces an authorization result.\u003C/p\u003E\n\u003Cp\u003EConceptually, the result can represent:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EResult\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\u003Cstrong\u003EAllow\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe applicable requirements permit the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EDeny\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EA required security condition rejects the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EReauthentication required\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe operation requires an additional authentication condition before proceeding\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese outcomes must not be confused with the result of an individual policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-allow-versus-final-allow\u0022\u003EPolicy Allow Versus Final Allow\u003C/h3\u003E\n\u003Cp\u003EAn individual policy may return an allow result because its own requirement has been satisfied.\u003C/p\u003E\n\u003Cp\u003EThat does not necessarily mean the entire operation is authorized.\u003C/p\u003E\n\u003Cp\u003EOther applicable policies may still deny the request or require reauthentication.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAn individual policy\u0027s approval is not a universal authorization grant.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deny-precedence\u0022\u003EDeny Precedence\u003C/h3\u003E\n\u003Cp\u003EA denial from an applicable mandatory policy prevents the operation from being authorized.\u003C/p\u003E\n\u003Cp\u003EThe pipeline must not allow a later permissive policy to override a previously established denial.\u003C/p\u003E\n\u003Cp\u003EThis supports predictable enforcement across multiple independent security rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reauthentication-requirements\u0022\u003EReauthentication Requirements\u003C/h3\u003E\n\u003Cp\u003ESome operations may require stronger or more recent authentication.\u003C/p\u003E\n\u003Cp\u003EA policy can represent that requirement separately from an ordinary denial.\u003C/p\u003E\n\u003Cp\u003EThe authorization system must distinguish between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn operation that is prohibited.\u003C/li\u003E\n\u003Cli\u003EAn operation that may proceed after additional authentication.\u003C/li\u003E\n\u003Cli\u003EAn operation that is currently authorized.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact precedence and propagation of reauthentication results must follow the implemented decision engine.\u003C/p\u003E\n\u003Cp\u003EA reauthentication result should not be interpreted as permission to execute the protected operation immediately.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-policies\u0022\u003EPermission Policies\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EMustHavePermissionPolicy\u003C/code\u003E represents the connection between the permission model and the policy pipeline.\u003C/p\u003E\n\u003Cp\u003EIts responsibility is to evaluate whether the actor has the permission required for the requested action.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EAccessContext.Action\n |\n v\nCompiledPermissionSet\n |\n v\nPermission Evaluation\n |\n v\nPolicy Decision\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis design separates permission storage and resolution from permission enforcement.\u003C/p\u003E\n\u003Cp\u003ERoles and assignments determine effective grants.\u003C/p\u003E\n\u003Cp\u003EThe compiled permission set represents those grants.\u003C/p\u003E\n\u003Cp\u003EThe permission policy evaluates the requested action against them.\u003C/p\u003E\n\u003Cp\u003EThe final authorization decision also considers the other applicable security policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permissions-are-not-a-replacement-for-policies\u0022\u003EPermissions Are Not a Replacement for Policies\u003C/h3\u003E\n\u003Cp\u003EFor example, an actor may possess:\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.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThat permission does not automatically authorize deleting any user in any tenant.\u003C/p\u003E\n\u003Cp\u003EThe operation must still satisfy the relevant target, tenant, and account security requirements.\u003C/p\u003E\n\u003Cp\u003EThis is why the pipeline evaluates multiple requirements rather than relying on a single permission match.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022orchestration-and-protected-execution\u0022\u003EOrchestration and Protected Execution\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessOrchestrator\u003C/code\u003E coordinates authorization and the protected operation.\u003C/p\u003E\n\u003Cp\u003EIts purpose is to establish a consistent enforcement boundary.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EOperation Requested\n |\n v\nPrepare AccessContext\n |\n v\nResolve and Evaluate Policies\n |\n \u002B---- Not Allowed ---\u0026gt; Stop\n |\n v\nExecute Protected Operation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe security requirement is straightforward:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA protected operation must not execute merely because its caller reached an application service or endpoint.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAuthorization must succeed at an enforcement boundary that covers the operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-and-in-process-execution\u0022\u003EHTTP and In-Process Execution\u003C/h3\u003E\n\u003Cp\u003EHTTP endpoints may enforce authorization through ASP.NET Core authentication and authorization integration.\u003C/p\u003E\n\u003Cp\u003EHowever, applications may also execute operations directly through in-process services.\u003C/p\u003E\n\u003Cp\u003EFor example, a Blazor Server application can invoke a server-side service without making a separate HTTP request.\u003C/p\u003E\n\u003Cp\u003EA controller-level \u003Ccode\u003E[Authorize]\u003C/code\u003E attribute does not automatically protect such a direct service invocation.\u003C/p\u003E\n\u003Cp\u003EWhere operations can be reached through multiple execution paths, authorization must be enforced at an appropriate shared boundary.\u003C/p\u003E\n\u003Cp\u003EThis prevents one entry point from bypassing rules enforced by another.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022determinism-and-security\u0022\u003EDeterminism and Security\u003C/h2\u003E\n\u003Cp\u003EA structured policy pipeline makes authorization behavior easier to reason about.\u003C/p\u003E\n\u003Cp\u003EFor the same effective context, policy configuration, and authoritative security state, the decision should follow the same evaluation rules.\u003C/p\u003E\n\u003Cp\u003EHowever, authorization can depend on mutable information:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAccount status.\u003C/li\u003E\n\u003Cli\u003ETenant membership.\u003C/li\u003E\n\u003Cli\u003ERole assignments.\u003C/li\u003E\n\u003Cli\u003EEffective permissions.\u003C/li\u003E\n\u003Cli\u003ESession security state.\u003C/li\u003E\n\u003Cli\u003ETarget ownership.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA policy evaluation that uses stale or inconsistent data may produce a result that differs from one using current authoritative state.\u003C/p\u003E\n\u003Cp\u003EDeterministic policy evaluation therefore depends on both the evaluation rules and the consistency of the information supplied to them.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022time-of-check-and-time-of-use\u0022\u003ETime-of-Check and Time-of-Use\u003C/h3\u003E\n\u003Cp\u003EAuthorization may be evaluated before a protected operation modifies data.\u003C/p\u003E\n\u003Cp\u003EIf relevant security or ownership information changes between the authorization decision and the mutation, a time-of-check/time-of-use problem may arise.\u003C/p\u003E\n\u003Cp\u003ESensitive operations may require transactional consistency, concurrency controls, or revalidation within the protected operation.\u003C/p\u003E\n\u003Cp\u003EA policy pipeline provides a structured decision process, but it does not automatically make every application-level data mutation atomic.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extensibility-boundaries\u0022\u003EExtensibility Boundaries\u003C/h2\u003E\n\u003Cp\u003EThe authorization pipeline is designed to support application-specific policies.\u003C/p\u003E\n\u003Cp\u003ECustom policies can introduce new requirements without replacing the underlying permission model or authentication engine.\u003C/p\u003E\n\u003Cp\u003EHowever, extensibility must preserve the security hierarchy.\u003C/p\u003E\n\u003Cp\u003EA custom policy should not:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EOverride mandatory invariants.\u003C/li\u003E\n\u003Cli\u003ETrust caller-supplied identity information without validation.\u003C/li\u003E\n\u003Cli\u003ETreat an action name as proof of actor-target ownership.\u003C/li\u003E\n\u003Cli\u003EBypass tenant isolation through a broad permission.\u003C/li\u003E\n\u003Cli\u003EAssume that JWT claims always reflect current authorization state.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ECustom policies should evaluate well-defined requirements using trusted context and participate in the established decision model.\u003C/p\u003E\n\u003Cp\u003EImplementation and registration examples belong in \u003Ca href=\u0022/docs/how-to\u0022\u003EHow-to Guides\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s authorization pipeline separates the responsibilities involved in an access decision.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E describes the operation.\u003C/p\u003E\n\u003Cp\u003EContext enrichment and permission compilation prepare the required authorization information.\u003C/p\u003E\n\u003Cp\u003EPolicy resolution identifies the relevant rules.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EIAccessAuthority\u003C/code\u003E evaluates those rules according to the pipeline\u0027s decision semantics.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessOrchestrator\u003C/code\u003E coordinates authorization with protected execution.\u003C/p\u003E\n\u003Cp\u003ETogether, these components provide a structured foundation for context-aware, policy-driven authorization.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe pipeline\u0027s security comes not from the number of policies it executes, but from consistently enforcing the applicable requirements before a protected operation is allowed to proceed.\u003C/strong\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/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Actions, permissions, roles, and scopes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/policies\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E \u2014 Policy concepts and access decision behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security-model\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Authentication security state and validation boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E \u2014 Credential validity and authorization freshness.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Headings": [ + { + "Id": "pipeline-architecture", + "Text": "Pipeline Architecture", + "Level": 0 + }, + { + "Id": "accesscontext-as-the-evaluation-boundary", + "Text": "AccessContext as the Evaluation Boundary", + "Level": 0 + }, + { + "Id": "actor-and-target-separation", + "Text": "Actor and Target Separation", + "Level": 1 + }, + { + "Id": "context-integrity", + "Text": "Context Integrity", + "Level": 1 + }, + { + "Id": "context-enrichment", + "Text": "Context Enrichment", + "Level": 0 + }, + { + "Id": "authorization-data-freshness", + "Text": "Authorization Data Freshness", + "Level": 1 + }, + { + "Id": "permission-compilation", + "Text": "Permission Compilation", + "Level": 0 + }, + { + "Id": "wildcard-permissions", + "Text": "Wildcard Permissions", + "Level": 1 + }, + { + "Id": "compilation-is-not-authorization", + "Text": "Compilation Is Not Authorization", + "Level": 1 + }, + { + "Id": "policy-registration-and-resolution", + "Text": "Policy Registration and Resolution", + "Level": 0 + }, + { + "Id": "action-based-selection", + "Text": "Action-Based Selection", + "Level": 1 + }, + { + "Id": "prefix-matching-and-boundaries", + "Text": "Prefix Matching and Boundaries", + "Level": 1 + }, + { + "Id": "resolution-versus-evaluation", + "Text": "Resolution Versus Evaluation", + "Level": 1 + }, + { + "Id": "runtime-applicability", + "Text": "Runtime Applicability", + "Level": 0 + }, + { + "Id": "applicability-is-not-a-security-bypass", + "Text": "Applicability Is Not a Security Bypass", + "Level": 1 + }, + { + "Id": "policy-evaluation-stages", + "Text": "Policy Evaluation Stages", + "Level": 0 + }, + { + "Id": "stage-1-invariants", + "Text": "Stage 1: Invariants", + "Level": 1 + }, + { + "Id": "stage-2-global-policies", + "Text": "Stage 2: Global Policies", + "Level": 1 + }, + { + "Id": "stage-3-runtime-policies", + "Text": "Stage 3: Runtime Policies", + "Level": 1 + }, + { + "Id": "iaccessauthority-and-decision-evaluation", + "Text": "IAccessAuthority and Decision Evaluation", + "Level": 0 + }, + { + "Id": "policy-allow-versus-final-allow", + "Text": "Policy Allow Versus Final Allow", + "Level": 1 + }, + { + "Id": "deny-precedence", + "Text": "Deny Precedence", + "Level": 1 + }, + { + "Id": "reauthentication-requirements", + "Text": "Reauthentication Requirements", + "Level": 1 + }, + { + "Id": "permission-policies", + "Text": "Permission Policies", + "Level": 0 + }, + { + "Id": "permissions-are-not-a-replacement-for-policies", + "Text": "Permissions Are Not a Replacement for Policies", + "Level": 1 + }, + { + "Id": "orchestration-and-protected-execution", + "Text": "Orchestration and Protected Execution", + "Level": 0 + }, + { + "Id": "http-and-in-process-execution", + "Text": "HTTP and In-Process Execution", + "Level": 1 + }, + { + "Id": "determinism-and-security", + "Text": "Determinism and Security", + "Level": 0 + }, + { + "Id": "time-of-check-and-time-of-use", + "Text": "Time-of-Check and Time-of-Use", + "Level": 1 + }, + { + "Id": "extensibility-boundaries", + "Text": "Extensibility Boundaries", + "Level": 0 + }, + { + "Id": "architectural-perspective", + "Text": "Architectural Perspective", + "Level": 0 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/refresh-rotation.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/refresh-rotation.json index 2782ef36..f0c60fbf 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/refresh-rotation.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/refresh-rotation.json @@ -1,86 +1,141 @@ { "Slug": "security/refresh-rotation", - "Title": "Refresh Rotation", - "Html": "\n\u003Cp\u003ERefresh tokens in UltimateAuth are not simple long-lived tokens.\u003C/p\u003E\n\u003Cp\u003EThey are part of a \u003Cstrong\u003Erotation-based security system\u003C/strong\u003E designed to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eprevent token replay\u003C/li\u003E\n\u003Cli\u003Edetect token theft\u003C/li\u003E\n\u003Cli\u003Eenforce forward-only session progression\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-rotation\u0022\u003E\uD83E\uDDE0 Why Rotation?\u003C/h2\u003E\n\u003Cp\u003EIn traditional systems:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Erefresh tokens are long-lived\u003C/li\u003E\n\u003Cli\u003Ethey can be reused multiple times\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 If stolen, an attacker can:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esilently keep refreshing access tokens\u003C/li\u003E\n\u003Cli\u003Estay undetected\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth solves this with:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Cstrong\u003Esingle-use refresh tokens (rotation)\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-model\u0022\u003E\uD83D\uDD01 Rotation Model\u003C/h2\u003E\n\u003Cp\u003EEach refresh token is:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eused exactly once\u003C/li\u003E\n\u003Cli\u003Ereplaced with a new token\u003C/li\u003E\n\u003Cli\u003Elinked to a chain\u003C/li\u003E\n\u003C/ul\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\u003EToken A \u2192 Token B \u2192 Token C \u2192 ...\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen a refresh happens:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EToken A is validated\u003C/li\u003E\n\u003Cli\u003EToken A is \u003Cstrong\u003Erevoked\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003EToken B is issued\u003C/li\u003E\n\u003Cli\u003EToken A is marked as replaced by B\u003C/li\u003E\n\u003C/ol\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-state\u0022\u003E\uD83D\uDD10 Token State\u003C/h2\u003E\n\u003Cp\u003EA refresh token can be:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EActive \u2192 valid and usable\u003C/li\u003E\n\u003Cli\u003ERevoked \u2192 already used or manually revoked\u003C/li\u003E\n\u003Cli\u003EExpired \u2192 lifetime exceeded\u003C/li\u003E\n\u003Cli\u003EReplaced \u2192 already rotated\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Only \u003Cstrong\u003Eactive tokens\u003C/strong\u003E are valid\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022reuse-detection\u0022\u003E\uD83D\uDEA8 Reuse Detection\u003C/h2\u003E\n\u003Cp\u003EThis is the most critical security feature.\u003C/p\u003E\n\u003Cp\u003EIf a refresh token is used \u003Cstrong\u003Eafter it has already been rotated\u003C/strong\u003E, it means:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 The token was reused\u003Cbr\u003E\n\uD83D\uDC49 Likely stolen\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-happens\u0022\u003EWhat happens?\u003C/h3\u003E\n\u003Cp\u003EWhen reuse is detected:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ethe system identifies the session chain\u003C/li\u003E\n\u003Cli\u003Ethe entire chain can be revoked\u003C/li\u003E\n\u003Cli\u003Eall related sessions become invalid\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 This immediately cuts off both:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eattacker\u003C/li\u003E\n\u003Cli\u003Elegitimate user (forcing reauthentication)\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-awareness\u0022\u003E\uD83D\uDD17 Chain Awareness\u003C/h2\u003E\n\u003Cp\u003ERefresh tokens belong to a \u003Cstrong\u003Esession chain\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThis enables:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Etracking rotation history\u003C/li\u003E\n\u003Cli\u003Edetecting anomalies\u003C/li\u003E\n\u003Cli\u003Eapplying revocation at the correct scope\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EWithout chains:\u003C/p\u003E\n\u003Cp\u003E\u274C You cannot safely detect reuse\u003Cbr\u003E\n\u274C You cannot know which tokens belong together\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-flow\u0022\u003E\uD83D\uDD04 Rotation Flow\u003C/h2\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 \u2192 Refresh(Token A)\n \u2192 Validate A\n \u2192 Revoke A\n \u2192 Issue B\n \u2192 Return new tokens\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022invalid-scenarios\u0022\u003E\u26A0\uFE0F Invalid Scenarios\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022expired-token\u0022\u003E1. Expired Token\u003C/h3\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\u003EToken expired \u2192 reject\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoked-token\u0022\u003E2. Revoked Token\u003C/h3\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\u003EToken already used \u2192 reuse detected\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-mismatch\u0022\u003E3. Session Mismatch\u003C/h3\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\u003EToken does not belong to expected session \u2192 reject\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-guarantees\u0022\u003E\uD83E\uDDE0 Security Guarantees\u003C/h2\u003E\n\u003Cp\u003ERotation ensures:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Erefresh tokens are forward-only\u003C/li\u003E\n\u003Cli\u003Eold tokens cannot be reused safely\u003C/li\u003E\n\u003Cli\u003Estolen tokens are detectable\u003C/li\u003E\n\u003Cli\u003Ecompromise triggers containment\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-matters\u0022\u003E\uD83D\uDD25 Why This Matters\u003C/h2\u003E\n\u003Cp\u003ECompared to traditional refresh tokens:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EFeature\u003C/th\u003E\n\u003Cth\u003ETraditional\u003C/th\u003E\n\u003Cth\u003EUltimateAuth\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReusable tokens\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReuse detection\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EChain awareness\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAutomatic containment\u003C/td\u003E\n\u003Ctd\u003E\u274C\u003C/td\u003E\n\u003Ctd\u003E\u2705\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=\u0022design-tradeoff\u0022\u003E\u26A0\uFE0F Design Tradeoff\u003C/h2\u003E\n\u003Cp\u003ERotation requires:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Etoken storage\u003C/li\u003E\n\u003Cli\u003Estate tracking\u003C/li\u003E\n\u003Cli\u003Eadditional validation logic\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 UltimateAuth chooses security over simplicity.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 A refresh token is not a reusable key\u003Cbr\u003E\n\uD83D\uDC49 It is a \u003Cstrong\u003Eone-time step in a chain\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EAccess Token Behavior\u003C/strong\u003E to understand how short-lived tokens interact with rotation.\u003C/p\u003E\n", + "Title": "Refresh Rotation \u0026 Replay Protection", + "Html": "\n\u003Cp\u003ERefresh credentials allow authentication to continue without requiring the user to repeat the original login process.\u003C/p\u003E\n\u003Cp\u003EBecause a refresh credential may remain useful longer than an access credential, its compromise can have significant security consequences.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERefresh rotation\u003C/strong\u003E is one mechanism for reducing this risk. It replaces previously issued refresh authentication material as part of a successful continuation operation.\u003C/p\u003E\n\u003Cp\u003EHowever, secure rotation involves more than generating a new token. It requires consistent state transitions, concurrency control, invalid credential handling, and a clear policy for suspected credential reuse.\u003C/p\u003E\n\u003Cp\u003EThis page examines those security properties and their limitations.\u003C/p\u003E\n\u003Cp\u003EFor token formats, access token validation, and general refresh behavior, see \u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-as-a-security-protocol\u0022\u003ERotation as a Security Protocol\u003C/h2\u003E\n\u003Cp\u003EIn a rotating refresh strategy, a successfully consumed refresh credential is replaced with new authentication material.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003ERefresh A\n |\n | Successful continuation\n v\nRefresh A consumed\nRefresh B issued\n |\n | Successful continuation\n v\nRefresh B consumed\nRefresh C issued\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe security objective is to prevent an older refresh credential from remaining indefinitely reusable after a successful continuation.\u003C/p\u003E\n\u003Cp\u003EA rotating refresh mechanism must maintain enough authoritative state to distinguish between credentials that are currently eligible for use and credentials that have already been consumed or invalidated.\u003C/p\u003E\n\u003Cp\u003EThis is fundamentally different from validating a self-contained access token using only its signature and expiration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-rotation-does-and-does-not-guarantee\u0022\u003EWhat Rotation Does and Does Not Guarantee\u003C/h3\u003E\n\u003Cp\u003ERotation can:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELimit the continued usefulness of previously consumed credentials.\u003C/li\u003E\n\u003Cli\u003EProvide a signal when an invalidated credential is presented again.\u003C/li\u003E\n\u003Cli\u003ESupport targeted security responses to suspicious continuation attempts.\u003C/li\u003E\n\u003Cli\u003EReduce the opportunities for prolonged reuse of the same refresh credential.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ERotation does not inherently:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPrevent the initial theft of a refresh credential.\u003C/li\u003E\n\u003Cli\u003EProve that a reuse attempt was made by an attacker.\u003C/li\u003E\n\u003Cli\u003EGuarantee that the legitimate client wins a race against an attacker.\u003C/li\u003E\n\u003Cli\u003EInvalidate every previously issued access token.\u003C/li\u003E\n\u003Cli\u003EGuarantee immediate containment without an enforced revocation mechanism.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe strength of the protection depends on how the refresh strategy implements and validates its state transitions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-considerations\u0022\u003EAuthentication Mode Considerations\u003C/h2\u003E\n\u003Cp\u003ERefresh behavior is not identical across UltimateAuth\u0027s authentication modes.\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\u003ERefresh and rotation considerations\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureOpaque\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses opaque session continuation rather than a separate access/refresh credential pair\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses opaque access and refresh credentials with server-authoritative session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESemiHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses JWT access tokens and server-managed refresh lifecycle state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureJwt\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses locally validated JWT access tokens and may use an optional refresh store\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe presence of a refresh operation does not necessarily mean that a separate refresh credential is replaced on every invocation.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured mode and strategy, a continuation operation may rotate credentials, update existing server-managed state, or follow another supported lifecycle behavior.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERotation guarantees must be evaluated against the selected refresh strategy, not assumed from the existence of a refresh endpoint.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-credential-state\u0022\u003ERefresh Credential State\u003C/h2\u003E\n\u003Cp\u003EA rotating refresh mechanism needs to distinguish credentials that remain eligible for continuation from credentials that are no longer acceptable.\u003C/p\u003E\n\u003Cp\u003EConceptually, relevant conditions include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECondition\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\u003Cstrong\u003EActive\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe credential is eligible for a refresh attempt, subject to all applicable checks\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EConsumed or replaced\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe credential has already participated in a successful rotation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoked\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe credential or associated refresh authority has been explicitly invalidated\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EExpired\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe credential has exceeded its permitted lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EInvalid or unknown\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe presented value cannot be resolved to an acceptable refresh credential\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese are conceptual security conditions, not necessarily distinct persisted enum values in every implementation.\u003C/p\u003E\n\u003Cp\u003EA consumed credential and an administratively revoked credential may both be unusable, but the reasons for rejection are different.\u003C/p\u003E\n\u003Cp\u003EThat distinction matters when evaluating whether a failed refresh represents ordinary expiration, an invalid request, or potential credential reuse.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-rotation-transaction\u0022\u003EThe Rotation Transaction\u003C/h2\u003E\n\u003Cp\u003EA successful rotating refresh operation involves a sequence of security-sensitive steps.\u003C/p\u003E\n\u003Cp\u003EConceptually:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EResolve the presented refresh credential.\u003C/li\u003E\n\u003Cli\u003EValidate its eligibility for continuation.\u003C/li\u003E\n\u003Cli\u003EValidate the applicable session, account, and client context.\u003C/li\u003E\n\u003Cli\u003EEstablish that the credential has not already been consumed.\u003C/li\u003E\n\u003Cli\u003EApply the configured rotation transition.\u003C/li\u003E\n\u003Cli\u003EIssue the replacement authentication material.\u003C/li\u003E\n\u003Cli\u003EPersist the resulting state consistently.\u003C/li\u003E\n\u003Cli\u003EReturn the renewed credentials to the client.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe exact implementation and ordering depend on the selected refresh strategy.\u003C/p\u003E\n\u003Cp\u003EThe important security requirement is that the transition must not leave multiple conflicting refresh credentials valid because two requests observed the same earlier state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022atomicity\u0022\u003EAtomicity\u003C/h3\u003E\n\u003Cp\u003EConsider a refresh credential \u003Ccode\u003EA\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ETwo requests arrive almost simultaneously:\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\u003ERequest 1: Refresh(A)\nRequest 2: Refresh(A)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn unsafe implementation might allow both requests to read \u003Ccode\u003EA\u003C/code\u003E as active before either marks it consumed.\u003C/p\u003E\n\u003Cp\u003EBoth requests could then issue different replacement credentials:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Refresh A\n |\n \u002B------\u002B------\u002B\n | |\n Request 1 Request 2\n | |\n v v\n Refresh B Refresh C\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIf both replacements remain valid, the system has unintentionally created competing continuation paths.\u003C/p\u003E\n\u003Cp\u003EA single-use rotation strategy must prevent this outcome through an appropriate atomic state transition or concurrency mechanism.\u003C/p\u003E\n\u003Cp\u003EPossible implementation techniques include transactional updates, compare-and-swap operations, or other storage-specific concurrency controls.\u003C/p\u003E\n\u003Cp\u003EThe precise mechanism is an implementation concern, but the security property is essential:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOnly the transition permitted by the configured rotation semantics may succeed.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022concurrent-refresh-requests\u0022\u003EConcurrent Refresh Requests\u003C/h2\u003E\n\u003Cp\u003EConcurrent refresh requests are not necessarily malicious.\u003C/p\u003E\n\u003Cp\u003EThey can occur when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESeveral API requests discover an expired access token at the same time.\u003C/li\u003E\n\u003Cli\u003EMultiple browser tabs attempt to refresh authentication.\u003C/li\u003E\n\u003Cli\u003EA client retries a request after a network interruption.\u003C/li\u003E\n\u003Cli\u003EBackground and foreground operations initiate refresh simultaneously.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EWithout coordination, a legitimate client can submit the same refresh credential more than once.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-side-coordination\u0022\u003EClient-Side Coordination\u003C/h3\u003E\n\u003Cp\u003EApplications should avoid issuing unnecessary concurrent refresh requests.\u003C/p\u003E\n\u003Cp\u003EA common approach is to coordinate refresh through a single in-progress operation and allow other requests to await its result.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EAPI Request A \u2500\u2500\u2510\nAPI Request B \u2500\u2500\u253C\u2500\u2500\u0026gt; One refresh operation\nAPI Request C \u2500\u2500\u2518 |\n v\n Renewed credentials\n |\n \u002B-----------\u002B-----------\u002B\n | | |\n v v v\n Retry A Retry B Retry C\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EClient coordination improves reliability and reduces accidental reuse attempts.\u003C/p\u003E\n\u003Cp\u003EHowever, it is not a replacement for server-side concurrency enforcement.\u003C/p\u003E\n\u003Cp\u003EDifferent devices, processes, or compromised clients cannot be assumed to share the same coordination mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-side-enforcement\u0022\u003EServer-Side Enforcement\u003C/h3\u003E\n\u003Cp\u003EThe server remains responsible for enforcing the configured refresh credential lifecycle.\u003C/p\u003E\n\u003Cp\u003EA client-side lock cannot guarantee that an attacker or another client instance will not present the same credential.\u003C/p\u003E\n\u003Cp\u003EFor single-use rotation, the server must ensure that a consumed credential cannot successfully establish another independent continuation path.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-credential-reuse\u0022\u003ERefresh Credential Reuse\u003C/h2\u003E\n\u003Cp\u003ERefresh credential reuse occurs when a credential is presented after it is no longer eligible for the operation.\u003C/p\u003E\n\u003Cp\u003EFor a single-use rotating strategy, presenting a previously consumed credential is a particularly important case.\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\u003EInitial state:\n Refresh A is active\n\nFirst request:\n Refresh(A) succeeds\n A is consumed\n B is issued\n\nLater request:\n Refresh(A) is attempted again\n A is no longer eligible\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe later request must not be accepted as a new successful use of \u003Ccode\u003EA\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reuse-is-a-security-signal\u0022\u003EReuse Is a Security Signal\u003C/h3\u003E\n\u003Cp\u003EA consumed refresh credential may be presented again because:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn attacker copied the credential before rotation.\u003C/li\u003E\n\u003Cli\u003EA legitimate client retried after losing the refresh response.\u003C/li\u003E\n\u003Cli\u003ETwo client operations raced to refresh simultaneously.\u003C/li\u003E\n\u003Cli\u003EClient credential storage was not updated consistently.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe server may not have enough information to distinguish these situations conclusively.\u003C/p\u003E\n\u003Cp\u003ETherefore, \u003Cstrong\u003Ereuse detection identifies a suspicious credential lifecycle event; it does not automatically prove credential theft.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022detection-versus-response\u0022\u003EDetection Versus Response\u003C/h3\u003E\n\u003Cp\u003EDetecting invalid reuse and responding to it are separate responsibilities.\u003C/p\u003E\n\u003Cp\u003EA refresh strategy may reject the request without taking broader action.\u003C/p\u003E\n\u003Cp\u003EA stricter security policy may invalidate additional refresh state or revoke an associated session or chain.\u003C/p\u003E\n\u003Cp\u003EThe appropriate response depends on the implementation, authentication mode, and supported security policy.\u003C/p\u003E\n\u003Cp\u003EA documentation claim that reuse always causes chain-wide revocation would be correct only if that behavior is actually enforced by the selected strategy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022replay-and-token-theft-scenarios\u0022\u003EReplay and Token Theft Scenarios\u003C/h2\u003E\n\u003Cp\u003ERotation is particularly useful when reasoning about stolen refresh credentials.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022scenario-1-the-legitimate-client-refreshes-first\u0022\u003EScenario 1: The Legitimate Client Refreshes First\u003C/h3\u003E\n\u003Cp\u003EAn attacker copies refresh credential \u003Ccode\u003EA\u003C/code\u003E, but the legitimate client uses it before the attacker.\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\u003ELegitimate client:\n A -\u0026gt; B\n\nAttacker:\n Attempts A\n A is already consumed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIf the rotation state is enforced correctly, the attacker\u0027s attempt to use \u003Ccode\u003EA\u003C/code\u003E cannot produce another successful continuation.\u003C/p\u003E\n\u003Cp\u003EThe server may also be able to identify the attempt as reuse.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022scenario-2-the-attacker-refreshes-first\u0022\u003EScenario 2: The Attacker Refreshes First\u003C/h3\u003E\n\u003Cp\u003EAn attacker copies active refresh credential \u003Ccode\u003EA\u003C/code\u003E and successfully uses it before the legitimate client.\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\u003EAttacker:\n A -\u0026gt; B\n\nLegitimate client:\n Attempts A\n A is already consumed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn this case, the attacker may already possess the replacement credential \u003Ccode\u003EB\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ERejecting the legitimate client\u0027s later use of \u003Ccode\u003EA\u003C/code\u003E is necessary, but it does not by itself remove the attacker\u0027s newly obtained credential.\u003C/p\u003E\n\u003Cp\u003EA broader revocation response may be required to contain the compromise.\u003C/p\u003E\n\u003Cp\u003EThis illustrates why rotation and reuse detection are not identical to automatic compromise recovery.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022scenario-3-a-refresh-response-is-lost\u0022\u003EScenario 3: A Refresh Response Is Lost\u003C/h3\u003E\n\u003Cp\u003EThe legitimate client sends \u003Ccode\u003EA\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe server successfully consumes \u003Ccode\u003EA\u003C/code\u003E and issues \u003Ccode\u003EB\u003C/code\u003E, but the response never reaches the client.\u003C/p\u003E\n\u003Cp\u003EThe client still possesses \u003Ccode\u003EA\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA retry may now appear to be reuse.\u003C/p\u003E\n\u003Cp\u003EThis creates an important reliability and security tradeoff.\u003C/p\u003E\n\u003Cp\u003EStrict single-use semantics can reject the retry, while a more tolerant continuation strategy requires carefully defined behavior that does not accidentally create an exploitable replay window.\u003C/p\u003E\n\u003Cp\u003EThe chosen strategy must handle this situation consistently.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-and-chain-association\u0022\u003ESession and Chain Association\u003C/h2\u003E\n\u003Cp\u003EIn session-managed authentication modes, refresh operations can be associated with session and chain state.\u003C/p\u003E\n\u003Cp\u003EThis allows the authentication infrastructure to evaluate continuation within the relevant security context.\u003C/p\u003E\n\u003Cp\u003EFor example, a refresh operation may need to establish that:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe refresh credential belongs to the expected authentication state.\u003C/li\u003E\n\u003Cli\u003EThe associated session remains eligible for continuation.\u003C/li\u003E\n\u003Cli\u003EThe relevant chain has not been revoked.\u003C/li\u003E\n\u003Cli\u003EThe account-level security state permits continuation.\u003C/li\u003E\n\u003Cli\u003EAny configured device context requirements are satisfied.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact checks depend on the mode and implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-level-containment\u0022\u003EChain-Level Containment\u003C/h3\u003E\n\u003Cp\u003EWhen a refresh compromise is associated with a session chain, chain-level revocation can provide a broader response than invalidating a single refresh credential.\u003C/p\u003E\n\u003Cp\u003EThis may be appropriate when multiple related sessions or continuation credentials could be affected.\u003C/p\u003E\n\u003Cp\u003EHowever, a chain is a logical session continuity boundary, not a cryptographically verified physical device.\u003C/p\u003E\n\u003Cp\u003EChain-level revocation controls the associated authentication state; it does not permanently prevent the physical device from authenticating again.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/security/session-security-model\u0022\u003ESession Security Model\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022invalid-refresh-conditions\u0022\u003EInvalid Refresh Conditions\u003C/h2\u003E\n\u003Cp\u003ENot every failed refresh should be classified as token reuse.\u003C/p\u003E\n\u003Cp\u003EDifferent failure conditions have different security meanings.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECondition\u003C/th\u003E\n\u003Cth\u003EExpected security interpretation\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUnknown credential\u003C/td\u003E\n\u003Ctd\u003ENo acceptable refresh authority was established\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EExpired credential\u003C/td\u003E\n\u003Ctd\u003EThe permitted continuation lifetime has ended\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EExplicitly revoked credential\u003C/td\u003E\n\u003Ctd\u003EThe refresh authority has been invalidated\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPreviously consumed credential\u003C/td\u003E\n\u003Ctd\u003EPotential reuse under a single-use rotation strategy\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession or chain mismatch\u003C/td\u003E\n\u003Ctd\u003EPresented credential is inconsistent with expected authentication context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAccount security invalidation\u003C/td\u003E\n\u003Ctd\u003EContinuation is no longer permitted under current account security state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EConcurrent use\u003C/td\u003E\n\u003Ctd\u003EMultiple attempts may compete for the same continuation authority\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA secure implementation must reject invalid continuation attempts according to the selected strategy.\u003C/p\u003E\n\u003Cp\u003EThe application should not assume that every rejection requires the same logging, revocation, or user-facing response.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-and-access-token-validity\u0022\u003ERotation and Access Token Validity\u003C/h2\u003E\n\u003Cp\u003ERefresh rotation affects authentication continuation.\u003C/p\u003E\n\u003Cp\u003EIt does not necessarily affect access tokens that were issued earlier.\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\u003EJWT Access A issued\nRefresh A issued\n |\n v\nRefresh A rotated to Refresh B\nJWT Access B issued\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIf \u003Ccode\u003EJWT Access A\u003C/code\u003E has not expired, it may remain valid under local JWT validation even after refresh rotation.\u003C/p\u003E\n\u003Cp\u003ESimilarly, revoking \u003Ccode\u003ERefresh B\u003C/code\u003E may prevent future refresh operations without immediately invalidating \u003Ccode\u003EJWT Access B\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis distinction is especially important in SemiHybrid and PureJwt.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-scope\u0022\u003ERevocation Scope\u003C/h3\u003E\n\u003Cp\u003EThere are separate questions to answer:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECan the old refresh credential be used again?\u003C/li\u003E\n\u003Cli\u003ECan the replacement refresh credential still be used?\u003C/li\u003E\n\u003Cli\u003EIs the associated server-managed session valid?\u003C/li\u003E\n\u003Cli\u003EAre previously issued access tokens still accepted?\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA refresh security operation may affect some of these conditions without affecting all of them.\u003C/p\u003E\n\u003Cp\u003EThe authentication mode determines where current server-side state is consulted.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-and-credential-storage\u0022\u003ERotation and Credential Storage\u003C/h2\u003E\n\u003Cp\u003ERefresh rotation depends on maintaining authoritative information about credential eligibility.\u003C/p\u003E\n\u003Cp\u003EFor opaque refresh credentials, an implementation may store a one-way verifier or hash rather than the original credential value.\u003C/p\u003E\n\u003Cp\u003EThis reduces the exposure of usable credentials if the persistence store is disclosed.\u003C/p\u003E\n\u003Cp\u003EHowever, secure storage alone does not provide rotation.\u003C/p\u003E\n\u003Cp\u003EThe implementation must also maintain the necessary lifecycle state and enforce transitions correctly.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022operational-requirements\u0022\u003EOperational Requirements\u003C/h3\u003E\n\u003Cp\u003EA production refresh mechanism should account for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESecure credential generation.\u003C/li\u003E\n\u003Cli\u003EAppropriate credential expiration.\u003C/li\u003E\n\u003Cli\u003EProtected transport and client storage.\u003C/li\u003E\n\u003Cli\u003EConsistent persistence updates.\u003C/li\u003E\n\u003Cli\u003EConcurrency control.\u003C/li\u003E\n\u003Cli\u003ESafe handling of invalid and consumed credentials.\u003C/li\u003E\n\u003Cli\u003ERevocation behavior appropriate to the authentication mode.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact persistence and concurrency mechanisms are implementation-specific.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-tradeoffs\u0022\u003ESecurity Tradeoffs\u003C/h2\u003E\n\u003Cp\u003EStrict rotation improves control over credential reuse, but it introduces operational complexity.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EBenefit\u003C/th\u003E\n\u003Cth\u003ECorresponding consideration\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EConsumed credentials cannot remain reusable\u003C/td\u003E\n\u003Ctd\u003ERequires authoritative lifecycle state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReuse attempts can become observable\u003C/td\u003E\n\u003Ctd\u003ELegitimate retries may resemble attacks\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EContinuation can be invalidated independently\u003C/td\u003E\n\u003Ctd\u003ERevocation must be consistently enforced\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReplacement credentials limit prolonged reuse\u003C/td\u003E\n\u003Ctd\u003EConcurrent refresh requests require coordination\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECompromise responses can target session state\u003C/td\u003E\n\u003Ctd\u003EBroader revocation may interrupt legitimate sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese tradeoffs are not reasons to avoid rotation.\u003C/p\u003E\n\u003Cp\u003EThey are reasons to treat it as a security protocol rather than a simple token replacement operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-design-principles\u0022\u003ESecurity Design Principles\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003ERotation must have defined semantics.\u003C/strong\u003E A refresh operation should not be described as single-use unless the configured strategy actually enforces single-use behavior.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EState transitions must be consistent.\u003C/strong\u003E Concurrent requests must not create conflicting valid continuation paths under a single-use strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EReuse detection is not theft attribution.\u003C/strong\u003E A reused credential is suspicious, but the cause may be malicious or accidental.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDetection and containment are separate.\u003C/strong\u003E Rejecting reuse, revoking refresh state, and revoking a session chain are different security actions.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient coordination is helpful but insufficient.\u003C/strong\u003E The server must enforce credential eligibility independently.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAccess and refresh validity remain distinct.\u003C/strong\u003E Refresh rotation does not automatically invalidate previously issued JWT access tokens.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EMode-specific behavior matters.\u003C/strong\u003E The same security guarantee must not be attributed to every authentication mode without considering its actual refresh implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003ERefresh rotation is a mechanism for controlling how authentication authority continues over time.\u003C/p\u003E\n\u003Cp\u003EIts security value comes from enforcing credential lifecycle transitions, preventing invalid continuation, and supporting an appropriate response when a previously consumed credential is presented again.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s authentication modes provide different continuation models. Rotation behavior must therefore be understood in the context of the selected mode, its server-managed state, and its configured refresh strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA secure rotating refresh mechanism does not merely issue a new credential. It ensures that the previous credential can no longer establish an unauthorized continuation path.\u003C/strong\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/security/token-security\u0022\u003EToken Security\u003C/a\u003E \u2014 Access and refresh credential security fundamentals.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security-model\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Hierarchical validation and revocation boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/session-model\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E \u2014 Session continuity, expiration, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Mode-specific authentication and refresh behavior.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { - "Id": "why-rotation", - "Text": "\uD83E\uDDE0 Why Rotation?", + "Id": "rotation-as-a-security-protocol", + "Text": "Rotation as a Security Protocol", "Level": 0 }, { - "Id": "rotation-model", - "Text": "\uD83D\uDD01 Rotation Model", + "Id": "what-rotation-does-and-does-not-guarantee", + "Text": "What Rotation Does and Does Not Guarantee", + "Level": 1 + }, + { + "Id": "authentication-mode-considerations", + "Text": "Authentication Mode Considerations", "Level": 0 }, { - "Id": "token-state", - "Text": "\uD83D\uDD10 Token State", + "Id": "refresh-credential-state", + "Text": "Refresh Credential State", "Level": 0 }, { - "Id": "reuse-detection", - "Text": "\uD83D\uDEA8 Reuse Detection", + "Id": "the-rotation-transaction", + "Text": "The Rotation Transaction", "Level": 0 }, { - "Id": "what-happens", - "Text": "What happens?", + "Id": "atomicity", + "Text": "Atomicity", "Level": 1 }, { - "Id": "chain-awareness", - "Text": "\uD83D\uDD17 Chain Awareness", + "Id": "concurrent-refresh-requests", + "Text": "Concurrent Refresh Requests", "Level": 0 }, { - "Id": "rotation-flow", - "Text": "\uD83D\uDD04 Rotation Flow", + "Id": "client-side-coordination", + "Text": "Client-Side Coordination", + "Level": 1 + }, + { + "Id": "server-side-enforcement", + "Text": "Server-Side Enforcement", + "Level": 1 + }, + { + "Id": "refresh-credential-reuse", + "Text": "Refresh Credential Reuse", "Level": 0 }, { - "Id": "invalid-scenarios", - "Text": "\u26A0\uFE0F Invalid Scenarios", + "Id": "reuse-is-a-security-signal", + "Text": "Reuse Is a Security Signal", + "Level": 1 + }, + { + "Id": "detection-versus-response", + "Text": "Detection Versus Response", + "Level": 1 + }, + { + "Id": "replay-and-token-theft-scenarios", + "Text": "Replay and Token Theft Scenarios", "Level": 0 }, { - "Id": "expired-token", - "Text": "1. Expired Token", + "Id": "scenario-1-the-legitimate-client-refreshes-first", + "Text": "Scenario 1: The Legitimate Client Refreshes First", "Level": 1 }, { - "Id": "revoked-token", - "Text": "2. Revoked Token", + "Id": "scenario-2-the-attacker-refreshes-first", + "Text": "Scenario 2: The Attacker Refreshes First", "Level": 1 }, { - "Id": "session-mismatch", - "Text": "3. Session Mismatch", + "Id": "scenario-3-a-refresh-response-is-lost", + "Text": "Scenario 3: A Refresh Response Is Lost", "Level": 1 }, { - "Id": "security-guarantees", - "Text": "\uD83E\uDDE0 Security Guarantees", + "Id": "session-and-chain-association", + "Text": "Session and Chain Association", "Level": 0 }, { - "Id": "why-this-matters", - "Text": "\uD83D\uDD25 Why This Matters", + "Id": "chain-level-containment", + "Text": "Chain-Level Containment", + "Level": 1 + }, + { + "Id": "invalid-refresh-conditions", + "Text": "Invalid Refresh Conditions", + "Level": 0 + }, + { + "Id": "rotation-and-access-token-validity", + "Text": "Rotation and Access Token Validity", + "Level": 0 + }, + { + "Id": "revocation-scope", + "Text": "Revocation Scope", + "Level": 1 + }, + { + "Id": "rotation-and-credential-storage", + "Text": "Rotation and Credential Storage", + "Level": 0 + }, + { + "Id": "operational-requirements", + "Text": "Operational Requirements", + "Level": 1 + }, + { + "Id": "security-tradeoffs", + "Text": "Security Tradeoffs", "Level": 0 }, { - "Id": "design-tradeoff", - "Text": "\u26A0\uFE0F Design Tradeoff", + "Id": "security-design-principles", + "Text": "Security Design Principles", "Level": 0 }, { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", + "Id": "architectural-perspective", + "Text": "Architectural Perspective", "Level": 0 }, { - "Id": "next-step", - "Text": "\u27A1\uFE0F Next Step", + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security-model.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security-model.json index 9c7c1f04..733ca89b 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security-model.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security-model.json @@ -1,96 +1,191 @@ { "Slug": "security/session-security-model", "Title": "Session Security Model", - "Html": "\n\u003Cp\u003EUltimateAuth is built around a \u003Cstrong\u003Ehierarchical session model\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThis is the foundation of its security design.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-a-session-model\u0022\u003E\uD83E\uDDE0 Why a Session Model?\u003C/h2\u003E\n\u003Cp\u003EMany systems treat authentication as one of these:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ea cookie\u003C/li\u003E\n\u003Cli\u003Ea bearer token\u003C/li\u003E\n\u003Cli\u003Ea login flag\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThat works for simple apps.\u003C/p\u003E\n\u003Cp\u003EIt breaks down when you need:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eper-device isolation\u003C/li\u003E\n\u003Cli\u003Etargeted revocation\u003C/li\u003E\n\u003Cli\u003Esecurity state propagation\u003C/li\u003E\n\u003Cli\u003Ereliable reauthentication boundaries\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth solves this by modeling authentication 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\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\u003ERoot \u2192 Chain \u2192 Session\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-three-layers\u0022\u003E\uD83E\uDDE9 The Three Layers\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root\u0022\u003E\uD83D\uDD39 Root\u003C/h3\u003E\n\u003Cp\u003EA \u003Cstrong\u003ERoot\u003C/strong\u003E represents the user-level authentication authority.\u003C/p\u003E\n\u003Cp\u003EIt is:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Etenant-bound\u003C/li\u003E\n\u003Cli\u003Euser-bound\u003C/li\u003E\n\u003Cli\u003Elong-lived\u003C/li\u003E\n\u003Cli\u003Esecurity-versioned\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe root is the highest-level trust anchor for authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain\u0022\u003E\uD83D\uDCF1 Chain\u003C/h3\u003E\n\u003Cp\u003EA \u003Cstrong\u003EChain\u003C/strong\u003E represents a device-level authentication boundary.\u003C/p\u003E\n\u003Cp\u003EIt groups sessions that belong to the same device or client context.\u003C/p\u003E\n\u003Cp\u003EA chain is where UltimateAuth models:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Edevice continuity\u003C/li\u003E\n\u003Cli\u003Etouch activity\u003C/li\u003E\n\u003Cli\u003Erotation tracking\u003C/li\u003E\n\u003Cli\u003Edevice-level revoke\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session\u0022\u003E\uD83D\uDD11 Session\u003C/h3\u003E\n\u003Cp\u003EA \u003Cstrong\u003ESession\u003C/strong\u003E is a single authentication instance.\u003C/p\u003E\n\u003Cp\u003EIt is the most granular identity proof in the system.\u003C/p\u003E\n\u003Cp\u003EA session contains:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ecreation time\u003C/li\u003E\n\u003Cli\u003Eexpiration time\u003C/li\u003E\n\u003Cli\u003Erevocation state\u003C/li\u003E\n\u003Cli\u003Esecurity version snapshot\u003C/li\u003E\n\u003Cli\u003Echain relationship\u003C/li\u003E\n\u003Cli\u003Edevice snapshot\u003C/li\u003E\n\u003Cli\u003Eclaims snapshot\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022relationship-model\u0022\u003E\uD83D\uDD17 Relationship Model\u003C/h2\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)\n \u2514\u2500\u2500 Session (login instance)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Root answers: \u003Cstrong\u003Ewhat is the current security authority for this user?\u003C/strong\u003E\u003Cbr\u003E\n\uD83D\uDC49 Chain answers: \u003Cstrong\u003Ewhich device context is this?\u003C/strong\u003E\u003Cbr\u003E\n\uD83D\uDC49 Session answers: \u003Cstrong\u003Ewhich authentication instance is being used?\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-versioning\u0022\u003E\uD83D\uDEE1 Security Versioning\u003C/h2\u003E\n\u003Cp\u003EOne of the most important protections in UltimateAuth is \u003Cstrong\u003Esecurity versioning\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EA root maintains a security version.\u003C/p\u003E\n\u003Cp\u003EEach session stores the security version that existed at creation time.\u003C/p\u003E\n\u003Cp\u003EValidation compares them.\u003C/p\u003E\n\u003Cp\u003EIf they no longer match:\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 \u2192 invalid\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is how UltimateAuth can invalidate existing sessions after events such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Epassword change\u003C/li\u003E\n\u003Cli\u003Ecredential reset\u003C/li\u003E\n\u003Cli\u003Eaccount recovery\u003C/li\u003E\n\u003Cli\u003Eadministrative security action\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022validation-model\u0022\u003E\uD83D\uDD0D Validation Model\u003C/h2\u003E\n\u003Cp\u003ESession validation is not a single check.\u003C/p\u003E\n\u003Cp\u003EIt is a layered verification process.\u003C/p\u003E\n\u003Cp\u003EA session is considered valid only if all of these still hold:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ethe session exists\u003C/li\u003E\n\u003Cli\u003Ethe session is active\u003C/li\u003E\n\u003Cli\u003Ethe chain exists\u003C/li\u003E\n\u003Cli\u003Ethe chain is active\u003C/li\u003E\n\u003Cli\u003Ethe chain belongs to the expected tenant\u003C/li\u003E\n\u003Cli\u003Ethe chain matches the session\u003C/li\u003E\n\u003Cli\u003Ethe root exists\u003C/li\u003E\n\u003Cli\u003Ethe root is not revoked\u003C/li\u003E\n\u003Cli\u003Ethe root matches the chain\u003C/li\u003E\n\u003Cli\u003Ethe root security version matches the session snapshot\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-awareness\u0022\u003E\uD83D\uDCF1 Device Awareness\u003C/h2\u003E\n\u003Cp\u003EChains provide device-level isolation.\u003C/p\u003E\n\u003Cp\u003EWhen a device identifier is available, validation can compare:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Edevice stored on chain\u003C/li\u003E\n\u003Cli\u003Edevice presented by request\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIf they do not match, UltimateAuth can reject the request depending on configuration.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This makes device-bound security enforceable without turning every authentication flow into a custom implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022expiration-and-activity\u0022\u003E\u23F1 Expiration and Activity\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Esession expiration\u003C/li\u003E\n\u003Cli\u003Echain activity\u003C/li\u003E\n\u003Cli\u003Eroot security state\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis is important.\u003C/p\u003E\n\u003Cp\u003EA session may expire because of time.\u003Cbr\u003E\nA chain may become inactive because of idle timeout.\u003Cbr\u003E\nA root may invalidate everything because security state changed.\u003C/p\u003E\n\u003Cp\u003EThese are different failure modes with different meanings.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-boundaries\u0022\u003E\uD83D\uDEAA Revocation Boundaries\u003C/h2\u003E\n\u003Cp\u003ERevocation is also hierarchical.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-revoke\u0022\u003ESession revoke\u003C/h3\u003E\n\u003Cp\u003EInvalidates one authentication instance.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-revoke\u0022\u003EChain revoke\u003C/h3\u003E\n\u003Cp\u003EInvalidates all sessions for one device.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-revoke\u0022\u003ERoot revoke\u003C/h3\u003E\n\u003Cp\u003EInvalidates all chains and sessions for the user.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This gives UltimateAuth targeted containment.\u003C/p\u003E\n\u003Cp\u003EInstead of \u201Clog out everywhere or nowhere,\u201D\u003Cbr\u003E\nyou can revoke exactly the right scope.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-this-matters\u0022\u003E\uD83D\uDD25 Why This Matters\u003C/h2\u003E\n\u003Cp\u003EThis model gives you controls that flat token systems usually do not provide:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Erevoke one device without affecting others\u003C/li\u003E\n\u003Cli\u003Einvalidate sessions after security changes\u003C/li\u003E\n\u003Cli\u003Ereason about device trust explicitly\u003C/li\u003E\n\u003Cli\u003Eseparate authentication lifetime from token lifetime\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022design-tradeoff\u0022\u003E\u26A0\uFE0F Design Tradeoff\u003C/h2\u003E\n\u003Cp\u003EThis model is more sophisticated than plain JWT-only authentication.\u003C/p\u003E\n\u003Cp\u003EThat is intentional.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth chooses:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eexplicit state\u003C/li\u003E\n\u003Cli\u003Erevocability\u003C/li\u003E\n\u003Cli\u003Etraceable security decisions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003Eover:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eminimal infrastructure\u003C/li\u003E\n\u003Cli\u003Epurely stateless assumptions\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Authentication in UltimateAuth is not \u201Ca token\u201D\u003Cbr\u003E\n\uD83D\uDC49 It is a \u003Cstrong\u003Esecurity hierarchy with revocation boundaries\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003ERefresh Token Rotation\u003C/strong\u003E to understand how continuation security works after login.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EA valid authentication credential is not necessarily sufficient to establish that an authentication session should still be trusted.\u003C/p\u003E\n\u003Cp\u003EA session may have been revoked, its associated account security state may have changed, or the client may no longer satisfy the conditions under which authentication was established.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth addresses these concerns through a \u003Cstrong\u003Ehierarchical session security model\u003C/strong\u003E, with explicit boundaries for session validity, device-associated continuity, and account-level invalidation.\u003C/p\u003E\n\u003Cp\u003EIn session-managed authentication modes, the security hierarchy is:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERoot \u2192 Chain \u2192 Session\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis page focuses on the security guarantees, validation requirements, and enforcement boundaries of that hierarchy.\u003C/p\u003E\n\u003Cp\u003EFor the underlying entities and their lifecycle, see \u003Ca href=\u0022/docs/sessions-devices/session-model\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-objectives\u0022\u003ESecurity Objectives\u003C/h2\u003E\n\u003Cp\u003EThe session security model is designed to support several distinct requirements.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EObjective\u003C/th\u003E\n\u003Cth\u003ESecurity purpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESession isolation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EInvalidate a particular authentication session without necessarily affecting others\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EChain isolation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EControl sessions associated with a particular device or client context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAccount-level invalidation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EInvalidate authentication state across the account\u0027s session hierarchy\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESecurity state propagation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EDetect changes to account-level authentication security\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EContext consistency\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEnsure that related session, chain, root, and tenant information remains consistent\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELifecycle enforcement\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EReject authentication state that has expired or been revoked\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese requirements are related, but they should not be collapsed into a single token expiration check.\u003C/p\u003E\n\u003Cp\u003EA token can remain structurally valid even when the server-managed authentication state associated with it is no longer acceptable.\u003C/p\u003E\n\u003Cp\u003EWhether that state is consulted during a protected request depends on the authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-boundaries\u0022\u003ESecurity Boundaries\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s session hierarchy separates security responsibilities 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\u003EAccount-level security\n |\n v\n SessionRoot\n |\n | Account-wide security state\n | Security version\n |\n v\n SessionChain\n |\n | Session continuity\n | Device context\n | Chain-level revocation\n |\n v\n Session\n |\n | Individual validity\n | Expiration\n | Revocation\n |\n v\n Authentication state\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach level provides a distinct boundary for evaluating or invalidating authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-security-boundary\u0022\u003ERoot Security Boundary\u003C/h3\u003E\n\u003Cp\u003EThe root represents account-level authentication security.\u003C/p\u003E\n\u003Cp\u003EIt provides a common authority for security state that affects sessions associated with the same account.\u003C/p\u003E\n\u003Cp\u003EThis enables account-wide invalidation without requiring the user identity itself to be deleted or recreated.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-security-boundary\u0022\u003EChain Security Boundary\u003C/h3\u003E\n\u003Cp\u003EA chain groups related authentication sessions and may be associated with a device or client environment.\u003C/p\u003E\n\u003Cp\u003EIt provides a boundary for revoking related sessions together.\u003C/p\u003E\n\u003Cp\u003EA chain is not proof of physical device identity, and its existence does not automatically mean that the associated device is trusted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-security-boundary\u0022\u003ESession Security Boundary\u003C/h3\u003E\n\u003Cp\u003EA session represents individual server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003EIt can be independently subject to expiration, revocation, and consistency checks.\u003C/p\u003E\n\u003Cp\u003EA session is not equivalent to an access token: the token is presented by the client, while the session represents server-managed security state where the selected mode uses it.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022layered-session-validation\u0022\u003ELayered Session Validation\u003C/h2\u003E\n\u003Cp\u003EIn a session-authoritative authentication mode, accepting a presented credential requires more than recognizing its identifier.\u003C/p\u003E\n\u003Cp\u003EThe associated session hierarchy must satisfy the applicable validity and consistency requirements.\u003C/p\u003E\n\u003Cp\u003EThese requirements can be understood in several categories.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-validity\u0022\u003ESession Validity\u003C/h3\u003E\n\u003Cp\u003EThe session must satisfy its own security conditions.\u003C/p\u003E\n\u003Cp\u003ERelevant checks include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe session exists in the authoritative session store.\u003C/li\u003E\n\u003Cli\u003EThe session has not been revoked.\u003C/li\u003E\n\u003Cli\u003EThe session has not exceeded its applicable expiration limits.\u003C/li\u003E\n\u003Cli\u003EThe session is associated with the expected chain.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA credential referencing a missing, expired, or revoked session must not establish a valid session-authoritative authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-validity\u0022\u003EChain Validity\u003C/h3\u003E\n\u003Cp\u003EThe associated chain must satisfy the applicable chain security conditions.\u003C/p\u003E\n\u003Cp\u003EThese may include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe chain exists.\u003C/li\u003E\n\u003Cli\u003EThe chain has not been revoked.\u003C/li\u003E\n\u003Cli\u003EThe chain belongs to the expected account and tenant context.\u003C/li\u003E\n\u003Cli\u003EThe session-to-chain relationship is consistent.\u003C/li\u003E\n\u003Cli\u003EAny applicable chain-level expiration or inactivity conditions are satisfied.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EChain validation prevents an otherwise recognizable session from bypassing a security restriction applied to its broader session continuity boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-validity\u0022\u003ERoot Validity\u003C/h3\u003E\n\u003Cp\u003EThe root must satisfy the account-level security requirements.\u003C/p\u003E\n\u003Cp\u003ERelevant conditions include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe expected root exists.\u003C/li\u003E\n\u003Cli\u003EThe root is associated with the expected identity and tenant context.\u003C/li\u003E\n\u003Cli\u003EThe chain belongs to the expected root.\u003C/li\u003E\n\u003Cli\u003EThe root has not been invalidated.\u003C/li\u003E\n\u003Cli\u003EApplicable security version requirements are satisfied.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis prevents a valid-looking session from being accepted when its account-level security authority has changed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022relationship-consistency\u0022\u003ERelationship Consistency\u003C/h3\u003E\n\u003Cp\u003EThe hierarchy must also be internally consistent.\u003C/p\u003E\n\u003Cp\u003EA session cannot be considered valid merely because a session record, a chain record, and a root record each exist independently.\u003C/p\u003E\n\u003Cp\u003ETheir ownership and association must match the expected authentication context.\u003C/p\u003E\n\u003Cp\u003EThis is especially important in multi-tenant applications, where a mismatched tenant or account association must not be accepted as a valid authentication relationship.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe security decision concerns the entire applicable hierarchy, not an isolated record.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-versioning\u0022\u003ESecurity Versioning\u003C/h2\u003E\n\u003Cp\u003ESecurity versioning allows UltimateAuth to represent changes in account-level authentication security.\u003C/p\u003E\n\u003Cp\u003EA root maintains a security version, and a session can retain the version associated with its creation.\u003C/p\u003E\n\u003Cp\u003EDuring validation, the system can compare these values.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022matching-security-state\u0022\u003EMatching Security State\u003C/h3\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\u003ERoot.SecurityVersion = 7\nSession.SecurityVersionAtCreation = 7\n\nSecurity version check: PASS\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe session was created under the current security version.\u003C/p\u003E\n\u003Cp\u003EThis does not mean that the session is automatically valid; expiration, revocation, and other applicable checks must still succeed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022changed-security-state\u0022\u003EChanged Security State\u003C/h3\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\u003ERoot.SecurityVersion = 8\nSession.SecurityVersionAtCreation = 7\n\nSecurity version check: FAIL\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe session was created under an earlier account-level security state.\u003C/p\u003E\n\u003Cp\u003EWhen security version validation is enforced, the mismatch invalidates that session.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-versioning-matters\u0022\u003EWhy Versioning Matters\u003C/h3\u003E\n\u003Cp\u003EWithout a shared security version, an account-wide security change may require every individual session to be located and updated before the change can be enforced.\u003C/p\u003E\n\u003Cp\u003EA security version provides a common condition against which existing session state can be evaluated.\u003C/p\u003E\n\u003Cp\u003EThis can support responses to events such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredential changes or recovery.\u003C/li\u003E\n\u003Cli\u003EAdministrative security actions.\u003C/li\u003E\n\u003Cli\u003EAccount compromise.\u003C/li\u003E\n\u003Cli\u003EAccount-level session invalidation.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EHowever, the triggering operation and configured session revocation policy determine whether a security version change should occur.\u003C/p\u003E\n\u003Cp\u003ENot every credential change must automatically invalidate every session.\u003C/p\u003E\n\u003Cp\u003EFor example, UltimateAuth supports credential-related session handling policies such as \u003Ccode\u003EKeepAll\u003C/code\u003E, \u003Ccode\u003EKeepCurrent\u003C/code\u003E, and \u003Ccode\u003ERevokeAll\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA security operation must preserve the intended policy semantics rather than indiscriminately invalidating the entire account hierarchy.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-context-validation\u0022\u003EDevice Context Validation\u003C/h2\u003E\n\u003Cp\u003EDevice-associated session management can add security conditions to session validation.\u003C/p\u003E\n\u003Cp\u003EA chain may be associated with a device identifier or other client context.\u003C/p\u003E\n\u003Cp\u003EWhen device binding checks are configured and enforced, the system can compare the expected context with the context presented by a request.\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\u003EChain.DeviceId = device-A\nRequest.DeviceId = device-B\n\nDevice context mismatch\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA configured mismatch policy may reject the operation.\u003C/p\u003E\n\u003Cp\u003EHowever, this comparison must be interpreted correctly.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identification-is-not-authentication\u0022\u003EIdentification Is Not Authentication\u003C/h3\u003E\n\u003Cp\u003EA device identifier is an identifying value, not necessarily a secret.\u003C/p\u003E\n\u003Cp\u003EIf an attacker can copy or forge that value, matching the identifier does not prove that the request originated from the legitimate physical device.\u003C/p\u003E\n\u003Cp\u003EDevice context validation can enforce consistency with recorded metadata, but stronger device authentication requires an appropriate proof mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-metadata-is-not-a-security-guarantee\u0022\u003EDevice Metadata Is Not a Security Guarantee\u003C/h3\u003E\n\u003Cp\u003EOperating system, browser, platform, and IP information may provide useful context.\u003C/p\u003E\n\u003Cp\u003EThey are not universally stable or unique.\u003C/p\u003E\n\u003Cp\u003EChanges to this information can occur during legitimate usage, and matching values can occur across unrelated clients.\u003C/p\u003E\n\u003Cp\u003EApplications should distinguish between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInformational device metadata.\u003C/li\u003E\n\u003Cli\u003EConfigured device consistency requirements.\u003C/li\u003E\n\u003Cli\u003ECryptographically established device binding.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe security guarantee must reflect the mechanism actually enforced.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022expiration-and-inactivity\u0022\u003EExpiration and Inactivity\u003C/h2\u003E\n\u003Cp\u003EExpiration is a security control, not merely a cleanup mechanism.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth distinguishes between time-based validity and explicit security invalidation.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured mode and options, relevant conditions may include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECondition\u003C/th\u003E\n\u003Cth\u003ESecurity meaning\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\u003EThe authentication state has exceeded its permitted duration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIdle timeout\u003C/td\u003E\n\u003Ctd\u003EThe required activity has not occurred within the allowed interval\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESliding expiration\u003C/td\u003E\n\u003Ctd\u003EQualifying activity can extend validity within configured limits\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAbsolute lifetime\u003C/td\u003E\n\u003Ctd\u003EAuthentication state cannot remain valid beyond a fixed boundary\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERevocation\u003C/td\u003E\n\u003Ctd\u003EAuthentication state has been explicitly invalidated\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA session may be expired without being revoked.\u003C/p\u003E\n\u003Cp\u003ESimilarly, a session may be revoked before reaching its natural expiration.\u003C/p\u003E\n\u003Cp\u003EThese conditions should remain distinguishable for correct enforcement and security diagnostics.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022activity-tracking-is-not-universal\u0022\u003EActivity Tracking Is Not Universal\u003C/h3\u003E\n\u003Cp\u003ESession activity may be recorded through touch operations.\u003C/p\u003E\n\u003Cp\u003EHowever, a locally validated JWT does not necessarily cause a server-side session touch on every protected request.\u003C/p\u003E\n\u003Cp\u003ETherefore, inactivity enforcement depends on where authoritative activity state is maintained and when it is checked.\u003C/p\u003E\n\u003Cp\u003EAn application should not assume that all authentication modes provide identical idle-timeout guarantees.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022hierarchical-revocation\u0022\u003EHierarchical Revocation\u003C/h2\u003E\n\u003Cp\u003ERevocation allows authentication state to be invalidated before its normal expiration.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s hierarchy supports different revocation scopes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-level-revocation\u0022\u003ESession-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ETargets an individual session.\u003C/p\u003E\n\u003Cp\u003EOther sessions may remain valid if their own security conditions continue to be satisfied.\u003C/p\u003E\n\u003Cp\u003EThis is appropriate for terminating a particular authentication instance.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-level-revocation\u0022\u003EChain-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ETargets the session continuity associated with a chain.\u003C/p\u003E\n\u003Cp\u003EIt can invalidate related sessions without necessarily affecting other chains belonging to the same account.\u003C/p\u003E\n\u003Cp\u003EThis supports targeted containment when a particular client environment is suspected of compromise.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-level-revocation\u0022\u003ERoot-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ETargets the account-level session security boundary.\u003C/p\u003E\n\u003Cp\u003EIt can invalidate the applicable session hierarchy, including chains and sessions associated with that root.\u003C/p\u003E\n\u003Cp\u003ESecurity versioning can contribute to this mechanism by invalidating sessions created under earlier account-level security state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-scope-and-enforcement\u0022\u003ERevocation Scope and Enforcement\u003C/h3\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 revoke\n -\u0026gt; Targeted session\n\nChain revoke\n -\u0026gt; Sessions associated with the chain\n\nRoot revoke\n -\u0026gt; Account-level session hierarchy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese are logical revocation scopes.\u003C/p\u003E\n\u003Cp\u003EThe effect on protected requests depends on whether the authentication mode consults the revoked state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERevoking server-side state is not equivalent to cryptographically invalidating an already-issued JWT.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-enforcement\u0022\u003EAuthentication Mode Enforcement\u003C/h2\u003E\n\u003Cp\u003EThe most important limitation of hierarchical session security is that its enforcement depends on the selected authentication mode.\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\u003EProtected request enforcement\u003C/th\u003E\n\u003Cth\u003ERevocation considerations\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureOpaque\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses authoritative server-side session validation\u003C/td\u003E\n\u003Ctd\u003ERevoked session state can be rejected on subsequent validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses server-authoritative opaque credential and session validation\u003C/td\u003E\n\u003Ctd\u003ESession and chain revocation can be enforced through authoritative validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESemiHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT access tokens are validated locally for protected requests\u003C/td\u003E\n\u003Ctd\u003EServer-side revocation may not invalidate an already-issued JWT until expiration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureJwt\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT access tokens are primarily validated without a server-side session lookup\u003C/td\u003E\n\u003Ctd\u003EImmediate session-hierarchy revocation must not be assumed\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=\u0022server-authoritative-modes\u0022\u003EServer-Authoritative Modes\u003C/h3\u003E\n\u003Cp\u003EPureOpaque and Hybrid can evaluate protected requests against current server-managed session state.\u003C/p\u003E\n\u003Cp\u003EThis allows applicable expiration, revocation, and security version conditions to affect subsequent authentication decisions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022locally-validated-jwt-modes\u0022\u003ELocally Validated JWT Modes\u003C/h3\u003E\n\u003Cp\u003ESemiHybrid and PureJwt can accept a valid JWT without consulting current session hierarchy state on every protected request.\u003C/p\u003E\n\u003Cp\u003EConsequently, changes to server-side session security may not immediately affect an already-issued access token.\u003C/p\u003E\n\u003Cp\u003EThe duration of this exposure depends on token lifetime and any additional validation or revocation mechanism configured by the application.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-tradeoff\u0022\u003ESecurity Tradeoff\u003C/h3\u003E\n\u003Cp\u003EThis is a deliberate architectural distinction.\u003C/p\u003E\n\u003Cp\u003EServer-authoritative validation provides stronger immediate control over active authentication state at the cost of server-side validation work.\u003C/p\u003E\n\u003Cp\u003ELocally validated JWTs reduce that dependency but cannot inherently provide the same immediate revocation behavior.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022threat-scenarios\u0022\u003EThreat Scenarios\u003C/h2\u003E\n\u003Cp\u003EThe session security model helps address several common security scenarios.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-session-credential-is-stolen\u0022\u003EA Session Credential Is Stolen\u003C/h3\u003E\n\u003Cp\u003EAn attacker obtains authentication material associated with one session.\u003C/p\u003E\n\u003Cp\u003EIf the compromise is detected, the affected session can be revoked.\u003C/p\u003E\n\u003Cp\u003EIn server-authoritative modes, subsequent attempts to use that session can be rejected through session validation.\u003C/p\u003E\n\u003Cp\u003EIf the attacker possesses an independently valid JWT, the result depends on JWT validation and revocation behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-client-environment-is-compromised\u0022\u003EA Client Environment Is Compromised\u003C/h3\u003E\n\u003Cp\u003EAn attacker may have access to authentication material associated with a particular browser or application installation.\u003C/p\u003E\n\u003Cp\u003EChain-level revocation provides a broader response than revoking only one session.\u003C/p\u003E\n\u003Cp\u003EIt can invalidate related session state while preserving unrelated chains where appropriate.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022an-account-is-compromised\u0022\u003EAn Account Is Compromised\u003C/h3\u003E\n\u003Cp\u003EAn account-level security event may require invalidating authentication state across multiple devices.\u003C/p\u003E\n\u003Cp\u003ERoot-level invalidation provides a common security boundary for the applicable session hierarchy.\u003C/p\u003E\n\u003Cp\u003EThe operation must still account for the configured authentication mode and credential-related revocation policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-tenant-relationship-is-inconsistent\u0022\u003EA Tenant Relationship Is Inconsistent\u003C/h3\u003E\n\u003Cp\u003EA session, chain, or root references an unexpected tenant or account relationship.\u003C/p\u003E\n\u003Cp\u003EA session-authoritative validation process must not accept the credential solely because the individual records exist.\u003C/p\u003E\n\u003Cp\u003EThe relationships must satisfy the expected security context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-device-identifier-is-spoofed\u0022\u003EA Device Identifier Is Spoofed\u003C/h3\u003E\n\u003Cp\u003EAn attacker presents a device identifier matching a known client.\u003C/p\u003E\n\u003Cp\u003EThe identifier match alone does not establish device ownership.\u003C/p\u003E\n\u003Cp\u003EThe request must still satisfy the authentication and applicable session validation requirements.\u003C/p\u003E\n\u003Cp\u003EThese scenarios illustrate how different controls address different classes of risk.\u003C/p\u003E\n\u003Cp\u003ENo single session metadata field or policy provides complete protection against every form of credential compromise.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-design-principles\u0022\u003ESecurity Design Principles\u003C/h2\u003E\n\u003Cp\u003EThe session security model follows several important principles.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EValidate relationships, not just identifiers.\u003C/strong\u003E Session, chain, root, account, and tenant associations must remain consistent.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESeparate authentication credentials from authoritative state.\u003C/strong\u003E A credential can reference security state without being equivalent to that state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EApply revocation at the appropriate scope.\u003C/strong\u003E Session, chain, and root invalidation serve different security purposes.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not equate device recognition with device proof.\u003C/strong\u003E Client metadata provides context but not necessarily strong device authentication.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERespect authentication mode boundaries.\u003C/strong\u003E A security control is only effective where its underlying state is actually checked.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EKeep lifecycle policy explicit.\u003C/strong\u003E Credential changes, recovery, expiration, and administrative actions may require different invalidation behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s hierarchical session model provides multiple security boundaries instead of relying on a single token validity check.\u003C/p\u003E\n\u003Cp\u003EThe root represents account-level security authority, the chain organizes related session continuity, and the session represents individual server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003ETogether, they support targeted revocation, security versioning, relationship validation, and device-aware session control.\u003C/p\u003E\n\u003Cp\u003ETheir practical security guarantees depend on the authentication mode and the validation mechanisms enforced during protected operations.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA secure session is not merely one that has not expired. It is one whose applicable security state, ownership relationships, and validation requirements still hold.\u003C/strong\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/sessions-devices/session-model\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E \u2014 Understand the underlying hierarchy and lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E \u2014 Explore device context and chain association.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Compare enforcement across authentication modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Understand credential changes and session revocation policies.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { - "Id": "why-a-session-model", - "Text": "\uD83E\uDDE0 Why a Session Model?", + "Id": "security-objectives", + "Text": "Security Objectives", "Level": 0 }, { - "Id": "the-three-layers", - "Text": "\uD83E\uDDE9 The Three Layers", + "Id": "security-boundaries", + "Text": "Security Boundaries", "Level": 0 }, { - "Id": "root", - "Text": "\uD83D\uDD39 Root", + "Id": "root-security-boundary", + "Text": "Root Security Boundary", "Level": 1 }, { - "Id": "chain", - "Text": "\uD83D\uDCF1 Chain", + "Id": "chain-security-boundary", + "Text": "Chain Security Boundary", "Level": 1 }, { - "Id": "session", - "Text": "\uD83D\uDD11 Session", + "Id": "session-security-boundary", + "Text": "Session Security Boundary", "Level": 1 }, { - "Id": "relationship-model", - "Text": "\uD83D\uDD17 Relationship Model", + "Id": "layered-session-validation", + "Text": "Layered Session Validation", "Level": 0 }, + { + "Id": "session-validity", + "Text": "Session Validity", + "Level": 1 + }, + { + "Id": "chain-validity", + "Text": "Chain Validity", + "Level": 1 + }, + { + "Id": "root-validity", + "Text": "Root Validity", + "Level": 1 + }, + { + "Id": "relationship-consistency", + "Text": "Relationship Consistency", + "Level": 1 + }, { "Id": "security-versioning", - "Text": "\uD83D\uDEE1 Security Versioning", + "Text": "Security Versioning", "Level": 0 }, { - "Id": "validation-model", - "Text": "\uD83D\uDD0D Validation Model", + "Id": "matching-security-state", + "Text": "Matching Security State", + "Level": 1 + }, + { + "Id": "changed-security-state", + "Text": "Changed Security State", + "Level": 1 + }, + { + "Id": "why-versioning-matters", + "Text": "Why Versioning Matters", + "Level": 1 + }, + { + "Id": "device-context-validation", + "Text": "Device Context Validation", "Level": 0 }, { - "Id": "device-awareness", - "Text": "\uD83D\uDCF1 Device Awareness", + "Id": "identification-is-not-authentication", + "Text": "Identification Is Not Authentication", + "Level": 1 + }, + { + "Id": "device-metadata-is-not-a-security-guarantee", + "Text": "Device Metadata Is Not a Security Guarantee", + "Level": 1 + }, + { + "Id": "expiration-and-inactivity", + "Text": "Expiration and Inactivity", "Level": 0 }, { - "Id": "expiration-and-activity", - "Text": "\u23F1 Expiration and Activity", + "Id": "activity-tracking-is-not-universal", + "Text": "Activity Tracking Is Not Universal", + "Level": 1 + }, + { + "Id": "hierarchical-revocation", + "Text": "Hierarchical Revocation", "Level": 0 }, { - "Id": "revocation-boundaries", - "Text": "\uD83D\uDEAA Revocation Boundaries", + "Id": "session-level-revocation", + "Text": "Session-Level Revocation", + "Level": 1 + }, + { + "Id": "chain-level-revocation", + "Text": "Chain-Level Revocation", + "Level": 1 + }, + { + "Id": "root-level-revocation", + "Text": "Root-Level Revocation", + "Level": 1 + }, + { + "Id": "revocation-scope-and-enforcement", + "Text": "Revocation Scope and Enforcement", + "Level": 1 + }, + { + "Id": "authentication-mode-enforcement", + "Text": "Authentication Mode Enforcement", "Level": 0 }, { - "Id": "session-revoke", - "Text": "Session revoke", + "Id": "server-authoritative-modes", + "Text": "Server-Authoritative Modes", "Level": 1 }, { - "Id": "chain-revoke", - "Text": "Chain revoke", + "Id": "locally-validated-jwt-modes", + "Text": "Locally Validated JWT Modes", "Level": 1 }, { - "Id": "root-revoke", - "Text": "Root revoke", + "Id": "security-tradeoff", + "Text": "Security Tradeoff", "Level": 1 }, { - "Id": "why-this-matters", - "Text": "\uD83D\uDD25 Why This Matters", + "Id": "threat-scenarios", + "Text": "Threat Scenarios", "Level": 0 }, { - "Id": "design-tradeoff", - "Text": "\u26A0\uFE0F Design Tradeoff", + "Id": "a-session-credential-is-stolen", + "Text": "A Session Credential Is Stolen", + "Level": 1 + }, + { + "Id": "a-client-environment-is-compromised", + "Text": "A Client Environment Is Compromised", + "Level": 1 + }, + { + "Id": "an-account-is-compromised", + "Text": "An Account Is Compromised", + "Level": 1 + }, + { + "Id": "a-tenant-relationship-is-inconsistent", + "Text": "A Tenant Relationship Is Inconsistent", + "Level": 1 + }, + { + "Id": "a-device-identifier-is-spoofed", + "Text": "A Device Identifier Is Spoofed", + "Level": 1 + }, + { + "Id": "security-design-principles", + "Text": "Security Design Principles", "Level": 0 }, { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", + "Id": "architectural-perspective", + "Text": "Architectural Perspective", "Level": 0 }, { - "Id": "next-step", - "Text": "\u27A1\uFE0F Next Step", + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/token-security.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/token-security.json new file mode 100644 index 00000000..7284aa47 --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/token-security.json @@ -0,0 +1,232 @@ +{ + "Slug": "security/token-security", + "Title": "Token Security", + "Html": "\n\u003Cp\u003EAuthentication tokens allow clients to present authentication state to protected applications and services.\u003C/p\u003E\n\u003Cp\u003EHowever, not all tokens provide the same security guarantees.\u003C/p\u003E\n\u003Cp\u003EAn opaque credential requires authoritative server-side validation. A JSON Web Token (JWT) can be verified locally using its signature and claims. A refresh credential enables authentication to continue without repeating the original login process.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports these different mechanisms through its authentication modes.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EToken security depends on the credential\u0027s purpose, how it is validated, how long it remains valid, and which security state is consulted during validation.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis page explains the security characteristics of access and refresh credentials, including issuance, expiration, rotation, replay risks, and revocation boundaries.\u003C/p\u003E\n\u003Cp\u003EFor the overall authentication architecture, 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=\u0022authentication-state-and-credentials\u0022\u003EAuthentication State and Credentials\u003C/h2\u003E\n\u003Cp\u003EA token is authentication material presented by a client.\u003C/p\u003E\n\u003Cp\u003EIt is not the user identity itself.\u003C/p\u003E\n\u003Cp\u003EDepending on the authentication mode, the token may reference server-managed authentication state or contain signed claims that can be validated without retrieving a session.\u003C/p\u003E\n\u003Cp\u003EThese approaches establish different trust boundaries.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-authoritative-authentication\u0022\u003EServer-Authoritative Authentication\u003C/h3\u003E\n\u003Cp\u003EIn server-authoritative modes, a presented opaque credential is validated against server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003EThe server can evaluate the current validity of the corresponding session and applicable security conditions.\u003C/p\u003E\n\u003Cp\u003EThis allows revocation and other authoritative state changes to affect subsequent protected requests.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022locally-validated-authentication\u0022\u003ELocally Validated Authentication\u003C/h3\u003E\n\u003Cp\u003EIn JWT-based modes, a protected resource can validate a token using its signature, issuer, audience, expiration, and other applicable token requirements.\u003C/p\u003E\n\u003Cp\u003EThe resource may not need to consult the issuing server or a session store for every request.\u003C/p\u003E\n\u003Cp\u003EThis reduces the dependency on server-side session lookups but changes how quickly revocation and authorization changes can be enforced.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ENeither model makes the token an identity record. The token is a credential whose validity is determined by the selected authentication mechanism.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-types\u0022\u003EToken Types\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses two principal access credential formats: opaque credentials and JWTs.\u003C/p\u003E\n\u003Cp\u003ERefresh credentials serve a separate purpose.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECredential\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003Cth\u003EValidation model\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EOpaque access credential\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthenticate protected requests\u003C/td\u003E\n\u003Ctd\u003EAuthoritative server-side validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EJWT access token\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthenticate protected requests\u003C/td\u003E\n\u003Ctd\u003ECryptographic and claims validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EOpaque refresh credential\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EContinue authentication and obtain renewed credentials\u003C/td\u003E\n\u003Ctd\u003ERefresh-specific validation and state checks\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe distinction between access and refresh credentials is important.\u003C/p\u003E\n\u003Cp\u003EAccess credentials are presented to protected resources.\u003C/p\u003E\n\u003Cp\u003ERefresh credentials are presented to the authentication system for a continuation operation. They should not be accepted as ordinary access credentials.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022opaque-access-credentials\u0022\u003EOpaque Access Credentials\u003C/h2\u003E\n\u003Cp\u003EAn opaque credential is a value whose contents do not expose the authenticated identity or authorization claims to the client.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EAccess credential:\nq8Vn2...random-opaque-value...M7x\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client presents the credential, but cannot determine its authorization meaning by decoding it.\u003C/p\u003E\n\u003Cp\u003EThe server resolves and validates the associated authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-properties\u0022\u003ESecurity Properties\u003C/h3\u003E\n\u003Cp\u003EOpaque access credentials provide several useful characteristics:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ETheir meaning is determined by authoritative server-side state.\u003C/li\u003E\n\u003Cli\u003EThey do not need to expose identity claims in a client-readable payload.\u003C/li\u003E\n\u003Cli\u003ETheir validity can be affected by session expiration or revocation.\u003C/li\u003E\n\u003Cli\u003EThey can participate in account-level and chain-level security enforcement.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EHowever, opaque credentials are still bearer credentials unless an additional proof mechanism is enforced.\u003C/p\u003E\n\u003Cp\u003EAn attacker who obtains a usable opaque credential may be able to present it.\u003C/p\u003E\n\u003Cp\u003EOpacity alone does not prevent credential theft or replay.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-side-validation\u0022\u003EServer-Side Validation\u003C/h3\u003E\n\u003Cp\u003EIn a session-authoritative mode, validation can enforce the applicable session hierarchy requirements, including expiration, revocation, ownership, and security state consistency.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/security/session-security-model\u0022\u003ESession Security Model\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022jwt-access-tokens\u0022\u003EJWT Access Tokens\u003C/h2\u003E\n\u003Cp\u003EA JWT is a structured token containing signed claims.\u003C/p\u003E\n\u003Cp\u003EA typical JWT contains three encoded sections:\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\u003Eheader.payload.signature\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIts payload may describe the authenticated subject, tenant, authorization information, token lifetime, and other relevant context.\u003C/p\u003E\n\u003Cp\u003EA JWT is not encrypted merely because it is encoded and signed.\u003C/p\u003E\n\u003Cp\u003EApplications should not place secrets or unnecessarily sensitive information in a JWT payload.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022cryptographic-validation\u0022\u003ECryptographic Validation\u003C/h3\u003E\n\u003Cp\u003EA protected resource must validate the JWT according to the configured token requirements.\u003C/p\u003E\n\u003Cp\u003ERelevant checks include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESignature validity.\u003C/li\u003E\n\u003Cli\u003EAccepted signing algorithm and key.\u003C/li\u003E\n\u003Cli\u003EExpected issuer.\u003C/li\u003E\n\u003Cli\u003EIntended audience.\u003C/li\u003E\n\u003Cli\u003EExpiration and other applicable time constraints.\u003C/li\u003E\n\u003Cli\u003ERequired token claims and application-specific restrictions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA successfully decoded JWT is not necessarily a valid JWT.\u003C/p\u003E\n\u003Cp\u003ELikewise, a correctly signed JWT is not automatically acceptable to every resource.\u003C/p\u003E\n\u003Cp\u003EThe resource must verify that the token was issued for its intended security context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022local-validation\u0022\u003ELocal Validation\u003C/h3\u003E\n\u003Cp\u003EJWTs can be validated without loading current server-side session state.\u003C/p\u003E\n\u003Cp\u003EThis is useful for distributed APIs and services that need to authenticate requests efficiently.\u003C/p\u003E\n\u003Cp\u003EHowever, local validation introduces an important limitation:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA token can remain cryptographically valid after the underlying account or authorization state has changed.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EWhether such changes immediately affect access depends on additional validation and revocation mechanisms.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-behavior\u0022\u003EAuthentication Mode Behavior\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth selects credential behavior according to the configured authentication mode.\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\u003EAccess mechanism\u003C/th\u003E\n\u003Cth\u003EContinuation mechanism\u003C/th\u003E\n\u003Cth\u003EProtected request authority\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureOpaque\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOpaque session credential\u003C/td\u003E\n\u003Ctd\u003ESession lifecycle\u003C/td\u003E\n\u003Ctd\u003EServer-managed session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOpaque access credential\u003C/td\u003E\n\u003Ctd\u003EOpaque refresh credential\u003C/td\u003E\n\u003Ctd\u003EServer-managed session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESemiHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT access token\u003C/td\u003E\n\u003Ctd\u003EServer-managed refresh flow\u003C/td\u003E\n\u003Ctd\u003ELocally validated JWT\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureJwt\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT access token\u003C/td\u003E\n\u003Ctd\u003EOptional refresh mechanism\u003C/td\u003E\n\u003Ctd\u003ELocally validated JWT\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=\u0022pureopaque\u0022\u003EPureOpaque\u003C/h3\u003E\n\u003Cp\u003EPureOpaque uses an opaque session credential.\u003C/p\u003E\n\u003Cp\u003EIt does not require a separate access-token and refresh-token pair.\u003C/p\u003E\n\u003Cp\u003EThe credential is validated through the authoritative session mechanism.\u003C/p\u003E\n\u003Cp\u003ESession lifecycle operations govern the continuation and validity of authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003EHybrid\u003C/h3\u003E\n\u003Cp\u003EHybrid uses distinct opaque access and refresh credentials.\u003C/p\u003E\n\u003Cp\u003EAccess requests are validated against authoritative server-managed state.\u003C/p\u003E\n\u003Cp\u003ERefresh operations use the refresh credential to continue authentication according to the configured lifecycle and rotation behavior.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EHybrid does not require JWT access tokens.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid\u0022\u003ESemiHybrid\u003C/h3\u003E\n\u003Cp\u003ESemiHybrid issues JWT access tokens while maintaining server-side session state for relevant lifecycle operations.\u003C/p\u003E\n\u003Cp\u003EProtected requests can validate JWT access tokens locally.\u003C/p\u003E\n\u003Cp\u003ERefresh and logout interact with the server-managed authentication lifecycle.\u003C/p\u003E\n\u003Cp\u003EThis means that server-side revocation does not necessarily invalidate an already-issued JWT immediately.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022purejwt\u0022\u003EPureJwt\u003C/h3\u003E\n\u003Cp\u003EPureJwt uses JWT access tokens without requiring the Root \u2192 Chain \u2192 Session hierarchy for ordinary protected request validation.\u003C/p\u003E\n\u003Cp\u003EAn optional refresh mechanism may maintain refresh-specific state.\u003C/p\u003E\n\u003Cp\u003EThe existence of a refresh store does not make JWT access validation session-authoritative.\u003C/p\u003E\n\u003Cp\u003EFor more detail, 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=\u0022access-token-lifetime\u0022\u003EAccess Token Lifetime\u003C/h2\u003E\n\u003Cp\u003EAccess credentials should have lifetimes appropriate to their authentication mode and security requirements.\u003C/p\u003E\n\u003Cp\u003EShort-lived JWT access tokens are particularly important because a token may remain usable until expiration even after server-side authorization state changes.\u003C/p\u003E\n\u003Cp\u003EFor example, an application might choose a JWT access lifetime of several minutes.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Cstrong\u003Ea lifetime such as 5\u201315 minutes is an example of an application security choice, not a universal UltimateAuth default or requirement\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe appropriate value depends on factors such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESensitivity of protected resources.\u003C/li\u003E\n\u003Cli\u003EExpected frequency of authorization changes.\u003C/li\u003E\n\u003Cli\u003EAcceptable revocation delay.\u003C/li\u003E\n\u003Cli\u003EAvailability and security of the refresh mechanism.\u003C/li\u003E\n\u003Cli\u003EClient behavior and network conditions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifetime-versus-revocation\u0022\u003ELifetime Versus Revocation\u003C/h3\u003E\n\u003Cp\u003EExpiration limits how long a credential can be accepted.\u003C/p\u003E\n\u003Cp\u003ERevocation invalidates authentication state before its ordinary expiration.\u003C/p\u003E\n\u003Cp\u003EThese mechanisms are related but not interchangeable.\u003C/p\u003E\n\u003Cp\u003EIn server-authoritative modes, current revocation state can be checked during protected requests.\u003C/p\u003E\n\u003Cp\u003EIn locally validated JWT modes, expiration may remain the primary limit on an already-issued access token unless an additional revocation check is implemented.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-credentials\u0022\u003ERefresh Credentials\u003C/h2\u003E\n\u003Cp\u003ERefresh credentials allow clients to continue authentication without repeating the original authentication ceremony.\u003C/p\u003E\n\u003Cp\u003EThey are intended for the authentication system\u0027s refresh operation, not for general API access.\u003C/p\u003E\n\u003Cp\u003EIn UltimateAuth, refresh behavior is mode-dependent.\u003C/p\u003E\n\u003Cp\u003EPureOpaque does not require a separate refresh credential pair, while Hybrid and SemiHybrid use refresh flows. PureJwt can support an optional refresh mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-security-requirements\u0022\u003ERefresh Security Requirements\u003C/h3\u003E\n\u003Cp\u003EA secure refresh operation must establish that the presented credential is eligible for continued authentication.\u003C/p\u003E\n\u003Cp\u003EDepending on the mode and configured strategy, this may involve checking:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredential existence and validity.\u003C/li\u003E\n\u003Cli\u003EExpiration.\u003C/li\u003E\n\u003Cli\u003ERevocation or consumption state.\u003C/li\u003E\n\u003Cli\u003EAssociated user or session context.\u003C/li\u003E\n\u003Cli\u003EApplicable client and device context.\u003C/li\u003E\n\u003Cli\u003ERotation and reuse conditions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA valid refresh credential should not automatically bypass account-level or session-level security requirements that apply to the selected mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022access-and-refresh-separation\u0022\u003EAccess and Refresh Separation\u003C/h3\u003E\n\u003Cp\u003EAccess and refresh credentials have different responsibilities.\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\n |\n v\nAccess credential\n |\n | Used for protected requests\n v\nAccess expires\n |\n v\nRefresh operation\n |\n | Validate refresh credential\n | Apply continuation rules\n v\nRenewed access credential\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe refresh operation does not extend the expiration timestamp of an already-issued JWT.\u003C/p\u003E\n\u003Cp\u003EIt issues new authentication material when continuation is permitted.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-rotation\u0022\u003ERefresh Rotation\u003C/h2\u003E\n\u003Cp\u003ERefresh rotation replaces refresh authentication material according to the configured refresh strategy.\u003C/p\u003E\n\u003Cp\u003EA rotating refresh mechanism can invalidate the previously used refresh credential and issue a replacement.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003ERefresh credential A\n |\n | Successful refresh\n v\nRefresh credential A consumed\nRefresh credential B issued\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis reduces the period during which an older refresh credential remains reusable.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Cstrong\u003Erotation behavior is not identical across every UltimateAuth mode or refresh strategy\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003ESome session continuation operations may update existing server-side state without replacing a separate refresh credential.\u003C/p\u003E\n\u003Cp\u003EThe effective behavior must follow the selected mode and its configured refresh strategy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-is-not-replay-prevention-by-itself\u0022\u003ERotation Is Not Replay Prevention by Itself\u003C/h3\u003E\n\u003Cp\u003ERotation limits credential reuse only when the server enforces the relevant consumption or replacement state.\u003C/p\u003E\n\u003Cp\u003EA secure rotating refresh mechanism must account for situations such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe same credential being submitted again.\u003C/li\u003E\n\u003Cli\u003EConcurrent refresh requests.\u003C/li\u003E\n\u003Cli\u003EA credential being reused after replacement.\u003C/li\u003E\n\u003Cli\u003EAn interrupted response after a successful rotation.\u003C/li\u003E\n\u003Cli\u003EAn attacker and legitimate client attempting to use the same credential.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact outcome depends on the implemented refresh strategy and concurrency guarantees.\u003C/p\u003E\n\u003Cp\u003ERotation should not be described as automatically eliminating every replay risk.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-token-reuse\u0022\u003ERefresh Token Reuse\u003C/h2\u003E\n\u003Cp\u003ERefresh token reuse occurs when a credential is presented after it has already been consumed, replaced, or otherwise invalidated.\u003C/p\u003E\n\u003Cp\u003EFor a single-use rotating refresh credential, this is a security-relevant event.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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 refreshes with A\n |\n v\nA replaced by B\n |\n v\nAnother request presents A\n |\n v\nReuse / invalid credential condition\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA reuse attempt may indicate credential theft, but it can also arise from concurrent requests or client synchronization failures.\u003C/p\u003E\n\u003Cp\u003EThe refresh implementation must distinguish these situations according to its supported guarantees and policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-response\u0022\u003ESecurity Response\u003C/h3\u003E\n\u003Cp\u003EPossible security responses include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERejecting the invalid refresh attempt.\u003C/li\u003E\n\u003Cli\u003EInvalidating the associated refresh state.\u003C/li\u003E\n\u003Cli\u003ERevoking an affected session.\u003C/li\u003E\n\u003Cli\u003ERevoking a broader session chain where supported and appropriate.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese are distinct actions.\u003C/p\u003E\n\u003Cp\u003EThe presence of a reuse-detection mechanism does not imply that every reuse attempt automatically revokes the entire device-associated chain.\u003C/p\u003E\n\u003Cp\u003ESuch behavior must be explicitly supported by the configured refresh strategy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-credential-storage\u0022\u003ERefresh Credential Storage\u003C/h2\u003E\n\u003Cp\u003ERefresh credentials are generally more sensitive to long-term compromise because they may allow new access credentials to be obtained.\u003C/p\u003E\n\u003Cp\u003ETheir storage therefore deserves stronger protection than ordinary client-visible access metadata.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-side-storage\u0022\u003EServer-Side Storage\u003C/h3\u003E\n\u003Cp\u003EFor persisted opaque refresh credentials, storing a one-way verifier or hash instead of the raw credential can reduce the impact of a storage disclosure.\u003C/p\u003E\n\u003Cp\u003EHowever, the exact persistence model depends on the selected authentication mode and refresh implementation.\u003C/p\u003E\n\u003Cp\u003EIt should not be assumed that every authentication mode uses the same refresh storage structure.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-side-storage\u0022\u003EClient-Side Storage\u003C/h3\u003E\n\u003Cp\u003EThe appropriate client storage mechanism depends on the application environment.\u003C/p\u003E\n\u003Cp\u003EFor browser applications, HttpOnly cookies can prevent JavaScript from directly reading refresh credentials, although cookie-based refresh flows still require appropriate CSRF and origin protections.\u003C/p\u003E\n\u003Cp\u003ENative applications may use platform-provided secure storage where suitable.\u003C/p\u003E\n\u003Cp\u003ERefresh credentials should not be exposed through application logs, diagnostic output, URLs, or unrelated API responses.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022transport-security\u0022\u003ETransport Security\u003C/h3\u003E\n\u003Cp\u003EAuthentication credentials must be protected in transit.\u003C/p\u003E\n\u003Cp\u003EProduction authentication and refresh endpoints should use HTTPS.\u003C/p\u003E\n\u003Cp\u003EApplications should also avoid sending refresh credentials to services that do not need to receive them.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022claims-and-authorization-freshness\u0022\u003EClaims and Authorization Freshness\u003C/h2\u003E\n\u003Cp\u003EJWT claims represent information established when the token was issued.\u003C/p\u003E\n\u003Cp\u003EThey may include standard or application-specific information such 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\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\u003Esub Subject identifier\niss Issuer\naud Audience\nexp Expiration\niat Issued-at time\njti Token identifier\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDepending on the application, a JWT may also include tenant information, roles, permissions, or session references.\u003C/p\u003E\n\u003Cp\u003EThese claims are not automatically updated when the underlying account changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-permission-removal\u0022\u003EExample: Permission Removal\u003C/h3\u003E\n\u003Cp\u003EConsider the following sequence:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EA JWT is issued containing an administrative permission.\u003C/li\u003E\n\u003Cli\u003EAn administrator removes that permission from the user.\u003C/li\u003E\n\u003Cli\u003EThe client continues presenting the previously issued JWT.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EIf the protected resource relies only on the signed claims in that JWT, the removed permission may remain effective until the token expires.\u003C/p\u003E\n\u003Cp\u003EThe signature remains valid because the token has not been modified.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-strategies\u0022\u003EAuthorization Strategies\u003C/h3\u003E\n\u003Cp\u003EApplications can address this limitation through their security architecture, including:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EShorter JWT lifetimes.\u003C/li\u003E\n\u003Cli\u003EReissuing claims during refresh.\u003C/li\u003E\n\u003Cli\u003EConsulting authoritative authorization state for sensitive operations.\u003C/li\u003E\n\u003Cli\u003EUsing server-authoritative authentication modes where immediate state enforcement is required.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe appropriate approach depends on the security requirements of the application.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E and \u003Ca href=\u0022/docs/plugin-domains/policies\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-revocation\u0022\u003EToken Revocation\u003C/h2\u003E\n\u003Cp\u003ERevocation behavior differs fundamentally between opaque credentials and locally validated JWTs.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022opaque-credential-revocation\u0022\u003EOpaque Credential Revocation\u003C/h3\u003E\n\u003Cp\u003EAn opaque credential depends on authoritative validation.\u003C/p\u003E\n\u003Cp\u003EIf the corresponding server-managed authentication state is revoked, subsequent validation can reject the credential.\u003C/p\u003E\n\u003Cp\u003EThe precise scope depends on whether the security operation targets a session, chain, or account-level root.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022jwt-revocation\u0022\u003EJWT Revocation\u003C/h3\u003E\n\u003Cp\u003EA signed JWT cannot be made cryptographically invalid merely by deleting a server-side session record.\u003C/p\u003E\n\u003Cp\u003EIts signature and expiration remain unchanged.\u003C/p\u003E\n\u003Cp\u003ETo reject an otherwise valid JWT before expiration, the resource must enforce an additional condition, such as an authoritative revocation check or another supported invalidation mechanism.\u003C/p\u003E\n\u003Cp\u003EWithout that additional enforcement, a locally validated JWT may remain acceptable until expiration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-revocation\u0022\u003ERefresh Revocation\u003C/h3\u003E\n\u003Cp\u003ERevoking a refresh credential can prevent further successful refresh operations.\u003C/p\u003E\n\u003Cp\u003EHowever, that does not necessarily invalidate access tokens already issued before the revocation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAccess revocation and refresh revocation are separate security concerns.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-security-and-token-security\u0022\u003ESession Security and Token Security\u003C/h2\u003E\n\u003Cp\u003EToken security and session security are closely related, but they operate at different levels.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcern\u003C/th\u003E\n\u003Cth\u003EToken Security\u003C/th\u003E\n\u003Cth\u003ESession Security\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential format\u003C/td\u003E\n\u003Ctd\u003EOpaque or JWT\u003C/td\u003E\n\u003Ctd\u003ERoot, chain, and session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECryptographic validation\u003C/td\u003E\n\u003Ctd\u003EJWT signature and claims\u003C/td\u003E\n\u003Ctd\u003ENot a substitute for token signature validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EExpiration\u003C/td\u003E\n\u003Ctd\u003EToken validity period\u003C/td\u003E\n\u003Ctd\u003ESession and chain lifecycle limits\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERotation\u003C/td\u003E\n\u003Ctd\u003EReplacement of authentication material\u003C/td\u003E\n\u003Ctd\u003EContinuity and associated state transitions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERevocation\u003C/td\u003E\n\u003Ctd\u003EWhether a presented credential remains acceptable\u003C/td\u003E\n\u003Ctd\u003EWhich authentication state has been invalidated\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDevice context\u003C/td\u003E\n\u003Ctd\u003EMay be associated with token use\u003C/td\u003E\n\u003Ctd\u003EManaged through applicable chain and session context\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA token may be valid according to its own format but unacceptable under authoritative session security rules.\u003C/p\u003E\n\u003Cp\u003EConversely, server-side session state may remain active while a particular access token has expired.\u003C/p\u003E\n\u003Cp\u003EBoth levels must be understood to reason correctly about the selected authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-security-misconceptions\u0022\u003ECommon Security Misconceptions\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-valid-signature-means-current-authorization\u0022\u003EA Valid Signature Means Current Authorization\u003C/h3\u003E\n\u003Cp\u003EA valid JWT signature proves that the token has not been modified and was signed by an accepted key.\u003C/p\u003E\n\u003Cp\u003EIt does not prove that the user\u0027s permissions or account status have remained unchanged since issuance.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-device-identifier-prevents-token-theft\u0022\u003EA Device Identifier Prevents Token Theft\u003C/h3\u003E\n\u003Cp\u003EA device identifier may support context consistency checks.\u003C/p\u003E\n\u003Cp\u003EIt does not independently prevent a stolen bearer credential from being replayed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-rotation-makes-token-theft-impossible\u0022\u003ERefresh Rotation Makes Token Theft Impossible\u003C/h3\u003E\n\u003Cp\u003ERotation can reduce reuse opportunities and support detection of invalid reuse.\u003C/p\u003E\n\u003Cp\u003EIt does not prevent a stolen credential from being used before the legitimate client or server detects the compromise.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-invalidates-every-jwt-immediately\u0022\u003ELogout Invalidates Every JWT Immediately\u003C/h3\u003E\n\u003Cp\u003ELogout may revoke server-managed session or refresh state.\u003C/p\u003E\n\u003Cp\u003EPreviously issued JWT access tokens can remain valid until expiration unless protected resources enforce an additional invalidation mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022short-lived-tokens-eliminate-the-need-for-session-security\u0022\u003EShort-Lived Tokens Eliminate the Need for Session Security\u003C/h3\u003E\n\u003Cp\u003EShort lifetimes reduce the exposure window of a compromised token.\u003C/p\u003E\n\u003Cp\u003EThey do not replace account-level security policies, credential recovery protections, or session revocation controls where those capabilities are required.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-design-principles\u0022\u003ESecurity Design Principles\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s token model is best understood through a few principles.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EUse credentials only for their intended purpose.\u003C/strong\u003E Access and refresh credentials must not be interchangeable.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EValidate according to the selected mode.\u003C/strong\u003E Opaque credentials require authoritative validation; JWTs require correct cryptographic and claims validation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ETreat JWT claims as issuance-time information.\u003C/strong\u003E Current authorization state may differ from what an older token contains.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EKeep access credentials appropriately short-lived.\u003C/strong\u003E Token lifetime should reflect the application\u0027s acceptable exposure window.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EEnforce refresh continuation rules.\u003C/strong\u003E Rotation, expiration, consumption, and reuse behavior must be consistent with the configured strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDistinguish token expiration from session revocation.\u003C/strong\u003E They invalidate authentication through different mechanisms.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not promise immediate JWT revocation without enforcement.\u003C/strong\u003E Server-side state changes affect locally validated tokens only when the resource checks the relevant state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth does not impose a single token model on every application.\u003C/p\u003E\n\u003Cp\u003EPureOpaque and Hybrid emphasize server-authoritative authentication state.\u003C/p\u003E\n\u003Cp\u003ESemiHybrid and PureJwt support locally validated JWT access, with different approaches to authentication continuation.\u003C/p\u003E\n\u003Cp\u003EThese choices affect performance, storage requirements, revocation behavior, and authorization freshness.\u003C/p\u003E\n\u003Cp\u003EThe purpose of the token security model is to make those tradeoffs explicit.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA token establishes access only while its applicable validation requirements remain satisfied. The strength and freshness of those requirements depend on the authentication mode.\u003C/strong\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/security/session-security-model\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand authoritative session validation and hierarchical revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Compare credential behavior across modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/session-model\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E \u2014 Explore session creation, continuation, and invalidation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Understand credential management and recovery security.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Headings": [ + { + "Id": "authentication-state-and-credentials", + "Text": "Authentication State and Credentials", + "Level": 0 + }, + { + "Id": "server-authoritative-authentication", + "Text": "Server-Authoritative Authentication", + "Level": 1 + }, + { + "Id": "locally-validated-authentication", + "Text": "Locally Validated Authentication", + "Level": 1 + }, + { + "Id": "token-types", + "Text": "Token Types", + "Level": 0 + }, + { + "Id": "opaque-access-credentials", + "Text": "Opaque Access Credentials", + "Level": 0 + }, + { + "Id": "security-properties", + "Text": "Security Properties", + "Level": 1 + }, + { + "Id": "server-side-validation", + "Text": "Server-Side Validation", + "Level": 1 + }, + { + "Id": "jwt-access-tokens", + "Text": "JWT Access Tokens", + "Level": 0 + }, + { + "Id": "cryptographic-validation", + "Text": "Cryptographic Validation", + "Level": 1 + }, + { + "Id": "local-validation", + "Text": "Local Validation", + "Level": 1 + }, + { + "Id": "authentication-mode-behavior", + "Text": "Authentication Mode Behavior", + "Level": 0 + }, + { + "Id": "pureopaque", + "Text": "PureOpaque", + "Level": 1 + }, + { + "Id": "hybrid", + "Text": "Hybrid", + "Level": 1 + }, + { + "Id": "semihybrid", + "Text": "SemiHybrid", + "Level": 1 + }, + { + "Id": "purejwt", + "Text": "PureJwt", + "Level": 1 + }, + { + "Id": "access-token-lifetime", + "Text": "Access Token Lifetime", + "Level": 0 + }, + { + "Id": "lifetime-versus-revocation", + "Text": "Lifetime Versus Revocation", + "Level": 1 + }, + { + "Id": "refresh-credentials", + "Text": "Refresh Credentials", + "Level": 0 + }, + { + "Id": "refresh-security-requirements", + "Text": "Refresh Security Requirements", + "Level": 1 + }, + { + "Id": "access-and-refresh-separation", + "Text": "Access and Refresh Separation", + "Level": 1 + }, + { + "Id": "refresh-rotation", + "Text": "Refresh Rotation", + "Level": 0 + }, + { + "Id": "rotation-is-not-replay-prevention-by-itself", + "Text": "Rotation Is Not Replay Prevention by Itself", + "Level": 1 + }, + { + "Id": "refresh-token-reuse", + "Text": "Refresh Token Reuse", + "Level": 0 + }, + { + "Id": "security-response", + "Text": "Security Response", + "Level": 1 + }, + { + "Id": "refresh-credential-storage", + "Text": "Refresh Credential Storage", + "Level": 0 + }, + { + "Id": "server-side-storage", + "Text": "Server-Side Storage", + "Level": 1 + }, + { + "Id": "client-side-storage", + "Text": "Client-Side Storage", + "Level": 1 + }, + { + "Id": "transport-security", + "Text": "Transport Security", + "Level": 1 + }, + { + "Id": "claims-and-authorization-freshness", + "Text": "Claims and Authorization Freshness", + "Level": 0 + }, + { + "Id": "example-permission-removal", + "Text": "Example: Permission Removal", + "Level": 1 + }, + { + "Id": "authorization-strategies", + "Text": "Authorization Strategies", + "Level": 1 + }, + { + "Id": "token-revocation", + "Text": "Token Revocation", + "Level": 0 + }, + { + "Id": "opaque-credential-revocation", + "Text": "Opaque Credential Revocation", + "Level": 1 + }, + { + "Id": "jwt-revocation", + "Text": "JWT Revocation", + "Level": 1 + }, + { + "Id": "refresh-revocation", + "Text": "Refresh Revocation", + "Level": 1 + }, + { + "Id": "session-security-and-token-security", + "Text": "Session Security and Token Security", + "Level": 0 + }, + { + "Id": "common-security-misconceptions", + "Text": "Common Security Misconceptions", + "Level": 0 + }, + { + "Id": "a-valid-signature-means-current-authorization", + "Text": "A Valid Signature Means Current Authorization", + "Level": 1 + }, + { + "Id": "a-device-identifier-prevents-token-theft", + "Text": "A Device Identifier Prevents Token Theft", + "Level": 1 + }, + { + "Id": "refresh-rotation-makes-token-theft-impossible", + "Text": "Refresh Rotation Makes Token Theft Impossible", + "Level": 1 + }, + { + "Id": "logout-invalidates-every-jwt-immediately", + "Text": "Logout Invalidates Every JWT Immediately", + "Level": 1 + }, + { + "Id": "short-lived-tokens-eliminate-the-need-for-session-security", + "Text": "Short-Lived Tokens Eliminate the Need for Session Security", + "Level": 1 + }, + { + "Id": "security-design-principles", + "Text": "Security Design Principles", + "Level": 0 + }, + { + "Id": "architectural-perspective", + "Text": "Architectural Perspective", + "Level": 0 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/device-management.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/device-management.json new file mode 100644 index 00000000..2fe0d598 --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/device-management.json @@ -0,0 +1,127 @@ +{ + "Slug": "sessions-devices/device-management", + "Title": "Device Management", + "Html": "\n\u003Cp\u003EAuthentication does not happen in isolation from the device or client environment in which it takes place.\u003C/p\u003E\n\u003Cp\u003EA user may authenticate from a desktop browser, a mobile application, or several different environments simultaneously. Each environment can have its own authentication state, activity history, and security requirements.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports \u003Cstrong\u003Edevice-aware session management\u003C/strong\u003E by associating authentication sessions with device context and organizing related sessions through \u003Ccode\u003ESessionChain\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis provides a foundation for device-level visibility, session continuity, and targeted security operations without treating device identity as a substitute for user authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-device-aware-authentication\u0022\u003EUnderstanding Device-Aware Authentication\u003C/h2\u003E\n\u003Cp\u003ETraditional token-based authentication can be implemented without explicitly modeling the client environment.\u003C/p\u003E\n\u003Cp\u003EA token is issued, validated, and eventually expires or is revoked.\u003C/p\u003E\n\u003Cp\u003EThat approach is sufficient for some applications, but it becomes limiting when an application needs to distinguish between different client environments belonging to the same user.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA user signs in from both a desktop browser and a mobile application.\u003C/li\u003E\n\u003Cli\u003EAn administrator needs to revoke sessions associated with one client environment.\u003C/li\u003E\n\u003Cli\u003EA session is refreshed repeatedly while remaining associated with the same device context.\u003C/li\u003E\n\u003Cli\u003EAn application needs to apply session limits or track activity across client environments.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth separates these concerns through its session and chain model.\u003C/p\u003E\n\u003Cp\u003EThe objective is not to prove the physical identity of a device. It is to provide a structured way to associate authentication activity with a recognizable client context.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-context-and-sessionchain\u0022\u003EDevice Context and SessionChain\u003C/h2\u003E\n\u003Cp\u003EThe central concept in UltimateAuth\u0027s device-aware model is \u003Ccode\u003ESessionChain\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA session represents authentication state.\u003C/p\u003E\n\u003Cp\u003EA \u003Ccode\u003ESessionChain\u003C/code\u003E groups related session activity under a persistent chain identity, which can be associated with a device or client environment.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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 Identity\n |\n \u002B-- SessionChain (Desktop Browser)\n | |\n | \u002B-- Session\n | \u002B-- Session / Rotation History\n |\n \u002B-- SessionChain (Mobile Application)\n |\n \u002B-- Session\n \u002B-- Session / Rotation History\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis diagram illustrates the relationship between identity, chain, and session. It does not imply that every authentication mode retains multiple simultaneous session records or stores a complete rotation history.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-chain-is-not-a-physical-device\u0022\u003EA Chain Is Not a Physical Device\u003C/h3\u003E\n\u003Cp\u003EA \u003Ccode\u003ESessionChain\u003C/code\u003E should not be understood as a physical hardware identifier.\u003C/p\u003E\n\u003Cp\u003EIt represents a logical continuity boundary for related authentication sessions.\u003C/p\u003E\n\u003Cp\u003EA single physical device may use different browsers, application installations, or isolated client storage environments.\u003C/p\u003E\n\u003Cp\u003EConversely, device identifiers supplied by clients are not necessarily reliable proof that requests originate from the same physical device.\u003C/p\u003E\n\u003Cp\u003EThe practical relationship is:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA device context can be associated with a SessionChain, but the chain itself is an authentication-session concept.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis distinction is important when designing security rules around device recognition and session revocation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-information\u0022\u003EDevice Information\u003C/h2\u003E\n\u003Cp\u003EDevice-aware session management can use information describing the client environment.\u003C/p\u003E\n\u003Cp\u003ERelevant information may include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EInformation\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\u003EDeviceId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifies a client-reported or application-managed device context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPlatform\u003C/td\u003E\n\u003Ctd\u003EDistinguishes environments such as web and mobile\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOperating system\u003C/td\u003E\n\u003Ctd\u003EProvides client environment metadata\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBrowser\u003C/td\u003E\n\u003Ctd\u003EDescribes the browser environment when applicable\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIP address\u003C/td\u003E\n\u003Ctd\u003EProvides network context for security and activity analysis\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe availability and reliability of this information depend on the client platform and the application\u0027s integration.\u003C/p\u003E\n\u003Cp\u003ENot every client can provide the same metadata.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deviceid-and-trust\u0022\u003EDeviceId and Trust\u003C/h3\u003E\n\u003Cp\u003EA \u003Ccode\u003EDeviceId\u003C/code\u003E is useful for identifying a client environment across requests or authentication operations.\u003C/p\u003E\n\u003Cp\u003EHowever, an identifier alone is not proof of device ownership.\u003C/p\u003E\n\u003Cp\u003EA value stored in browser storage, for example, may be copied, cleared, or replaced. An identifier generated by a mobile application may change after application data is removed.\u003C/p\u003E\n\u003Cp\u003EFor that reason, device recognition must not be confused with authentication.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s authentication and session validation rules remain responsible for determining whether a request is authorized to use the associated authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-fingerprinting\u0022\u003EDevice Fingerprinting\u003C/h3\u003E\n\u003Cp\u003EPlatform, operating system, browser, and network information may help describe a client environment.\u003C/p\u003E\n\u003Cp\u003EThese attributes are not inherently unique or stable.\u003C/p\u003E\n\u003Cp\u003EMultiple devices can share similar attributes, and a single device can present different attributes over time.\u003C/p\u003E\n\u003Cp\u003EThey should therefore be treated as contextual signals rather than a guaranteed device fingerprint.\u003C/p\u003E\n\u003Cp\u003EApplications requiring stronger device assurance need an appropriate proof or binding mechanism in addition to ordinary metadata.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-continuity\u0022\u003EDevice Continuity\u003C/h2\u003E\n\u003Cp\u003EDevice continuity describes how authentication activity remains associated with an existing client context.\u003C/p\u003E\n\u003Cp\u003EA session chain provides the logical structure for maintaining that continuity.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022first-authentication\u0022\u003EFirst Authentication\u003C/h3\u003E\n\u003Cp\u003EWhen an authentication flow establishes a new session, the session infrastructure determines the appropriate chain association.\u003C/p\u003E\n\u003Cp\u003EIf a new chain is required, the session is associated with that chain.\u003C/p\u003E\n\u003Cp\u003EThe resulting chain can represent the beginning of a device-associated authentication history.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022subsequent-authentication\u0022\u003ESubsequent Authentication\u003C/h3\u003E\n\u003Cp\u003EA later authentication from the same client environment may be associated with an existing chain when the configured matching and security conditions permit it.\u003C/p\u003E\n\u003Cp\u003EHowever, recognizing a device identifier does not automatically require the system to reuse a previous chain.\u003C/p\u003E\n\u003Cp\u003EChain association must respect the applicable lifecycle, validity, and security rules.\u003C/p\u003E\n\u003Cp\u003EFor example, a revoked chain should not become trusted again merely because a client presents the same device identifier.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-continuity-is-not-session-continuity\u0022\u003EDevice Continuity Is Not Session Continuity\u003C/h3\u003E\n\u003Cp\u003EA client environment can remain recognizable even when its previous authentication session has expired.\u003C/p\u003E\n\u003Cp\u003ELikewise, a new authentication session can be established without inheriting the validity of an earlier session.\u003C/p\u003E\n\u003Cp\u003EThe chain model allows related activity to be organized while keeping session validity subject to authentication and security rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022activity-tracking\u0022\u003EActivity Tracking\u003C/h2\u003E\n\u003Cp\u003EDevice-associated session management can maintain information about authentication activity.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhen a chain was created.\u003C/li\u003E\n\u003Cli\u003EWhen related authentication activity was last observed.\u003C/li\u003E\n\u003Cli\u003EHow often relevant session activity was recorded.\u003C/li\u003E\n\u003Cli\u003EHow many rotation operations were associated with the chain.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFields such as \u003Ccode\u003ELastSeenAt\u003C/code\u003E, \u003Ccode\u003ETouchCount\u003C/code\u003E, and \u003Ccode\u003ERotationCount\u003C/code\u003E help describe these aspects of the session lifecycle.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022touch-operations\u0022\u003ETouch Operations\u003C/h3\u003E\n\u003Cp\u003EA touch operation records qualifying session activity according to the configured session behavior.\u003C/p\u003E\n\u003Cp\u003EDepending on the authentication mode and session strategy, this may update activity metadata such as the last-seen timestamp or touch counter.\u003C/p\u003E\n\u003Cp\u003ETouch behavior should not be interpreted as a guarantee that every authenticated HTTP request updates persistent device activity.\u003C/p\u003E\n\u003Cp\u003EDifferent authentication modes have different requirements for consulting and updating server-side session state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-tracking\u0022\u003ERotation Tracking\u003C/h3\u003E\n\u003Cp\u003EToken or session rotation may replace authentication material while preserving the logical relationship with an existing chain.\u003C/p\u003E\n\u003Cp\u003EWhere rotation tracking is supported, the chain can maintain information about those transitions.\u003C/p\u003E\n\u003Cp\u003EThis makes it possible to distinguish the continuity of a client environment from the lifecycle of an individual credential or session.\u003C/p\u003E\n\u003Cp\u003ERotation counts are operational signals. They do not independently establish that a device is legitimate or that suspicious activity has occurred.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-and-device-revocation\u0022\u003ELogout and Device Revocation\u003C/h2\u003E\n\u003Cp\u003ELogout and chain revocation address different security scopes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003ELogout\u003C/h3\u003E\n\u003Cp\u003ELogout terminates the authentication state targeted by the logout operation.\u003C/p\u003E\n\u003Cp\u003EIts precise effect depends on the authentication mode, the credentials involved, and the configured session behavior.\u003C/p\u003E\n\u003Cp\u003EA logout operation does not necessarily mean that every session associated with the same device context has been revoked.\u003C/p\u003E\n\u003Cp\u003EIt also does not necessarily require deleting the associated chain record.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-revocation\u0022\u003EChain Revocation\u003C/h3\u003E\n\u003Cp\u003EChain revocation targets the logical group of sessions associated with a chain.\u003C/p\u003E\n\u003Cp\u003EIt provides a broader security operation than terminating a single session.\u003C/p\u003E\n\u003Cp\u003EThis is useful when an application needs to invalidate the authentication continuity associated with a particular client environment.\u003C/p\u003E\n\u003Cp\u003EFor example, if a user believes a mobile application session has been compromised, the application may revoke the corresponding chain rather than revoking unrelated sessions associated with other environments.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-does-not-establish-device-trust\u0022\u003ERevocation Does Not Establish Device Trust\u003C/h3\u003E\n\u003Cp\u003EA retained chain record is not equivalent to a trusted device.\u003C/p\u003E\n\u003Cp\u003ELikewise, removing or revoking a chain does not physically block a device from attempting to authenticate again.\u003C/p\u003E\n\u003Cp\u003EWhether a new authentication attempt is permitted depends on the authentication flow and the applicable security policies.\u003C/p\u003E\n\u003Cp\u003EThe chain model provides a boundary for managing authentication state; it is not a permanent device allowlist or hardware security mechanism.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-binding-and-security-boundaries\u0022\u003EDevice Binding and Security Boundaries\u003C/h2\u003E\n\u003Cp\u003EDevice-aware authentication can associate session state with device context.\u003C/p\u003E\n\u003Cp\u003EHowever, the strength of that association depends on what is actually validated.\u003C/p\u003E\n\u003Cp\u003EThere is an important distinction between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice metadata:\u003C/strong\u003E Information describing a client environment.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice identification:\u003C/strong\u003E An identifier used to recognize a client context.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice binding:\u003C/strong\u003E A security requirement connecting authentication material to a validated device-related condition.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice authentication:\u003C/strong\u003E Proof that a client possesses an appropriate device-bound secret or cryptographic key.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese concepts provide different levels of assurance.\u003C/p\u003E\n\u003Cp\u003EAn application must not assume that supplying a \u003Ccode\u003EDeviceId\u003C/code\u003E alone prevents stolen credentials from being reused elsewhere.\u003C/p\u003E\n\u003Cp\u003ESecurity guarantees depend on the binding and validation mechanisms enforced by the configured authentication infrastructure.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-management-across-authentication-modes\u0022\u003EDevice Management Across Authentication Modes\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports different authentication modes, and device-aware session behavior must be understood within those modes.\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\u003EDevice and session considerations\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureOpaque\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EServer-side session state is authoritative for protected requests, allowing session validity to be evaluated against server-managed state.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOpaque access and refresh credentials are associated with server-authoritative session state.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESemiHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EServer-side session state participates in lifecycle operations, while issued JWT access tokens may remain valid until expiration.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureJwt\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EProtected requests are primarily validated from JWT contents; server-side device and session enforcement depends on the configured supporting infrastructure.\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe presence of a device-associated chain does not imply identical enforcement behavior in every mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-and-jwt-validity\u0022\u003ERevocation and JWT Validity\u003C/h3\u003E\n\u003Cp\u003EIn modes that validate protected requests against authoritative server-side session state, session revocation can affect subsequent requests through that validation.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, a previously issued access token may remain valid until expiration if protected requests do not consult current revocation state.\u003C/p\u003E\n\u003Cp\u003ETherefore, revoking a device-associated chain must not be described as universally invalidating every previously issued access token immediately.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-and-device-limits\u0022\u003ESession and Device Limits\u003C/h2\u003E\n\u003Cp\u003EApplications may need to limit how many authentication sessions or client environments a user can maintain.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s session configuration provides the architectural place for managing supported session and chain constraints.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured capabilities, these constraints may concern:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe number of chains associated with a user.\u003C/li\u003E\n\u003Cli\u003EThe number of sessions associated with a chain.\u003C/li\u003E\n\u003Cli\u003ESession expiration and activity behavior.\u003C/li\u003E\n\u003Cli\u003EThe treatment of device context changes.\u003C/li\u003E\n\u003Cli\u003ERevocation and lifecycle rules.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact available options and enforcement behavior depend on the authentication mode and session configuration.\u003C/p\u003E\n\u003Cp\u003EDevice management should not be understood as a separate enforcement system operating independently of session management.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022operational-visibility\u0022\u003EOperational Visibility\u003C/h2\u003E\n\u003Cp\u003EDevice-associated session information can help applications present useful security information to users or administrators.\u003C/p\u003E\n\u003Cp\u003EFor example, an account management interface may display recognizable client environments and provide an option to revoke related sessions.\u003C/p\u003E\n\u003Cp\u003ESuch interfaces should distinguish between recorded information and verified facts.\u003C/p\u003E\n\u003Cp\u003EA browser name or IP address may help a user recognize an activity record, but it does not conclusively identify the person or physical device involved.\u003C/p\u003E\n\u003Cp\u003ELikewise, activity counters can support monitoring without independently providing anomaly detection.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides session and chain concepts that applications can use for these experiences, while the application\u0027s security and user-interface requirements determine how the information should be presented.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EDevice management in UltimateAuth is built on the relationship between identity, session, and chain.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe user identity identifies the account. The session represents authentication state. The SessionChain organizes related session continuity, potentially associated with a device context.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis model enables targeted session management and device-aware security operations without making device metadata an independent source of authentication trust.\u003C/p\u003E\n\u003Cp\u003EIt also preserves an important architectural boundary: device recognition, session validity, and authorization remain distinct security concerns.\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/sessions-devices/session-model\u0022\u003ESession Model\u003C/a\u003E \u2014 Understand sessions, chains, and their lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/session-security\u0022\u003ESession Security\u003C/a\u003E \u2014 Explore expiration, rotation, touch, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Compare session validation across authentication modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/request-lifecycle\u0022\u003ERequest Lifecycle\u003C/a\u003E \u2014 Understand how protected requests are authenticated and authorized.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Headings": [ + { + "Id": "understanding-device-aware-authentication", + "Text": "Understanding Device-Aware Authentication", + "Level": 0 + }, + { + "Id": "device-context-and-sessionchain", + "Text": "Device Context and SessionChain", + "Level": 0 + }, + { + "Id": "a-chain-is-not-a-physical-device", + "Text": "A Chain Is Not a Physical Device", + "Level": 1 + }, + { + "Id": "device-information", + "Text": "Device Information", + "Level": 0 + }, + { + "Id": "deviceid-and-trust", + "Text": "DeviceId and Trust", + "Level": 1 + }, + { + "Id": "device-fingerprinting", + "Text": "Device Fingerprinting", + "Level": 1 + }, + { + "Id": "device-continuity", + "Text": "Device Continuity", + "Level": 0 + }, + { + "Id": "first-authentication", + "Text": "First Authentication", + "Level": 1 + }, + { + "Id": "subsequent-authentication", + "Text": "Subsequent Authentication", + "Level": 1 + }, + { + "Id": "device-continuity-is-not-session-continuity", + "Text": "Device Continuity Is Not Session Continuity", + "Level": 1 + }, + { + "Id": "activity-tracking", + "Text": "Activity Tracking", + "Level": 0 + }, + { + "Id": "touch-operations", + "Text": "Touch Operations", + "Level": 1 + }, + { + "Id": "rotation-tracking", + "Text": "Rotation Tracking", + "Level": 1 + }, + { + "Id": "logout-and-device-revocation", + "Text": "Logout and Device Revocation", + "Level": 0 + }, + { + "Id": "logout", + "Text": "Logout", + "Level": 1 + }, + { + "Id": "chain-revocation", + "Text": "Chain Revocation", + "Level": 1 + }, + { + "Id": "revocation-does-not-establish-device-trust", + "Text": "Revocation Does Not Establish Device Trust", + "Level": 1 + }, + { + "Id": "device-binding-and-security-boundaries", + "Text": "Device Binding and Security Boundaries", + "Level": 0 + }, + { + "Id": "device-management-across-authentication-modes", + "Text": "Device Management Across Authentication Modes", + "Level": 0 + }, + { + "Id": "revocation-and-jwt-validity", + "Text": "Revocation and JWT Validity", + "Level": 1 + }, + { + "Id": "session-and-device-limits", + "Text": "Session and Device Limits", + "Level": 0 + }, + { + "Id": "operational-visibility", + "Text": "Operational Visibility", + "Level": 0 + }, + { + "Id": "architectural-perspective", + "Text": "Architectural Perspective", + "Level": 0 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/session-lifecycle.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/session-lifecycle.json new file mode 100644 index 00000000..45f5c18d --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/session-lifecycle.json @@ -0,0 +1,162 @@ +{ + "Slug": "sessions-devices/session-lifecycle", + "Title": "Session Model \u0026 Lifecycle", + "Html": "\n\u003Cp\u003EAuthentication is more than issuing a credential and checking its expiration.\u003C/p\u003E\n\u003Cp\u003EIn stateful authentication systems, successful authentication establishes security state that must be managed throughout its lifetime. Sessions may be refreshed, extended, revoked, or invalidated in response to changes in account security.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth models this state through a structured hierarchy:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERoot \u2192 Chain \u2192 Session\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis hierarchy separates account-level authentication security, device-associated session continuity, and individual authentication sessions.\u003C/p\u003E\n\u003Cp\u003EThe model provides the foundation for session management, security versioning, targeted revocation, and lifecycle enforcement.\u003C/p\u003E\n\u003Cp\u003EIts runtime behavior depends on the configured authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-the-session-hierarchy\u0022\u003EUnderstanding the Session Hierarchy\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth organizes server-managed authentication state into three levels.\u003C/p\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\u003ERepresents\u003C/th\u003E\n\u003Cth\u003EPrimary responsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESessionRoot\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAccount-level authentication security\u003C/td\u003E\n\u003Ctd\u003EGlobal session state and security versioning\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESessionChain\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERelated session continuity\u003C/td\u003E\n\u003Ctd\u003EDevice association, activity, and chain-level control\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 state\u003C/td\u003E\n\u003Ctd\u003ESession validity, expiration, and revocation\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EConceptually:\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 Identity\n |\n \u2514\u2500\u2500 SessionRoot\n |\n \u251C\u2500\u2500 SessionChain (Desktop Browser)\n | |\n | \u251C\u2500\u2500 Session\n | \u2514\u2500\u2500 Session\n |\n \u2514\u2500\u2500 SessionChain (Mobile Application)\n |\n \u2514\u2500\u2500 Session\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is a logical representation of the hierarchy. It does not imply that every authentication mode stores the same records or maintains multiple simultaneous sessions within each chain.\u003C/p\u003E\n\u003Cp\u003EEach level addresses a different security concern.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessionroot-account-level-security\u0022\u003ESessionRoot: Account-Level Security\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ESessionRoot\u003C/code\u003E represents the account-level security boundary for server-managed authentication sessions.\u003C/p\u003E\n\u003Cp\u003EIt associates the authentication session hierarchy with a user identity and maintains security information relevant across that identity\u0027s sessions.\u003C/p\u003E\n\u003Cp\u003EThe root is not the user account itself.\u003C/p\u003E\n\u003Cp\u003EThe Users domain may own the user\u0027s identifiers, profile, and lifecycle. The session root belongs to authentication security management.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-responsibilities\u0022\u003ERoot Responsibilities\u003C/h3\u003E\n\u003Cp\u003EA session root provides a common boundary for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAssociating session chains with an identity.\u003C/li\u003E\n\u003Cli\u003EMaintaining account-level session security state.\u003C/li\u003E\n\u003Cli\u003ECoordinating global session invalidation.\u003C/li\u003E\n\u003Cli\u003ESupporting security version checks.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe distinction is important because changes to authentication security do not necessarily require changes to the user entity.\u003C/p\u003E\n\u003Cp\u003EFor example, invalidating existing sessions should not require recreating the user or changing the user\u0027s identifiers.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-versioning\u0022\u003ESecurity Versioning\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth\u0027s session model supports security versioning.\u003C/p\u003E\n\u003Cp\u003EA root can maintain a security version representing the current account-level authentication security state.\u003C/p\u003E\n\u003Cp\u003ESessions can retain the corresponding version from their creation.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003ERoot.SecurityVersion = 4\nSession.SecurityVersionAtCreation = 4\n\nResult: Version matches\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter an account-level security invalidation:\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\u003ERoot.SecurityVersion = 5\nSession.SecurityVersionAtCreation = 4\n\nResult: Version mismatch\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen the applicable session validation checks detect a version mismatch, the older session is no longer valid.\u003C/p\u003E\n\u003Cp\u003EThis allows the authentication system to invalidate previously established session state through a shared security boundary.\u003C/p\u003E\n\u003Cp\u003EThe effect on already-issued JWT access tokens still depends on whether protected requests consult current server-side security state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessionchain-session-continuity\u0022\u003ESessionChain: Session Continuity\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ESessionChain\u003C/code\u003E represents a logical grouping of related authentication sessions.\u003C/p\u003E\n\u003Cp\u003EA chain can be associated with a particular device or client environment, allowing session activity to be managed within a recognizable continuity boundary.\u003C/p\u003E\n\u003Cp\u003EFor example, a user may have separate chains for a desktop browser and a mobile application.\u003C/p\u003E\n\u003Cp\u003EHowever, a chain is not a physical device identity or a guarantee that the device has been cryptographically verified.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-responsibilities\u0022\u003EChain Responsibilities\u003C/h3\u003E\n\u003Cp\u003EA session chain can provide a boundary for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAssociating related sessions.\u003C/li\u003E\n\u003Cli\u003EMaintaining device or client context.\u003C/li\u003E\n\u003Cli\u003ERecording relevant activity.\u003C/li\u003E\n\u003Cli\u003ETracking session rotation.\u003C/li\u003E\n\u003Cli\u003EApplying chain-level security operations.\u003C/li\u003E\n\u003Cli\u003ERevoking related sessions together.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis allows UltimateAuth to distinguish between the lifecycle of an individual session and the continuity of the environment associated with it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-association\u0022\u003EDevice Association\u003C/h3\u003E\n\u003Cp\u003EA chain may include device-related information such as a device identifier, platform, operating system, or browser.\u003C/p\u003E\n\u003Cp\u003EThese attributes help describe the client environment.\u003C/p\u003E\n\u003Cp\u003EThey must not be treated as independent proof of device ownership.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E for the device association model and its security boundaries.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-individual-authentication-state\u0022\u003ESession: Individual Authentication State\u003C/h2\u003E\n\u003Cp\u003EA \u003Ccode\u003ESession\u003C/code\u003E represents an individual unit of server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003EIt is associated with a chain and participates in the validity rules established by the authentication mode and session configuration.\u003C/p\u003E\n\u003Cp\u003EA session may contain information relevant to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EIts associated identity and chain.\u003C/li\u003E\n\u003Cli\u003ECreation and expiration.\u003C/li\u003E\n\u003Cli\u003ERevocation status.\u003C/li\u003E\n\u003Cli\u003ESecurity version at creation.\u003C/li\u003E\n\u003Cli\u003EAuthentication lifecycle and validity.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA session is not necessarily equivalent to an access token.\u003C/p\u003E\n\u003Cp\u003EIn opaque authentication modes, a client credential may reference server-managed session state.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, an issued access token may be validated locally without consulting the session hierarchy on every protected request.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe token is authentication material. The session is a security-state concept.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EKeeping these concepts separate is essential for understanding UltimateAuth\u0027s authentication modes.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-creation\u0022\u003ESession Creation\u003C/h2\u003E\n\u003Cp\u003EA session lifecycle begins when an authentication flow successfully establishes the required authentication state.\u003C/p\u003E\n\u003Cp\u003EFor modes using the server-managed session hierarchy, the process conceptually involves:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EResolving the authenticated user identity.\u003C/li\u003E\n\u003Cli\u003EResolving or establishing the corresponding session root.\u003C/li\u003E\n\u003Cli\u003EDetermining the appropriate session chain.\u003C/li\u003E\n\u003Cli\u003ECreating or updating the required session state.\u003C/li\u003E\n\u003Cli\u003EIssuing the authentication material required by the selected mode.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe authentication flow must satisfy its verification and security requirements before establishing a valid authenticated session.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-resolution\u0022\u003ERoot Resolution\u003C/h3\u003E\n\u003Cp\u003EAn existing account-level session root can be reused when appropriate.\u003C/p\u003E\n\u003Cp\u003EIf the required root does not exist, the session infrastructure can establish it as part of session creation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-resolution\u0022\u003EChain Resolution\u003C/h3\u003E\n\u003Cp\u003EThe session infrastructure determines whether authentication should be associated with an existing chain or a new chain.\u003C/p\u003E\n\u003Cp\u003EA previously observed device identifier does not automatically establish that an existing chain is valid or eligible for reuse.\u003C/p\u003E\n\u003Cp\u003EThe decision must respect the applicable chain lifecycle and security conditions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-establishment\u0022\u003ESession Establishment\u003C/h3\u003E\n\u003Cp\u003EOnce the appropriate security context has been established, the authentication system creates or updates the session state required by the authentication mode.\u003C/p\u003E\n\u003Cp\u003EThe resulting client credential is then issued according to that mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022active-session-usage\u0022\u003EActive Session Usage\u003C/h2\u003E\n\u003Cp\u003EAfter authentication, the client presents the appropriate authentication material when accessing protected resources.\u003C/p\u003E\n\u003Cp\u003EWhat happens next depends on the configured mode.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, protected requests can be evaluated against current server-managed session state.\u003C/p\u003E\n\u003Cp\u003EIn locally validated JWT modes, protected requests may be authenticated using the token\u0027s cryptographic validity and claims without loading the current session record.\u003C/p\u003E\n\u003Cp\u003EThis distinction affects session activity tracking, expiration enforcement, and revocation behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022touch-operations\u0022\u003ETouch Operations\u003C/h3\u003E\n\u003Cp\u003EA touch operation records qualifying session activity.\u003C/p\u003E\n\u003Cp\u003EDepending on the session strategy, it may update information such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ELastSeenAt\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ETouchCount\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003ERelevant session activity or expiration state\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ETouch behavior is not necessarily performed for every HTTP request.\u003C/p\u003E\n\u003Cp\u003EApplications should not assume that an authenticated request always results in a persistent session update.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022sliding-expiration\u0022\u003ESliding Expiration\u003C/h3\u003E\n\u003Cp\u003ESliding expiration allows eligible authentication state to remain valid as qualifying activity occurs.\u003C/p\u003E\n\u003Cp\u003EUnlike a fixed expiration, sliding expiration can extend a session\u0027s usable lifetime according to configured rules.\u003C/p\u003E\n\u003Cp\u003EHowever, sliding expiration does not necessarily remove absolute lifetime restrictions.\u003C/p\u003E\n\u003Cp\u003EA session may still become invalid when a maximum permitted lifetime is reached.\u003C/p\u003E\n\u003Cp\u003EThe exact behavior depends on the selected authentication mode and its session configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-and-rotation\u0022\u003ERefresh and Rotation\u003C/h2\u003E\n\u003Cp\u003ERefresh and rotation are related to session lifecycle but are not identical operations.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERefresh\u003C/strong\u003E is an authentication operation used to continue or renew authentication state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERotation\u003C/strong\u003E replaces authentication material or session-related credentials according to the configured security strategy.\u003C/p\u003E\n\u003Cp\u003EThe relationship between these operations varies by authentication mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque\u0022\u003EPureOpaque\u003C/h3\u003E\n\u003Cp\u003EPureOpaque uses server-authoritative opaque session authentication.\u003C/p\u003E\n\u003Cp\u003EIt does not require a separate access-token and refresh-token pair.\u003C/p\u003E\n\u003Cp\u003ESession continuity and activity management are handled through the applicable opaque session lifecycle.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003EHybrid\u003C/h3\u003E\n\u003Cp\u003EHybrid uses distinct opaque access and refresh credentials backed by server-authoritative session state.\u003C/p\u003E\n\u003Cp\u003ERefresh operations can renew authentication material while preserving the appropriate session or chain relationship.\u003C/p\u003E\n\u003Cp\u003EThe exact rotation behavior follows the configured refresh strategy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid\u0022\u003ESemiHybrid\u003C/h3\u003E\n\u003Cp\u003ESemiHybrid uses JWT access tokens while maintaining server-side session state for lifecycle operations such as refresh and logout.\u003C/p\u003E\n\u003Cp\u003ERefresh may issue new JWT access material and update the relevant server-managed state.\u003C/p\u003E\n\u003Cp\u003EAlready-issued access tokens may remain valid until expiration because protected requests do not necessarily consult that state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022purejwt\u0022\u003EPureJwt\u003C/h3\u003E\n\u003Cp\u003EPureJwt primarily relies on stateless JWT validation for protected requests.\u003C/p\u003E\n\u003Cp\u003EA refresh mechanism may use an independent refresh store, but the server-managed Root \u2192 Chain \u2192 Session hierarchy must not be assumed to govern every protected request.\u003C/p\u003E\n\u003Cp\u003EThis is an important distinction between the framework\u0027s common authentication concepts and the persistence requirements of individual modes.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-expiration\u0022\u003ESession Expiration\u003C/h2\u003E\n\u003Cp\u003ESession expiration defines when authentication state is no longer eligible for continued use.\u003C/p\u003E\n\u003Cp\u003EDepending on the authentication mode and configuration, expiration may involve:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EExpiration mechanism\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\u003Cstrong\u003EFixed lifetime\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication state expires after a defined duration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EIdle timeout\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication state expires after insufficient qualifying activity\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESliding expiration\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEligible activity extends the usable lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAbsolute expiration\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication state cannot extend beyond a maximum lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese mechanisms are not necessarily all enabled or applicable in every authentication mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022expired-versus-revoked\u0022\u003EExpired Versus Revoked\u003C/h3\u003E\n\u003Cp\u003EExpiration and revocation represent different reasons for invalidation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EExpiration\u003C/strong\u003E occurs because a configured time-based condition has been reached.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERevocation\u003C/strong\u003E occurs because authentication state has been explicitly invalidated through a security operation.\u003C/p\u003E\n\u003Cp\u003EBoth can make a session unusable, but they represent different lifecycle events and may be recorded or enforced differently.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-revocation\u0022\u003ESession Revocation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s hierarchical session model supports security operations at different levels.\u003C/p\u003E\n\u003Cp\u003EThe scope of revocation determines how broadly authentication state is affected.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-level-revocation\u0022\u003ESession-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ESession-level revocation targets an individual session.\u003C/p\u003E\n\u003Cp\u003EIt is appropriate when a specific authentication instance must be invalidated without necessarily affecting other sessions associated with the same user.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-level-revocation\u0022\u003EChain-Level Revocation\u003C/h3\u003E\n\u003Cp\u003EChain-level revocation targets the session continuity represented by a chain.\u003C/p\u003E\n\u003Cp\u003EIt allows related sessions to be invalidated together.\u003C/p\u003E\n\u003Cp\u003EThis can be useful when a particular client environment is suspected of compromise or when a user wants to terminate sessions associated with one device context.\u003C/p\u003E\n\u003Cp\u003ERevoking a chain does not mean that the corresponding physical device is permanently blocked from authenticating again.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-level-revocation\u0022\u003ERoot-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ERoot-level revocation affects the account-level authentication security boundary.\u003C/p\u003E\n\u003Cp\u003EIt provides a mechanism for invalidating authentication state associated with the user\u0027s session hierarchy.\u003C/p\u003E\n\u003Cp\u003ESecurity version changes can support this operation by making previously created sessions fail subsequent version validation.\u003C/p\u003E\n\u003Cp\u003EThis is useful for broad security responses, including operations that require invalidating all applicable existing sessions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-scope\u0022\u003ERevocation Scope\u003C/h3\u003E\n\u003Cp\u003EConceptually:\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 revocation\n \u2514\u2500\u2500 One session\n\nChain revocation\n \u2514\u2500\u2500 Related sessions in one chain\n\nRoot revocation\n \u2514\u2500\u2500 Account-level session hierarchy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe effective invalidation of protected requests depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EIn particular, server-side revocation does not automatically invalidate a previously issued JWT that is still cryptographically valid and is not checked against current revocation state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-security-and-credential-changes\u0022\u003ESession Security and Credential Changes\u003C/h2\u003E\n\u003Cp\u003ESession lifecycle is closely related to credential security.\u003C/p\u003E\n\u003Cp\u003EA password change or recovery operation may require existing authentication sessions to be preserved or revoked.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports session handling policies for credential changes and resets, including:\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\u003EBehavior\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 applicable 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\u003EThese policies allow applications to choose the security response appropriate to the operation.\u003C/p\u003E\n\u003Cp\u003EFor example, an authenticated password change and an anonymous password recovery operation may require different session handling.\u003C/p\u003E\n\u003Cp\u003EThe Credentials domain initiates the relevant security behavior, while the session infrastructure applies the corresponding lifecycle operations.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-configuration\u0022\u003ESession Configuration\u003C/h2\u003E\n\u003Cp\u003ESession behavior is configured through UltimateAuth\u0027s session options, including \u003Ccode\u003EUAuthSessionOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe configuration model governs supported aspects of session lifecycle such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession lifetimes.\u003C/li\u003E\n\u003Cli\u003EActivity and expiration behavior.\u003C/li\u003E\n\u003Cli\u003ERefresh and rotation strategies.\u003C/li\u003E\n\u003Cli\u003ESession and chain limits.\u003C/li\u003E\n\u003Cli\u003EDevice-related session constraints.\u003C/li\u003E\n\u003Cli\u003ERevocation and lifecycle enforcement.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ENot every option applies to every authentication mode.\u003C/p\u003E\n\u003Cp\u003EFor example, a configuration setting that controls server-side session activity may not affect the validation of a stateless JWT access token.\u003C/p\u003E\n\u003Cp\u003EApplications should configure session behavior with the selected authentication mode and its enforcement boundaries in mind.\u003C/p\u003E\n\u003Cp\u003EDetailed configuration examples belong in the configuration reference and How-to Guides.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates authentication identity, session continuity, and individual authentication state.\u003C/p\u003E\n\u003Cp\u003EThe Root \u2192 Chain \u2192 Session hierarchy expresses those responsibilities at different security levels:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ERoot\u003C/strong\u003E provides account-level authentication security.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EChain\u003C/strong\u003E organizes related session continuity.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession\u003C/strong\u003E represents individual server-managed authentication state.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis structure supports targeted session management without requiring all security operations to act on every session belonging to a user.\u003C/p\u003E\n\u003Cp\u003EAt the same time, UltimateAuth does not assume that every authentication mode requires identical server-side state.\u003C/p\u003E\n\u003Cp\u003EThe hierarchy is most directly relevant to session-managed authentication, while JWT-based modes preserve their own validation and lifecycle characteristics.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication establishes trust. Session management determines how that trust is maintained, renewed, and invalidated over time.\u003C/strong\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/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E \u2014 Device context, chain association, and device-aware session management.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/session-security\u0022\u003ESession Security\u003C/a\u003E \u2014 Session expiration, rotation, revocation, and security controls.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Understand how session behavior differs between modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/request-lifecycle\u0022\u003ERequest Lifecycle\u003C/a\u003E \u2014 Learn how protected requests validate authentication state.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Headings": [ + { + "Id": "understanding-the-session-hierarchy", + "Text": "Understanding the Session Hierarchy", + "Level": 0 + }, + { + "Id": "sessionroot-account-level-security", + "Text": "SessionRoot: Account-Level Security", + "Level": 0 + }, + { + "Id": "root-responsibilities", + "Text": "Root Responsibilities", + "Level": 1 + }, + { + "Id": "security-versioning", + "Text": "Security Versioning", + "Level": 1 + }, + { + "Id": "sessionchain-session-continuity", + "Text": "SessionChain: Session Continuity", + "Level": 0 + }, + { + "Id": "chain-responsibilities", + "Text": "Chain Responsibilities", + "Level": 1 + }, + { + "Id": "device-association", + "Text": "Device Association", + "Level": 1 + }, + { + "Id": "session-individual-authentication-state", + "Text": "Session: Individual Authentication State", + "Level": 0 + }, + { + "Id": "session-creation", + "Text": "Session Creation", + "Level": 0 + }, + { + "Id": "root-resolution", + "Text": "Root Resolution", + "Level": 1 + }, + { + "Id": "chain-resolution", + "Text": "Chain Resolution", + "Level": 1 + }, + { + "Id": "session-establishment", + "Text": "Session Establishment", + "Level": 1 + }, + { + "Id": "active-session-usage", + "Text": "Active Session Usage", + "Level": 0 + }, + { + "Id": "touch-operations", + "Text": "Touch Operations", + "Level": 1 + }, + { + "Id": "sliding-expiration", + "Text": "Sliding Expiration", + "Level": 1 + }, + { + "Id": "refresh-and-rotation", + "Text": "Refresh and Rotation", + "Level": 0 + }, + { + "Id": "pureopaque", + "Text": "PureOpaque", + "Level": 1 + }, + { + "Id": "hybrid", + "Text": "Hybrid", + "Level": 1 + }, + { + "Id": "semihybrid", + "Text": "SemiHybrid", + "Level": 1 + }, + { + "Id": "purejwt", + "Text": "PureJwt", + "Level": 1 + }, + { + "Id": "session-expiration", + "Text": "Session Expiration", + "Level": 0 + }, + { + "Id": "expired-versus-revoked", + "Text": "Expired Versus Revoked", + "Level": 1 + }, + { + "Id": "session-revocation", + "Text": "Session Revocation", + "Level": 0 + }, + { + "Id": "session-level-revocation", + "Text": "Session-Level Revocation", + "Level": 1 + }, + { + "Id": "chain-level-revocation", + "Text": "Chain-Level Revocation", + "Level": 1 + }, + { + "Id": "root-level-revocation", + "Text": "Root-Level Revocation", + "Level": 1 + }, + { + "Id": "revocation-scope", + "Text": "Revocation Scope", + "Level": 1 + }, + { + "Id": "session-security-and-credential-changes", + "Text": "Session Security and Credential Changes", + "Level": 0 + }, + { + "Id": "session-configuration", + "Text": "Session Configuration", + "Level": 0 + }, + { + "Id": "architectural-perspective", + "Text": "Architectural Perspective", + "Level": 0 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ No newline at end of file From c48cf71410c7708fd0669689e51cda19cbd25866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Fri, 16 Oct 2026 19:24:29 +0300 Subject: [PATCH 3/7] Improve Getting Started Content --- docs/content/auth-flows/index.md | 2 +- docs/content/configuration/index.md | 2 +- docs/content/getting-started/ecosystem.md | 4 +- docs/content/getting-started/index.md | 463 +++++-- docs/content/getting-started/quickstart.md | 324 +++-- .../getting-started/real-world-setup.md | 1075 +++++++++++++++-- .../sessions-devices/device-management.md | 2 +- .../sessions-devices/session-lifecycle.md | 2 +- .../wwwroot/docs/auth-flows/index.json | 2 +- .../docs/configuration/client-options.json | 395 +++++- .../wwwroot/docs/configuration/index.json | 2 +- .../docs/configuration/server-options.json | 523 +++++++- .../wwwroot/docs/docs-index.json | 20 +- .../docs/getting-started/ecosystem.json | 2 +- .../wwwroot/docs/getting-started/index.json | 2 +- 15 files changed, 2469 insertions(+), 351 deletions(-) diff --git a/docs/content/auth-flows/index.md b/docs/content/auth-flows/index.md index 53f3ce21..a6980990 100644 --- a/docs/content/auth-flows/index.md +++ b/docs/content/auth-flows/index.md @@ -1,5 +1,5 @@ --- -title: Auth Flows +title: Auth Flows Overview order: 1 group: auth-flows --- diff --git a/docs/content/configuration/index.md b/docs/content/configuration/index.md index 981df390..30861406 100644 --- a/docs/content/configuration/index.md +++ b/docs/content/configuration/index.md @@ -1,5 +1,5 @@ --- -title: Configuration +title: Configuration Overview order: 1 group: configuration --- diff --git a/docs/content/getting-started/ecosystem.md b/docs/content/getting-started/ecosystem.md index 953dfcad..73d266ab 100644 --- a/docs/content/getting-started/ecosystem.md +++ b/docs/content/getting-started/ecosystem.md @@ -836,6 +836,6 @@ Server +-- Persistence implementations ``` -Continue with the [Quick Start](./quickstart.md) to build your first UltimateAuth application. +Continue with the [Quick Start](/docs/getting-started/quickstart) to build your first UltimateAuth application. -For persistent storage, UAuthHub, standalone WebAssembly, Resource API, and other deployment models, continue with the [Real-World Setup](./real-world-setup.md). +For persistent storage, UAuthHub, standalone WebAssembly, Resource API, and other deployment models, continue with the [Real-World Setup](/docs/getting-started/real-world-setup). diff --git a/docs/content/getting-started/index.md b/docs/content/getting-started/index.md index f3bc3edd..8457639d 100644 --- a/docs/content/getting-started/index.md +++ b/docs/content/getting-started/index.md @@ -4,169 +4,428 @@ order: 1 group: getting-started --- -# πŸš€ Getting Started +# Welcome to UltimateAuth -Welcome to **UltimateAuth**, a unified authentication and authorization framework for modern .NET applications. +**Modern authentication and authorization for .NET β€” built around a unified security lifecycle.** -Authentication should not become a different system every time your application gains a new client, tenant, device, or security requirement. UltimateAuth brings these concerns into **one coherent model**β€”without pretending that every application needs the same authentication strategy. +Authentication starts with a login. But it doesn't end there. -**One framework. Multiple authentication models. A consistent security lifecycle.** +As applications grow, they introduce new challenges: multiple clients, refresh tokens, device sessions, credential recovery, tenant isolation, permissions, and revocation. What begins as a simple login system can gradually become a collection of disconnected security mechanisms. -## What is UltimateAuth? +UltimateAuth takes a different approach. -UltimateAuth is a modular, flow-driven framework for building and managing authentication in .NET. It connects concepts that are often implemented separately: +It brings authentication, authorization, users, credentials, sessions, and client integration into **one coherent framework**, with a shared model for how security decisions are made and how authentication state changes over time. -- **Sessions, cookies and tokens** β€” stateful session management alongside access and refresh token workflows. -- **Authentication and authorization** β€” identity establishment and access decisions within a consistent architecture. -- **Users and credentials** β€” identity, identifiers, credentials, and their security state. -- **Devices and session continuity** β€” device-aware session chains, refresh, logout, and revocation. -- **Clients and delivery mechanisms** β€” browser, API, Blazor, and mobile scenarios with client-aware behavior. -- **Tenants and isolation** β€” tenant-aware request context and authentication boundaries. -- **Security policies and execution flows** β€” explicit decisions, validation, and controlled state transitions. +**One framework. Multiple client environments. A consistent security lifecycle.** -The goal is not to hide these concepts. It is to make them **work together predictably**. +[Explore the Source](https://github.com/CodeBeamOrg/UltimateAuth) -## Unified Does Not Mean One-Size-Fits-All +## Why UltimateAuth? -A server-rendered web application, a browser-based SPA, a mobile app, and a backend API do not share the same trust boundaries or credential transport requirements. +Most applications don't struggle with generating an access token. -UltimateAuth provides a common foundation while allowing behavior to be shaped by the **effective authentication mode**, **client profile**, **tenant**, and **request context**. +They struggle with everything that comes afterward. -| Concern | What UltimateAuth brings together | Why it matters | -| --- | --- | --- | -| Authentication models | Session-oriented and token-oriented workflows | Different clients can participate in one security architecture | -| Session lifecycle | Root β†’ Chain β†’ Session | Manage authentication at identity, device, and session scope | -| Client environments | ASP.NET Core, Blazor, APIs, and .NET mobile clients | Avoid rebuilding the core authentication model for each frontend | -| Multi-tenancy | Tenant resolution and tenant-bound context | Keep authentication operations within the intended tenant boundary | -| Credentials and identities | User identifiers, credential providers, and security state | Separate *who the user is* from *how they prove it* | -| Security enforcement | Context, validation, authority, and orchestration | Make decisions explicit rather than scattering checks across endpoints | -| Credential delivery | Mode- and client-aware response policies | Handle cookies, tokens, and redirects according to the scenario | -| Session termination | Logout, targeted revocation, and device management | Respond proportionately to ordinary sign-out and security incidents | +Consider an application that starts with a single web frontend. Eventually, it adds a mobile application, an administration panel, multiple organizations, and external APIs. -**Unified** is therefore about a shared security modelβ€”not forcing all applications to use identical cookies, tokens, or endpoints. +Suddenly, authentication raises questions that a basic login implementation doesn't answer: -## Authentication Is More Than Issuing a Token +- How should browser sessions and API tokens coexist? +- Can users see and revoke sessions associated with their devices? +- What happens to existing sessions after a password reset? +- How do we prevent refresh token replay? +- How do we enforce tenant boundaries consistently? +- How do we keep authorization decisions separate from authentication? +- Can multiple application types use the same backend security model? -In many applications, authentication is described as a short sequence: validate credentials, generate a token, return it to the client. +These concerns are related. Implementing each independently makes the system harder to maintain, audit, and extend. -That is only part of the story. +**UltimateAuth is designed to manage these concerns as parts of the same security lifecycle.** -A production authentication system must also answer questions such as: +## A Different Way to Think About Authentication -- Which tenant does this request belong to? -- Which user and credential are being evaluated? -- Is an existing session still valid? -- Is the request coming from a known device? -- Can access be renewed, restricted, or terminated? -- What should happen when a credential changes or a device is no longer trusted? +Traditional authentication integration is often organized around credentials and their immediate results: -UltimateAuth treats authentication as a **controlled lifecycle**. Login, refresh, and logout are operations within that lifecycleβ€”not unrelated endpoints that happen to exchange tokens. +```text +Credentials + ↓ +Validate + ↓ +Issue Token + ↓ +Authenticated +``` -## Sessions Are First-Class +That sequence is necessary, but incomplete. -Where session-backed modes apply, UltimateAuth models authentication through a hierarchy: +UltimateAuth approaches authentication as a longer-running lifecycle: + +```text + UltimateAuth + β”‚ + β–Ό + Request Context + Client Β· Tenant Β· Device + β”‚ + β–Ό + Authentication + Validation Β· Decisions + β”‚ + β–Ό + Security State + User Β· Root Β· Chain Β· Session + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β–Ό β–Ό β–Ό + Refresh Logout Revoke + β”‚ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β–Ό + Controlled State + Transitions +``` + +This model makes it possible to reason about authentication beyond a single request. + +Login establishes authentication. Refresh evaluates whether it may continue. Logout and revocation terminate authentication at the appropriate scope. Credential changes and security policies can affect existing sessions. + +The result is a framework where these operations share a consistent security vocabulary. + +## What Makes UltimateAuth Different? + +| Capability | UltimateAuth's approach | +|---|---| +| **Unified architecture** | Authentication, sessions, users, credentials, and authorization share a common foundation | +| **Client-aware behavior** | Authentication can adapt to the client profile and effective server configuration | +| **Session lifecycle** | Root, chain, and session concepts support different scopes of session management | +| **Flow-driven execution** | Authentication operations use explicit context, validation, decisions, and state transitions | +| **Credential lifecycle** | Credential changes, recovery, and session invalidation are related security operations | +| **Tenant awareness** | Tenant context participates in authentication and access decisions | +| **Modularity** | Core, Server, Client, and domain packages separate responsibilities | +| **Self-hosting** | Applications can operate their own authentication infrastructure | + + + +UltimateAuth does not claim that tokens, sessions, PKCE, or policy-based authorization are new concepts. + +Its value is in **bringing established security concepts together under a consistent architecture**, rather than requiring every application to connect them independently. + +## One Security Model Across Different Clients + +A browser application, a server-rendered application, and a native client have different security constraints. + +They should not necessarily use identical credential storage, transport, or authentication flows. + +UltimateAuth separates the common authentication model from client-specific integration. + +```text + Your .NET Applications + + Blazor Server Blazor WebAssembly + β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + UltimateAuth Client + β”‚ + β–Ό + UltimateAuth Server + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β–Ό β–Ό β–Ό + Users Sessions Authorization + β”‚ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β–Ό + Shared Security + Lifecycle +``` + +The architecture also provides foundations for .NET MAUI, traditional ASP.NET Core applications, and API-oriented integrations. + +**Support depends on the client adapter and authentication modes available in the installed version.** A supported architectural profile should not be confused with a fully released platform adapter. + +The goal is to share authentication concepts without pretending that all clients have the same trust boundaries. + +## Sessions Are More Than Tokens + +One of UltimateAuth's central concepts is its stateful session hierarchy. ```text User - └── Root Global session-security boundary - └── Chain Device-associated lifecycle - └── Session Individual authentication instance + └── Root + β”œβ”€β”€ Chain β€” Browser A + β”‚ β”œβ”€β”€ Session + β”‚ └── Session + β”‚ + └── Chain β€” Device B + β”œβ”€β”€ Session + └── Session ``` -This structure gives the system a vocabulary for different security actions. A session can be invalidated, a device-associated chain can be terminated, and a broader security boundary can be revoked. +Each level represents a different security scope. + +**Root** represents a broader authentication security boundary. + +**Chain** represents the continuity of authentication associated with a device or client context. + +**Session** represents an individual authentication instance within that chain. + +This distinction makes several operations easier to express: + +- End the current session. +- Revoke a particular device-associated chain. +- Revoke other active chains while preserving the current one. +- Revoke a broader authentication scope after a security-sensitive event. -**A token is not the entire authentication model.** It is a credential used within that model, subject to the applicable mode and validation rules. +A token is a credential used to access the system. It is not necessarily the complete record of the user's authentication lifecycle. -Not every authentication mode has identical persistence or session behavior. The session hierarchy describes the stateful lifecycle where that model is in use. +The hierarchy applies to session-backed authentication modes; different modes can have different persistence and validation characteristics. -## Flow-Driven by Design +[Explore Session Management](https://github.com/CodeBeamOrg/UltimateAuth) -UltimateAuth organizes authentication operations around explicit flows. A flow brings together the request context, operation-specific validation, security decisions, permitted state transitions, and response behavior. +## Authentication Flows, Not Disconnected Endpoints + +UltimateAuth organizes security-sensitive operations around explicit flows. + +A flow can combine: + +1. Request and tenant context. +2. User, credential, and session resolution. +3. Input and security validation. +4. Authorization and authority decisions. +5. Controlled state transitions. +6. Client-appropriate results and responses. ```text Request - β†’ Tenant, session, and user resolution - β†’ AuthFlowContext - β†’ Flow-specific validation - β†’ Security decision and orchestration - β†’ State transition (when permitted) - β†’ Result and client-appropriate response + ↓ +AuthFlowContext + ↓ +Validation + ↓ +Security Decision + ↓ +Execution + ↓ +State Transition + ↓ +Response +``` + +This approach provides a consistent place for security rules to be evaluated. + +It also makes individual operations easier to test and inspect because the important decisions are part of an explicit execution model. + +**The purpose is not abstraction for its own sake. It is to make authentication behavior understandable and predictable.** + +## Security Continues After Login + +A secure authentication system must account for events that happen after a user signs in. + +| Event | UltimateAuth concept | +|---|---| +| A user signs in | Login flow | +| A session needs renewal | Refresh flow | +| A user signs out | Logout flow | +| A device should lose access | Chain revocation | +| A credential is changed | Credential lifecycle and session policy | +| An account is recovered | Recovery proof and session invalidation | +| A user requests a protected resource | Authorization decision | +| A request targets a tenant | Tenant-aware security context | + +This is why UltimateAuth treats session management, credential management, and authorization as connected parts of the system. + +For example, a password reset is not simply a database update. It can also affect the validity of existing authentication sessions according to the configured credential session policy. + +Similarly, logout and revocation are not interchangeable operations. They have different scopes and consequences. + +## Authentication and Authorization Together + +Knowing who a user is does not determine what that user is allowed to do. + +UltimateAuth separates authentication from authorization while keeping them within the same overall security architecture. + +Authentication establishes identity and applicable security context. + +Authorization evaluates whether an actor may perform an action on a resource. + +For example: + +```text +Actor: Authenticated user +Action: update +Resource: products +Result: Allowed or Denied ``` -This is not a claim that pipelines or orchestration are new inventions. The value is in applying them **consistently across authentication operations**, so the rules are easier to reason about, test, and extend. +Roles and permissions can be managed through the authorization domain, while the server remains responsible for enforcing access decisions. + +This separation helps applications avoid spreading authorization logic across UI components, controllers, and unrelated service methods. + +Client-side authorization checks can improve the interface, but **server-side enforcement remains the security boundary**. + +## Tenant-Aware by Design + +Multi-tenant authentication involves more than adding a tenant identifier to a user record. + +A request must be interpreted within the correct tenant context, and security operations must respect that boundary. + +UltimateAuth provides tenant resolution and propagation mechanisms that participate in its request and flow infrastructure. + +This allows applications to build tenant-aware authentication and authorization behavior without treating tenancy as an unrelated afterthought. -For the implementation model, see [Auth Flows](/docs/auth-flows/index). +Tenant context must still be configured correctly, and a client-supplied tenant identifier is never sufficient proof of authorization. -## Tenant-Aware from the Request Boundary +## Modular Without Becoming Fragmented -Multi-tenancy is not simply a `TenantId` added to a user table. Authentication must understand *which tenant's security context* a request is operating within. +UltimateAuth is divided into packages with distinct responsibilities. -UltimateAuth resolves tenant context as part of the ASP.NET Core request pipeline and carries it into the authentication flow. The flow context then combines that boundary with client settings, device information, and applicable session validation. +| Package or domain | Responsibility | +|---|---| +| **Core** | Shared contracts, domain concepts, and authentication foundations | +| **Server** | Authentication execution, runtime configuration, and server integration | +| **Client** | Platform-independent authentication and domain APIs | +| **Client.Blazor** | Blazor-specific transport, storage, and authentication state integration | +| **Users** | User lifecycle, profiles, and identifiers | +| **Credentials** | Credential operations, changes, and recovery | +| **Authorization** | Roles, permissions, and access checks | +| **Policies** | Application access policy integration | + +This structure supports both a straightforward integration experience and deeper customization where the application requires it. + +Developers can begin with the public registration and client APIs without first learning every internal service. + +When necessary, they can explore the underlying flows, policies, stores, and extension points. + +## Designed for Practical .NET Development + +UltimateAuth is intended to fit naturally into .NET applications. + +A server application begins by registering UltimateAuth and configuring the security behavior it needs: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Login.MaxFailedAttempts = 5; + options.Session.Lifetime = TimeSpan.FromDays(7); + options.CredentialSessionPolicy.OnRecovery = CredentialSessionBehavior.RevokeAll; +}); +``` + +A Blazor application registers the client adapter: + +```csharp +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.AutoDetectClientProfile = true; + options.Endpoints.BasePath = "/auth"; +}); +``` -This helps teams design multi-tenant applications without treating tenant isolation as an afterthought. **Tenant resolution and configuration still need to be correctly integrated by the host application.** +These examples illustrate the configuration entry points rather than a complete application setup. -## Built for Different Clients, Not Just Different Screens +The server and client have different responsibilities, and a complete deployment also needs the appropriate hosting, persistence, transport, and authentication integration. -UltimateAuth is designed for applications that may span several environments: +The important idea is that common behavior is configured through structured options rather than scattered application-specific code. -- **ASP.NET Core web applications** with server-managed authentication concerns. -- **Blazor Server and Blazor WebAssembly** with different hosting and transport constraints. -- **.NET MAUI** and other native clients with device-oriented authentication needs. -- **HTTP APIs** serving browsers, applications, and other services. +[Start with QuickStart](/docs/getting-started/quickstart) -Client profiles and effective server options help adapt authentication behavior to the requesting client. Response policies determine how applicable credentials are delivered and whether a redirect belongs in the interaction. +## Who Is UltimateAuth For? -The result is a common backend security vocabulary across multiple application typesβ€”not a promise that every client uses the same transport or requires zero configuration. +UltimateAuth is particularly relevant if you are building: -## Security Is a Lifecycle, Too +- **A Blazor application** that needs authentication and authorization integrated with its hosting model. +- **A multi-client .NET system** where different frontends should share the same authentication backend. +- **An application with device-aware sessions** where users or administrators need control over active authentication. +- **A multi-tenant platform** with explicit tenant resolution and security boundaries. +- **A security-sensitive application** that requires controlled refresh, credential recovery, and revocation behavior. +- **A self-hosted system** where the team wants visibility into and control over its authentication infrastructure. -Security is not finished when login succeeds. UltimateAuth's architecture accounts for what happens *after* access is established: +**From a simple login page to a complex authentication platform, UltimateAuth is designed to make getting started easy without limiting what comes next.** -| Situation | Security question | UltimateAuth concept | -| --- | --- | --- | -| A user signs in | Should this attempt establish authentication? | Login flow and security decisions | -| A session needs renewal | Can continuity be preserved safely? | Refresh flow and session lifecycle | -| A user signs out | Which authentication scope should end? | Logout | -| A device is lost | How can that device's access be terminated? | Chain-level management and revocation | -| A credential is compromised | How should existing authentication be affected? | Credential security state and revocation policies | -| An administrator manages access | Is the operation permitted for this actor and target? | Authorization and access context | +You shouldn't need to understand an entire identity framework just to add authentication to a small .NET application. And you shouldn't need to replace your authentication system when that application grows. -A key distinction is **logout versus revoke**. Ordinary logout ends authentication according to its scope; revocation is an irreversible invalidation of the targeted security state. Signing in again may establish *new* authentication, but it does not restore what was revoked. +UltimateAuth is built for both. -Explore the details in [Login Flow](/docs/auth-flows/login), [Refresh Flow](/docs/auth-flows/refresh), and [Logout Flow](/docs/auth-flows/logout). +### Simple Applications. Simple Beginnings. -## Modular and Extensible +For a small application, authentication should be straightforward. -UltimateAuth is intended to serve both straightforward applications and systems with more demanding security requirements. Its architecture separates responsibilities such as core contracts, server execution, client integration, user management, credentials, and authorization. +UltimateAuth aims to provide a lightweight integration experience with clear configuration, sensible defaults, and ready-to-use authentication flows. -This separation makes it possible to understand and evolve individual concerns without reducing authentication to a collection of disconnected custom handlers. +Instead of requiring developers to assemble numerous identity components before they can begin, UltimateAuth provides a cohesive foundation that can be configured through a small number of well-defined entry points. -**Extensible does not mean every possible flow is already implemented.** Available modes, integrations, and capabilities should be evaluated against the version you are using; the documentation for each feature describes its current behavior and limitations. +**Start with what you need. You don't have to configure everything the framework supports.** -## When Should You Choose UltimateAuth? +### Complex Applications. Room to Grow. -UltimateAuth is especially relevant when: +When requirements become more demanding, the same framework provides a deeper security architecture. -- You need quickly-installed, consistent, secure and easy-to-use auth in your .NET application. -- Your .NET application hasβ€”or will haveβ€”**more than one client type**. -- You need **device-aware session management**, not only token generation. -- You are designing a **multi-tenant** system with clear authentication boundaries. -- You want **authentication and authorization** to follow a consistent architectural model. -- You need control over **refresh, logout, revocation, and credential lifecycle**. -- You value a **modular, self-hosted, open-source** foundation you can understand and extend. +UltimateAuth is particularly useful for applications requiring multiple client types, device-aware session management, tenant isolation, advanced authorization, credential recovery, controlled refresh, and targeted revocation. -Also for a small application with minimal authentication needs, UltimateAuth brings the simplest possible solution. +Its modular architecture, explicit authentication flows, configurable policies, and extension points allow developers to introduce additional capabilities without abandoning the original authentication foundation. -## Open Source First +**The complexity of your application should not determine how difficult authentication is to begin with.** -UltimateAuth is built with a simple commitment: **self-hosted authentication should remain accessible**. The framework is open source, and self-hosting is intended to remain free. A hosted offering may provide an operational alternative, not a separate tier of authentication features. +### One Framework for Both -The ambition is to build a framework that developers can adopt, inspect, contribute to, and trustβ€”not merely a service they must depend on. +Whether you're building a small personal project, a business application, a SaaS platform, or a larger distributed system, UltimateAuth follows the same principle: + +**Easy to start. Powerful when needed. Consistent as you grow.** + +You can begin with a straightforward authentication setup and progressively adopt the capabilities your application actually requires. + +UltimateAuth can also serve smaller applications, but its architecture becomes especially valuable when authentication requirements extend beyond a basic login form. + +If your application only needs a minimal authentication mechanism, evaluate whether UltimateAuth's broader lifecycle model provides enough value for your use case. + +## Current Scope and Maturity + +UltimateAuth is an evolving open-source framework. + +Its architecture covers several authentication models and client environments, but not every designed capability is necessarily available in the current release. + +In particular: + +- Authentication mode availability must be checked against the current server implementation. +- Client adapter availability varies by platform. +- Some advanced session and device policy options may be reserved for future enforcement. +- Production adoption requires appropriate configuration, persistence, deployment security, and testing. + +The documentation distinguishes supported behavior from planned or partially implemented capabilities. + +This matters because a trustworthy authentication framework should be clear not only about what it can do, but also about its current limits. + +## Open Source First. Self-Hosted by Design. + +UltimateAuth is developed as an open-source project with self-hosting at its foundation. + +The project's direction is to keep self-hosted authentication free, while a potential managed hosting service could provide an alternative for teams that prefer not to operate the infrastructure themselves. + +The intention is not to divide authentication capabilities into free and paid feature tiers. + +It is to make the framework accessible, inspectable, extensible, and useful to the .NET community. + +[View UltimateAuth on GitHub](https://github.com/CodeBeamOrg/UltimateAuth) + +## Choose Your Starting Point + +You do not need to understand the entire architecture before building your first application. + +| I want to… | Start here | +|---|---| +| Set up UltimateAuth for the first time | [QuickStart](/docs/getting-started/quickstart) | +| Understand how authentication works internally | [Auth Flows](/docs/auth-flows/index) | +| Configure session, token, and security behavior | [Server Options](/docs/configuration/server-options) | +| Integrate authentication into a Blazor application | [Client Options](/docs/configuration/client-options) | +| Understand the source code and contribute | [GitHub Repository](https://github.com/CodeBeamOrg/UltimateAuth) | + +--- -## Where to Go Next +### Authentication is a lifecycle, not a login endpoint. -You do not need to understand every internal component before getting started. +UltimateAuth brings together the concepts needed to establish, maintain, evaluate, and terminate authentication within a consistent .NET architecture. -**Continue to Quick Start** to set up your first UltimateAuth application. Then explore [Auth Flows](/docs/auth-flows/index) to understand how the pieces work together. +**Start simple. Understand the security model. Extend when you need to.** -**The idea to remember:** UltimateAuth unifies authentication concepts without erasing their differences. One framework, a consistent lifecycle, and room for the security model your application actually needs. +[Continue to QuickStart](/docs/getting-started/quickstart) \ No newline at end of file diff --git a/docs/content/getting-started/quickstart.md b/docs/content/getting-started/quickstart.md index 1d5b1706..1181cddb 100644 --- a/docs/content/getting-started/quickstart.md +++ b/docs/content/getting-started/quickstart.md @@ -4,141 +4,315 @@ order: 3 group: getting-started --- -# ⚑ Quick Start +# ⚑ QuickStart -In this guide, you will set up UltimateAuth in a few minutes and perform your **first login**. +**Your first UltimateAuth application, in just a few steps.** ---- +This guide walks you through creating a Blazor Server application, adding UltimateAuth with in-memory storage, and signing in with a sample account. + +You'll start with a minimal setup. No database configuration, migrations, or custom authentication infrastructure required. + +## Prerequisites + +You'll need: -## 1. Create a Project +- .NET SDK (8 and higher, latest recommended) +- A code editor such as Visual Studio, VS Code, or Rider +- Basic familiarity with Blazor -Start by creating a new Blazor app: +This guide uses **Blazor Web App with Interactive Server rendering** and UltimateAuth's in-memory persistence bundle. + +## 1. Create a Blazor Application + +Open a terminal and create a new project: ```bash -dotnet new blazorserver -n UltimateAuthDemo +dotnet new blazor -n UltimateAuthDemo -int Server cd UltimateAuthDemo ``` -## 2. Install Packages +This creates a Blazor Web App with Interactive Server components. -Install the required UltimateAuth packages: +## 2. Install UltimateAuth -```csharp +Install the Blazor client adapter and the in-memory bundle: + +```bash dotnet add package CodeBeam.UltimateAuth.Client.Blazor dotnet add package CodeBeam.UltimateAuth.InMemory.Bundle ``` -## 3. Configure Services +For this tutorial, also install the sample seed package: + +```bash +dotnet add package CodeBeam.UltimateAuth.Sample.Seed +``` + +The packages serve different purposes: + +| Package | Purpose | +|---|---| +| `Client.Blazor` | Blazor components, authentication state, and client integration | +| `InMemory.Bundle` | In-memory implementations for authentication domains | +| `Sample.Seed` | Development accounts and sample authorization data | + +The in-memory bundle includes the relevant persistence implementations, so you do not need to register each store individually. + +## 3. Configure UltimateAuth Services + +Open `Program.cs`. + +Add the following namespaces: + +```csharp +using CodeBeam.UltimateAuth.Client.Blazor; +using CodeBeam.UltimateAuth.Client.Blazor.Extensions; +using CodeBeam.UltimateAuth.InMemory; +using CodeBeam.UltimateAuth.Sample.Seed.Extensions; +using CodeBeam.UltimateAuth.Server.Extensions; +``` + +After creating the builder, register UltimateAuth: -Update your Program.cs: ```csharp builder.Services .AddUltimateAuthServer() .AddUltimateAuthInMemory(); -builder.Services - .AddUltimateAuthClientBlazor(); +builder.Services.AddUltimateAuthClientBlazor(); + +builder.Services.AddUltimateAuthSampleSeed(); ``` -## 4. Configure Middleware -In `Program.cs` +That's the core service configuration. + +`AddUltimateAuthServer()` registers the authentication server. + +`AddUltimateAuthInMemory()` provides in-memory stores. + +`AddUltimateAuthClientBlazor()` registers the Blazor client integration. + +`AddUltimateAuthSampleSeed()` prepares the development seed services. + +You don't need to call `AddUltimateAuth()` or `AddUltimateAuthClient()` separately. + +## 4. Configure the HTTP Pipeline + +In `Program.cs`, configure the application pipeline: + ```csharp +var app = builder.Build(); + +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error", createScopeForErrors: true); + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); + app.UseUltimateAuthWithAspNetCore(); app.MapUltimateAuthEndpoints(); -``` -## 5. Enable Blazor Integration -In `Program.cs` -```csharp app.MapRazorComponents() - .AddInteractiveServerRenderMode() // or webassembly (depends on your application type) + .AddInteractiveServerRenderMode() .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient()); + +if (app.Environment.IsDevelopment()) +{ + await app.SeedUltimateAuthAsync(); +} + +app.Run(); ``` -## 6. Add UAuth Script -Add this to `App.razor` or `index.html`: +Keep the `AddRazorComponents().AddInteractiveServerComponents()` registration generated by the Blazor project template. -```csharp +`UseUltimateAuthWithAspNetCore()` integrates UltimateAuth's middleware with ASP.NET Core authentication and authorization. + +`MapUltimateAuthEndpoints()` registers the authentication endpoints. + +`AddUltimateAuthRoutes()` includes UltimateAuth's Blazor client route assemblies in endpoint discovery. + +The development-only seed call creates the sample users before the application begins accepting requests. + +## 5. Add the UltimateAuth Script + +Open `Components/App.razor`. + +Keep the existing Blazor script and add UltimateAuth's JavaScript file: + +```html ``` -## 7. Configure Application Lifecycle -Replace `Routes.razor` with this code: -```csharp +Place these near the end of the document body. + +UltimateAuth's Blazor integration uses this script for browser-side functionality. + +## 6. Initialize the UltimateAuth Application + +Open `Components/Routes.razor`. + +Replace its contents with: + +```razor +@using CodeBeam.UltimateAuth.Client.Blazor + ``` -## 8. Recommended Setup (Optional) -Add these for better experience: +`UAuthApp` is the root integration component for UltimateAuth in Blazor. -For login page (Use this only once in your application) -```csharp +It initializes the client runtime, provides authentication state to child components, and coordinates the authenticated session lifecycle. + +With `UseBuiltInRouter="true"`, it also provides the application's router. + +You don't need to create a separate `Router` or `CascadingAuthenticationState` component for this setup. + +## 7. Create Your Login Page + +Create `Components/Pages/Login.razor`: + +```razor +@page "/login" @attribute [UAuthLoginPage] + +@using CodeBeam.UltimateAuth.Client.Blazor + +

Sign in

+ +
+ + +
+ +
+ + +
+ + +
+ +@code { + private string _identifier = ""; + private string _password = ""; +} ``` -For protected pages -```csharp +`UAuthLoginForm` handles the login submission through UltimateAuth's client flow. + +`[UAuthLoginPage]` identifies this component as the application's login page. + +Only designate one login page for this purpose. + +The form above is intentionally minimal. You can replace the inputs with your preferred UI components later. + +## 8. Add a Protected Page + +Create `Components/Pages/Dashboard.razor`: + +```razor +@page "/dashboard" @attribute [UAuthAuthorize] -``` -For any page that you use UltimateAuth features like AuthState etc. -```csharp -@inherits UAuthPageBase +@using CodeBeam.UltimateAuth.Client.Blazor + +

Dashboard

+ +

You have reached an authenticated page.

``` -## 9. Seed Data For QuickStart (Optional) -This code creates admin and user users with same password and admin role. +`[UAuthAuthorize]` declares an UltimateAuth authorization requirement for the page. -For in memory -```csharp -builder.Services.AddUltimateAuthSampleSeed(); +**Important:** Client-side route protection is not a substitute for server-side authorization. Protected APIs and resources must enforce authorization on the server. + +## 9. Run the Application + +Start the application: + +```bash +dotnet run ``` -For entity framework core, use scoped services: -```csharp -builder.Services.AddScopedUltimateAuthSampleSeed(); +Open the HTTPS address displayed in your terminal, then navigate to: + +```text +/login ``` -In pipeline configuration -```csharp -if (app.Environment.IsDevelopment()) -{ - await app.SeedUltimateAuthAsync(); -} +The sample seed package creates two development accounts: + +| Username | Password | Role | +|---|---|---| +| `admin` | `admin` | Administrator | +| `user` | `user` | Standard user | + +Sign in with: + +```text +Username: admin +Password: admin ``` -## 10. Perform Your First Login -Example using IUAuthClient: -```csharp -[Inject] IUAuthClient UAuthClient { get; set; } = null!; +After successful authentication, navigate to: -private async Task Login() -{ - await UAuthClient.Flows.LoginAsync(new LoginRequest - { - Identifier = "admin", - Secret = "admin" - }); -} +```text +/dashboard ``` -## πŸŽ‰ That’s It -You now have a working authentication system with: +You now have a Blazor application using UltimateAuth authentication. + +## πŸŽ‰ Your First UltimateAuth Application Is Running + +With this setup, you have: + +- An authentication server integrated into ASP.NET Core. +- In-memory user, credential, session, token, and authorization storage. +- A Blazor authentication client. +- A working login form. +- Authentication state initialization. +- Session lifecycle coordination. +- Sample users and authorization data. +- A page declaring an authentication requirement. + +You did not need to create your own password hashing, token issuance, session validation, or authentication middleware implementations. + +## What Happened When You Logged In? + +For this Blazor Server scenario, UltimateAuth uses the applicable session-oriented authentication model. -- Session-based authentication -- Automatic client detection -- Built-in login flow -- Secure session handling +The login flow validates the credentials and establishes authentication state on the server. -## What Just Happened? +Where a new session is created, it participates in UltimateAuth's root, chain, and session lifecycle. -When you logged in: +The Blazor client then coordinates authentication state for the application. -- A session (with root and chain) was created on the server, -- Your client received an authentication grant (cookie or token), -- UltimateAuth established your auth state automatically. +You can explore these operations in detail later through the Authentication Flows and Session Management documentation. -πŸ‘‰ You didn’t manage cookies, tokens, or redirects manually. +## Important: In-Memory Storage + +This QuickStart intentionally uses in-memory persistence. + +It is ideal for: + +- Learning UltimateAuth. +- Local development. +- Testing authentication flows. +- Building prototypes. + +**In-memory authentication data is not durable.** Users, credentials, sessions, and other stored state may be lost when the application restarts. + +The sample credentials are also for development only. Do not deploy the seeded accounts or their example passwords in a production application. + +For production use, configure durable persistence, appropriate secrets and signing keys, HTTPS, and the security settings required by your deployment. ## Next Steps -Discover the setup for real world applications with entity framework core. + +- **[Ecosystem](/docs/getting-started/ecosystem)** β€” Explore packages, integrations, and supported environments. +- **[Server Options](/docs/configuration/server-options)** β€” Configure sessions, tokens, login policies, and security behavior. +- **[Client Options](/docs/configuration/client-options)** β€” Customize Blazor integration and authentication flows. +- **[Authentication Flows](/docs/auth-flows/index)** β€” Understand login, refresh, logout, and revocation. +- **[Entity Framework Core Setup](/docs/getting-started/entity-framework-core)** β€” Move from in-memory storage to persistent authentication data. \ No newline at end of file diff --git a/docs/content/getting-started/real-world-setup.md b/docs/content/getting-started/real-world-setup.md index 695b8e5a..4f98c524 100644 --- a/docs/content/getting-started/real-world-setup.md +++ b/docs/content/getting-started/real-world-setup.md @@ -4,214 +4,1079 @@ order: 4 group: getting-started --- -# πŸ— Real-World Setup +# πŸ—οΈ Real-World Setup -The Quick Start uses an in-memory setup for simplicity. -In real-world applications, you should replace it with a persistent configuration as shown below. +**One authentication framework. Different deployment models.** -In real applications, you will typically configure: +The [QuickStart](/docs/getting-started/quickstart) demonstrates UltimateAuth with Blazor Server and in-memory storage. -- A persistent database -- An appropriate client profile -- A suitable authentication mode +Real applications may require persistent data, a separate authentication server, browser-based clients, or independently deployed APIs. -This guide shows how to set up UltimateAuth for real-world scenarios. +UltimateAuth supports these scenarios through different service registrations and hosting integrations. You can choose the architecture that fits your application without configuring every available component. -## πŸ—„οΈ Using Entity Framework Core -For production, you should use a persistent store. (In-memory provider is volatile and automatically resets on each restart.) +This guide covers the setup patterns demonstrated by the UltimateAuth samples. -In this setup, you no longer need the `CodeBeam.UltimateAuth.InMemory.Bundle` package. +## 1. Choose Your Architecture -### Install Packages +Start by identifying where authentication is hosted and how your application communicates with it. + +| Application architecture | Authentication host | Application registration | +|---|---|---| +| Blazor Server, single project | Same application | Server + persistence + Blazor client | +| Blazor Server with EF Core | Same application | Server + EF Core + Blazor client | +| Dedicated UAuthHub | Separate authentication application | Server + persistence + Hub | +| Standalone Blazor WASM | Remote UAuthHub | Blazor client | +| Blazor Web App, Interactive WASM | Remote UAuthHub | Host integration + WASM client | +| Separate Resource API | Remote UAuthHub | Resource API integration | + +These models can be combined. + +For example, an application might use an EF Core-backed UAuthHub, a standalone Blazor WASM frontend, and a separate Resource API. + +The frontend handles user interaction, UAuthHub manages authentication, and the Resource API enforces access to protected resources. + +## 2. Persistent Storage with Entity Framework Core + +For applications that need authentication state to survive restarts, use a persistent storage provider. + +UltimateAuth provides an Entity Framework Core bundle that registers its reference domain implementations and EF Core-backed stores. + +### Install packages ```bash dotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle +dotnet add package Microsoft.EntityFrameworkCore.Sqlite +dotnet add package Microsoft.EntityFrameworkCore.Design ``` -### Configure Services +The SQLite provider is used here for simplicity. You can use another compatible EF Core database provider. + +When using the EF Core bundle, you do not need the in-memory bundle. + +### Register the services + +In `Program.cs`: + ```csharp -builder.Services - .AddUltimateAuthServer() +using CodeBeam.UltimateAuth.EntityFrameworkCore; +using CodeBeam.UltimateAuth.Server.Extensions; +using Microsoft.EntityFrameworkCore; + +builder.Services.AddUltimateAuthServer() .AddUltimateAuthEntityFrameworkCore(db => { - db.UseSqlite("Data Source=uauth.db"); - // or UseSqlServer(...) / UseNpgsql(...) / UseMySql(...) etc. + db.UseSqlite("Data Source=uauth.db", sqlite => sqlite.MigrationsAssembly(typeof(Program).Assembly.GetName().Name!)); }); - -builder.Services - .AddUltimateAuthClientBlazor(); ``` -### Database Migrations +`AddUltimateAuthEntityFrameworkCore()` registers the EF Core stores and reference domain implementations. -UltimateAuth integrates with Entity Framework Core, but database migrations belong to your application. +The bundle provides a unified `UAuthDbContext` for the supported authentication domains. -UltimateAuth does not automatically create or apply migrations on your behalf. This keeps your database schema lifecycle under your control and allows migrations to follow the same deployment and review process as the rest of your application. +You do not need to register each user, session, credential, token, or authorization store individually. -After configuring the Entity Framework Core provider, create the initial migration and update the database using either the .NET CLI or Visual Studio Package Manager Console. +### Configure the database connection -#### Option A β€” .NET CLI +For a deployed application, prefer configuration over hardcoded connection strings. -If you use the .NET CLI: +`appsettings.json`: -```bash -dotnet ef migrations add InitUAuth --context UAuthDbContext -dotnet ef database update --context UAuthDbContext +```json +{ + "ConnectionStrings": { + "UltimateAuth": "Data Source=uauth.db" + } +} ``` -If the dotnet ef command is not available, install the EF Core CLI tool: +`Program.cs`: + +```csharp +var connectionString = builder.Configuration.GetConnectionString("UltimateAuth") + ?? throw new InvalidOperationException("UltimateAuth connection string is missing."); + +builder.Services.AddUltimateAuthServer() + .AddUltimateAuthEntityFrameworkCore(db => + { + db.UseSqlite(connectionString); + }); +``` + +For production databases, use your deployment environment's secret management facilities rather than storing credentials in source control. + +### Database migrations + +UltimateAuth does not automatically create or apply migrations when the EF Core bundle is registered. + +Your application owns the database migration lifecycle. + +Install the EF Core CLI if necessary: ```bash dotnet tool install --global dotnet-ef ``` -Your project also needs the Entity Framework Core design package: +Create the initial migration: ```bash -dotnet add package Microsoft.EntityFrameworkCore.Design +dotnet ef migrations add InitUAuth --context UAuthDbContext ``` -#### Option B β€” Visual Studio Package Manager Console - -If you use Visual Studio, you can perform the same operation from Tools β†’ NuGet Package Manager β†’ Package Manager Console: +Apply it: ```bash +dotnet ef database update --context UAuthDbContext +``` + +For Visual Studio Package Manager Console: + +```powershell Add-Migration InitUAuth -Context UAuthDbContext Update-Database -Context UAuthDbContext ``` -For Package Manager Console tooling, make sure the required EF Core tooling package is available: +Install `Microsoft.EntityFrameworkCore.Tools` if required. + +### Applying migrations programmatically + +The EF Core samples demonstrate initialization through a scoped `UAuthDbContext`. + +For a controlled development environment: + +```csharp +using CodeBeam.UltimateAuth.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +using (var scope = app.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + await db.Database.MigrateAsync(); +} +``` + +This applies pending migrations. It does not generate migration files. + +**Production guidance:** Prefer applying reviewed migrations as a separate deployment step, particularly when multiple application instances share a database. + +Do not copy the sample initializer's `reset: true` behavior into production. The samples use database reset functionality for repeatable demonstrations, not as a safe deployment strategy. + +### Optional: sample accounts with EF Core + +The sample seed package can also work with EF Core persistence. ```bash -Install-Package Microsoft.EntityFrameworkCore.Tools +dotnet add package CodeBeam.UltimateAuth.Sample.Seed +``` + +Register the scoped seed services: + +```csharp +builder.Services.AddScopedUltimateAuthSampleSeed(); +``` + +After preparing the database: + +```csharp +if (app.Environment.IsDevelopment()) +{ + await app.SeedUltimateAuthAsync(); +} ``` -### Who Owns the Migrations? +The difference matters: + +- `AddUltimateAuthSampleSeed()` is used by the in-memory samples. +- `AddScopedUltimateAuthSampleSeed()` is used by the EF Core samples. + +Do not deploy the demonstration accounts and passwords to production. + +## 3. Blazor Server β€” Integrated Authentication -Your application does. +**Best for:** A Blazor Server application that wants to host its own authentication system. -This is intentional. UltimateAuth provides the authentication and identity model through its Entity Framework Core integration, while your application remains responsible for managing the resulting database schema. +This is the simplest deployment model for a server-side Blazor application. -This means you can: +The application hosts UltimateAuth Server and uses the Blazor client adapter in the same project. -- review migrations before applying them, -- include UltimateAuth schema changes in your normal deployment process, -- control when database changes are applied, -- maintain migration history alongside your application, -- use the database provider and deployment strategy appropriate for your environment. +A separate UAuthHub is not required. -When upgrading UltimateAuth, review the release notes for persistence-related schema changes and create a new migration when required. +### Packages -> **Tip:** Treat UltimateAuth model changes like any other Entity Framework Core model change: upgrade the package, create a migration, review the generated migration, and apply it through your normal deployment process. +```bash +dotnet add package CodeBeam.UltimateAuth.Client.Blazor +dotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle +dotnet add package Microsoft.EntityFrameworkCore.Sqlite +``` + +### Services -## Configure Services With Options -UltimateAuth provides rich options for server and client service registration. ```csharp -builder.Services.AddUltimateAuthServer(o => { - o.Diagnostics.EnableRefreshDetails = true; - o.Login.MaxFailedAttempts = 4; - o.Identifiers.AllowMultipleUsernames = true; -}); +using CodeBeam.UltimateAuth.Client.Blazor.Extensions; +using CodeBeam.UltimateAuth.EntityFrameworkCore; +using CodeBeam.UltimateAuth.Server.Extensions; +using Microsoft.EntityFrameworkCore; + +builder.Services + .AddRazorComponents() + .AddInteractiveServerComponents(); + +builder.Services.AddUltimateAuthServer() + .AddUltimateAuthEntityFrameworkCore(db => + { + db.UseSqlite("Data Source=uauth.db"); + }); + +builder.Services.AddUltimateAuthClientBlazor(); ``` -## Blazor Standalone WASM Setup -Blazor WASM applications run entirely on the client and cannot securely handle credentials. -For this reason, UltimateAuth uses a dedicated Auth server called **UAuthHub**. +### Pipeline -WASM `Program.cs`: ```csharp -builder.Services.AddUltimateAuthClientBlazor(o => +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +app.UseUltimateAuthWithAspNetCore(); +app.UseAntiforgery(); + +app.MapUltimateAuthEndpoints(); + +app.MapRazorComponents() + .AddInteractiveServerRenderMode() + .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient()); +``` + +Also configure the `UAuthApp` root component, login page, and UltimateAuth JavaScript file as demonstrated in QuickStart. + +### Authentication behavior + +The default client profile resolution selects `PureOpaque` for Blazor Server. + +This uses session-oriented authentication rather than requiring the application to manage access and refresh tokens manually. + +You can configure session lifetimes, login policies, credential policies, and other behavior through `AddUltimateAuthServer()`. + +For example: + +```csharp +builder.Services.AddUltimateAuthServer(options => { - o.Endpoints.BasePath = "https://localhost:6110/auth"; // UAuthHub URL - o.Pkce.ReturnUrl = "https://localhost:6130/home"; // Your (WASM) application domain + return path + options.Login.MaxFailedAttempts = 5; + options.Login.LockoutDuration = TimeSpan.FromMinutes(15); + options.Session.Lifetime = TimeSpan.FromDays(7); + options.Identifiers.Behavior.AllowMultipleUsernames = true; }); ``` -UAuthHub `Program.cs`: +The effective authentication mode is resolved at runtime. The application does not need to select a single mode globally. + +## 4. UAuthHub β€” Dedicated Authentication Server + +**Best for:** Applications where multiple clients share one authentication authority. + +UAuthHub hosts UltimateAuth's authentication services separately from your frontend applications. + +It is particularly relevant for standalone WASM applications, Interactive WASM applications, and systems with separate Resource APIs. + +A typical architecture looks like this: + +```text + Standalone WASM Interactive WASM + β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + UAuthHub + Authentication + Sessions & Users + β”‚ + β–Ό + Persistence + + Resource API ───► UAuthHub + (validation) +``` + +### Create the Hub application + +A UAuthHub can be hosted in an ASP.NET Core Blazor Server application. + +The current samples use Blazor Server to provide the Hub's login UI. + +### In-memory UAuthHub + +For development: + +```bash +dotnet add package CodeBeam.UltimateAuth.Client.Blazor +dotnet add package CodeBeam.UltimateAuth.InMemory.Bundle +``` + +Register: + ```csharp +using CodeBeam.UltimateAuth.Client.Blazor.Extensions; +using CodeBeam.UltimateAuth.InMemory; +using CodeBeam.UltimateAuth.Server.Extensions; + builder.Services.AddUltimateAuthServer() .AddUltimateAuthInMemory() - .AddUAuthHub(o => o.AllowedClientOrigins.Add("https://localhost:6130")); // WASM application's URL + .AddUAuthHub(options => + { + options.AllowedClientOrigins.Add("https://localhost:6130"); + }); + +builder.Services.AddUltimateAuthClientBlazor(); ``` -UAuthHub Pipeline Configuration +The Hub's Blazor client registration is needed when the Hub itself hosts UltimateAuth-powered Blazor pages and login UI. + +### EF Core UAuthHub + +For persistent storage: + ```csharp +using CodeBeam.UltimateAuth.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +builder.Services.AddUltimateAuthServer() + .AddUltimateAuthEntityFrameworkCore(db => + { + db.UseSqlite("Data Source=uauthhub.db"); + }) + .AddUAuthHub(options => + { + options.AllowedClientOrigins.Add("https://localhost:6132"); + }); + +builder.Services.AddUltimateAuthClientBlazor(); +``` + +The `AllowedClientOrigins` collection identifies browser origins permitted to interact with the Hub. + +Use exact origins, including scheme and port. Do not include paths such as `/home`. + +### Hub pipeline + +```csharp +app.UseHttpsRedirection(); + +app.UseUltimateAuthWithAspNetCore(); +app.UseAntiforgery(); + app.MapUltimateAuthEndpoints(); app.MapUAuthHub(); + +app.MapStaticAssets(); + +app.MapRazorComponents() + .AddInteractiveServerRenderMode() + .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient()); +``` + +`MapUltimateAuthEndpoints()` maps the authentication endpoints. + +`MapUAuthHub()` maps the Hub-specific entry endpoint. + +Both registrations are used by the current UAuthHub samples. + +The Hub also needs its login page and Blazor application lifecycle components. + +### Choosing origins + +For example: + +| Application | URL | +|---|---| +| UAuthHub | `https://localhost:6112` | +| Standalone WASM | `https://localhost:6130` | +| Interactive WASM | `https://localhost:6132` | + +Configure the Hub's allowed origins for the actual frontend applications it serves. + +These localhost addresses are illustrative and must match your deployment. + +## 5. Blazor Standalone WebAssembly + +**Best for:** A browser-hosted application with a separate UAuthHub. + +Standalone Blazor WASM runs in the browser. It does not host UltimateAuth Server or database persistence. + +Authentication is handled by the remote UAuthHub. + +### Client packages + +```bash +dotnet add package CodeBeam.UltimateAuth.Client.Blazor +``` + +### Configure services + +In the standalone WASM application's `Program.cs`: + +```csharp +using CodeBeam.UltimateAuth.Client.Blazor.Extensions; +using CodeBeam.UltimateAuth.Core.Domain; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); + +builder.RootComponents.Add("#app"); +builder.RootComponents.Add("head::after"); + +builder.Services.AddScoped(sp => new HttpClient +{ + BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) +}); + +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.Endpoints.BasePath = "https://localhost:6110/auth"; + options.Pkce.ReturnUrl = "https://localhost:6130/home"; + options.Login.AllowCredentialPost = true; + options.Reauth.Behavior = ReauthBehavior.RaiseEvent; +}); + +await builder.Build().RunAsync(); ``` -## Blazor Web App Setup -A blazor web app contains two projects that includes host and client. You need to arrange them both. +The current standalone WASM sample explicitly configures `Login.AllowCredentialPost` and the PKCE return URL. + +These are client-side settings. The UAuthHub must be configured independently. + +### What the URLs mean + +`Endpoints.BasePath` points to the remote authentication endpoints. + +`Pkce.ReturnUrl` identifies the frontend destination used by the relevant PKCE flow. + +The Hub must recognize the frontend's origin, and any return URL used in an authentication flow must be validated against the application's security policy. + +### Root component + +A standalone WASM application should initialize UltimateAuth through `UAuthApp`. + +For example, its root application or routing component can contain: + +```razor +@using CodeBeam.UltimateAuth.Client.Blazor + + +``` + +Include the UltimateAuth JavaScript asset in the application's HTML host page. + +### Authentication modes + +The default `BlazorWasm` profile resolves to `Hybrid` in the current mode resolver. + +Do not manually force `PureOpaque` for a standalone browser client simply because a Blazor Server application uses it. + +The transport and authentication lifecycle must match the client environment. + +## 6. Blazor Web App β€” Interactive WebAssembly + +**Best for:** A Blazor Web App with an ASP.NET Core host and a separate `.Client` project. + +This scenario differs from standalone WASM because components may participate in both server-side rendering and browser-side interactivity. + +The current UltimateAuth sample registers the Blazor client integration in **both projects**. + +It also adds a separate ASP.NET Core compatibility registration in the host. + +### Project structure + +```text +MyApplication/ +β”œβ”€β”€ MyApplication/ +β”‚ β”œβ”€β”€ Program.cs +β”‚ └── Components/ +β”‚ └── App.razor +β”‚ +└── MyApplication.Client/ + β”œβ”€β”€ Program.cs + └── Pages/ +``` + +The host serves the application. + +The `.Client` project contains browser-executed components and services. + +A remote UAuthHub manages authentication. + +### 6.1 Host project packages + +Install the Blazor client integration and ASP.NET Core compatibility package in the host: + +```bash +dotnet add package CodeBeam.UltimateAuth.Client.Blazor +dotnet add package CodeBeam.UltimateAuth.Client.AspNetCore +``` + +### 6.2 Host project services + +In the host `Program.cs`: -In the host project: ```csharp -builder.Services.AddUltimateAuthClientBlazor(o => +using CodeBeam.UltimateAuth.Client.AspNetCore; +using CodeBeam.UltimateAuth.Client.Blazor.Extensions; +using CodeBeam.UltimateAuth.Core.Domain; + +builder.Services + .AddRazorComponents() + .AddInteractiveWebAssemblyComponents(); + +builder.Services.AddUltimateAuthClientBlazor(options => { - o.Endpoints.BasePath = "https://localhost:6112/auth"; // UAuthHub URL - o.Pkce.ReturnUrl = "https://localhost:6132/home"; // Current application domain + path + options.Endpoints.BasePath = "https://localhost:6112/auth"; + options.Pkce.ReturnUrl = "https://localhost:6132/home"; + options.Login.AllowCredentialPost = true; + options.Reauth.Behavior = ReauthBehavior.RaiseEvent; }); -// In pipeline configuration +builder.Services.AddUltimateAuthAspNetCoreCompatibility(); +``` + +`AddUltimateAuthAspNetCoreCompatibility()` registers the compatibility authentication scheme and ASP.NET Core authorization services used by this hosting pattern. + +It does **not** turn the host into an UltimateAuth authentication server. + +It is also not equivalent to `AddUltimateAuthResourceApi()`. + +### 6.3 Host project pipeline + +```csharp +app.UseHttpsRedirection(); + +app.UseAntiforgery(); + +app.MapStaticAssets(); + app.MapRazorComponents() .AddInteractiveWebAssemblyRenderMode() - .AddAdditionalAssemblies(UAuthAssemblies.BlazorClient().First()); + .AddAdditionalAssemblies(typeof(MyApplication.Client._Imports).Assembly, UAuthAssemblies.BlazorClient().First()); +``` + +Replace `MyApplication.Client._Imports` with the corresponding type from your client project. + +The additional assemblies allow the host to discover routable components from the WASM client project and UltimateAuth's Blazor client assembly. + +The sample does not register `AddUltimateAuthServer()` in this host because authentication is delegated to a separate UAuthHub. + +### 6.4 Client project packages + +Install the Blazor client adapter in the `.Client` project: + +```bash +dotnet add package CodeBeam.UltimateAuth.Client.Blazor +``` + +### 6.5 Client project services + +In `MyApplication.Client/Program.cs`: + +```csharp +using CodeBeam.UltimateAuth.Client.Blazor.Extensions; +using CodeBeam.UltimateAuth.Core.Domain; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); + +builder.Services.AddScoped(sp => new HttpClient +{ + BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) +}); + +builder.Services.AddUltimateAuthClientBlazor(options => +{ + options.Endpoints.BasePath = "https://localhost:6112/auth"; + options.Pkce.ReturnUrl = "https://localhost:6132/home"; + options.Login.AllowCredentialPost = true; + options.Reauth.Behavior = ReauthBehavior.RaiseEvent; +}); + +await builder.Build().RunAsync(); +``` + +The host and client configurations should agree on the remote UAuthHub URL and authentication flow settings. + +### Why both registrations are necessary + +The host and browser client have separate dependency injection containers. + +Registering `AddUltimateAuthClientBlazor()` in the host does not automatically register those services inside the WebAssembly runtime. + +Likewise, registering the client adapter only in the `.Client` project does not supply the services required during server-side integration. + +The additional ASP.NET Core compatibility registration addresses the host's ASP.NET Core authentication and authorization integration requirements. + +**Important:** This compatibility layer is not a replacement for server-side authorization of your protected business APIs. + +## 7. Resource API β€” Separate Protected Backend + +**Best for:** An API that needs UltimateAuth authentication validation but does not host the authentication system itself. + +A Resource API can be an ASP.NET Core Web API, MVC application, or another supported server-side application. + +It does not need to be a Blazor application. + +### Architecture + +```text + Blazor Client + β”‚ + β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β” + β–Ό β–Ό + UAuthHub Resource API + β–² β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + Authentication + validation +``` + +The Resource API uses the configured UAuthHub for remote authentication and session validation behavior. + +### Install the server integration package + +```bash +dotnet add package CodeBeam.UltimateAuth.Server ``` -In the client project: +### Configure services + +In the Resource API's `Program.cs`: + ```csharp -builder.Services.AddUltimateAuthClientBlazor(o => +using CodeBeam.UltimateAuth.Server.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); + +builder.Services.AddUltimateAuthResourceApi(options => { - o.Endpoints.BasePath = "https://localhost:6112/auth"; // UAuthHub URL - o.Pkce.ReturnUrl = "https://localhost:6132/home"; // Current application domain + path + options.UAuthHubBaseUrl = "https://localhost:6112"; + options.AllowedClientOrigins.Add("https://localhost:6132"); + options.CorsPolicyName = "resourceApi"; }); +``` + +`UAuthHubBaseUrl` is the base address of the authentication server. + +Notice that this example uses the Hub origin without the `/auth` suffix, matching the current Resource API samples. + +`AllowedClientOrigins` controls the browser origins permitted by the Resource API's CORS configuration. + +### Configure the pipeline + +```csharp +var app = builder.Build(); + +app.UseHttpsRedirection(); + +app.UseUltimateAuthResourceApiWithAspNetCore(); + +app.MapControllers(); + +app.Run(); +``` + +`UseUltimateAuthResourceApiWithAspNetCore()` installs the Resource API integration middleware together with ASP.NET Core authentication and authorization middleware. + +You do not need to register a local UltimateAuth Server or its persistence bundle in the Resource API. + +### Protecting endpoints + +Use ASP.NET Core authorization attributes for endpoints that require authentication: + +```csharp +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +[ApiController] +[Route("api/products")] +public class ProductsController : ControllerBase +{ + [HttpGet] + [Authorize] + public IActionResult GetProducts() + { + return Ok(new[] + { + new { Id = 1, Name = "Example product" } + }); + } +} +``` + +The authorization attribute is enforced by ASP.NET Core on the server. + +For permission-specific rules, configure and apply the corresponding authorization policies. + +Do not rely on Blazor UI authorization checks to protect Resource API endpoints. + +### CORS configuration + +The current Resource API integration registers CORS from the configured allowed origins. + +For example: + +```csharp +options.AllowedClientOrigins.Add("https://localhost:6132"); +``` + +Only add origins that should be allowed to call your API from a browser. + +CORS is a browser access control mechanism, not an authentication or authorization mechanism. + +A request must still pass the applicable authentication and authorization checks. + +### Resource API vs UAuthHub + +These are different responsibilities. + +| UAuthHub | Resource API | +|---|---| +| Hosts authentication flows | Hosts application resources | +| Manages authentication sessions | Validates remote authentication context | +| Uses authentication persistence | Does not require local authentication stores | +| Provides login and related endpoints | Provides application endpoints | +| Serves authentication clients | Serves authorized API consumers | + +Use a Resource API when you want independent deployment of business endpoints without duplicating the authentication server. + +## 8. Resource API with an EF Core-Backed UAuthHub + +The Resource API does not need to use EF Core just because UAuthHub does. + +For example: + +```text +Interactive WASM + β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ί Resource API + β”‚ + β–Ό + UAuthHub + β”‚ + β–Ό + EF Core Database +``` -builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); +The Hub stores users, credentials, sessions, and related security state in its database. -// Optional if you use external API calls in your client project. -builder.Services.AddHttpClient("resourceApi", client => +The Resource API communicates with the Hub through the configured remote validation integration. + +Its registration remains: + +```csharp +builder.Services.AddUltimateAuthResourceApi(options => { - client.BaseAddress = new Uri("https://localhost:6122"); + options.UAuthHubBaseUrl = "https://localhost:6112"; + options.AllowedClientOrigins.Add("https://localhost:6132"); + options.CorsPolicyName = "resourceApi"; }); ``` -> If you want to use embedded UAuthHub in host project, you can register server services as shown in quickstart. +This separation allows the API and authentication infrastructure to be deployed, scaled, and maintained independently. + +## 9. Calling a Resource API from Blazor -> ℹ️ UltimateAuth automatically selects the appropriate authentication mode (PureOpaque, Hybrid, etc.) based on the client type. +The current samples demonstrate calling a separate Resource API from Blazor applications. -## ResourceApi Setup -You may want to secure your custom API with UltimateAuth. UltimateAuth provides a lightweight option for this case. (ResourceApi doesn't have to be a blazor application, it can be any server-side project like MVC.) +The client integration and the business API's HTTP client are separate concerns. + +### Interactive WASM host + +In the ASP.NET Core host: -ResourceApi's `Program.cs` ```csharp -builder.Services.AddUltimateAuthResourceApi(o => +builder.Services.AddHttpClient( + client => { - o.UAuthHubBaseUrl = "https://localhost:6110"; - o.AllowedClientOrigins.Add("https://localhost:6130"); + client.BaseAddress = new Uri("https://localhost:6122"); }); ``` -Configure pipeline: +### Interactive WASM client + +Register the same application API service in the `.Client` project: + ```csharp -app.UseUltimateAuthResourceApiWithAspNetCore(); +builder.Services.AddHttpClient( + client => + { + client.BaseAddress = new Uri("https://localhost:6122"); + }); +``` + +The host and client each require the service when application components or services execute in their respective environments. + +`SampleProductApiService` is an example application service, not a required UltimateAuth framework type. + +### Standalone WASM + +A standalone WASM application can register an HTTP client for its Resource API: + +```csharp +builder.Services.AddScoped(sp => new HttpClient +{ + BaseAddress = new Uri("https://localhost:6120") +}); +``` + +If your application has multiple HTTP clients, prefer explicitly named or typed clients to avoid ambiguity. + +The current samples also demonstrate browser request credentials handling for cookie-based requests. + +For cross-origin cookie authentication, the browser request, API CORS policy, cookie attributes, and Hub configuration must all be compatible. + +Do not assume that registering an `HttpClient` automatically forwards authentication credentials to every remote API. + +## 10. Authentication Modes and Client Profiles + +UltimateAuth separates **client profile detection** from **authentication mode enforcement**. + +The default resolver maps client profiles to authentication modes. + +| Client profile | Default resolved mode | +|---|---| +| Blazor Server | `PureOpaque` | +| Blazor WASM | `Hybrid` | +| Web Server | `Hybrid` | +| MAUI | `Hybrid` | +| API | `PureJwt` | + +This mapping describes the resolver's behavior, not a guarantee that every mode is currently fully supported by the server. + +In particular, `PureJwt` and `SemiHybrid` have implementation limitations in the current release. + +### Restrict allowed modes + +On the authentication server: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.AllowedModes = + [ + UAuthMode.PureOpaque, + UAuthMode.Hybrid + ]; +}); +``` + +`AllowedModes` is a guardrail. + +It does not select a mode for every request. + +The effective mode is resolved from the client profile and then checked against the server's allowed-mode policy. + +For most applications, use the default mode resolution unless you have a specific integration requirement. + +## 11. Server Options for Real Applications + +Production configuration should reflect your application's security requirements. + +For example: + +```csharp +builder.Services.AddUltimateAuthServer(options => +{ + options.Login.MaxFailedAttempts = 5; + options.Login.LockoutDuration = TimeSpan.FromMinutes(15); + options.Identifiers.Behavior.AllowMultipleUsernames = true; + options.UserProfile.EnableMultiProfile = false; +}); +``` + +Server options also compose the Core-defined login, session, token, PKCE, and multi-tenant options. + +Configure those through `AddUltimateAuthServer()`. + +Do not independently configure Core options and assume the server will automatically use them. + +### Configuration files + +UltimateAuth also supports configuration through `appsettings.json`. + +```json +{ + "UltimateAuth": { + "Server": { + "Login": { + "MaxFailedAttempts": 5, + "LockoutDuration": "00:15:00" + } + } + } +} ``` -Notes: -- ResourceApi should connect with an UAuthHub, not a pure-server. Make sure `.AddUAuthHub()` after calling `builder.Services.AddUltimateAuthServer()`. -- UltimateAuth automatically configures CORS based on the provided origins. +The server options registration binds the `UltimateAuth:Server` configuration section. + +Remember that effective mode defaults and mode-specific overrides are applied after the base options have been configured. + +For detailed configuration behavior, see Server Options and Advanced Configuration. + +## 12. Development and Production Differences + +The samples intentionally prioritize visibility and repeatability. + +A production application needs additional safeguards. + +| Area | Sample or development | Production | +|---|---|---| +| Persistence | InMemory or local SQLite | Durable database | +| Seed accounts | Example users and passwords | Real provisioning | +| Database initialization | May reset sample data | Reviewed migrations | +| HTTPS | Local development certificate | Proper TLS termination | +| CORS | Localhost origins | Exact trusted origins | +| Signing keys | Development defaults | Managed, persistent keys | +| Diagnostics | Detailed debugging | Restrict sensitive diagnostics | +| Credential recovery | Development notifier | Real secure notification channel | +| Identifier verification | Development bypass | Proof-based verification | +| Secrets | Local settings | Secure configuration provider | + +The sample projects include development-only implementations for credential reset notifications and identifier verification. + +Those implementations demonstrate extension points and should not be treated as production verification systems. + +### Credential recovery + +If your application exposes credential recovery, configure a real implementation of the credential reset notifier. + +Recovery secrets must be delivered through an appropriate channel and must not be exposed directly through anonymous API responses. + +### Identifier verification + +If your application supports verified email addresses, phone numbers, or other identifiers, use an implementation that verifies ownership through suitable proof. + +Do not use a development bypass verifier in production. + +### Signing keys + +Configure persistent signing keys for authentication modes that issue JWTs. + +Development signing key providers are not a substitute for production key management. + +## 13. Deployment Checklist + +Before deploying, verify the following: + +- [ ] The selected architecture matches the actual client hosting model. +- [ ] The authentication server uses appropriate persistent storage. +- [ ] Database migrations have been reviewed and applied. +- [ ] Sample users and development-only verification services are disabled. +- [ ] Production signing keys and secrets are configured. +- [ ] HTTPS is enforced at the appropriate boundary. +- [ ] UAuthHub and Resource API origins are explicitly configured. +- [ ] Authentication endpoints and application endpoints are protected appropriately. +- [ ] Client and server endpoint URLs match the deployment. +- [ ] Session expiration, refresh, logout, and revocation have been tested. +- [ ] Protected resources enforce server-side authorization. +- [ ] Authentication state and persistence survive the expected application lifecycle. + +## 14. Choosing Your Architecture + +**Start with one server and one client. Connect more clients whenever you need them.** + +UltimateAuth is built around two fundamental responsibilities: + +- **UltimateAuth Server** β€” Manages authentication, users, credentials, sessions, authorization, and the security lifecycle. +- **UltimateAuth Client** β€” Integrates an application with the authentication system using the appropriate client profile and authentication flows. + +Every application needs access to an authentication server, but that server does not have to be a separate application. It can be hosted within your existing ASP.NET Core application or deployed independently as UAuthHub. + +### Single Application β€” The Simplest Setup + +For a traditional ASP.NET Core or Blazor Server application, UltimateAuth Server and Client can run together. + +```text +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Your Application β”‚ +β”‚ β”‚ +β”‚ UltimateAuth Client β”‚ +β”‚ β”‚ β”‚ +β”‚ UltimateAuth Server β”‚ +β”‚ β”‚ β”‚ +β”‚ Database β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +This approach is ideal when you want authentication without introducing another service or deployment. + +### Multiple Applications β€” One Authentication Server + +As your system grows, you can connect multiple client applications to the same UltimateAuth Server. + +```text + Blazor Server Blazor WASM + β”‚ β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ β”‚ β”‚ + Razor Pages MAUI Other .NET Clients + β”‚ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + UltimateAuth Server + UAuthHub + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β–Ό β–Ό β–Ό + Users Sessions Authorization + β”‚ + β–Ό + Persistence +``` + +Each client can have its own application structure, hosting model, and authentication requirements while sharing the same authentication authority. + +For example, a business platform might use Blazor Server for administration, Blazor WASM for its customer portal, MAUI for mobile access, and Razor Pages for an internal application. + +**Adding another client does not require replacing the existing clients or creating another UltimateAuth Server.** + +The same users, credentials, security policies, and session infrastructure can serve multiple applications, subject to the authentication modes and client integrations supported by the installed version. + +### Distributed Applications β€” Separate Resource APIs + +When your architecture includes independently deployed backend services, Resource APIs can integrate with the same authentication authority. + +```text + Multiple Client Applications + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β” + β–Ό β–Ό + UAuthHub Resource APIs + β–² β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + Authentication + Validation +``` + +Resource APIs protect application resources without becoming additional authentication servers. + +This lets you separate business services from authentication infrastructure while maintaining a shared security model. + +### One Foundation, Different Scales -Use ResourceApi when: +| Your scenario | Recommended approach | +|---|---| +| A simple Blazor Server application | Integrated Server + Client | +| A traditional ASP.NET Core application | Server hosted within the application | +| A standalone Blazor WASM application | Client + remote UAuthHub | +| An Interactive WASM application | Host + WASM Client + remote UAuthHub | +| Multiple web, desktop, or mobile applications | Multiple Clients + shared UAuthHub | +| Multiple backend services | Shared UAuthHub + Resource APIs | -- You have a separate backend API -- You want to validate sessions or tokens externally -- Your API is not hosting UltimateAuth directly +**UltimateAuth does not require a complex architecture to get started, and it does not restrict you to a single client as your application grows.** -## 🧠 How to Think About Setup +Start with the simplest deployment that meets your needs. Add new client applications, persistent storage, or separate Resource APIs as your requirements evolve. -In UltimateAuth: +The authentication foundation remains consistent. -- The **Server** manages authentication flows and sessions -- The **Client** interacts through flows (not tokens directly) -- The **Storage layer** (InMemory / EF Core) defines persistence -- The **Application type** determines runtime behavior +## Next Steps -πŸ‘‰ You configure the system once, and UltimateAuth adapts automatically. +- [QuickStart](/docs/getting-started/quickstart) β€” Build the minimal in-memory application. +- [Ecosystem](/docs/getting-started/ecosystem) β€” Explore packages and supported integrations. +- [Server Options](/docs/configuration/server-options) β€” Configure server authentication and security behavior. +- [Client Options](/docs/configuration/client-options) β€” Configure client profiles, endpoints, refresh, and reauthentication. +- [Advanced Configuration](/docs/configuration/advanced-configuration) β€” Explore effective options, dependency injection, and extension points. \ No newline at end of file diff --git a/docs/content/sessions-devices/device-management.md b/docs/content/sessions-devices/device-management.md index 80bc61be..23e837b9 100644 --- a/docs/content/sessions-devices/device-management.md +++ b/docs/content/sessions-devices/device-management.md @@ -1,6 +1,6 @@ --- title: Device Management -order: 3 +order: 2 group: sessions-devices --- diff --git a/docs/content/sessions-devices/session-lifecycle.md b/docs/content/sessions-devices/session-lifecycle.md index 65632538..d07c843b 100644 --- a/docs/content/sessions-devices/session-lifecycle.md +++ b/docs/content/sessions-devices/session-lifecycle.md @@ -1,6 +1,6 @@ --- title: Session Model & Lifecycle -order: 2 +order: 1 group: sessions-devices --- diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json index 62941635..8ca77d6a 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json @@ -1,6 +1,6 @@ { "Slug": "auth-flows/index", - "Title": "Auth Flows", + "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", "Headings": [ { diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/client-options.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/client-options.json index 22e70422..e2476278 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/client-options.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/client-options.json @@ -1,56 +1,411 @@ { "Slug": "configuration/client-options", "Title": "Client Options", - "Html": "\n\u003Cp\u003EClient Options define how UltimateAuth behaves on the \u003Cstrong\u003Eclient side\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 While Server controls the system,\u003Cbr\u003E\n\uD83D\uDC49 Client controls how it is \u003Cstrong\u003Eused\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-are-client-options\u0022\u003E\uD83E\uDDE0 What Are Client Options?\u003C/h2\u003E\n\u003Cp\u003EClient options configure:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient profile (WASM, Server, MAUI, API)\u003C/li\u003E\n\u003Cli\u003EEndpoint communication\u003C/li\u003E\n\u003Cli\u003EPKCE behavior\u003C/li\u003E\n\u003Cli\u003EToken refresh\u003C/li\u003E\n\u003Cli\u003ERe-authentication behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic-usage\u0022\u003E\u2699\uFE0F Basic Usage\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.AutoDetectClientProfile = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-profile\u0022\u003E\uD83E\uDDE9 Client Profile\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth automatically detects client type by default:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBlazor WASM\u003C/li\u003E\n\u003Cli\u003EBlazor Server\u003C/li\u003E\n\u003Cli\u003EMAUI\u003C/li\u003E\n\u003Cli\u003EWebServer\u003C/li\u003E\n\u003Cli\u003EAPI\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EYou can override manually:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.ClientProfile = UAuthClientProfile.BlazorWasm;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Manual override is useful for testing or special scenarios\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoints\u0022\u003E\uD83C\uDF10 Endpoints\u003C/h2\u003E\n\u003Cp\u003EDefines where requests are sent:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Endpoints.BasePath = \u0026quot;https://localhost:5001/auth\u0026quot;;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Required for WASM / remote clients\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pkce-configuration\u0022\u003E\uD83D\uDD10 PKCE Configuration\u003C/h2\u003E\n\u003Cp\u003EUsed for browser-based login flows:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Pkce.ReturnUrl = \u0026quot;https://localhost:5002/home\u0026quot;;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Required for WASM scenarios\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022auto-refresh\u0022\u003E\uD83D\uDD01 Auto Refresh\u003C/h2\u003E\n\u003Cp\u003EControls token/session refresh 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\u003Eo.AutoRefresh.Interval = TimeSpan.FromMinutes(1);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Keeps authentication alive automatically\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022re-authentication\u0022\u003E\uD83D\uDD04 Re-authentication\u003C/h2\u003E\n\u003Cp\u003EControls behavior when session expires:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Reauth.Behavior = ReauthBehavior.RaiseEvent;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Allows silent or interactive re-login\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Server decides\u003Cbr\u003E\n\uD83D\uDC49 Client adapts\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient options control runtime behavior\u003C/li\u003E\n\u003Cli\u003EProfile detection is automatic\u003C/li\u003E\n\u003Cli\u003EPKCE is required for public clients\u003C/li\u003E\n\u003Cli\u003ERefresh and re-auth are configurable\u003C/li\u003E\n\u003Cli\u003EWorks together with Server options\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EConfiguration Sources\u003C/strong\u003E\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003E\u003Ccode\u003EUAuthClientOptions\u003C/code\u003E controls how an UltimateAuth client communicates with the authentication server, executes authentication flows, maintains client-side authentication state, and responds when authentication must be renewed.\u003C/p\u003E\n\u003Cp\u003EThe client configuration is intentionally separate from server security configuration.\u003C/p\u003E\n\u003Cp\u003EThe server determines which authentication operations are permitted, how credentials are validated, and when sessions expire. The client determines how those operations are initiated and how the application responds to their outcomes.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the current client configuration API, platform integration, defaults, practical examples, and important security considerations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022register-the-ultimateauth-client\u0022\u003E1. Register the UltimateAuth Client\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides a platform-independent client SDK and platform-specific adapters.\u003C/p\u003E\n\u003Cp\u003EFor Blazor applications, use:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor(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\u003E\u003Ccode\u003EAddUltimateAuthClientBlazor()\u003C/code\u003E registers the underlying client services and the Blazor-specific infrastructure, including browser transport, storage, device identification, and authentication state integration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-client-registration\u0022\u003ECore Client Registration\u003C/h3\u003E\n\u003Cp\u003EThe platform-independent 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.AddUltimateAuthClient(options =\u0026gt;\n{\n options.AutoDetectClientProfile = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis registers the shared client SDK.\u003C/p\u003E\n\u003Cp\u003EHowever, it does not provide every platform-specific implementation needed to run authentication flows.\u003C/p\u003E\n\u003Cp\u003EApplications should use an appropriate platform adapter rather than relying on the core client registration alone.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-not-register-both-client-methods\u0022\u003EDo Not Register Both Client Methods\u003C/h3\u003E\n\u003Cp\u003EWhen 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\u003Ebuilder.Services.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Edo not also call:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe Blazor registration already includes the core client services.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-and-client-in-one-application\u0022\u003EServer and Client in One Application\u003C/h3\u003E\n\u003Cp\u003EA Blazor Server application can register both UltimateAuth Server and the Blazor client adapter:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.FromDays(7);\n});\n\nbuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.ClientProfile = UAuthClientProfile.BlazorServer;\n options.AutoDetectClientProfile = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese registrations configure different components.\u003C/p\u003E\n\u003Cp\u003EThe server controls authentication security and lifecycle rules.\u003C/p\u003E\n\u003Cp\u003EThe client controls the application-side integration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-options-architecture\u0022\u003E2. Client Options Architecture\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthClientOptions\u003C/code\u003E contains 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\u003EType\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\u003EClientProfile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClientProfile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifies the client platform\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAutoDetectClientProfile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ebool\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEnables automatic 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\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDefault navigation destination after authentication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEndpoints\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClientEndpointOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication endpoint paths\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClientLoginFlowOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin flow configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkce\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClientPkceLoginFlowOptions\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\u003EAutoRefresh\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClientAutoRefreshOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBackground refresh coordination\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReauth\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClientReauthOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReauthentication behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMultiTenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClientMultiTenantOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant propagation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EStateEvents\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthStateEventOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication state event handling\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese options are configured through the same \u003Ccode\u003EUAuthClientOptions\u003C/code\u003E instance.\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.DefaultReturnUrl = \u0026quot;/dashboard\u0026quot;;\n options.Pkce.Enabled = true;\n options.AutoRefresh.Interval = TimeSpan.FromMinutes(5);\n options.StateEvents.HandlingMode = UAuthStateEventHandlingMode.Patch;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-profiles\u0022\u003E3. Client Profiles\u003C/h2\u003E\n\u003Cp\u003EThe client profile identifies the application environment and influences authentication flow behavior.\u003C/p\u003E\n\u003Cp\u003EConfigure 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\u003Eoptions.ClientProfile\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUltimateAuth defines profiles including:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProfile\u003C/th\u003E\n\u003Cth\u003ETypical application\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EInteractive Blazor Server applications\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorWasm\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBrowser-hosted Blazor WebAssembly\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMaui\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E.NET MAUI applications\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EWebServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETraditional ASP.NET Core web applications\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EApi\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAPI-oriented clients\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthHub\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUltimateAuth Hub\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENotSpecified\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EProfile not explicitly selected\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EDifferent client profiles may result in different authentication modes on the server.\u003C/p\u003E\n\u003Cp\u003EFor example, the default server mode resolver maps Blazor Server to \u003Ccode\u003EPureOpaque\u003C/code\u003E and Blazor WebAssembly to \u003Ccode\u003EHybrid\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe client profile does not itself authorize an authentication mode. The server must support and permit the resolved mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-client-profile-detection\u0022\u003E4. Automatic Client Profile Detection\u003C/h2\u003E\n\u003Cp\u003EAutomatic profile detection is enabled by 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.AutoDetectClientProfile = true;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen enabled, UltimateAuth detects the client environment if \u003Ccode\u003EClientProfile\u003C/code\u003E remains \u003Ccode\u003ENotSpecified\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe current detector checks for:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EUAuthHub registration.\u003C/li\u003E\n\u003Cli\u003E.NET MAUI.\u003C/li\u003E\n\u003Cli\u003EBlazor WebAssembly.\u003C/li\u003E\n\u003Cli\u003EBlazor Server.\u003C/li\u003E\n\u003Cli\u003EA fallback to \u003Ccode\u003EWebServer\u003C/code\u003E.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022default-configuration\u0022\u003EDefault 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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.AutoDetectClientProfile = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ENo explicit profile is required for ordinary supported environments where detection succeeds.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022manual-configuration\u0022\u003EManual Configuration\u003C/h3\u003E\n\u003Cp\u003EFor applications that need predictable behavior independent of assembly-based detection:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.AutoDetectClientProfile = false;\n options.ClientProfile = UAuthClientProfile.BlazorWasm;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EManual configuration can be useful for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESpecialized hosting environments.\u003C/li\u003E\n\u003Cli\u003EIntegration tests.\u003C/li\u003E\n\u003Cli\u003EApplications with multiple hosting models.\u003C/li\u003E\n\u003Cli\u003ECases where assembly-based detection is ambiguous.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-validation-rule\u0022\u003EImportant Validation Rule\u003C/h3\u003E\n\u003Cp\u003EThis 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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.AutoDetectClientProfile = false;\n options.ClientProfile = UAuthClientProfile.NotSpecified;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen automatic detection is disabled, an explicit client profile must be provided.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022explicit-profiles-take-precedence\u0022\u003EExplicit Profiles Take Precedence\u003C/h3\u003E\n\u003Cp\u003EThe post-configuration detector does not replace a profile that has already been explicitly selected.\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.ClientProfile = UAuthClientProfile.BlazorServer;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe explicit profile is retained even when automatic detection remains enabled.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022detection-limitations\u0022\u003EDetection Limitations\u003C/h3\u003E\n\u003Cp\u003EThe current detector uses service registration and loaded assemblies to identify the environment.\u003C/p\u003E\n\u003Cp\u003EAssembly-based detection may not be completely reliable in every hosting arrangement.\u003C/p\u003E\n\u003Cp\u003EFor complex Blazor Web App hosting scenarios, explicitly configuring the profile can make authentication behavior easier to reason about.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoint-configuration\u0022\u003E5. Endpoint Configuration\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Endpoints\u003C/code\u003E defines the endpoint paths used by the client SDK.\u003C/p\u003E\n\u003Cp\u003EThe default base path 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\u003E/auth\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-a-remote-authentication-server\u0022\u003EConfigure a Remote Authentication Server\u003C/h3\u003E\n\u003Cp\u003EFor a client communicating with a separately hosted UAuthHub:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;https://auth.example.com/auth\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor an application using same-origin authentication endpoints:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;/auth\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe base path must match the deployed authentication server\u0027s routing configuration.\u003C/p\u003E\n\u003Cp\u003EChanging a client endpoint does not create or expose the corresponding server endpoint.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022default-endpoint-paths\u0022\u003EDefault Endpoint Paths\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\u003EDefault\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBasePath\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E/auth\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E/login\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryLogin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E/try-login\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogout\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E/logout\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERefresh\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E/refresh\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReauth\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E/reauth\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EValidate\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E/validate\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkceAuthorize\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E/pkce/authorize\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkceTryComplete\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E/pkce/try-complete\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkceComplete\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E/pkce/complete\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHubLoginPath\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E/uauthhub/entry\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=\u0022customize-individual-paths\u0022\u003ECustomize Individual Paths\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;/auth\u0026quot;;\n options.Endpoints.Login = \u0026quot;/login\u0026quot;;\n options.Endpoints.Logout = \u0026quot;/logout\u0026quot;;\n options.Endpoints.Refresh = \u0026quot;/refresh\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese values should reflect the actual routes registered by UltimateAuth Server.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022required-endpoint-validation\u0022\u003ERequired Endpoint Validation\u003C/h3\u003E\n\u003Cp\u003EThe client options validator requires:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EEndpoints.BasePath\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EEndpoints.Login\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EEndpoints.Logout\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EEndpoints.Refresh\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EEndpoints.Validate\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese properties must not be null, empty, or whitespace-only.\u003C/p\u003E\n\u003Cp\u003EThe current validator does not apply the same required-value checks to every optional endpoint path.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remote-clients-and-browser-security\u0022\u003ERemote Clients and Browser Security\u003C/h3\u003E\n\u003Cp\u003EWhen using a remote authentication server, also consider:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EHTTPS.\u003C/li\u003E\n\u003Cli\u003ECross-origin request configuration.\u003C/li\u003E\n\u003Cli\u003ECookie \u003Ccode\u003ESameSite\u003C/code\u003E and \u003Ccode\u003ESecure\u003C/code\u003E attributes.\u003C/li\u003E\n\u003Cli\u003EAllowed client origins.\u003C/li\u003E\n\u003Cli\u003EPKCE redirects.\u003C/li\u003E\n\u003Cli\u003EBrowser credential policies.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA correct endpoint URL alone does not guarantee that cross-origin authentication will work.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022login-flow-options\u0022\u003E6. Login Flow Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Login\u003C/code\u003E controls client-side login behavior.\u003C/p\u003E\n\u003Cp\u003EThe available properties are:\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\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReturnUrl\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin-specific return destination\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAllowCredentialPost\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAllow direct credential posting\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-login-navigation\u0022\u003EConfigure Login Navigation\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Login.ReturnUrl = \u0026quot;/dashboard\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe login-specific return URL is used when the login flow does not provide a more specific destination.\u003C/p\u003E\n\u003Cp\u003EIf it is not set, UltimateAuth can fall back to \u003Ccode\u003EDefaultReturnUrl\u003C/code\u003E or the current location according to the flow.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022direct-credential-posting\u0022\u003EDirect Credential Posting\u003C/h3\u003E\n\u003Cp\u003EThe default 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 an important security setting.\u003C/p\u003E\n\u003Cp\u003EDirectly posting user credentials is not appropriate for public browser clients such as Blazor WebAssembly applications.\u003C/p\u003E\n\u003Cp\u003EPublic clients should use the supported PKCE-based login flow.\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\u003EA trusted server-hosted application may explicitly enable direct credential posting where its authentication architecture requires 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\u003Ebuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.ClientProfile = UAuthClientProfile.BlazorServer;\n options.Login.AllowCredentialPost = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDo not enable this setting for a public client merely to simplify login implementation.\u003C/p\u003E\n\u003Cp\u003EThe server still determines whether the requested authentication operation is permitted.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pkce-configuration\u0022\u003E7. PKCE Configuration\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Pkce\u003C/code\u003E controls client-side PKCE login behavior.\u003C/p\u003E\n\u003Cp\u003EPKCE allows a public client to complete an authorization flow without relying on a confidential client secret.\u003C/p\u003E\n\u003Cp\u003EThe current options are:\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\u003EEnabled\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEnable client PKCE support\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\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPost-login return destination\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAutoRedirect\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAutomatically navigate after beginning PKCE\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnAuthorized\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECallback after the authorization response is issued\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=\u0022basic-configuration\u0022\u003EBasic 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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Pkce.Enabled = true;\n options.Pkce.ReturnUrl = \u0026quot;/dashboard\u0026quot;;\n options.Pkce.AutoRedirect = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022disable-automatic-redirect\u0022\u003EDisable Automatic Redirect\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Pkce.AutoRedirect = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen \u003Ccode\u003EAutoRedirect\u003C/code\u003E is disabled, \u003Ccode\u003EBeginPkceAsync\u003C/code\u003E does not automatically navigate to the next location.\u003C/p\u003E\n\u003Cp\u003EThe application is responsible for handling navigation.\u003C/p\u003E\n\u003Cp\u003EThis can be useful when integrating UltimateAuth into an existing routing or navigation system.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-callback\u0022\u003EAuthorization Callback\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Pkce.OnAuthorized = response =\u0026gt;\n {\n // Inspect or process the authorization response.\n return Task.CompletedTask;\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe callback receives:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EPkceAuthorizeResponse\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIt runs after the authorization response is issued and before the normal Hub navigation.\u003C/p\u003E\n\u003Cp\u003EUse the callback for application-specific integration, not to bypass PKCE verification.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-and-server-pkce-settings-are-different\u0022\u003EClient and Server PKCE Settings Are Different\u003C/h3\u003E\n\u003Cp\u003EClient 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\u003Eoptions.Pkce.AutoRedirect = true;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EServer 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\u003Eoptions.Pkce.AuthorizationCodeLifetimeSeconds = 120;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client controls how the flow is initiated and navigated.\u003C/p\u003E\n\u003Cp\u003EThe server controls the authorization code\u0027s validity and verification.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022return-url-configuration\u0022\u003E8. Return URL Configuration\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports several return URL configuration points.\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\u003EDefaultReturnUrl\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EGlobal default for interactive authentication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogin.ReturnUrl\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin-specific default\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkce.ReturnUrl\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPKCE-specific default\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=\u0022global-default\u0022\u003EGlobal Default\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.DefaultReturnUrl = \u0026quot;/dashboard\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-specific-defaults\u0022\u003EFlow-Specific Defaults\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.DefaultReturnUrl = \u0026quot;/\u0026quot;;\n options.Login.ReturnUrl = \u0026quot;/account\u0026quot;;\n options.Pkce.ReturnUrl = \u0026quot;/dashboard\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFlow-specific values allow different authentication paths to use different destinations.\u003C/p\u003E\n\u003Cp\u003EAn explicitly supplied return URL for an individual operation may take precedence over these defaults.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003ESecurity Considerations\u003C/h3\u003E\n\u003Cp\u003EReturn URLs are navigation destinations and must be treated carefully.\u003C/p\u003E\n\u003Cp\u003EDo not redirect users to arbitrary external URLs without appropriate validation.\u003C/p\u003E\n\u003Cp\u003EOpen redirects can be abused for phishing and other attacks.\u003C/p\u003E\n\u003Cp\u003EPrefer local application routes unless cross-origin navigation is an intentional and validated part of the authentication architecture.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-refresh\u0022\u003E9. Automatic Refresh\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.AutoRefresh\u003C/code\u003E controls background refresh coordination.\u003C/p\u003E\n\u003Cp\u003EThe available properties are:\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\u003EEnabled\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEnables or disables automatic background refresh\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EInterval\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EInterval between background refresh attempts\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-automatic-refresh\u0022\u003EConfigure Automatic Refresh\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.AutoRefresh.Enabled = true;\n options.AutoRefresh.Interval = TimeSpan.FromMinutes(5);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022disable-automatic-refresh\u0022\u003EDisable Automatic Refresh\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.AutoRefresh.Enabled = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022default-interval\u0022\u003EDefault Interval\u003C/h3\u003E\n\u003Cp\u003EIf \u003Ccode\u003EInterval\u003C/code\u003E is not supplied, the client registration applies:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Interval ??= TimeSpan.FromMinutes(5);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe resulting default interval is five minutes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022platform-behavior\u0022\u003EPlatform Behavior\u003C/h3\u003E\n\u003Cp\u003EThe effective \u003Ccode\u003EEnabled\u003C/code\u003E setting should be considered together with the platform\u0027s post-configuration and session coordination behavior.\u003C/p\u003E\n\u003Cp\u003EThe current Blazor session coordinator checks \u003Ccode\u003EAutoRefresh.Enabled\u003C/code\u003E before starting its background loop.\u003C/p\u003E\n\u003Cp\u003EWhen enabled, it periodically invokes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Flows.RefreshAsync(true);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-refresh-is-not-a-security-policy\u0022\u003EAutomatic Refresh Is Not a Security Policy\u003C/h3\u003E\n\u003Cp\u003EAutomatic refresh does not control:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EServer session lifetime.\u003C/li\u003E\n\u003Cli\u003ERefresh token lifetime.\u003C/li\u003E\n\u003Cli\u003ESession revocation.\u003C/li\u003E\n\u003Cli\u003ECredential recovery policies.\u003C/li\u003E\n\u003Cli\u003ERefresh token replay protection.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese remain server-side responsibilities.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-refresh-does-not-guarantee-session-continuity\u0022\u003EAutomatic Refresh Does Not Guarantee Session Continuity\u003C/h3\u003E\n\u003Cp\u003EBackground refresh may fail because:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe session has expired.\u003C/li\u003E\n\u003Cli\u003EThe refresh credential has been revoked.\u003C/li\u003E\n\u003Cli\u003EThe browser is suspended.\u003C/li\u003E\n\u003Cli\u003ENetwork connectivity is unavailable.\u003C/li\u003E\n\u003Cli\u003EThe server requires reauthentication.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAutomatic refresh is a convenience and continuity mechanism, not a guarantee that authentication remains valid indefinitely.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022coordinator-lifecycle\u0022\u003ECoordinator Lifecycle\u003C/h3\u003E\n\u003Cp\u003EThe Blazor adapter provides an \u003Ccode\u003EISessionCoordinator\u003C/code\u003E implementation.\u003C/p\u003E\n\u003Cp\u003EThe coordinator must be started by the application\u0027s supported initialization lifecycle.\u003C/p\u003E\n\u003Cp\u003ESetting \u003Ccode\u003EAutoRefresh.Enabled = true\u003C/code\u003E does not mean that a background task automatically runs without the coordinator being started.\u003C/p\u003E\n\u003Cp\u003EApplications should follow the client initialization guidance for their hosting model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022reauthentication-options\u0022\u003E10. Reauthentication Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Reauth\u003C/code\u003E controls how the Blazor session coordinator responds when refresh reports that reauthentication is required.\u003C/p\u003E\n\u003Cp\u003EThe available properties are:\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\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBehavior\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EReauthBehavior.Redirect\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERedirectPath\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\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=\u0022available-behaviors\u0022\u003EAvailable Behaviors\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\u003EReauthBehavior.Redirect\nReauthBehavior.None\nReauthBehavior.RaiseEvent\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022redirect\u0022\u003ERedirect\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Reauth.Behavior = ReauthBehavior.Redirect;\n options.Reauth.RedirectPath = \u0026quot;/login\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen the coordinator receives a \u003Ccode\u003EReauthRequired\u003C/code\u003E refresh outcome, it navigates to the configured redirect path.\u003C/p\u003E\n\u003Cp\u003EIf \u003Ccode\u003ERedirectPath\u003C/code\u003E is null, the coordinator falls back to \u003Ccode\u003EEndpoints.Login\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe current Blazor coordinator uses forced navigation for this redirect.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022raise-an-event\u0022\u003ERaise an Event\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Reauth.Behavior = ReauthBehavior.RaiseEvent;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows an application to handle reauthentication through the coordinator\u0027s \u003Ccode\u003EReauthRequired\u003C/code\u003E event.\u003C/p\u003E\n\u003Cp\u003EFor example, an application may display a reauthentication dialog rather than immediately navigating.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022no-automatic-action\u0022\u003ENo Automatic Action\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Reauth.Behavior = ReauthBehavior.None;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWith \u003Ccode\u003ENone\u003C/code\u003E, the coordinator does not automatically redirect or raise its reauthentication event.\u003C/p\u003E\n\u003Cp\u003EThe application must still handle unauthenticated states appropriately.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important\u0022\u003EImportant\u003C/h3\u003E\n\u003Cp\u003EReauthentication behavior does not make an expired or revoked session valid.\u003C/p\u003E\n\u003Cp\u003EIt only controls how the client responds when authentication must be reestablished.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022multi-tenant-client-configuration\u0022\u003E11. Multi-Tenant Client Configuration\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.MultiTenant\u003C/code\u003E controls tenant propagation from the client to the authentication server.\u003C/p\u003E\n\u003Cp\u003EThe client does not independently authorize or resolve tenant ownership.\u003C/p\u003E\n\u003Cp\u003EIt carries tenant context using the configured transport.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022available-properties\u0022\u003EAvailable Properties\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\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\u003EEnabled\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEnable tenant propagation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant identifier to propagate\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETransport\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ETenantTransport.None\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant transport mechanism\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=\u0022available-transports\u0022\u003EAvailable Transports\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\u003ETenantTransport.None\nTenantTransport.Header\nTenantTransport.Route\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022header-based-tenant-propagation\u0022\u003EHeader-Based Tenant Propagation\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.MultiTenant.Enabled = true;\n options.MultiTenant.Tenant = \u0026quot;tenant-a\u0026quot;;\n options.MultiTenant.Transport = TenantTransport.Header;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server must be configured to resolve tenant information from the corresponding transport.\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.MultiTenant.Enabled = true;\n options.MultiTenant.EnableHeader = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022route-based-tenant-propagation\u0022\u003ERoute-Based Tenant Propagation\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.MultiTenant.Enabled = true;\n options.MultiTenant.Tenant = \u0026quot;tenant-a\u0026quot;;\n options.MultiTenant.Transport = TenantTransport.Route;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server must expose routes compatible with tenant-based routing.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-boundary\u0022\u003ESecurity Boundary\u003C/h3\u003E\n\u003Cp\u003EA client-provided tenant identifier is not proof of tenant membership.\u003C/p\u003E\n\u003Cp\u003EThe server must validate the resolved tenant and enforce tenant isolation.\u003C/p\u003E\n\u003Cp\u003ENever rely on client-side tenant selection as an authorization boundary.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-state-events\u0022\u003E12. Authentication State Events\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.StateEvents\u003C/code\u003E controls how the client processes authentication-related state events.\u003C/p\u003E\n\u003Cp\u003EThe available property 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.StateEvents.HandlingMode\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIts default 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\u003EUAuthStateEventHandlingMode.Patch\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022available-modes\u0022\u003EAvailable Modes\u003C/h3\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\u003EPatch\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EApply a local authentication state update\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EValidate\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevalidate authentication state after the event\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENone\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDo not update authentication state in response to the event\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=\u0022patch\u0022\u003EPatch\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.StateEvents.HandlingMode = UAuthStateEventHandlingMode.Patch;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EPatch\u003C/code\u003E is the default.\u003C/p\u003E\n\u003Cp\u003EIt allows the client to update local state without performing a full authentication validation for every event.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validate\u0022\u003EValidate\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.StateEvents.HandlingMode = UAuthStateEventHandlingMode.Validate;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EValidate\u003C/code\u003E causes the client to revalidate authentication state in response to events.\u003C/p\u003E\n\u003Cp\u003EThis may introduce additional requests but can be appropriate when the application prefers server-confirmed state updates.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022none\u0022\u003ENone\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.StateEvents.HandlingMode = UAuthStateEventHandlingMode.None;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ENone\u003C/code\u003E disables automatic authentication state updates in response to these events.\u003C/p\u003E\n\u003Cp\u003EApplications using this mode must provide any required state synchronization through other mechanisms.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022local-events-are-not-distributed-notifications\u0022\u003ELocal Events Are Not Distributed Notifications\u003C/h3\u003E\n\u003Cp\u003EClient state events do not automatically notify every browser, device, or application instance.\u003C/p\u003E\n\u003Cp\u003EFor example, an administrator changing a user\u0027s role does not automatically push an event to that user\u0027s open browser.\u003C/p\u003E\n\u003Cp\u003EDistributed synchronization requires additional application infrastructure.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-from-appsettings.json\u0022\u003E13. Configuration from appsettings.json\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth Client binds the configuration section:\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;Endpoints\u0026quot;: {\n \u0026quot;BasePath\u0026quot;: \u0026quot;https://auth.example.com/auth\u0026quot;\n },\n \u0026quot;Pkce\u0026quot;: {\n \u0026quot;Enabled\u0026quot;: true,\n \u0026quot;ReturnUrl\u0026quot;: \u0026quot;/dashboard\u0026quot;,\n \u0026quot;AutoRedirect\u0026quot;: true\n },\n \u0026quot;AutoRefresh\u0026quot;: {\n \u0026quot;Enabled\u0026quot;: true,\n \u0026quot;Interval\u0026quot;: \u0026quot;00:05:00\u0026quot;\n },\n \u0026quot;Reauth\u0026quot;: {\n \u0026quot;Behavior\u0026quot;: \u0026quot;Redirect\u0026quot;,\n \u0026quot;RedirectPath\u0026quot;: \u0026quot;/login\u0026quot;\n },\n \u0026quot;StateEvents\u0026quot;: {\n \u0026quot;HandlingMode\u0026quot;: \u0026quot;Patch\u0026quot;\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe example uses the standard ASP.NET Core configuration binding conventions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-precedence\u0022\u003EConfiguration Precedence\u003C/h3\u003E\n\u003Cp\u003EThe registration pipeline applies:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EOption type defaults.\u003C/li\u003E\n\u003Cli\u003EThe configuration delegate.\u003C/li\u003E\n\u003Cli\u003EValues from \u003Ccode\u003EUltimateAuth:Client\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EPost-configuration processing.\u003C/li\u003E\n\u003Cli\u003ERegistered options validation when the options are resolved.\u003C/li\u003E\n\u003C/ol\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.DefaultReturnUrl = \u0026quot;/\u0026quot;;\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;Client\u0026quot;: {\n \u0026quot;DefaultReturnUrl\u0026quot;: \u0026quot;/dashboard\u0026quot;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe configured return URL becomes \u003Ccode\u003E/dashboard\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022environment-variables\u0022\u003EEnvironment Variables\u003C/h3\u003E\n\u003Cp\u003EASP.NET Core supports double underscores for hierarchical configuration keys.\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__Client__Endpoints__BasePath=https://auth.example.com/auth\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis corresponds 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:Client:Endpoints:BasePath\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse environment-specific configuration for deployment-dependent values such as authentication server addresses and application return paths.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-options-validation\u0022\u003E14. Client Options Validation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth registers validators for:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthClientOptions\nUAuthClientEndpointOptions\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-profile-validation\u0022\u003EClient Profile Validation\u003C/h3\u003E\n\u003Cp\u003EThe validator rejects a configuration where:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AutoDetectClientProfile = false;\noptions.ClientProfile = UAuthClientProfile.NotSpecified;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn explicit profile is required when detection is disabled.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoint-validation\u0022\u003EEndpoint Validation\u003C/h3\u003E\n\u003Cp\u003EThe following endpoint properties must be present:\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\u003EEndpoints.BasePath\nEndpoints.Login\nEndpoints.Logout\nEndpoints.Refresh\nEndpoints.Validate\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validation-timing\u0022\u003EValidation Timing\u003C/h3\u003E\n\u003Cp\u003EUnlike the server registration, the current client registration does not explicitly call \u003Ccode\u003EValidateOnStart()\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EValidation therefore occurs through the normal options resolution lifecycle.\u003C/p\u003E\n\u003Cp\u003EApplications that require early validation can arrange for the configured options to be resolved during startup.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validation-scope\u0022\u003EValidation Scope\u003C/h3\u003E\n\u003Cp\u003EThe current validators check essential client profile and endpoint requirements.\u003C/p\u003E\n\u003Cp\u003EThey do not automatically validate every possible combination of client flow, server deployment, cookie behavior, and authentication mode.\u003C/p\u003E\n\u003Cp\u003EApplications should test their actual authentication flows against the deployed server configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-blazor-webassembly-example\u0022\u003E15. Complete Blazor WebAssembly Example\u003C/h2\u003E\n\u003Cp\u003EThe following example configures a Blazor WebAssembly client using a remote authentication 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\u003Ebuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n // Client profile\n options.AutoDetectClientProfile = false;\n options.ClientProfile = UAuthClientProfile.BlazorWasm;\n\n // Authentication server\n options.Endpoints.BasePath = \u0026quot;https://auth.example.com/auth\u0026quot;;\n\n // Navigation\n options.DefaultReturnUrl = \u0026quot;/\u0026quot;;\n options.Login.ReturnUrl = \u0026quot;/dashboard\u0026quot;;\n\n // Public client security\n options.Login.AllowCredentialPost = false;\n\n // PKCE\n options.Pkce.Enabled = true;\n options.Pkce.ReturnUrl = \u0026quot;/dashboard\u0026quot;;\n options.Pkce.AutoRedirect = true;\n\n // Background refresh\n options.AutoRefresh.Enabled = false;\n\n // Reauthentication\n options.Reauth.Behavior = ReauthBehavior.Redirect;\n options.Reauth.RedirectPath = \u0026quot;/login\u0026quot;;\n\n // State updates\n options.StateEvents.HandlingMode = UAuthStateEventHandlingMode.Patch;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example emphasizes PKCE-based authentication and avoids direct credential posting from a public browser client.\u003C/p\u003E\n\u003Cp\u003EThe remote server must be configured with compatible endpoints, origins, authentication modes, and credential delivery policies.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-blazor-server-example\u0022\u003E16. Complete Blazor Server Example\u003C/h2\u003E\n\u003Cp\u003EThe following example configures a Blazor Server client.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n // Client profile\n options.AutoDetectClientProfile = false;\n options.ClientProfile = UAuthClientProfile.BlazorServer;\n\n // Same-origin authentication endpoints\n options.Endpoints.BasePath = \u0026quot;/auth\u0026quot;;\n\n // Navigation\n options.DefaultReturnUrl = \u0026quot;/\u0026quot;;\n options.Login.ReturnUrl = \u0026quot;/dashboard\u0026quot;;\n\n // Background refresh\n options.AutoRefresh.Enabled = true;\n options.AutoRefresh.Interval = TimeSpan.FromMinutes(5);\n\n // Reauthentication\n options.Reauth.Behavior = ReauthBehavior.Redirect;\n options.Reauth.RedirectPath = \u0026quot;/login\u0026quot;;\n\n // Authentication state\n options.StateEvents.HandlingMode = UAuthStateEventHandlingMode.Patch;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EBlazor Server applications can use session-oriented authentication and the corresponding server-side security policies.\u003C/p\u003E\n\u003Cp\u003EThe application must still initialize the client and session coordinator through its supported lifecycle.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-and-client-configuration-together\u0022\u003E17. Server and Client Configuration Together\u003C/h2\u003E\n\u003Cp\u003EAn application that hosts both UltimateAuth Server and the Blazor client adapter can configure them independently.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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// Server: authentication and security policies\nbuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.AllowedModes =\n [\n UAuthMode.PureOpaque,\n UAuthMode.Hybrid\n ];\n\n options.Session.Lifetime = TimeSpan.FromDays(7);\n options.Login.MaxFailedAttempts = 5;\n});\n\n// Client: platform integration and flow behavior\nbuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.ClientProfile = UAuthClientProfile.BlazorServer;\n options.AutoDetectClientProfile = false;\n options.Endpoints.BasePath = \u0026quot;/auth\u0026quot;;\n options.AutoRefresh.Enabled = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe two configuration objects have different responsibilities.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ERequirement\u003C/th\u003E\n\u003Cth\u003EConfigure on\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\u003EServer\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAccess token lifetime\u003C/td\u003E\n\u003Ctd\u003EServer\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERefresh token replay policy\u003C/td\u003E\n\u003Ctd\u003EServer\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential recovery rules\u003C/td\u003E\n\u003Ctd\u003EServer\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAllowed authentication modes\u003C/td\u003E\n\u003Ctd\u003EServer\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient profile\u003C/td\u003E\n\u003Ctd\u003EClient\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPKCE navigation\u003C/td\u003E\n\u003Ctd\u003EClient\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAutomatic background refresh\u003C/td\u003E\n\u003Ctd\u003EClient\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReauthentication navigation\u003C/td\u003E\n\u003Ctd\u003EClient\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthentication state event handling\u003C/td\u003E\n\u003Ctd\u003EClient\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ETenant validation\u003C/td\u003E\n\u003Ctd\u003EServer\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ETenant propagation\u003C/td\u003E\n\u003Ctd\u003EClient\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe client cannot override server-side security decisions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-configuration-mistakes\u0022\u003E18. Common Configuration Mistakes\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022registering-the-core-client-and-blazor-adapter-separately\u0022\u003ERegistering the Core Client and Blazor Adapter Separately\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EAddUltimateAuthClientBlazor()\u003C/code\u003E for Blazor applications.\u003C/p\u003E\n\u003Cp\u003EIt already registers the underlying client SDK.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022disabling-profile-detection-without-selecting-a-profile\u0022\u003EDisabling Profile Detection Without Selecting a Profile\u003C/h3\u003E\n\u003Cp\u003EWhen \u003Ccode\u003EAutoDetectClientProfile\u003C/code\u003E is false, set \u003Ccode\u003EClientProfile\u003C/code\u003E explicitly.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022relying-on-automatic-detection-in-an-ambiguous-hosting-environment\u0022\u003ERelying on Automatic Detection in an Ambiguous Hosting Environment\u003C/h3\u003E\n\u003Cp\u003EFor complex hosting arrangements, consider specifying the client profile explicitly.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022enabling-direct-credential-posting-in-a-public-client\u0022\u003EEnabling Direct Credential Posting in a Public Client\u003C/h3\u003E\n\u003Cp\u003EDo not enable \u003Ccode\u003EAllowCredentialPost\u003C/code\u003E for browser-hosted public clients.\u003C/p\u003E\n\u003Cp\u003EUse PKCE.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-client-auto-refresh-extends-server-lifetimes\u0022\u003EAssuming Client Auto-Refresh Extends Server Lifetimes\u003C/h3\u003E\n\u003Cp\u003EAutomatic refresh only initiates refresh operations.\u003C/p\u003E\n\u003Cp\u003EThe server controls whether refresh is permitted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-auto-refresh-starts-automatically\u0022\u003EAssuming Auto-Refresh Starts Automatically\u003C/h3\u003E\n\u003Cp\u003EThe Blazor session coordinator must be started through the supported application lifecycle.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuring-only-the-client-endpoint\u0022\u003EConfiguring Only the Client Endpoint\u003C/h3\u003E\n\u003Cp\u003EThe server must expose compatible routes and allow the intended cross-origin interaction.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022treating-tenant-propagation-as-authorization\u0022\u003ETreating Tenant Propagation as Authorization\u003C/h3\u003E\n\u003Cp\u003EA tenant identifier supplied by the client does not grant access to that tenant.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-state-events-as-distributed-notifications\u0022\u003EUsing State Events as Distributed Notifications\u003C/h3\u003E\n\u003Cp\u003EClient events update local client state; they do not automatically synchronize all connected devices.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-every-client-setting-is-validated-at-startup\u0022\u003EAssuming Every Client Setting Is Validated at Startup\u003C/h3\u003E\n\u003Cp\u003EThe current client registration validates options when they are resolved, rather than explicitly requiring startup validation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-options-quick-reference\u0022\u003E19. Client Options Quick Reference\u003C/h2\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\u003EDescription\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\u003E\u003Ccode\u003ENotSpecified\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EClient environment\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAutoDetectClientProfile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAutomatic 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\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EGlobal return URL\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEndpoints.BasePath\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E/auth\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication endpoint base\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogin.ReturnUrl\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin return URL\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELogin.AllowCredentialPost\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDirect credential posting\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkce.Enabled\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPKCE flow support\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkce.ReturnUrl\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPKCE return URL\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkce.AutoRedirect\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAutomatic PKCE navigation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkce.OnAuthorized\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPKCE authorization callback\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAutoRefresh.Interval\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E5 minutes after post-configuration\u003C/td\u003E\n\u003Ctd\u003EBackground refresh interval\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReauth.Behavior\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERedirect\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReauthentication handling\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EReauth.RedirectPath\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReauthentication redirect\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMultiTenant.Enabled\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant propagation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMultiTenant.Tenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant identifier\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMultiTenant.Transport\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ENone\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant transport\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EStateEvents.HandlingMode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPatch\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EState event handling\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EAutoRefresh.Enabled\u003C/code\u003E should be evaluated with the applicable platform and post-configuration behavior rather than inferred solely from its property initializer.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EConfiguration Overview\u003C/strong\u003E \u2014 Understand the configuration architecture and source precedence.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EServer Options\u003C/strong\u003E \u2014 Configure authentication security, sessions, tokens, and endpoints.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAdvanced Configuration\u003C/strong\u003E \u2014 Explore effective options, mode resolution, and extension points.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClient Authentication\u003C/strong\u003E \u2014 Implement login, PKCE, refresh, and logout flows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClient Session Management\u003C/strong\u003E \u2014 Manage authenticated sessions and revocation.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { - "Id": "what-are-client-options", - "Text": "\uD83E\uDDE0 What Are Client Options?", + "Id": "register-the-ultimateauth-client", + "Text": "1. Register the UltimateAuth Client", "Level": 0 }, { - "Id": "basic-usage", - "Text": "\u2699\uFE0F Basic Usage", + "Id": "core-client-registration", + "Text": "Core Client Registration", + "Level": 1 + }, + { + "Id": "do-not-register-both-client-methods", + "Text": "Do Not Register Both Client Methods", + "Level": 1 + }, + { + "Id": "server-and-client-in-one-application", + "Text": "Server and Client in One Application", + "Level": 1 + }, + { + "Id": "client-options-architecture", + "Text": "2. Client Options Architecture", "Level": 0 }, { - "Id": "client-profile", - "Text": "\uD83E\uDDE9 Client Profile", + "Id": "client-profiles", + "Text": "3. Client Profiles", + "Level": 0 + }, + { + "Id": "automatic-client-profile-detection", + "Text": "4. Automatic Client Profile Detection", + "Level": 0 + }, + { + "Id": "default-configuration", + "Text": "Default Configuration", + "Level": 1 + }, + { + "Id": "manual-configuration", + "Text": "Manual Configuration", + "Level": 1 + }, + { + "Id": "important-validation-rule", + "Text": "Important Validation Rule", + "Level": 1 + }, + { + "Id": "explicit-profiles-take-precedence", + "Text": "Explicit Profiles Take Precedence", + "Level": 1 + }, + { + "Id": "detection-limitations", + "Text": "Detection Limitations", + "Level": 1 + }, + { + "Id": "endpoint-configuration", + "Text": "5. Endpoint Configuration", "Level": 0 }, { - "Id": "endpoints", - "Text": "\uD83C\uDF10 Endpoints", + "Id": "configure-a-remote-authentication-server", + "Text": "Configure a Remote Authentication Server", + "Level": 1 + }, + { + "Id": "default-endpoint-paths", + "Text": "Default Endpoint Paths", + "Level": 1 + }, + { + "Id": "customize-individual-paths", + "Text": "Customize Individual Paths", + "Level": 1 + }, + { + "Id": "required-endpoint-validation", + "Text": "Required Endpoint Validation", + "Level": 1 + }, + { + "Id": "remote-clients-and-browser-security", + "Text": "Remote Clients and Browser Security", + "Level": 1 + }, + { + "Id": "login-flow-options", + "Text": "6. Login Flow Options", "Level": 0 }, + { + "Id": "configure-login-navigation", + "Text": "Configure Login Navigation", + "Level": 1 + }, + { + "Id": "direct-credential-posting", + "Text": "Direct Credential Posting", + "Level": 1 + }, + { + "Id": "trusted-server-hosted-clients", + "Text": "Trusted Server-Hosted Clients", + "Level": 1 + }, { "Id": "pkce-configuration", - "Text": "\uD83D\uDD10 PKCE Configuration", + "Text": "7. PKCE Configuration", + "Level": 0 + }, + { + "Id": "basic-configuration", + "Text": "Basic Configuration", + "Level": 1 + }, + { + "Id": "disable-automatic-redirect", + "Text": "Disable Automatic Redirect", + "Level": 1 + }, + { + "Id": "authorization-callback", + "Text": "Authorization Callback", + "Level": 1 + }, + { + "Id": "client-and-server-pkce-settings-are-different", + "Text": "Client and Server PKCE Settings Are Different", + "Level": 1 + }, + { + "Id": "return-url-configuration", + "Text": "8. Return URL Configuration", + "Level": 0 + }, + { + "Id": "global-default", + "Text": "Global Default", + "Level": 1 + }, + { + "Id": "flow-specific-defaults", + "Text": "Flow-Specific Defaults", + "Level": 1 + }, + { + "Id": "security-considerations", + "Text": "Security Considerations", + "Level": 1 + }, + { + "Id": "automatic-refresh", + "Text": "9. Automatic Refresh", + "Level": 0 + }, + { + "Id": "configure-automatic-refresh", + "Text": "Configure Automatic Refresh", + "Level": 1 + }, + { + "Id": "disable-automatic-refresh", + "Text": "Disable Automatic Refresh", + "Level": 1 + }, + { + "Id": "default-interval", + "Text": "Default Interval", + "Level": 1 + }, + { + "Id": "platform-behavior", + "Text": "Platform Behavior", + "Level": 1 + }, + { + "Id": "automatic-refresh-is-not-a-security-policy", + "Text": "Automatic Refresh Is Not a Security Policy", + "Level": 1 + }, + { + "Id": "automatic-refresh-does-not-guarantee-session-continuity", + "Text": "Automatic Refresh Does Not Guarantee Session Continuity", + "Level": 1 + }, + { + "Id": "coordinator-lifecycle", + "Text": "Coordinator Lifecycle", + "Level": 1 + }, + { + "Id": "reauthentication-options", + "Text": "10. Reauthentication Options", + "Level": 0 + }, + { + "Id": "available-behaviors", + "Text": "Available Behaviors", + "Level": 1 + }, + { + "Id": "redirect", + "Text": "Redirect", + "Level": 1 + }, + { + "Id": "raise-an-event", + "Text": "Raise an Event", + "Level": 1 + }, + { + "Id": "no-automatic-action", + "Text": "No Automatic Action", + "Level": 1 + }, + { + "Id": "important", + "Text": "Important", + "Level": 1 + }, + { + "Id": "multi-tenant-client-configuration", + "Text": "11. Multi-Tenant Client Configuration", + "Level": 0 + }, + { + "Id": "available-properties", + "Text": "Available Properties", + "Level": 1 + }, + { + "Id": "available-transports", + "Text": "Available Transports", + "Level": 1 + }, + { + "Id": "header-based-tenant-propagation", + "Text": "Header-Based Tenant Propagation", + "Level": 1 + }, + { + "Id": "route-based-tenant-propagation", + "Text": "Route-Based Tenant Propagation", + "Level": 1 + }, + { + "Id": "security-boundary", + "Text": "Security Boundary", + "Level": 1 + }, + { + "Id": "authentication-state-events", + "Text": "12. Authentication State Events", + "Level": 0 + }, + { + "Id": "available-modes", + "Text": "Available Modes", + "Level": 1 + }, + { + "Id": "patch", + "Text": "Patch", + "Level": 1 + }, + { + "Id": "validate", + "Text": "Validate", + "Level": 1 + }, + { + "Id": "none", + "Text": "None", + "Level": 1 + }, + { + "Id": "local-events-are-not-distributed-notifications", + "Text": "Local Events Are Not Distributed Notifications", + "Level": 1 + }, + { + "Id": "configuration-from-appsettings.json", + "Text": "13. Configuration from appsettings.json", "Level": 0 }, { - "Id": "auto-refresh", - "Text": "\uD83D\uDD01 Auto Refresh", + "Id": "configuration-precedence", + "Text": "Configuration Precedence", + "Level": 1 + }, + { + "Id": "environment-variables", + "Text": "Environment Variables", + "Level": 1 + }, + { + "Id": "client-options-validation", + "Text": "14. Client Options Validation", "Level": 0 }, { - "Id": "re-authentication", - "Text": "\uD83D\uDD04 Re-authentication", + "Id": "client-profile-validation", + "Text": "Client Profile Validation", + "Level": 1 + }, + { + "Id": "endpoint-validation", + "Text": "Endpoint Validation", + "Level": 1 + }, + { + "Id": "validation-timing", + "Text": "Validation Timing", + "Level": 1 + }, + { + "Id": "validation-scope", + "Text": "Validation Scope", + "Level": 1 + }, + { + "Id": "complete-blazor-webassembly-example", + "Text": "15. Complete Blazor WebAssembly Example", + "Level": 0 + }, + { + "Id": "complete-blazor-server-example", + "Text": "16. Complete Blazor Server Example", "Level": 0 }, { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", + "Id": "server-and-client-configuration-together", + "Text": "17. Server and Client Configuration Together", "Level": 0 }, { - "Id": "key-takeaways", - "Text": "\uD83D\uDCCC Key Takeaways", + "Id": "common-configuration-mistakes", + "Text": "18. Common Configuration Mistakes", + "Level": 0 + }, + { + "Id": "registering-the-core-client-and-blazor-adapter-separately", + "Text": "Registering the Core Client and Blazor Adapter Separately", + "Level": 1 + }, + { + "Id": "disabling-profile-detection-without-selecting-a-profile", + "Text": "Disabling Profile Detection Without Selecting a Profile", + "Level": 1 + }, + { + "Id": "relying-on-automatic-detection-in-an-ambiguous-hosting-environment", + "Text": "Relying on Automatic Detection in an Ambiguous Hosting Environment", + "Level": 1 + }, + { + "Id": "enabling-direct-credential-posting-in-a-public-client", + "Text": "Enabling Direct Credential Posting in a Public Client", + "Level": 1 + }, + { + "Id": "assuming-client-auto-refresh-extends-server-lifetimes", + "Text": "Assuming Client Auto-Refresh Extends Server Lifetimes", + "Level": 1 + }, + { + "Id": "assuming-auto-refresh-starts-automatically", + "Text": "Assuming Auto-Refresh Starts Automatically", + "Level": 1 + }, + { + "Id": "configuring-only-the-client-endpoint", + "Text": "Configuring Only the Client Endpoint", + "Level": 1 + }, + { + "Id": "treating-tenant-propagation-as-authorization", + "Text": "Treating Tenant Propagation as Authorization", + "Level": 1 + }, + { + "Id": "using-state-events-as-distributed-notifications", + "Text": "Using State Events as Distributed Notifications", + "Level": 1 + }, + { + "Id": "assuming-every-client-setting-is-validated-at-startup", + "Text": "Assuming Every Client Setting Is Validated at Startup", + "Level": 1 + }, + { + "Id": "client-options-quick-reference", + "Text": "19. Client Options Quick Reference", "Level": 0 }, { - "Id": "next-step", - "Text": "\u27A1\uFE0F Next Step", + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json index e0768834..0ae66497 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json @@ -1,6 +1,6 @@ { "Slug": "configuration/index", - "Title": "Configuration", + "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", "Headings": [ { diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/server-options.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/server-options.json index bccfc60a..62cc23a3 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/server-options.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/server-options.json @@ -1,76 +1,541 @@ { "Slug": "configuration/server-options", "Title": "Server Options", - "Html": "\n\u003Cp\u003EUltimateAuth is configured primarily through \u003Cstrong\u003EServer Options\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 This is the main entry point for configuring authentication behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-are-server-options\u0022\u003E\uD83E\uDDE0 What Are Server Options?\u003C/h2\u003E\n\u003Cp\u003EServer options define how UltimateAuth behaves \u003Cstrong\u003Einside your application\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThey control:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication behavior\u003C/li\u003E\n\u003Cli\u003ESecurity policies\u003C/li\u003E\n\u003Cli\u003EToken issuance\u003C/li\u003E\n\u003Cli\u003ESession lifecycle\u003C/li\u003E\n\u003Cli\u003EEndpoint exposure\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic-usage\u0022\u003E\u2699\uFE0F Basic Usage\u003C/h2\u003E\n\u003Cp\u003EYou configure server options in \u003Ccode\u003EProgram.cs\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\u003Ebuilder.Services.AddUltimateAuthServer(o =\u0026gt;\n{\n o.Login.MaxFailedAttempts = 5;\n o.Session.IdleTimeout = TimeSpan.FromDays(7);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Chr\u003E\n\u003Cp\u003EYou can also use \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;IdleTimeout\u0026quot;: \u0026quot;07.00.00.00\u0026quot;\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003Eappsettings.json\u003C/code\u003E overrides \u003Ccode\u003EProgram.cs\u003C/code\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-composition\u0022\u003E\uD83E\uDDE9 Core Composition\u003C/h2\u003E\n\u003Cp\u003EServer options include Core behavior:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogin\u003C/li\u003E\n\u003Cli\u003ESession\u003C/li\u003E\n\u003Cli\u003EToken\u003C/li\u003E\n\u003Cli\u003EPKCE\u003C/li\u003E\n\u003Cli\u003EMulti-tenancy\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 These are defined in Core\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 But configured via Server\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-you-dont-configure-modes-directly\u0022\u003E\u26A0\uFE0F Important: You Don\u2019t Configure Modes Directly\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth does NOT expect you to select a single auth mode.\u003C/p\u003E\n\u003Cp\u003EInstead:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Mode is resolved at runtime\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022allowed-modes-guardrail\u0022\u003E\uD83D\uDEE1 Allowed Modes (Guardrail)\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.AllowedModes = new[]\n{\n UAuthMode.Hybrid,\n UAuthMode.PureOpaque\n};\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 This does NOT select a mode\u003Cbr\u003E\n\uD83D\uDC49 It restricts which modes are allowed\u003C/p\u003E\n\u003Cp\u003EIf a resolved mode is not allowed:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Request fails early\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-behavior-effective-options\u0022\u003E\u26A1 Runtime Behavior (Effective Options)\u003C/h2\u003E\n\u003Cp\u003EServer options are not used directly.\u003C/p\u003E\n\u003Cp\u003EThey are transformed into:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 \u003Ccode\u003EEffectiveUAuthServerOptions\u003C/code\u003E\u003C/p\u003E\n\u003Cp\u003EThis happens per request:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMode is resolved\u003C/li\u003E\n\u003Cli\u003EDefaults are applied\u003C/li\u003E\n\u003Cli\u003EOverrides are applied\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 What actually runs is \u003Cstrong\u003EEffective Options\u003C/strong\u003E\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-based-defaults\u0022\u003E\uD83D\uDD04 Mode-Based Defaults\u003C/h2\u003E\n\u003Cp\u003EEach auth mode applies different defaults automatically:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPureOpaque \u2192 session-heavy\u003C/li\u003E\n\u003Cli\u003EHybrid \u2192 session \u002B token\u003C/li\u003E\n\u003Cli\u003EPureJwt \u2192 token-only\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You don\u2019t need to manually configure everything\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoint-control\u0022\u003E\uD83C\uDF9B Endpoint Control\u003C/h2\u003E\n\u003Cp\u003EYou can control which features are enabled:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Endpoints.Authentication = true;\no.Endpoints.Session = true;\no.Endpoints.Authorization = true;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EYou can also disable specific actions:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.Endpoints.DisabledActions.Add(\u0026quot;UAuthActions.Users.Create.Anonymous\u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Useful for API hardening\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022cookie-transport-behavior\u0022\u003E\uD83C\uDF6A Cookie \u0026amp; Transport Behavior\u003C/h2\u003E\n\u003Cp\u003EServer options define how credentials are transported:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECookies\u003C/li\u003E\n\u003Cli\u003EHeaders\u003C/li\u003E\n\u003Cli\u003ETokens\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Unsafe combinations are rejected at startup\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022hub-configuration\u0022\u003E\uD83C\uDF10 Hub Configuration\u003C/h2\u003E\n\u003Cp\u003EIf using UAuthHub:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eo.HubDeploymentMode = UAuthHubDeploymentMode.Integrated;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\uD83D\uDC49 Defines how auth server is deployed\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-resolution\u0022\u003E\uD83D\uDD01 Session Resolution\u003C/h2\u003E\n\u003Cp\u003EControls how session IDs are extracted:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECookie\u003C/li\u003E\n\u003Cli\u003EHeader\u003C/li\u003E\n\u003Cli\u003EBearer\u003C/li\u003E\n\u003Cli\u003EQuery\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 Fully configurable\u003C/p\u003E\n\u003Cbr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mental-model\u0022\u003E\uD83E\uDDE0 Mental Model\u003C/h2\u003E\n\u003Cp\u003EIf you remember one thing:\u003C/p\u003E\n\u003Cp\u003E\uD83D\uDC49 Server options define \u003Cstrong\u003Ewhat is allowed\u003C/strong\u003E\u003Cbr\u003E\n\uD83D\uDC49 Runtime determines \u003Cstrong\u003Ewhat is used\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022key-takeaways\u0022\u003E\uD83D\uDCCC Key Takeaways\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003EServer options are the main configuration entry\u003C/li\u003E\n\u003Cli\u003ECore behavior is configured via server\u003C/li\u003E\n\u003Cli\u003EModes are not selected manually\u003C/li\u003E\n\u003Cli\u003EEffective options are computed per request\u003C/li\u003E\n\u003Cli\u003ESecurity is enforced by design\u003C/li\u003E\n\u003C/ul\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003E\u27A1\uFE0F Next Step\u003C/h2\u003E\n\u003Cp\u003EContinue to \u003Cstrong\u003EClient Options\u003C/strong\u003E\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E is the primary configuration surface for UltimateAuth Server.\u003C/p\u003E\n\u003Cp\u003EIt controls authentication behavior, session lifecycles, token issuance, credential security, tenant resolution, endpoint exposure, and integration with UAuthHub.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth composes its Core-defined options directly into \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E. When using UltimateAuth Server, you configure both shared authentication behavior and server-specific features through the same options object.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the available configuration groups, their defaults, practical examples, and the distinction between configured and effective runtime behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022register-ultimateauth-server\u0022\u003E1. Register UltimateAuth Server\u003C/h2\u003E\n\u003Cp\u003EConfigure the server through \u003Ccode\u003EAddUltimateAuthServer()\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Login.MaxFailedAttempts = 5;\n options.Session.Lifetime = TimeSpan.FromDays(7);\n options.Token.AccessTokenLifetime = TimeSpan.FromMinutes(10);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe configuration delegate receives:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server registration also supports an optional policy 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\u003Ebuilder.Services.AddUltimateAuthServer(\n configure: options =\u0026gt;\n {\n options.Login.MaxFailedAttempts = 5;\n },\n configurePolicies: policies =\u0026gt;\n {\n // Register application access policies.\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server automatically registers its Core infrastructure. You do not need to call \u003Ccode\u003EAddUltimateAuth()\u003C/code\u003E separately.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-from-appsettings.json\u0022\u003EConfiguration from appsettings.json\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth Server binds the configuration section:\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:\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 \u0026quot;LockoutDuration\u0026quot;: \u0026quot;00:15:00\u0026quot;\n },\n \u0026quot;Session\u0026quot;: {\n \u0026quot;Lifetime\u0026quot;: \u0026quot;7.00:00:00\u0026quot;\n },\n \u0026quot;Token\u0026quot;: {\n \u0026quot;AccessTokenLifetime\u0026quot;: \u0026quot;00:10:00\u0026quot;\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EASP.NET Core represents \u003Ccode\u003ETimeSpan\u003C/code\u003E values using strings such as \u003Ccode\u003E00:15:00\u003C/code\u003E for fifteen minutes and \u003Ccode\u003E7.00:00:00\u003C/code\u003E for seven days.\u003C/p\u003E\n\u003Cp\u003EThe configuration section is bound after the registration delegate, so configuration provider values take precedence for matching properties.\u003C/p\u003E\n\u003Cp\u003EFor configuration source ordering and environment-specific overrides, see \u003Ca href=\u0022/docs/configuration/configuration\u0022\u003EConfiguration Overview\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-options-architecture\u0022\u003E2. Server Options Architecture\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E contains two categories of configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-defined-options\u0022\u003ECore-defined options\u003C/h3\u003E\n\u003Cp\u003EThese types are defined in UltimateAuth Core and composed into the server options:\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\u003EPurpose\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\u003E\u003Ccode\u003EUAuthLoginOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin attempts and lockout behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESession\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthSessionOptions\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\u003E\u003Ccode\u003EUAuthTokenOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAccess and refresh token behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkce\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthPkceOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPKCE authorization code lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMultiTenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthMultiTenantOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant identification and resolution\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPagination\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthPaginationOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPagination defaults and limits\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEvents\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthEvents\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication lifecycle callbacks\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=\u0022server-specific-options\u0022\u003EServer-specific options\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\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAllowedModes\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERestrict 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\u003EDescribe the relationship between UAuthHub and the application\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHub\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EHub integration and flow settings\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECookie\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECookie names, security attributes, and lifetime behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessionResolution\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession identifier sources and precedence\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEndpoints\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEndpoint groups and disabled actions\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 selection for UI and API requests\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAuthResponse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication credential delivery and redirect behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialSessionPolicy\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession invalidation after credential changes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResetCredential\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential recovery code and token settings\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiers\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier behavior, normalization, and uniqueness\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifierValidation\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUsername, email, and phone validation\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 allowed 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 capabilities\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENavigation\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication navigation behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDiagnostics\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional diagnostic response details\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EAdvanced configuration hooks are also available, including \u003Ccode\u003EConfigureMode()\u003C/code\u003E and \u003Ccode\u003EOnConfigureEndpoints\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022login-options\u0022\u003E3. Login Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Login\u003C/code\u003E controls login attempt limits, lockout behavior, and related security 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.Login.LockoutDuration = TimeSpan.FromMinutes(15);\n options.Login.FailureWindow = TimeSpan.FromMinutes(15);\n options.Login.ExtendLockOnFailure = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022available-properties\u0022\u003EAvailable Properties\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\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\u003EMaxFailedAttempts\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E10\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EFailed login threshold; \u003Ccode\u003E0\u003C/code\u003E disables lockout\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELockoutDuration\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E15 minutes\u003C/td\u003E\n\u003Ctd\u003ELockout duration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EFailureWindow\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E15 minutes\u003C/td\u003E\n\u003Ctd\u003EWindow for counting failed attempts\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EExtendLockOnFailure\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EExtend lockout when another attempt fails during lockout\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIncludeFailureDetails\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EInclude detailed authentication failure information\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETryLoginDuration\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E180 seconds\u003C/td\u003E\n\u003Ctd\u003EDuration associated with the try-login flow\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=\u0022example-restrict-login-failure-details\u0022\u003EExample: Restrict Login Failure Details\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.IncludeFailureDetails = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis setting is useful when an application does not want to expose detailed lockout information to callers.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important\u0022\u003EImportant\u003C/h3\u003E\n\u003Cp\u003ELockout configuration is not equivalent to complete network-level rate limiting.\u003C/p\u003E\n\u003Cp\u003EProduction deployments should also consider infrastructure protections against abusive traffic and distributed login attempts.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-options\u0022\u003E4. Session Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Session\u003C/code\u003E controls session lifetime, expiration, sliding behavior, and session-chain limits.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.FromDays(7);\n options.Session.SlidingExpiration = true;\n options.Session.IdleTimeout = TimeSpan.FromDays(3);\n options.Session.MaxLifetime = TimeSpan.FromDays(30);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-lifetime-properties\u0022\u003ESession Lifetime Properties\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\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\u003ELifetime\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E7 days\u003C/td\u003E\n\u003Ctd\u003EStandard session lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMaxLifetime\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional absolute lifetime limit\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESlidingExpiration\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAllow eligible activity or refresh operations to extend expiration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdleTimeout\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional inactivity timeout\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ETouchInterval\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E5 minutes\u003C/td\u003E\n\u003Ctd\u003EMinimum interval between last-seen updates\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese settings have different purposes.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003ELifetime\u003C/code\u003E determines the standard lifetime assigned to a session.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EMaxLifetime\u003C/code\u003E establishes an optional hard boundary that cannot be extended indefinitely.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EIdleTimeout\u003C/code\u003E limits how long a session can remain inactive.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003ETouchInterval\u003C/code\u003E reduces unnecessary persistence operations caused by frequent activity updates.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-chain-properties\u0022\u003ESession-Chain Properties\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\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\u003EMaxChainsPerUser\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E0\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReserved user-level chain limit\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMaxSessionsPerChain\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E100\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession-chain rotation limit\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMaxChainsPerPlatform\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReserved platform-specific chain limits\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPlatformCategories\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReserved platform group definitions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMaxChainsPerCategory\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReserved category-specific chain limits\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEnableIpBinding\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReserved IP binding setting\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEnableUserAgentBinding\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReserved User-Agent binding setting\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDeviceMismatchBehavior\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EReject\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReserved device mismatch behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003ECurrent implementation limitation:\u003C/strong\u003E The source identifies several device limits and binding settings as reserved for future enforcement. Setting these properties must not be treated as proof that the corresponding protection is active.\u003C/p\u003E\n\u003Cp\u003EFor example, do not rely on:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.MaxChainsPerUser = 1;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eas a production guarantee that only one device chain can exist unless the current runtime implementation has been verified to enforce that restriction.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-specific-session-defaults\u0022\u003EMode-Specific Session Defaults\u003C/h3\u003E\n\u003Cp\u003ESession settings can also be affected by authentication mode defaults.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EPureOpaque\u003C/code\u003E supplies a longer idle timeout and touch interval when those values have not been configured.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EHybrid\u003C/code\u003E explicitly sets \u003Ccode\u003ETouchInterval\u003C/code\u003E to \u003Ccode\u003Enull\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/configuration/advanced-configuration\u0022\u003EAdvanced Configuration\u003C/a\u003E for the effective-options resolution order.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-options\u0022\u003E5. Token Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Token\u003C/code\u003E configures access and refresh token 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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Token.Issuer = \u0026quot;MyApplication\u0026quot;;\n options.Token.Audience = \u0026quot;MyApplicationApi\u0026quot;;\n options.Token.AccessTokenLifetime = TimeSpan.FromMinutes(10);\n options.Token.RefreshTokenLifetime = TimeSpan.FromDays(7);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022available-properties-1\u0022\u003EAvailable Properties\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\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\u003EIssueJwt\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEnable JWT issuance where supported by the effective mode\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIssueOpaque\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEnable opaque token issuance where supported\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIssueRefresh\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEnable refresh token issuance where supported\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAccessTokenLifetime\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E10 minutes\u003C/td\u003E\n\u003Ctd\u003EAccess token lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERefreshTokenLifetime\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E7 days\u003C/td\u003E\n\u003Ctd\u003ERefresh token lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOpaqueIdBytes\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E32\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERandom byte length used for opaque token identifiers\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERefreshTokenConcurrentRequestWindow\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E2 seconds\u003C/td\u003E\n\u003Ctd\u003EConcurrent duplicate refresh classification window\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIssuer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuth\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT issuer\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAudience\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthClient\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT audience\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAddJwtIdClaim\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EInclude a JWT identifier claim\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EKeyId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional signing key 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=\u0022refresh-token-concurrency-window\u0022\u003ERefresh Token Concurrency Window\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.Token.RefreshTokenConcurrentRequestWindow = TimeSpan.FromSeconds(2);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis option controls how the server distinguishes a closely timed duplicate refresh request from a confirmed replay.\u003C/p\u003E\n\u003Cp\u003EA duplicate refresh request is still rejected.\u003C/p\u003E\n\u003Cp\u003EThe window affects the replay-response classification, including whether the token family should be revoked.\u003C/p\u003E\n\u003Cp\u003EIt does \u003Cstrong\u003Enot\u003C/strong\u003E allow the same consumed refresh token to be used successfully more than once.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-defaults-may-override-token-values\u0022\u003EMode Defaults May Override Token Values\u003C/h3\u003E\n\u003Cp\u003EThe effective \u003Ccode\u003EHybrid\u003C/code\u003E configuration explicitly sets:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EToken.AccessTokenLifetime = TimeSpan.FromMinutes(10);\nToken.RefreshTokenLifetime = TimeSpan.FromDays(7);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ETherefore, a different value configured globally may be replaced when the effective mode is resolved.\u003C/p\u003E\n\u003Cp\u003ETo customize the lifetime for \u003Ccode\u003EHybrid\u003C/code\u003E, use:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.ConfigureMode(UAuthMode.Hybrid, mode =\u0026gt;\n {\n mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(5);\n });\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis distinction is essential when configuring token lifetimes.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pkce-options\u0022\u003E6. PKCE Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Pkce\u003C/code\u003E configures the lifetime of PKCE authorization 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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Pkce.AuthorizationCodeLifetimeSeconds = 120;\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\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\u003EAuthorizationCodeLifetimeSeconds\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E120\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELifetime of a PKCE authorization code\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EPKCE is particularly important for browser-based clients such as Blazor WebAssembly.\u003C/p\u003E\n\u003Cp\u003EA shorter authorization code lifetime reduces the period during which an intercepted or exposed code could potentially be used.\u003C/p\u003E\n\u003Cp\u003EHowever, the value must remain long enough for legitimate authorization exchanges to complete.\u003C/p\u003E\n\u003Cp\u003EThe server\u0027s PKCE configuration is separate from the client\u0027s PKCE flow settings.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-modes\u0022\u003E7. Authentication Modes\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports authentication mode resolution based on the client profile.\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 property restricts the modes the server is permitted to use.\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\u003E\u003Ccode\u003EAllowedModes\u003C/code\u003E is a restriction, not a mode selector.\u003C/p\u003E\n\u003Cp\u003EThe effective mode is resolved by \u003Ccode\u003EIEffectiveAuthModeResolver\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022default-mode-resolution\u0022\u003EDefault Mode Resolution\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EClient profile\u003C/th\u003E\n\u003Cth\u003EDefault resolved mode\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EWebServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorWasm\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMaui\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EApi\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureJwt\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe current implementation has mode templates for \u003Ccode\u003EPureOpaque\u003C/code\u003E, \u003Ccode\u003EHybrid\u003C/code\u003E, \u003Ccode\u003ESemiHybrid\u003C/code\u003E, and \u003Ccode\u003EPureJwt\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Ccode\u003ESemiHybrid\u003C/code\u003E and \u003Ccode\u003EPureJwt\u003C/code\u003E are not yet accepted as enabled modes by the current server options validator.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not enable an unsupported mode merely because it exists in the enum or has a mode-default template.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThe default \u003Ccode\u003EApi\u003C/code\u003E profile mapping to \u003Ccode\u003EPureJwt\u003C/code\u003E is a current compatibility limitation to consider when deploying API-profile authentication flows.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-mode-specific-behavior\u0022\u003EConfigure Mode-Specific Behavior\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EConfigureMode()\u003C/code\u003E for behavior that should differ by 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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.ConfigureMode(UAuthMode.PureOpaque, mode =\u0026gt;\n {\n mode.Session.IdleTimeout = TimeSpan.FromDays(7);\n });\n\n options.ConfigureMode(UAuthMode.Hybrid, mode =\u0026gt;\n {\n mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(5);\n });\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe mode override runs after the built-in mode defaults.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/configuration/advanced-configuration\u0022\u003EAdvanced Configuration\u003C/a\u003E for details.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoint-options\u0022\u003E8. Endpoint Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Endpoints\u003C/code\u003E controls endpoint paths, feature groups, and individual action restrictions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022base-path\u0022\u003EBase Path\u003C/h3\u003E\n\u003Cp\u003EThe default server endpoint base path 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\u003E/auth\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EConfigure 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.Endpoints.BasePath = \u0026quot;/auth\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EChanging the base path can help avoid route conflicts when integrating UltimateAuth into an existing application.\u003C/p\u003E\n\u003Cp\u003EThe client must use compatible endpoint paths.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoint-groups\u0022\u003EEndpoint 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\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\u003EAuthentication\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkce\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPKCE endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESession\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession management endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserLifecycle\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser lifecycle endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserProfile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser profile endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserIdentifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier management endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentials\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential management endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAuthorization\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthorization endpoints\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Endpoints.Authentication = true;\n options.Endpoints.Session = true;\n options.Endpoints.Authorization = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDisabling an endpoint group controls endpoint exposure. It does not automatically remove the underlying domain services or invalidate previously issued credentials.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022disable-individual-actions\u0022\u003EDisable Individual Actions\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EDisabledActions\u003C/code\u003E is a collection of action identifiers:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.DisabledActions.Add(\u0026quot;users.create.anonymous\u0026quot;);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe string must match the action identifier used by the server\u0027s endpoint registration and action policy.\u003C/p\u003E\n\u003Cp\u003EDo not pass a C# constant name as a string unless that exact text is also the registered action value.\u003C/p\u003E\n\u003Cp\u003EFor example, these are not necessarily equivalent:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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\u0026quot;UAuthActions.Users.Create.Anonymous\u0026quot;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eand:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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\u0026quot;users.create.anonymous\u0026quot;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse the actual action value defined by the corresponding \u003Ccode\u003EUAuthActions\u003C/code\u003E constant.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoint-hardening\u0022\u003EEndpoint Hardening\u003C/h3\u003E\n\u003Cp\u003EDisable endpoints that are not required by the application.\u003C/p\u003E\n\u003Cp\u003EFor example, an application that delegates user provisioning to administrators may choose to disable anonymous user creation.\u003C/p\u003E\n\u003Cp\u003EEndpoint exposure is a separate concern from authorization: an exposed endpoint must still enforce the applicable security policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-resolution\u0022\u003E9. Session Resolution\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.SessionResolution\u003C/code\u003E controls how incoming requests provide session identifiers.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports four resolution sources:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EBearer credentials.\u003C/li\u003E\n\u003Cli\u003ECustom headers.\u003C/li\u003E\n\u003Cli\u003ECookies.\u003C/li\u003E\n\u003Cli\u003EQuery parameters.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022default-configuration\u0022\u003EDefault Configuration\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\u003EDefault\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEnableBearer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEnableHeader\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEnableCookie\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEnableQuery\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHeaderName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EX-UAuth-Session\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EQueryParameterName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Esession_id\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe default precedence order 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\u003EBearer\nHeader\nCookie\nQuery\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-resolution-sources\u0022\u003EConfigure Resolution Sources\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.SessionResolution.EnableBearer = true;\n options.SessionResolution.EnableHeader = false;\n options.SessionResolution.EnableCookie = true;\n options.SessionResolution.EnableQuery = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example disables custom-header and query-string session resolution.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-resolution-order\u0022\u003EChange Resolution Order\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.SessionResolution.Order =\n [\n \u0026quot;Cookie\u0026quot;,\n \u0026quot;Bearer\u0026quot;,\n \u0026quot;Header\u0026quot;,\n \u0026quot;Query\u0026quot;\n ];\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EResolution order matters when a request contains multiple candidate credentials.\u003C/p\u003E\n\u003Cp\u003EThe selected credential must still pass authentication and session validation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-recommendation\u0022\u003ESecurity Recommendation\u003C/h3\u003E\n\u003Cp\u003EAvoid query-string credentials unless the application has a specific, well-understood requirement.\u003C/p\u003E\n\u003Cp\u003EURLs may be recorded in browser history, reverse-proxy logs, analytics systems, and other infrastructure.\u003C/p\u003E\n\u003Cp\u003EFor most browser and API integrations, cookies or authorization headers are preferable.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022cookie-configuration\u0022\u003E10. Cookie Configuration\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Cookie\u003C/code\u003E controls cookies used for authentication credentials.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth defines three cookie 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\u003Eoptions.Cookie.Session\noptions.Cookie.AccessToken\noptions.Cookie.RefreshToken\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022default-cookie-names\u0022\u003EDefault Cookie Names\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECookie\u003C/th\u003E\n\u003Cth\u003EDefault name\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Euas\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAccess token\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Euat\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERefresh token\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Euar\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe default cookie options use:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EHttpOnly = true\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ESecurePolicy = CookieSecurePolicy.Always\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EPath = \u0026quot;/\u0026quot;\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EDomain = null\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EMaxAge = null\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\u003Ccode\u003ESameSite\u003C/code\u003E is nullable and can be resolved through the applicable cookie and deployment policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022customize-cookie-names\u0022\u003ECustomize Cookie Names\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.Cookie.Session.Name = \u0026quot;myapp_session\u0026quot;;\n options.Cookie.RefreshToken.Name = \u0026quot;myapp_refresh\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-cookie-scope\u0022\u003EConfigure Cookie Scope\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.Cookie.Session.Path = \u0026quot;/\u0026quot;;\n options.Cookie.Session.Domain = null;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA null domain produces a host-only cookie.\u003C/p\u003E\n\u003Cp\u003ESetting a parent domain can broaden cookie availability to subdomains and should be done only when necessary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022cookie-security\u0022\u003ECookie Security\u003C/h3\u003E\n\u003Cp\u003EAvoid disabling \u003Ccode\u003EHttpOnly\u003C/code\u003E or relaxing secure transport requirements for authentication cookies.\u003C/p\u003E\n\u003Cp\u003ECookie settings interact with:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe deployment mode.\u003C/li\u003E\n\u003Cli\u003ECross-site requests.\u003C/li\u003E\n\u003Cli\u003EBrowser cookie policies.\u003C/li\u003E\n\u003Cli\u003ECredential delivery configuration.\u003C/li\u003E\n\u003Cli\u003EHTTPS requirements.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor cross-site deployments, configure cookie behavior together with CORS and the relevant browser security requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022hub-deployment-mode\u0022\u003E11. Hub Deployment Mode\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.HubDeploymentMode\u003C/code\u003E describes how UAuthHub is deployed relative to the application.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.HubDeploymentMode = UAuthHubDeploymentMode.Embedded;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022available-modes\u0022\u003EAvailable Modes\u003C/h3\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\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEmbedded\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EHub and application share the same origin\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIntegrated\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EHub is separate but within the same-site boundary\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EExternal\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EHub is hosted on a different site or domain\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe default 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\u003EUAuthHubDeploymentMode.Integrated\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022embedded\u0022\u003EEmbedded\u003C/h3\u003E\n\u003Cp\u003ESuitable for applications hosting UltimateAuth within the same application and origin.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.HubDeploymentMode = UAuthHubDeploymentMode.Embedded;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022integrated\u0022\u003EIntegrated\u003C/h3\u003E\n\u003Cp\u003ESuitable for deployments such 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\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\u003Eauth.example.com\napp.example.com\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Ewhere the authentication service and application are separate origins within the same site.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022external\u0022\u003EExternal\u003C/h3\u003E\n\u003Cp\u003ESuitable for a separate authentication provider domain.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.HubDeploymentMode = UAuthHubDeploymentMode.External;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EExternal deployment requires careful coordination of cookie policies, redirects, allowed origins, and browser restrictions.\u003C/p\u003E\n\u003Cp\u003EThe deployment mode influences authentication transport behavior; it is not merely descriptive metadata.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022hub-options\u0022\u003E12. Hub Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Hub\u003C/code\u003E configures UAuthHub integration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022available-properties-2\u0022\u003EAvailable Properties\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\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\u003EClientBaseAddress\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional client application base address\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAllowedClientOrigins\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEmpty set\u003C/td\u003E\n\u003Ctd\u003EAllowed client origins\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EFlowLifetime\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E5 minutes\u003C/td\u003E\n\u003Ctd\u003ELifetime of Hub flow artifacts\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELoginPath\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E/login\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EHub login page path\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=\u0022example\u0022\u003EExample\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.Hub.ClientBaseAddress = \u0026quot;https://app.example.com\u0026quot;;\n options.Hub.AllowedClientOrigins.Add(\u0026quot;https://app.example.com\u0026quot;);\n options.Hub.FlowLifetime = TimeSpan.FromMinutes(5);\n options.Hub.LoginPath = \u0026quot;/login\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe Hub flow lifetime is not the same as the authenticated session lifetime.\u003C/p\u003E\n\u003Cp\u003EHub artifacts support authentication orchestration and should generally be short-lived.\u003C/p\u003E\n\u003Cp\u003EOnly trusted origins should be included in \u003Ccode\u003EAllowedClientOrigins\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022primary-credential-policy\u0022\u003E13. Primary Credential Policy\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.PrimaryCredential\u003C/code\u003E controls which credential kind is preferred for UI and API requests.\u003C/p\u003E\n\u003Cp\u003EThe defaults are:\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\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUi\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryTokenKind.Session\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EApi\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryTokenKind.AccessToken\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=\u0022example-1\u0022\u003EExample\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.PrimaryCredential.Ui = PrimaryTokenKind.Session;\n options.PrimaryCredential.Api = PrimaryTokenKind.AccessToken;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows UltimateAuth to distinguish between session-oriented browser interactions and access-token-oriented API interactions.\u003C/p\u003E\n\u003Cp\u003EThe primary credential policy does not bypass authentication validation.\u003C/p\u003E\n\u003Cp\u003EIt determines which credential kind should be preferred within the applicable request-processing rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-session-policy\u0022\u003E14. Credential Session Policy\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.CredentialSessionPolicy\u003C/code\u003E determines what happens to active session chains after credential changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022available-properties-3\u0022\u003EAvailable Properties\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\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\u003EUser changes their own credential\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\u003EAdministrator changes a user\u0027s credential\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\u003ECredential recovery completes\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=\u0022example-2\u0022\u003EExample\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\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022available-behaviors\u0022\u003EAvailable Behaviors\u003C/h3\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\u003EEffect\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\u003EKeep 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\u003EKeep the current chain and revoke other applicable 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\u003Cp\u003EFor recovery flows, \u003Ccode\u003EPreserveCurrent\u003C/code\u003E is not always meaningful because the user may not have an authenticated current session.\u003C/p\u003E\n\u003Cp\u003EThe current implementation treats this situation using the more restrictive \u003Ccode\u003ERevokeAll\u003C/code\u003E behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-recommendation-1\u0022\u003ESecurity Recommendation\u003C/h3\u003E\n\u003Cp\u003EPreserving the current session after a normal password change can provide a reasonable user experience.\u003C/p\u003E\n\u003Cp\u003ERevoking all sessions after administrative credential changes or account recovery is generally more conservative.\u003C/p\u003E\n\u003Cp\u003EChoose the policy according to the application\u0027s security requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-reset-options\u0022\u003E15. Credential Reset Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.ResetCredential\u003C/code\u003E configures credential recovery proof lifetimes and attempt limits.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022defaults\u0022\u003EDefaults\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\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\u003ETokenValidity\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E30 minutes\u003C/td\u003E\n\u003Ctd\u003EReset token lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECodeValidity\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E10 minutes\u003C/td\u003E\n\u003Ctd\u003ENumeric reset code lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMaxAttempts\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E3\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EMaximum permitted recovery attempts\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECodeLength\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E6\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENumeric reset code length\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=\u0022example-3\u0022\u003EExample\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.ResetCredential.TokenValidity = TimeSpan.FromMinutes(20);\n options.ResetCredential.CodeValidity = TimeSpan.FromMinutes(5);\n options.ResetCredential.MaxAttempts = 3;\n options.ResetCredential.CodeLength = 6;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeLength\u003C/code\u003E applies to numeric reset codes, not token-based recovery proofs.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-security-boundary\u0022\u003EImportant Security Boundary\u003C/h3\u003E\n\u003Cp\u003EReset proofs must be delivered through a trusted server-side mechanism.\u003C/p\u003E\n\u003Cp\u003EDo not return recovery tokens to anonymous callers simply to simplify a client-side reset form.\u003C/p\u003E\n\u003Cp\u003EThe application should provide the appropriate \u003Ccode\u003EICredentialResetNotifier\u003C/code\u003E implementation for supported recovery flows.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022login-identifier-options\u0022\u003E16. Login Identifier Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.LoginIdentifiers\u003C/code\u003E determines which identifier types can be used for login.\u003C/p\u003E\n\u003Cp\u003EThe default allowed 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\u003EUserIdentifierType.Username\nUserIdentifierType.Email\nUserIdentifierType.Phone\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022restrict-login-identifier-types\u0022\u003ERestrict Login Identifier 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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.LoginIdentifiers.AllowedTypes =\n new HashSet\u0026lt;UserIdentifierType\u0026gt;\n {\n UserIdentifierType.Email\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis configures email as the allowed login identifier type.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022require-verification\u0022\u003ERequire Verification\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.LoginIdentifiers.RequireVerificationForEmail = true;\n options.LoginIdentifiers.RequireVerificationForPhone = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese settings control whether the corresponding identifiers must be verified before being accepted for login.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-resolvers\u0022\u003ECustom Resolvers\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\u003EDefault\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEnableCustomResolvers\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECustomResolversFirst\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese options control the participation and ordering of custom login identifier resolvers.\u003C/p\u003E\n\u003Cp\u003EAn identifier existing on a user account does not necessarily mean it is permitted for login.\u003C/p\u003E\n\u003Cp\u003EIdentifier management and login eligibility are separate concerns.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-validation\u0022\u003E17. Identifier Validation\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.IdentifierValidation\u003C/code\u003E provides rules for usernames, email addresses, and phone numbers.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022username-rules\u0022\u003EUsername Rules\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.IdentifierValidation.UserName.Enabled = true;\n options.IdentifierValidation.UserName.MinLength = 3;\n options.IdentifierValidation.UserName.MaxLength = 32;\n options.IdentifierValidation.UserName.AllowedRegex = \u0026quot;^[a-zA-Z0-9._-]\u002B$\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022default-validation-rules\u0022\u003EDefault Validation Rules\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EIdentifier\u003C/th\u003E\n\u003Cth\u003EEnabled\u003C/th\u003E\n\u003Cth\u003EMinimum length\u003C/th\u003E\n\u003Cth\u003EMaximum length\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUsername\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E3\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E64\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EEmail\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E3\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E256\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPhone\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E3\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E24\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EUsername validation additionally supports \u003Ccode\u003EAllowedRegex\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe default username expression 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\u003EREGEX\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-regex\u0022\u003E^[a-zA-Z0-9._-]\u002B$\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-behavior\u0022\u003EIdentifier Behavior\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Identifiers\u003C/code\u003E contains additional 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\u003Eoptions.Identifiers.Behavior\noptions.Identifiers.Validation\noptions.Identifiers.Normalization\noptions.Identifiers.Uniqueness\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese configure identifier behavior beyond the top-level validation rules.\u003C/p\u003E\n\u003Cp\u003EIdentifier normalization and uniqueness are particularly important because they affect how duplicate identifiers are detected.\u003C/p\u003E\n\u003Cp\u003EUse the provided options rather than implementing inconsistent normalization independently in the client.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022user-profile-options\u0022\u003E18. User Profile Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.UserProfile\u003C/code\u003E controls profile-related capabilities.\u003C/p\u003E\n\u003Cp\u003EThe current 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.UserProfile.EnableMultiProfile\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIts default is \u003Ccode\u003Efalse\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022enable-multiple-profiles\u0022\u003EEnable Multiple Profiles\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.UserProfile.EnableMultiProfile = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows applications to use UltimateAuth\u0027s multiple-profile capability where supported by the user domain.\u003C/p\u003E\n\u003Cp\u003EFor example, an application may maintain separate profile contexts for different purposes.\u003C/p\u003E\n\u003Cp\u003EMulti-profile support does not create separate user identities or independent authentication accounts.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022multi-tenant-options\u0022\u003E19. Multi-Tenant Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.MultiTenant\u003C/code\u003E controls whether tenant-aware behavior is enabled and how tenant identifiers are resolved.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022available-properties-4\u0022\u003EAvailable Properties\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\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\u003EEnabled\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEnable multi-tenant behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENormalizeToLowercase\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENormalize tenant identifiers\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEnableRoute\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve tenants from routes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEnableHeader\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve tenants from headers\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEnableDomain\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Efalse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve tenants from hosts\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHeaderName\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EX-Tenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant header name\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=\u0022example-header-based-tenant-resolution\u0022\u003EExample: Header-Based Tenant Resolution\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.MultiTenant.Enabled = true;\n options.MultiTenant.EnableHeader = true;\n options.MultiTenant.HeaderName = \u0026quot;X-Tenant\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-domain-based-resolution\u0022\u003EExample: Domain-Based Resolution\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.MultiTenant.Enabled = true;\n options.MultiTenant.EnableDomain = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EMultiple resolution mechanisms can be enabled.\u003C/p\u003E\n\u003Cp\u003EThe server composes the configured tenant resolvers according to its resolution infrastructure.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-boundary\u0022\u003ESecurity Boundary\u003C/h3\u003E\n\u003Cp\u003ETenant identification is not tenant authorization.\u003C/p\u003E\n\u003Cp\u003EA request providing another tenant identifier must not gain access to that tenant without appropriate authorization.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for tenant isolation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pagination-options\u0022\u003E20. Pagination Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Pagination\u003C/code\u003E controls pagination defaults and maximum page sizes.\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\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDefaultPageSize\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E250\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMaxPageSize\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E1000\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=\u0022example-4\u0022\u003EExample\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.Pagination.DefaultPageSize = 50;\n options.Pagination.MaxPageSize = 200;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese settings affect paginated queries exposed by UltimateAuth domain services.\u003C/p\u003E\n\u003Cp\u003EFor administrative interfaces, choosing a moderate page size can reduce unnecessary data transfer and processing.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022navigation-options\u0022\u003E21. Navigation Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Navigation\u003C/code\u003E configures automatic authentication navigation behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022available-properties-5\u0022\u003EAvailable Properties\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\u003EDefault\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEnableAutomaticNavigationRedirect\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Etrue\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELoginResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAccessDeniedResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Enull\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=\u0022disable-automatic-redirects\u0022\u003EDisable Automatic Redirects\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.Navigation.EnableAutomaticNavigationRedirect = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-login-redirect\u0022\u003ECustom Login Redirect\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.Navigation.LoginResolver = context =\u0026gt;\n {\n return \u0026quot;/account/login\u0026quot;;\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-access-denied-redirect\u0022\u003ECustom Access Denied Redirect\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.Navigation.AccessDeniedResolver = context =\u0026gt;\n {\n return \u0026quot;/account/access-denied\u0026quot;;\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese delegates receive the current \u003Ccode\u003EHttpContext\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe application should return valid, trusted navigation destinations.\u003C/p\u003E\n\u003Cp\u003EAvoid constructing redirects from unvalidated user-controlled URLs.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-response-options\u0022\u003E22. Authentication Response Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.AuthResponse\u003C/code\u003E controls how authentication credentials and redirect behavior are represented in responses.\u003C/p\u003E\n\u003Cp\u003EThe available groups 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\u003Eoptions.AuthResponse.SessionIdDelivery\noptions.AuthResponse.AccessTokenDelivery\noptions.AuthResponse.RefreshTokenDelivery\n\noptions.AuthResponse.Login\noptions.AuthResponse.Logout\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese settings govern credential delivery and authentication response behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-mode-specific-response-defaults\u0022\u003EImportant: Mode-Specific Response Defaults\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth provides response templates for authentication modes.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EPureOpaque\u003C/code\u003E uses a session-oriented cookie response, while \u003Ccode\u003EHybrid\u003C/code\u003E combines session and token delivery behavior.\u003C/p\u003E\n\u003Cp\u003EThe effective response may therefore depend on the resolved authentication mode and flow.\u003C/p\u003E\n\u003Cp\u003EDo not assume that a single globally configured response delivery setting applies unchanged to every authentication mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003ESecurity Considerations\u003C/h3\u003E\n\u003Cp\u003ECredential delivery settings determine whether sensitive authentication material is exposed through cookies, headers, or other response mechanisms.\u003C/p\u003E\n\u003Cp\u003EOnly customize them when the application has a clear requirement and the selected transport is appropriate for the client platform.\u003C/p\u003E\n\u003Cp\u003EFor advanced response customization, 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=\u0022diagnostics-options\u0022\u003E23. Diagnostics Options\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Diagnostics\u003C/code\u003E provides optional diagnostic behavior.\u003C/p\u003E\n\u003Cp\u003EThe current property 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.Diagnostics.EnableRefreshDetails\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIts default is \u003Ccode\u003Efalse\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-5\u0022\u003EExample\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.Diagnostics.EnableRefreshDetails = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen enabled, the option allows additional refresh-related diagnostic response information, such as the \u003Ccode\u003EX-UAuth-Refresh\u003C/code\u003E header.\u003C/p\u003E\n\u003Cp\u003EThis is primarily useful during development and debugging.\u003C/p\u003E\n\u003Cp\u003EAvoid enabling detailed authentication diagnostics in production unless the exposure is understood and justified.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-lifecycle-events\u0022\u003E24. Authentication Lifecycle Events\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003Eoptions.Events\u003C/code\u003E exposes server-side authentication lifecycle callbacks.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022available-events\u0022\u003EAvailable Events\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.Events.OnAnyEvent\noptions.Events.OnUserLoggedIn\noptions.Events.OnUserLoggedOut\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-login-logging\u0022\u003EExample: Login Logging\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.Events.OnUserLoggedIn = context =\u0026gt;\n {\n // Record the successful login.\n return Task.CompletedTask;\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-global-event-handler\u0022\u003EExample: Global Event Handler\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.Events.OnAnyEvent = context =\u0026gt;\n {\n // Process an authentication lifecycle event.\n return Task.CompletedTask;\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ELifecycle events are intended for lightweight reactions such as logging, metrics, and notifications.\u003C/p\u003E\n\u003Cp\u003EThey should not replace security decisions, authentication validation, or application policies.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/configuration/advanced-configuration\u0022\u003EAdvanced Configuration\u003C/a\u003E for event and extension guidance.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022effective-server-options\u0022\u003E25. Effective Server Options\u003C/h2\u003E\n\u003Cp\u003EThe configured \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E instance is the base configuration.\u003C/p\u003E\n\u003Cp\u003EAt runtime, UltimateAuth can resolve:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EEffectiveUAuthServerOptions\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe current resolution process 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\u003EConfigured UAuthServerOptions\n |\n v\n Resolve auth mode\n |\n v\n Clone base options\n |\n v\n Apply mode defaults\n |\n v\n Apply ConfigureMode overrides\n |\n v\n EffectiveUAuthServerOptions\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis means that the effective settings used for a \u003Ccode\u003EHybrid\u003C/code\u003E client can differ from those used for a \u003Ccode\u003EPureOpaque\u003C/code\u003E client.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-6\u0022\u003EExample\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.FromDays(7);\n\n options.ConfigureMode(UAuthMode.Hybrid, mode =\u0026gt;\n {\n mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(5);\n });\n\n options.ConfigureMode(UAuthMode.PureOpaque, mode =\u0026gt;\n {\n mode.Session.IdleTimeout = TimeSpan.FromDays(3);\n });\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe original options remain the common starting point.\u003C/p\u003E\n\u003Cp\u003EMode defaults and overrides determine the effective values for the resolved mode.\u003C/p\u003E\n\u003Cp\u003EFor implementation details and inspection of effective options, 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=\u0022validation-and-configuration-safety\u0022\u003E26. Validation and Configuration Safety\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth Server registers multiple options validators and enables startup validation.\u003C/p\u003E\n\u003Cp\u003EThese validators cover areas including:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogin settings.\u003C/li\u003E\n\u003Cli\u003ESession settings.\u003C/li\u003E\n\u003Cli\u003EToken settings.\u003C/li\u003E\n\u003Cli\u003EPKCE.\u003C/li\u003E\n\u003Cli\u003EMulti-tenancy.\u003C/li\u003E\n\u003Cli\u003EIdentifier configuration.\u003C/li\u003E\n\u003Cli\u003ESession resolution.\u003C/li\u003E\n\u003Cli\u003EPagination.\u003C/li\u003E\n\u003Cli\u003EGeneral server configuration.\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\u003EThe server rejects an invalid session lifetime under the applicable validation rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-invalid-base-path\u0022\u003EExample: Invalid Base 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;//auth\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe general server validator rejects paths containing \u003Ccode\u003E//\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022unsupported-modes\u0022\u003EUnsupported 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.PureJwt\n ];\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe current validator rejects explicitly enabling \u003Ccode\u003EPureJwt\u003C/code\u003E because that mode is not yet implemented as a supported server mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validation-limitations\u0022\u003EValidation Limitations\u003C/h3\u003E\n\u003Cp\u003EStartup validation protects the configured options according to the registered validators.\u003C/p\u003E\n\u003Cp\u003EIt does not guarantee that every custom service implementation or mode-specific override is secure.\u003C/p\u003E\n\u003Cp\u003EWhen using advanced customization, test the resulting effective configuration and authentication flows.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-configuration-example\u0022\u003E27. Complete Configuration Example\u003C/h2\u003E\n\u003Cp\u003EThe following example combines several commonly used server 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 // Authentication modes\n options.AllowedModes =\n [\n UAuthMode.PureOpaque,\n UAuthMode.Hybrid\n ];\n\n // Login security\n options.Login.MaxFailedAttempts = 5;\n options.Login.LockoutDuration = TimeSpan.FromMinutes(15);\n options.Login.IncludeFailureDetails = false;\n\n // Sessions\n options.Session.Lifetime = TimeSpan.FromDays(7);\n options.Session.MaxLifetime = TimeSpan.FromDays(30);\n\n // Token identity\n options.Token.Issuer = \u0026quot;MyApplication\u0026quot;;\n options.Token.Audience = \u0026quot;MyApplicationApi\u0026quot;;\n\n // Hybrid-specific token lifetime\n options.ConfigureMode(UAuthMode.Hybrid, mode =\u0026gt;\n {\n mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(10);\n });\n\n // PKCE\n options.Pkce.AuthorizationCodeLifetimeSeconds = 120;\n\n // Hub deployment\n options.HubDeploymentMode = UAuthHubDeploymentMode.Integrated;\n options.Hub.ClientBaseAddress = \u0026quot;https://app.example.com\u0026quot;;\n options.Hub.AllowedClientOrigins.Add(\u0026quot;https://app.example.com\u0026quot;);\n\n // Credential security\n options.CredentialSessionPolicy.OnSelfChange = CredentialSessionBehavior.PreserveCurrent;\n options.CredentialSessionPolicy.OnAdminChange = CredentialSessionBehavior.RevokeAll;\n options.CredentialSessionPolicy.OnRecovery = CredentialSessionBehavior.RevokeAll;\n\n // Credential recovery\n options.ResetCredential.TokenValidity = TimeSpan.FromMinutes(30);\n options.ResetCredential.MaxAttempts = 3;\n\n // Identifier login rules\n options.LoginIdentifiers.RequireVerificationForEmail = true;\n\n // Session transport\n options.SessionResolution.EnableCookie = true;\n options.SessionResolution.EnableBearer = true;\n options.SessionResolution.EnableQuery = false;\n\n // Endpoint exposure\n options.Endpoints.Authentication = true;\n options.Endpoints.Session = true;\n options.Endpoints.Credentials = true;\n options.Endpoints.Authorization = true;\n\n // Pagination\n options.Pagination.DefaultPageSize = 50;\n options.Pagination.MaxPageSize = 200;\n\n // Diagnostics\n options.Diagnostics.EnableRefreshDetails = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is an illustrative configuration, not a universal production preset.\u003C/p\u003E\n\u003Cp\u003EApplications should choose session lifetimes, authentication modes, credential transport, tenant behavior, and endpoint exposure according to their own deployment and security requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-configuration-mistakes\u0022\u003E28. Common Configuration Mistakes\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuring-core-options-separately\u0022\u003EConfiguring Core Options Separately\u003C/h3\u003E\n\u003Cp\u003EWhen using \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E, configure Core-defined options through \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA separate \u003Ccode\u003EUAuthOptions\u003C/code\u003E configuration does not automatically override the server\u0027s composed options.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-allowedmodes-selects-the-mode\u0022\u003EAssuming AllowedModes Selects the Mode\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EAllowedModes\u003C/code\u003E is a restriction.\u003C/p\u003E\n\u003Cp\u003EMode selection is performed by the authentication mode resolver.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022ignoring-mode-defaults\u0022\u003EIgnoring Mode Defaults\u003C/h3\u003E\n\u003Cp\u003EMode-specific defaults can replace globally configured values.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003EConfigureMode()\u003C/code\u003E for explicit mode-specific behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022enabling-unsupported-modes\u0022\u003EEnabling Unsupported Modes\u003C/h3\u003E\n\u003Cp\u003EAn authentication mode appearing in an enum does not guarantee runtime support.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022treating-reserved-session-options-as-enforced-security-features\u0022\u003ETreating Reserved Session Options as Enforced Security Features\u003C/h3\u003E\n\u003Cp\u003ESome device and session-chain options are reserved for future enforcement.\u003C/p\u003E\n\u003Cp\u003EVerify actual runtime support before relying on them.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022exposing-unnecessary-endpoints\u0022\u003EExposing Unnecessary Endpoints\u003C/h3\u003E\n\u003Cp\u003EDisable endpoint groups or actions that the application does not require.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022allowing-query-string-credentials-without-a-specific-need\u0022\u003EAllowing Query-String Credentials Without a Specific Need\u003C/h3\u003E\n\u003Cp\u003EQuery-string credentials can leak through URLs and infrastructure logs.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022weakening-cookie-security\u0022\u003EWeakening Cookie Security\u003C/h3\u003E\n\u003Cp\u003EDo not relax secure transport or cookie protections without understanding the consequences.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-configuration-validation-covers-everything\u0022\u003EAssuming Configuration Validation Covers Everything\u003C/h3\u003E\n\u003Cp\u003EStartup validation does not replace testing, infrastructure security, or validation of custom extension implementations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-options-quick-reference\u0022\u003E29. Server Options Quick Reference\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConfiguration group\u003C/th\u003E\n\u003Cth\u003ETypical use\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 lockout\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESession\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession expiration 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 and lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPkce\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPKCE code lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAllowedModes\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPermitted authentication modes\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\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPagination\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EQuery page limits\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEndpoints\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEndpoint exposure\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESessionResolution\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential source precedence\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECookie\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECookie security and scope\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 topology\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EHub\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EHub integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPrimaryCredential\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUI/API primary credential selection\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAuthResponse\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential delivery\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialSessionPolicy\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession invalidation after credential changes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResetCredential\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential recovery rules\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifiers\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier 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\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ELoginIdentifiers\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin identifier eligibility\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUserProfile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EMulti-profile capability\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENavigation\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication redirects\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDiagnostics\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERefresh diagnostics\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EEvents\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication lifecycle callbacks\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EConfigureMode()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EMode-specific option overrides\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnConfigureEndpoints\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAdvanced endpoint customization\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=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/configuration\u0022\u003EConfiguration Overview\u003C/a\u003E \u2014 Understand the configuration architecture and precedence.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E \u2014 Configure client SDK behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/advanced-configuration\u0022\u003EAdvanced Configuration\u003C/a\u003E \u2014 Explore effective options, mode resolution, and extension points.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Manage sessions through the client.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/authentication\u0022\u003EAuthentication\u003C/a\u003E \u2014 Use the client authentication flows.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { - "Id": "what-are-server-options", - "Text": "\uD83E\uDDE0 What Are Server Options?", + "Id": "register-ultimateauth-server", + "Text": "1. Register UltimateAuth Server", "Level": 0 }, { - "Id": "basic-usage", - "Text": "\u2699\uFE0F Basic Usage", + "Id": "configuration-from-appsettings.json", + "Text": "Configuration from appsettings.json", + "Level": 1 + }, + { + "Id": "server-options-architecture", + "Text": "2. Server Options Architecture", "Level": 0 }, { - "Id": "core-composition", - "Text": "\uD83E\uDDE9 Core Composition", + "Id": "core-defined-options", + "Text": "Core-defined options", + "Level": 1 + }, + { + "Id": "server-specific-options", + "Text": "Server-specific options", + "Level": 1 + }, + { + "Id": "login-options", + "Text": "3. Login Options", "Level": 0 }, { - "Id": "important-you-dont-configure-modes-directly", - "Text": "\u26A0\uFE0F Important: You Don\u2019t Configure Modes Directly", + "Id": "available-properties", + "Text": "Available Properties", + "Level": 1 + }, + { + "Id": "example-restrict-login-failure-details", + "Text": "Example: Restrict Login Failure Details", + "Level": 1 + }, + { + "Id": "important", + "Text": "Important", + "Level": 1 + }, + { + "Id": "session-options", + "Text": "4. Session Options", "Level": 0 }, { - "Id": "allowed-modes-guardrail", - "Text": "\uD83D\uDEE1 Allowed Modes (Guardrail)", + "Id": "session-lifetime-properties", + "Text": "Session Lifetime Properties", + "Level": 1 + }, + { + "Id": "session-chain-properties", + "Text": "Session-Chain Properties", + "Level": 1 + }, + { + "Id": "mode-specific-session-defaults", + "Text": "Mode-Specific Session Defaults", + "Level": 1 + }, + { + "Id": "token-options", + "Text": "5. Token Options", "Level": 0 }, { - "Id": "runtime-behavior-effective-options", - "Text": "\u26A1 Runtime Behavior (Effective Options)", + "Id": "available-properties-1", + "Text": "Available Properties", + "Level": 1 + }, + { + "Id": "refresh-token-concurrency-window", + "Text": "Refresh Token Concurrency Window", + "Level": 1 + }, + { + "Id": "mode-defaults-may-override-token-values", + "Text": "Mode Defaults May Override Token Values", + "Level": 1 + }, + { + "Id": "pkce-options", + "Text": "6. PKCE Options", "Level": 0 }, { - "Id": "mode-based-defaults", - "Text": "\uD83D\uDD04 Mode-Based Defaults", + "Id": "authentication-modes", + "Text": "7. Authentication Modes", "Level": 0 }, { - "Id": "endpoint-control", - "Text": "\uD83C\uDF9B Endpoint Control", + "Id": "restrict-allowed-modes", + "Text": "Restrict Allowed Modes", + "Level": 1 + }, + { + "Id": "default-mode-resolution", + "Text": "Default Mode Resolution", + "Level": 1 + }, + { + "Id": "configure-mode-specific-behavior", + "Text": "Configure Mode-Specific Behavior", + "Level": 1 + }, + { + "Id": "endpoint-options", + "Text": "8. Endpoint Options", "Level": 0 }, { - "Id": "cookie-transport-behavior", - "Text": "\uD83C\uDF6A Cookie \u0026 Transport Behavior", + "Id": "base-path", + "Text": "Base Path", + "Level": 1 + }, + { + "Id": "endpoint-groups", + "Text": "Endpoint Groups", + "Level": 1 + }, + { + "Id": "disable-individual-actions", + "Text": "Disable Individual Actions", + "Level": 1 + }, + { + "Id": "endpoint-hardening", + "Text": "Endpoint Hardening", + "Level": 1 + }, + { + "Id": "session-resolution", + "Text": "9. Session Resolution", "Level": 0 }, { - "Id": "hub-configuration", - "Text": "\uD83C\uDF10 Hub Configuration", + "Id": "default-configuration", + "Text": "Default Configuration", + "Level": 1 + }, + { + "Id": "configure-resolution-sources", + "Text": "Configure Resolution Sources", + "Level": 1 + }, + { + "Id": "change-resolution-order", + "Text": "Change Resolution Order", + "Level": 1 + }, + { + "Id": "security-recommendation", + "Text": "Security Recommendation", + "Level": 1 + }, + { + "Id": "cookie-configuration", + "Text": "10. Cookie Configuration", "Level": 0 }, { - "Id": "session-resolution", - "Text": "\uD83D\uDD01 Session Resolution", + "Id": "default-cookie-names", + "Text": "Default Cookie Names", + "Level": 1 + }, + { + "Id": "customize-cookie-names", + "Text": "Customize Cookie Names", + "Level": 1 + }, + { + "Id": "configure-cookie-scope", + "Text": "Configure Cookie Scope", + "Level": 1 + }, + { + "Id": "cookie-security", + "Text": "Cookie Security", + "Level": 1 + }, + { + "Id": "hub-deployment-mode", + "Text": "11. Hub Deployment Mode", + "Level": 0 + }, + { + "Id": "available-modes", + "Text": "Available Modes", + "Level": 1 + }, + { + "Id": "embedded", + "Text": "Embedded", + "Level": 1 + }, + { + "Id": "integrated", + "Text": "Integrated", + "Level": 1 + }, + { + "Id": "external", + "Text": "External", + "Level": 1 + }, + { + "Id": "hub-options", + "Text": "12. Hub Options", + "Level": 0 + }, + { + "Id": "available-properties-2", + "Text": "Available Properties", + "Level": 1 + }, + { + "Id": "example", + "Text": "Example", + "Level": 1 + }, + { + "Id": "primary-credential-policy", + "Text": "13. Primary Credential Policy", + "Level": 0 + }, + { + "Id": "example-1", + "Text": "Example", + "Level": 1 + }, + { + "Id": "credential-session-policy", + "Text": "14. Credential Session Policy", + "Level": 0 + }, + { + "Id": "available-properties-3", + "Text": "Available Properties", + "Level": 1 + }, + { + "Id": "example-2", + "Text": "Example", + "Level": 1 + }, + { + "Id": "available-behaviors", + "Text": "Available Behaviors", + "Level": 1 + }, + { + "Id": "security-recommendation-1", + "Text": "Security Recommendation", + "Level": 1 + }, + { + "Id": "credential-reset-options", + "Text": "15. Credential Reset Options", + "Level": 0 + }, + { + "Id": "defaults", + "Text": "Defaults", + "Level": 1 + }, + { + "Id": "example-3", + "Text": "Example", + "Level": 1 + }, + { + "Id": "important-security-boundary", + "Text": "Important Security Boundary", + "Level": 1 + }, + { + "Id": "login-identifier-options", + "Text": "16. Login Identifier Options", + "Level": 0 + }, + { + "Id": "restrict-login-identifier-types", + "Text": "Restrict Login Identifier Types", + "Level": 1 + }, + { + "Id": "require-verification", + "Text": "Require Verification", + "Level": 1 + }, + { + "Id": "custom-resolvers", + "Text": "Custom Resolvers", + "Level": 1 + }, + { + "Id": "identifier-validation", + "Text": "17. Identifier Validation", + "Level": 0 + }, + { + "Id": "username-rules", + "Text": "Username Rules", + "Level": 1 + }, + { + "Id": "default-validation-rules", + "Text": "Default Validation Rules", + "Level": 1 + }, + { + "Id": "identifier-behavior", + "Text": "Identifier Behavior", + "Level": 1 + }, + { + "Id": "user-profile-options", + "Text": "18. User Profile Options", + "Level": 0 + }, + { + "Id": "enable-multiple-profiles", + "Text": "Enable Multiple Profiles", + "Level": 1 + }, + { + "Id": "multi-tenant-options", + "Text": "19. Multi-Tenant Options", + "Level": 0 + }, + { + "Id": "available-properties-4", + "Text": "Available Properties", + "Level": 1 + }, + { + "Id": "example-header-based-tenant-resolution", + "Text": "Example: Header-Based Tenant Resolution", + "Level": 1 + }, + { + "Id": "example-domain-based-resolution", + "Text": "Example: Domain-Based Resolution", + "Level": 1 + }, + { + "Id": "security-boundary", + "Text": "Security Boundary", + "Level": 1 + }, + { + "Id": "pagination-options", + "Text": "20. Pagination Options", + "Level": 0 + }, + { + "Id": "example-4", + "Text": "Example", + "Level": 1 + }, + { + "Id": "navigation-options", + "Text": "21. Navigation Options", + "Level": 0 + }, + { + "Id": "available-properties-5", + "Text": "Available Properties", + "Level": 1 + }, + { + "Id": "disable-automatic-redirects", + "Text": "Disable Automatic Redirects", + "Level": 1 + }, + { + "Id": "custom-login-redirect", + "Text": "Custom Login Redirect", + "Level": 1 + }, + { + "Id": "custom-access-denied-redirect", + "Text": "Custom Access Denied Redirect", + "Level": 1 + }, + { + "Id": "authentication-response-options", + "Text": "22. Authentication Response Options", + "Level": 0 + }, + { + "Id": "important-mode-specific-response-defaults", + "Text": "Important: Mode-Specific Response Defaults", + "Level": 1 + }, + { + "Id": "security-considerations", + "Text": "Security Considerations", + "Level": 1 + }, + { + "Id": "diagnostics-options", + "Text": "23. Diagnostics Options", + "Level": 0 + }, + { + "Id": "example-5", + "Text": "Example", + "Level": 1 + }, + { + "Id": "authentication-lifecycle-events", + "Text": "24. Authentication Lifecycle Events", + "Level": 0 + }, + { + "Id": "available-events", + "Text": "Available Events", + "Level": 1 + }, + { + "Id": "example-login-logging", + "Text": "Example: Login Logging", + "Level": 1 + }, + { + "Id": "example-global-event-handler", + "Text": "Example: Global Event Handler", + "Level": 1 + }, + { + "Id": "effective-server-options", + "Text": "25. Effective Server Options", "Level": 0 }, { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", + "Id": "example-6", + "Text": "Example", + "Level": 1 + }, + { + "Id": "validation-and-configuration-safety", + "Text": "26. Validation and Configuration Safety", "Level": 0 }, { - "Id": "key-takeaways", - "Text": "\uD83D\uDCCC Key Takeaways", + "Id": "example-invalid-session-lifetime", + "Text": "Example: Invalid Session Lifetime", + "Level": 1 + }, + { + "Id": "example-invalid-base-path", + "Text": "Example: Invalid Base Path", + "Level": 1 + }, + { + "Id": "unsupported-modes", + "Text": "Unsupported Modes", + "Level": 1 + }, + { + "Id": "validation-limitations", + "Text": "Validation Limitations", + "Level": 1 + }, + { + "Id": "complete-configuration-example", + "Text": "27. Complete Configuration Example", + "Level": 0 + }, + { + "Id": "common-configuration-mistakes", + "Text": "28. Common Configuration Mistakes", + "Level": 0 + }, + { + "Id": "configuring-core-options-separately", + "Text": "Configuring Core Options Separately", + "Level": 1 + }, + { + "Id": "assuming-allowedmodes-selects-the-mode", + "Text": "Assuming AllowedModes Selects the Mode", + "Level": 1 + }, + { + "Id": "ignoring-mode-defaults", + "Text": "Ignoring Mode Defaults", + "Level": 1 + }, + { + "Id": "enabling-unsupported-modes", + "Text": "Enabling Unsupported Modes", + "Level": 1 + }, + { + "Id": "treating-reserved-session-options-as-enforced-security-features", + "Text": "Treating Reserved Session Options as Enforced Security Features", + "Level": 1 + }, + { + "Id": "exposing-unnecessary-endpoints", + "Text": "Exposing Unnecessary Endpoints", + "Level": 1 + }, + { + "Id": "allowing-query-string-credentials-without-a-specific-need", + "Text": "Allowing Query-String Credentials Without a Specific Need", + "Level": 1 + }, + { + "Id": "weakening-cookie-security", + "Text": "Weakening Cookie Security", + "Level": 1 + }, + { + "Id": "assuming-configuration-validation-covers-everything", + "Text": "Assuming Configuration Validation Covers Everything", + "Level": 1 + }, + { + "Id": "server-options-quick-reference", + "Text": "29. Server Options Quick Reference", "Level": 0 }, { - "Id": "next-step", - "Text": "\u27A1\uFE0F Next Step", + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json index 92b5f3a0..9f55f4e8 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json @@ -77,7 +77,7 @@ "GroupOrder": 2 }, { - "Title": "Auth Flows", + "Title": "Auth Flows Overview", "Slug": "auth-flows/index", "Order": 1, "Group": "auth-flows", @@ -107,14 +107,14 @@ { "Title": "Session Model \u0026 Lifecycle", "Slug": "sessions-devices/session-lifecycle", - "Order": 2, + "Order": 1, "Group": "sessions-devices", "GroupOrder": 4 }, { "Title": "Device Management", "Slug": "sessions-devices/device-management", - "Order": 3, + "Order": 2, "Group": "sessions-devices", "GroupOrder": 4 }, @@ -203,7 +203,7 @@ "GroupOrder": 6 }, { - "Title": "Configuration", + "Title": "Configuration Overview", "Slug": "configuration/index", "Order": 1, "Group": "configuration", @@ -212,20 +212,20 @@ { "Title": "Server Options", "Slug": "configuration/server-options", - "Order": 3, + "Order": 2, "Group": "configuration", "GroupOrder": 7 }, { - "Title": "Advanced Configuration", - "Slug": "configuration/advanced-configuration", - "Order": 4, + "Title": "Client Options", + "Slug": "configuration/client-options", + "Order": 3, "Group": "configuration", "GroupOrder": 7 }, { - "Title": "Client Options", - "Slug": "configuration/client-options", + "Title": "Advanced Configuration", + "Slug": "configuration/advanced-configuration", "Order": 4, "Group": "configuration", "GroupOrder": 7 diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/ecosystem.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/ecosystem.json index 5d58ecc3..fda41be2 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/ecosystem.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/ecosystem.json @@ -1,7 +1,7 @@ { "Slug": "getting-started/ecosystem", "Title": "UltimateAuth Ecosystem", - "Html": "\n\u003Cp\u003EUltimateAuth is designed as a modular authentication and identity platform for .NET.\u003C/p\u003E\n\u003Cp\u003EThe platform has three primary foundations:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E UltimateAuth\n |\n Core\n |\n \u002B------------\u002B------------\u002B\n | |\n Server Client\n | |\n Server composition Client integration\n through bundles for the application\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003ECore\u003C/strong\u003E defines the common language and foundational abstractions of the platform.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EServer\u003C/strong\u003E hosts and executes authentication and identity capabilities.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient\u003C/strong\u003E provides the application-facing model used by applications to interact with those capabilities.\u003C/p\u003E\n\u003Cp\u003EAn application does not necessarily need both Server and Client. Depending on its role, it may use:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EServer only\u003C/li\u003E\n\u003Cli\u003EClient only\u003C/li\u003E\n\u003Cli\u003EServer and Client together\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis separation is intentional. UltimateAuth is designed so that application topology does not dictate a monolithic package structure.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022platform-at-a-glance\u0022\u003EPlatform at a Glance\u003C/h2\u003E\n\u003Cp\u003EA useful way to think about UltimateAuth 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\n|\n\u002B-- Core\n|\n\u002B-- Server\n| |\n| \u002B-- Essential server capabilities\n| | \u002B-- Authentication\n| | \u002B-- Sessions\n| | \u002B-- Tokens\n| | \u002B-- Policies\n| |\n| \u002B-- Plugin Domains\n| | \u002B-- Users\n| | \u002B-- Credentials\n| | \u002B-- Authorization\n| |\n| \u002B-- Implementations\n| \u002B-- Reference\n| \u002B-- InMemory\n| \u002B-- Entity Framework Core\n|\n\u002B-- Client\n |\n \u002B-- Client.Blazor\n \u002B-- Client.AspNetCore compatibility\n \u002B-- Future client integrations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe important distinction is that these packages are not intended to make installation complicated.\u003C/p\u003E\n\u003Cp\u003EThe internal architecture is modular, while the installation experience is deliberately composed around \u003Cstrong\u003Eserver bundles\u003C/strong\u003E and \u003Cstrong\u003Eclient-specific packages\u003C/strong\u003E.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core\u0022\u003ECore\u003C/h2\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.core\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Core\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003ECore is the foundation of the entire UltimateAuth platform.\u003C/p\u003E\n\u003Cp\u003EIt contains the shared primitives, domain concepts, contracts, abstractions, value objects, configuration foundations, and common infrastructure required by the rest of UltimateAuth.\u003C/p\u003E\n\u003Cp\u003EConcepts such as UltimateAuth\u0027s identity and session model originate from this layer.\u003C/p\u003E\n\u003Cp\u003ECore is intentionally independent from a particular application type or persistence technology.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Core\n |\n \u002B--------\u002B--------\u002B\n | |\n Server Client\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EServer and Client share the same platform vocabulary because both are built on Core.\u003C/p\u003E\n\u003Cp\u003EMost application developers do not need to install Core directly. It is normally brought into the application through the appropriate Server bundle or Client integration package.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server\u0022\u003EServer\u003C/h2\u003E\n\u003Cp\u003EThe Server side is the authority that executes UltimateAuth authentication and identity behavior.\u003C/p\u003E\n\u003Cp\u003EIt is responsible for the server-side runtime, including authentication flows, security decisions, orchestration, endpoints, middleware integration, and the coordination of server capabilities.\u003C/p\u003E\n\u003Cp\u003EAt source level, \u003Ccode\u003ECodeBeam.UltimateAuth.Server\u003C/code\u003E is the central server project and depends on major server domains such 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\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\u003ECodeBeam.UltimateAuth.Server\n|\n\u002B-- Core\n\u002B-- Authorization\n\u002B-- Credentials\n\u002B-- Users\n\u002B-- Policies\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOther essential server capabilities, including authentication, sessions, and tokens, are kept in focused projects rather than being merged into one oversized server assembly.\u003C/p\u003E\n\u003Cp\u003EThis gives UltimateAuth a modular internal architecture without forcing that complexity onto application developers.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-i-install-codebeam.ultimateauth.server-directly\u0022\u003EDo I install \u003Ccode\u003ECodeBeam.UltimateAuth.Server\u003C/code\u003E directly?\u003C/h3\u003E\n\u003Cp\u003EFor normal application setup, \u003Cstrong\u003Eno\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe recommended server installation unit is a \u003Cstrong\u003EBundle\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EA bundle represents a complete server composition for a particular infrastructure strategy.\u003C/p\u003E\n\u003Cp\u003EInstead of manually selecting all server packages and implementations, choose the bundle that matches your application.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Your Server\n |\n Bundle\n |\n \u002B---------------\u002B---------------\u002B\n | | |\n Server Domains Infrastructure\n | | |\n Runtime Reference Persistence\n behavior providers\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is an important part of the UltimateAuth design:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EThe framework remains modular internally, while common server configurations remain simple to install.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-bundles\u0022\u003EServer Bundles\u003C/h2\u003E\n\u003Cp\u003EBundles are server-side composition packages.\u003C/p\u003E\n\u003Cp\u003EThey collect the UltimateAuth server capabilities and the implementations required for a particular configuration into a single installation unit.\u003C/p\u003E\n\u003Cp\u003EYou normally choose \u003Cstrong\u003Eone server bundle\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reference-bundle\u0022\u003EReference Bundle\u003C/h3\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.reference.bundle\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Reference.Bundle\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003EThe Reference Bundle provides the recommended UltimateAuth server composition without selecting a persistence implementation for your application.\u003C/p\u003E\n\u003Cp\u003EUse it when you want UltimateAuth\u0027s reference behavior but provide your own persistence infrastructure.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EReference.Bundle\n|\n\u002B-- Server\n\u002B-- UltimateAuth reference implementations\n\u002B-- Required server capabilities\n|\n\u002B-- Persistence: supplied by your application\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is the lowest-level recommended server composition for applications that want to own their storage implementation.\u003C/p\u003E\n\u003Cp\u003EIt is also an important extensibility point: UltimateAuth provides a recommended architecture without requiring applications to use one particular persistence technology.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inmemory-bundle\u0022\u003EInMemory Bundle\u003C/h3\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.inmemory.bundle\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.InMemory.Bundle\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003EThe InMemory Bundle provides a complete server setup backed by UltimateAuth\u0027s in-memory implementations.\u003C/p\u003E\n\u003Cp\u003EIt is primarily intended for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Edevelopment\u003C/li\u003E\n\u003Cli\u003EQuick Start\u003C/li\u003E\n\u003Cli\u003Esamples\u003C/li\u003E\n\u003Cli\u003Eautomated tests\u003C/li\u003E\n\u003Cli\u003Eevaluation\u003C/li\u003E\n\u003Cli\u003Eprototypes\u003C/li\u003E\n\u003C/ul\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\u003EInMemory.Bundle\n|\n\u002B-- Server\n\u002B-- Reference implementations\n\u002B-- InMemory persistence implementations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is the easiest way to start an UltimateAuth server.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022entity-framework-core-bundle\u0022\u003EEntity Framework Core Bundle\u003C/h3\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.entityframeworkcore.bundle\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003EThe Entity Framework Core Bundle provides the complete server composition using UltimateAuth\u0027s EF Core persistence implementations.\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\u003EEntityFrameworkCore.Bundle\n|\n\u002B-- Server\n\u002B-- Reference implementations\n\u002B-- EF Core persistence implementations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor applications that use EF Core for persistent UltimateAuth data, this is normally the server package to install.\u003C/p\u003E\n\u003Cp\u003EThe developer does not need to manually assemble the individual Users, Credentials, Authorization, Sessions, Tokens, and other persistence packages.\u003C/p\u003E\n\u003Cp\u003EThe bundle performs that composition.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-bundles-matter\u0022\u003EWhy Bundles Matter\u003C/h3\u003E\n\u003Cp\u003EA highly modular framework can easily make the developer experience worse if every application has to understand and install its internal package graph.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth deliberately separates \u003Cstrong\u003Earchitecture modularity\u003C/strong\u003E from \u003Cstrong\u003Einstallation complexity\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EInternally:\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\u003EServer\n \u002B Users\n \u002B Credentials\n \u002B Authorization\n \u002B Authentication\n \u002B Sessions\n \u002B Tokens\n \u002B Policies\n \u002B persistence implementations\n \u002B reference implementations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFrom the application:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis gives UltimateAuth two useful properties at the same time:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003Eindividual capabilities remain independently replaceable and evolvable;\u003C/li\u003E\n\u003Cli\u003Ethe common path remains simple.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EYou should not need to understand the complete internal dependency graph before building your first application.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client\u0022\u003EClient\u003C/h2\u003E\n\u003Cp\u003EThe Client side has a different packaging model.\u003C/p\u003E\n\u003Cp\u003EThere are \u003Cstrong\u003Eno Client bundles\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EInstead, applications install the client package designed for their application 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\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Client Foundation\n |\n \u002B-------------\u002B-------------\u002B\n | | |\n Blazor MAUI Future\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor example, a Blazor application installs:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA future MAUI integration can follow the same model without changing the underlying UltimateAuth Client architecture.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022codebeam.ultimateauth.client\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client\u003C/code\u003E\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client\u003C/code\u003E is the shared client foundation.\u003C/p\u003E\n\u003Cp\u003EIt contains common client contracts, abstractions, flow APIs, state concepts, and runtime-independent client behavior.\u003C/p\u003E\n\u003Cp\u003EIt should be thought of as the foundation on which concrete UltimateAuth client integrations are built.\u003C/p\u003E\n\u003Cp\u003EMost applications should therefore install their platform-specific client package rather than installing \u003Ccode\u003ECodeBeam.UltimateAuth.Client\u003C/code\u003E directly.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022codebeam.ultimateauth.client.blazor\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.Blazor\u003C/code\u003E\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.Blazor\u003C/code\u003E is the concrete UltimateAuth client integration for Blazor.\u003C/p\u003E\n\u003Cp\u003EIt builds on the Client foundation and adds the Blazor-specific runtime and UI integration required by Blazor applications.\u003C/p\u003E\n\u003Cp\u003EThis includes capabilities such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthApp\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthStateView\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003Eauthentication state integration\u003C/li\u003E\n\u003Cli\u003EBlazor authorization integration\u003C/li\u003E\n\u003Cli\u003Erouting integration\u003C/li\u003E\n\u003Cli\u003Eauthentication flow components\u003C/li\u003E\n\u003Cli\u003EJavaScript transport integration\u003C/li\u003E\n\u003Cli\u003Eclient lifecycle coordination\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EConceptually:\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\u003ECore\n |\nClient\n |\nClient.Blazor\n |\nYour Blazor Application\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis model keeps the shared Client architecture independent from Blazor while still giving Blazor developers one natural package to install.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-compatibility-packages\u0022\u003EClient Compatibility Packages\u003C/h3\u003E\n\u003Cp\u003ENot every client-side package represents a complete client implementation.\u003C/p\u003E\n\u003Cp\u003ESome packages exist to bridge UltimateAuth with a host framework.\u003C/p\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.client.aspnetcore\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.AspNetCore\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.AspNetCore\u003C/code\u003E is a lightweight ASP.NET Core compatibility layer for UltimateAuth client applications.\u003C/p\u003E\n\u003Cp\u003EIt allows client-oriented applications to integrate with ASP.NET Core authentication and authorization infrastructure without introducing the full UltimateAuth Server runtime.\u003C/p\u003E\n\u003Cp\u003EThis is particularly useful for hosting models such as Blazor Web App with Interactive WebAssembly.\u003C/p\u003E\n\u003Cp\u003EIt can enable compatibility with ASP.NET Core features such as \u003Ccode\u003E[Authorize]\u003C/code\u003E, but it does \u003Cstrong\u003Enot\u003C/strong\u003E implement authentication itself and does not turn the application into an UltimateAuth Server.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022application-composition\u0022\u003EApplication Composition\u003C/h2\u003E\n\u003Cp\u003EBecause Server and Client are independent platform sides, different applications can compose UltimateAuth differently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-only\u0022\u003EServer Only\u003C/h3\u003E\n\u003Cp\u003ETypical examples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ededicated authentication servers\u003C/li\u003E\n\u003Cli\u003Ebackend services hosting UltimateAuth\u003C/li\u003E\n\u003Cli\u003Eserver-side identity infrastructure\u003C/li\u003E\n\u003C/ul\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\u003EApplication\n|\n\u002B-- Server Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-only\u0022\u003EClient Only\u003C/h3\u003E\n\u003Cp\u003ETypical examples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Estandalone Blazor WebAssembly\u003C/li\u003E\n\u003Cli\u003Efuture MAUI applications\u003C/li\u003E\n\u003Cli\u003Eapplications using a remote UAuthHub\u003C/li\u003E\n\u003C/ul\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\u003EApplication\n|\n\u002B-- Client.Blazor\n |\n \u002B---- remote UltimateAuth Server / UAuthHub\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-client\u0022\u003EServer \u002B Client\u003C/h3\u003E\n\u003Cp\u003ESome applications host UltimateAuth and consume its client API in the same application.\u003C/p\u003E\n\u003Cp\u003EBlazor Server is a common 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\u003EBlazor Server Application\n|\n\u002B-- Server Bundle\n|\n\u002B-- Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe two sides remain architecturally distinct even when deployed in the same process.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022plugin-domains\u0022\u003EPlugin Domains\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth extends its identity platform through \u003Cstrong\u003EPlugin Domains\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe current primary Plugin Domains are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUsers\u003C/li\u003E\n\u003Cli\u003ECredentials\u003C/li\u003E\n\u003Cli\u003EAuthorization\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA Plugin Domain is more than a single assembly. It is a capability area with contracts, runtime behavior, recommended implementations, and infrastructure implementations.\u003C/p\u003E\n\u003Cp\u003EThis structure allows a domain to remain replaceable without requiring applications to redesign the rest of UltimateAuth.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022anatomy-of-a-plugin-domain\u0022\u003EAnatomy of a Plugin Domain\u003C/h3\u003E\n\u003Cp\u003EA typical Plugin Domain follows this 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\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\u003EPlugin Domain\n|\n\u002B-- Contracts\n|\n\u002B-- Domain / Runtime\n|\n\u002B-- Reference\n|\n\u002B-- Persistence\n |\n \u002B-- InMemory\n \u002B-- EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach layer has a distinct purpose.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022contracts\u0022\u003EContracts\u003C/h3\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\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\u003ECodeBeam.UltimateAuth.Users.Contracts\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EContracts define the public boundary of the domain. Server and Client can communicate through same contracts without requiring one to depend on the other.\u003C/p\u003E\n\u003Cp\u003EThey are intentionally kept close to Core and represent the smallest dependency surface for applications or extensions that only need to interact with the domain contract.\u003C/p\u003E\n\u003Cp\u003EDepending on the domain, contracts can contain concepts such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Erequests\u003C/li\u003E\n\u003Cli\u003Eresults\u003C/li\u003E\n\u003Cli\u003Epublic models\u003C/li\u003E\n\u003Cli\u003Eabstractions\u003C/li\u003E\n\u003Cli\u003Edomain-facing contracts\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis allows another implementation to integrate with UltimateAuth without depending on the entire built-in implementation.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022domain-runtime\u0022\u003EDomain / Runtime\u003C/h3\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\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\u003ECodeBeam.UltimateAuth.Users\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe main domain project provides the runtime and application behavior of that Plugin Domain.\u003C/p\u003E\n\u003Cp\u003EIt operates against abstractions rather than requiring one persistence implementation.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reference-implementations\u0022\u003EReference Implementations\u003C/h3\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\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\u003ECodeBeam.UltimateAuth.Users.Reference\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EReference packages contain the implementation UltimateAuth recommends as the standard baseline for the domain.\u003C/p\u003E\n\u003Cp\u003EThe distinction between \u003Cstrong\u003Econtract\u003C/strong\u003E and \u003Cstrong\u003Ereference implementation\u003C/strong\u003E is intentional.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth does not leave extensibility points empty and require every developer to design critical identity behavior from scratch.\u003C/p\u003E\n\u003Cp\u003EInstead, the platform provides:\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\u003EContract\n |\n \u002B---- UltimateAuth Reference Implementation\n |\n \u002B---- Your Custom Implementation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor the common path, use the UltimateAuth reference implementation.\u003C/p\u003E\n\u003Cp\u003EFor specialized requirements, replace the appropriate implementation while preserving the surrounding platform contract and security boundaries.\u003C/p\u003E\n\u003Cp\u003EThis pattern gives advanced applications extensibility without making simple applications incomplete.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022persistence-implementations\u0022\u003EPersistence Implementations\u003C/h3\u003E\n\u003Cp\u003EPlugin Domains can provide persistence-specific packages independently.\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|\n\u002B-- Users.Contracts\n\u002B-- Users\n\u002B-- Users.Reference\n\u002B-- Users.InMemory\n\u002B-- Users.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same pattern applies to other Plugin Domains where appropriate.\u003C/p\u003E\n\u003Cp\u003EA developer using a server bundle normally does not need to install these packages individually. The selected bundle composes the appropriate persistence strategy.\u003C/p\u003E\n\u003Cp\u003ETheir separation exists primarily to preserve architectural modularity and replaceability.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022current-plugin-domains\u0022\u003ECurrent Plugin Domains\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022users\u0022\u003EUsers\u003C/h3\u003E\n\u003Cp\u003EThe Users domain owns user lifecycle and user-management capabilities.\u003C/p\u003E\n\u003Cp\u003EIts package family 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\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\u003ECodeBeam.UltimateAuth.Users.Contracts\nCodeBeam.UltimateAuth.Users\nCodeBeam.UltimateAuth.Users.Reference\nCodeBeam.UltimateAuth.Users.InMemory\nCodeBeam.UltimateAuth.Users.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentials\u0022\u003ECredentials\u003C/h3\u003E\n\u003Cp\u003EThe Credentials domain owns credential-related capabilities and credential management.\u003C/p\u003E\n\u003Cp\u003EIts package family 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\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\u003ECodeBeam.UltimateAuth.Credentials.Contracts\nCodeBeam.UltimateAuth.Credentials\nCodeBeam.UltimateAuth.Credentials.Reference\nCodeBeam.UltimateAuth.Credentials.InMemory\nCodeBeam.UltimateAuth.Credentials.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization\u0022\u003EAuthorization\u003C/h3\u003E\n\u003Cp\u003EThe Authorization domain provides UltimateAuth authorization capabilities.\u003C/p\u003E\n\u003Cp\u003EIts package family 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\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\u003ECodeBeam.UltimateAuth.Authorization.Contracts\nCodeBeam.UltimateAuth.Authorization\nCodeBeam.UltimateAuth.Authorization.Reference\nCodeBeam.UltimateAuth.Authorization.InMemory\nCodeBeam.UltimateAuth.Authorization.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same architectural pattern makes these capabilities independently evolvable while keeping their public boundaries explicit.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022essential-server-capabilities\u0022\u003EEssential Server Capabilities\u003C/h2\u003E\n\u003Cp\u003EAuthentication, Sessions, and Tokens are slightly different from Plugin Domains.\u003C/p\u003E\n\u003Cp\u003EThey are fundamental parts of an UltimateAuth Server and are not treated as optional identity plugins in the same sense as Users, Credentials, or Authorization.\u003C/p\u003E\n\u003Cp\u003EHowever, they are still separated into focused projects.\u003C/p\u003E\n\u003Cp\u003EConceptually:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E UltimateAuth Server\n |\n \u002B--------------------\u002B--------------------\u002B\n | | |\n Authentication Sessions Tokens\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhy separate them if the Server needs them?\u003C/p\u003E\n\u003Cp\u003EBecause \u003Cstrong\u003Erequired does not have to mean monolithic\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EKeeping these capabilities isolated:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eprevents the central Server project from becoming an oversized implementation assembly;\u003C/li\u003E\n\u003Cli\u003Ekeeps responsibilities and dependency boundaries explicit;\u003C/li\u003E\n\u003Cli\u003Eallows implementations to evolve independently;\u003C/li\u003E\n\u003Cli\u003Emakes persistence providers independently composable;\u003C/li\u003E\n\u003Cli\u003Ekeeps replacement and testing boundaries smaller.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThey can be thought of as \u003Cstrong\u003Emodular server subsystems\u003C/strong\u003E rather than full Plugin Domains.\u003C/p\u003E\n\u003Cp\u003EFor example, persistence implementations can still exist independently:\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\n\u002B-- Authentication.InMemory\n\u002B-- Authentication.EntityFrameworkCore\n\nSessions\n\u002B-- Sessions.InMemory\n\u002B-- Sessions.EntityFrameworkCore\n\nTokens\n\u002B-- Tokens.InMemory\n\u002B-- Tokens.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe selected Server bundle composes the required implementations for the application.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies-and-security\u0022\u003EPolicies and Security\u003C/h2\u003E\n\u003Cp\u003ESome capabilities are deliberately isolated even though they support multiple areas of the platform.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies\u0022\u003EPolicies\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Policies\u003C/code\u003E contains policy infrastructure used to keep security and application decisions explicit and extensible.\u003C/p\u003E\n\u003Cp\u003EPolicies are part of the server architecture rather than application-specific transport logic.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-implementations\u0022\u003ESecurity Implementations\u003C/h3\u003E\n\u003Cp\u003ESecurity algorithms can also live behind focused implementations.\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\u003ECodeBeam.UltimateAuth.Security.Argon2\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EKeeping security implementations separate avoids coupling the entire platform to one concrete algorithm or provider.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022modularity-without-an-incomplete-framework\u0022\u003EModularity Without an Incomplete Framework\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s modularity has an important design goal:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EExtensibility should not require developers to build the missing half of the framework themselves.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EEvery major extensibility boundary should have a practical UltimateAuth-provided path.\u003C/p\u003E\n\u003Cp\u003EThe common model 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\u003E UltimateAuth Contract\n |\n \u002B-----------\u002B-----------\u002B\n | |\n Reference Path Custom Path\n | |\n Works out of the box Replace when needed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis means the platform can provide strong defaults and reference implementations while still allowing advanced applications to replace individual capabilities.\u003C/p\u003E\n\u003Cp\u003EA customization should be local to the capability being replaced rather than requiring the authentication architecture to be rewritten.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022replaceability-by-design\u0022\u003EReplaceability by Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is intentionally built from small architectural boundaries.\u003C/p\u003E\n\u003Cp\u003EThe goal is not modularity for its own sake.\u003C/p\u003E\n\u003Cp\u003EThe goal is to reduce the cost of change.\u003C/p\u003E\n\u003Cp\u003EAn application may start with:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EEntityFrameworkCore.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Ewhile still having clearly separated domains and infrastructure underneath.\u003C/p\u003E\n\u003Cp\u003EIf a specialized application later needs a custom implementation for one capability, the architecture already has a boundary for it.\u003C/p\u003E\n\u003Cp\u003EThis provides a different model from choosing between two extremes:\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\u003ESimple but monolithic\n OR\nFlexible but difficult to configure\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUltimateAuth aims for:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Simple installation\n \u002B\n Modular internals\n \u002B\n Reference implementations\n \u002B\n Explicit contracts\n =\n Low-cost customization\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ESecurity-sensitive extension points still remain subject to UltimateAuth\u0027s security invariants and authority boundaries. Replaceability is intended to enable integration and specialization, not to bypass required security decisions.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-packages\u0022\u003EChoosing Packages\u003C/h2\u003E\n\u003Cp\u003EFor most developers, package selection should be simple.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-with-inmemory-persistence\u0022\u003EBlazor Server with InMemory persistence\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.InMemory.Bundle\ndotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe application both hosts UltimateAuth and consumes the Blazor client integration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-with-entity-framework-core\u0022\u003EBlazor Server with Entity Framework Core\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\ndotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-persistence\u0022\u003ECustom persistence\u003C/h3\u003E\n\u003Cp\u003EStart 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\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Reference.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eand provide the required persistence implementations for your architecture.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022standalone-blazor-webassembly\u0022\u003EStandalone Blazor WebAssembly\u003C/h3\u003E\n\u003Cp\u003EInstall the client integration:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe authentication authority lives in a remote UltimateAuth Server or UAuthHub.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-rule-of-thumb\u0022\u003EThe Rule of Thumb\u003C/h2\u003E\n\u003Cp\u003EYou generally do not need to assemble UltimateAuth package-by-package.\u003C/p\u003E\n\u003Cp\u003EFor the \u003Cstrong\u003EServer\u003C/strong\u003E:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EChoose the Bundle that matches your infrastructure.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EFor the \u003Cstrong\u003EClient\u003C/strong\u003E:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EChoose the Client package that matches your application platform.\u003C/p\u003E\n\u003C/blockquote\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\u003ESERVER\n\u0026quot;What infrastructure do I use?\u0026quot;\n |\n \u002B-- Reference.Bundle\n \u002B-- InMemory.Bundle\n \u002B-- EntityFrameworkCore.Bundle\n\n\nCLIENT\n\u0026quot;What kind of application am I building?\u0026quot;\n |\n \u002B-- Client.Blazor\n \u002B-- Client.Maui (future)\n \u002B-- ...\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe detailed package graph exists so that UltimateAuth remains extensible.\u003C/p\u003E\n\u003Cp\u003EThe bundles and client integrations exist so that you do not have to manage that graph for ordinary applications.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch1 id=\u0022next\u0022\u003ENext\u003C/h1\u003E\n\u003Cp\u003EYou now have the mental model needed to understand the rest of the UltimateAuth documentation:\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\u003ECore\n |\n \u002B-- Server --\u0026gt; choose a Bundle\n |\n \u002B-- Client --\u0026gt; choose your platform integration\n\nServer\n |\n \u002B-- Essential server subsystems\n |\n \u002B-- Plugin Domains\n |\n \u002B-- Contracts\n \u002B-- Runtime\n \u002B-- Reference implementations\n \u002B-- Persistence implementations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EContinue with the \u003Ca href=\u0022./quickstart.md\u0022\u003EQuick Start\u003C/a\u003E to build your first UltimateAuth application.\u003C/p\u003E\n\u003Cp\u003EFor persistent storage, UAuthHub, standalone WebAssembly, Resource API, and other deployment models, continue with the \u003Ca href=\u0022./real-world-setup.md\u0022\u003EReal-World Setup\u003C/a\u003E.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth is designed as a modular authentication and identity platform for .NET.\u003C/p\u003E\n\u003Cp\u003EThe platform has three primary foundations:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E UltimateAuth\n |\n Core\n |\n \u002B------------\u002B------------\u002B\n | |\n Server Client\n | |\n Server composition Client integration\n through bundles for the application\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003ECore\u003C/strong\u003E defines the common language and foundational abstractions of the platform.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EServer\u003C/strong\u003E hosts and executes authentication and identity capabilities.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient\u003C/strong\u003E provides the application-facing model used by applications to interact with those capabilities.\u003C/p\u003E\n\u003Cp\u003EAn application does not necessarily need both Server and Client. Depending on its role, it may use:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EServer only\u003C/li\u003E\n\u003Cli\u003EClient only\u003C/li\u003E\n\u003Cli\u003EServer and Client together\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis separation is intentional. UltimateAuth is designed so that application topology does not dictate a monolithic package structure.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022platform-at-a-glance\u0022\u003EPlatform at a Glance\u003C/h2\u003E\n\u003Cp\u003EA useful way to think about UltimateAuth 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\n|\n\u002B-- Core\n|\n\u002B-- Server\n| |\n| \u002B-- Essential server capabilities\n| | \u002B-- Authentication\n| | \u002B-- Sessions\n| | \u002B-- Tokens\n| | \u002B-- Policies\n| |\n| \u002B-- Plugin Domains\n| | \u002B-- Users\n| | \u002B-- Credentials\n| | \u002B-- Authorization\n| |\n| \u002B-- Implementations\n| \u002B-- Reference\n| \u002B-- InMemory\n| \u002B-- Entity Framework Core\n|\n\u002B-- Client\n |\n \u002B-- Client.Blazor\n \u002B-- Client.AspNetCore compatibility\n \u002B-- Future client integrations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe important distinction is that these packages are not intended to make installation complicated.\u003C/p\u003E\n\u003Cp\u003EThe internal architecture is modular, while the installation experience is deliberately composed around \u003Cstrong\u003Eserver bundles\u003C/strong\u003E and \u003Cstrong\u003Eclient-specific packages\u003C/strong\u003E.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core\u0022\u003ECore\u003C/h2\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.core\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Core\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003ECore is the foundation of the entire UltimateAuth platform.\u003C/p\u003E\n\u003Cp\u003EIt contains the shared primitives, domain concepts, contracts, abstractions, value objects, configuration foundations, and common infrastructure required by the rest of UltimateAuth.\u003C/p\u003E\n\u003Cp\u003EConcepts such as UltimateAuth\u0027s identity and session model originate from this layer.\u003C/p\u003E\n\u003Cp\u003ECore is intentionally independent from a particular application type or persistence technology.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Core\n |\n \u002B--------\u002B--------\u002B\n | |\n Server Client\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EServer and Client share the same platform vocabulary because both are built on Core.\u003C/p\u003E\n\u003Cp\u003EMost application developers do not need to install Core directly. It is normally brought into the application through the appropriate Server bundle or Client integration package.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server\u0022\u003EServer\u003C/h2\u003E\n\u003Cp\u003EThe Server side is the authority that executes UltimateAuth authentication and identity behavior.\u003C/p\u003E\n\u003Cp\u003EIt is responsible for the server-side runtime, including authentication flows, security decisions, orchestration, endpoints, middleware integration, and the coordination of server capabilities.\u003C/p\u003E\n\u003Cp\u003EAt source level, \u003Ccode\u003ECodeBeam.UltimateAuth.Server\u003C/code\u003E is the central server project and depends on major server domains such 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\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\u003ECodeBeam.UltimateAuth.Server\n|\n\u002B-- Core\n\u002B-- Authorization\n\u002B-- Credentials\n\u002B-- Users\n\u002B-- Policies\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOther essential server capabilities, including authentication, sessions, and tokens, are kept in focused projects rather than being merged into one oversized server assembly.\u003C/p\u003E\n\u003Cp\u003EThis gives UltimateAuth a modular internal architecture without forcing that complexity onto application developers.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-i-install-codebeam.ultimateauth.server-directly\u0022\u003EDo I install \u003Ccode\u003ECodeBeam.UltimateAuth.Server\u003C/code\u003E directly?\u003C/h3\u003E\n\u003Cp\u003EFor normal application setup, \u003Cstrong\u003Eno\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe recommended server installation unit is a \u003Cstrong\u003EBundle\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EA bundle represents a complete server composition for a particular infrastructure strategy.\u003C/p\u003E\n\u003Cp\u003EInstead of manually selecting all server packages and implementations, choose the bundle that matches your application.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Your Server\n |\n Bundle\n |\n \u002B---------------\u002B---------------\u002B\n | | |\n Server Domains Infrastructure\n | | |\n Runtime Reference Persistence\n behavior providers\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is an important part of the UltimateAuth design:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EThe framework remains modular internally, while common server configurations remain simple to install.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-bundles\u0022\u003EServer Bundles\u003C/h2\u003E\n\u003Cp\u003EBundles are server-side composition packages.\u003C/p\u003E\n\u003Cp\u003EThey collect the UltimateAuth server capabilities and the implementations required for a particular configuration into a single installation unit.\u003C/p\u003E\n\u003Cp\u003EYou normally choose \u003Cstrong\u003Eone server bundle\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reference-bundle\u0022\u003EReference Bundle\u003C/h3\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.reference.bundle\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Reference.Bundle\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003EThe Reference Bundle provides the recommended UltimateAuth server composition without selecting a persistence implementation for your application.\u003C/p\u003E\n\u003Cp\u003EUse it when you want UltimateAuth\u0027s reference behavior but provide your own persistence infrastructure.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EReference.Bundle\n|\n\u002B-- Server\n\u002B-- UltimateAuth reference implementations\n\u002B-- Required server capabilities\n|\n\u002B-- Persistence: supplied by your application\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is the lowest-level recommended server composition for applications that want to own their storage implementation.\u003C/p\u003E\n\u003Cp\u003EIt is also an important extensibility point: UltimateAuth provides a recommended architecture without requiring applications to use one particular persistence technology.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inmemory-bundle\u0022\u003EInMemory Bundle\u003C/h3\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.inmemory.bundle\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.InMemory.Bundle\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003EThe InMemory Bundle provides a complete server setup backed by UltimateAuth\u0027s in-memory implementations.\u003C/p\u003E\n\u003Cp\u003EIt is primarily intended for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Edevelopment\u003C/li\u003E\n\u003Cli\u003EQuick Start\u003C/li\u003E\n\u003Cli\u003Esamples\u003C/li\u003E\n\u003Cli\u003Eautomated tests\u003C/li\u003E\n\u003Cli\u003Eevaluation\u003C/li\u003E\n\u003Cli\u003Eprototypes\u003C/li\u003E\n\u003C/ul\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\u003EInMemory.Bundle\n|\n\u002B-- Server\n\u002B-- Reference implementations\n\u002B-- InMemory persistence implementations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is the easiest way to start an UltimateAuth server.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022entity-framework-core-bundle\u0022\u003EEntity Framework Core Bundle\u003C/h3\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.entityframeworkcore.bundle\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003EThe Entity Framework Core Bundle provides the complete server composition using UltimateAuth\u0027s EF Core persistence implementations.\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\u003EEntityFrameworkCore.Bundle\n|\n\u002B-- Server\n\u002B-- Reference implementations\n\u002B-- EF Core persistence implementations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor applications that use EF Core for persistent UltimateAuth data, this is normally the server package to install.\u003C/p\u003E\n\u003Cp\u003EThe developer does not need to manually assemble the individual Users, Credentials, Authorization, Sessions, Tokens, and other persistence packages.\u003C/p\u003E\n\u003Cp\u003EThe bundle performs that composition.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-bundles-matter\u0022\u003EWhy Bundles Matter\u003C/h3\u003E\n\u003Cp\u003EA highly modular framework can easily make the developer experience worse if every application has to understand and install its internal package graph.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth deliberately separates \u003Cstrong\u003Earchitecture modularity\u003C/strong\u003E from \u003Cstrong\u003Einstallation complexity\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EInternally:\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\u003EServer\n \u002B Users\n \u002B Credentials\n \u002B Authorization\n \u002B Authentication\n \u002B Sessions\n \u002B Tokens\n \u002B Policies\n \u002B persistence implementations\n \u002B reference implementations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFrom the application:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis gives UltimateAuth two useful properties at the same time:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003Eindividual capabilities remain independently replaceable and evolvable;\u003C/li\u003E\n\u003Cli\u003Ethe common path remains simple.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EYou should not need to understand the complete internal dependency graph before building your first application.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client\u0022\u003EClient\u003C/h2\u003E\n\u003Cp\u003EThe Client side has a different packaging model.\u003C/p\u003E\n\u003Cp\u003EThere are \u003Cstrong\u003Eno Client bundles\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EInstead, applications install the client package designed for their application 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\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Client Foundation\n |\n \u002B-------------\u002B-------------\u002B\n | | |\n Blazor MAUI Future\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor example, a Blazor application installs:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA future MAUI integration can follow the same model without changing the underlying UltimateAuth Client architecture.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022codebeam.ultimateauth.client\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client\u003C/code\u003E\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client\u003C/code\u003E is the shared client foundation.\u003C/p\u003E\n\u003Cp\u003EIt contains common client contracts, abstractions, flow APIs, state concepts, and runtime-independent client behavior.\u003C/p\u003E\n\u003Cp\u003EIt should be thought of as the foundation on which concrete UltimateAuth client integrations are built.\u003C/p\u003E\n\u003Cp\u003EMost applications should therefore install their platform-specific client package rather than installing \u003Ccode\u003ECodeBeam.UltimateAuth.Client\u003C/code\u003E directly.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022codebeam.ultimateauth.client.blazor\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.Blazor\u003C/code\u003E\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.Blazor\u003C/code\u003E is the concrete UltimateAuth client integration for Blazor.\u003C/p\u003E\n\u003Cp\u003EIt builds on the Client foundation and adds the Blazor-specific runtime and UI integration required by Blazor applications.\u003C/p\u003E\n\u003Cp\u003EThis includes capabilities such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthApp\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EUAuthStateView\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003Eauthentication state integration\u003C/li\u003E\n\u003Cli\u003EBlazor authorization integration\u003C/li\u003E\n\u003Cli\u003Erouting integration\u003C/li\u003E\n\u003Cli\u003Eauthentication flow components\u003C/li\u003E\n\u003Cli\u003EJavaScript transport integration\u003C/li\u003E\n\u003Cli\u003Eclient lifecycle coordination\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EConceptually:\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\u003ECore\n |\nClient\n |\nClient.Blazor\n |\nYour Blazor Application\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis model keeps the shared Client architecture independent from Blazor while still giving Blazor developers one natural package to install.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-compatibility-packages\u0022\u003EClient Compatibility Packages\u003C/h3\u003E\n\u003Cp\u003ENot every client-side package represents a complete client implementation.\u003C/p\u003E\n\u003Cp\u003ESome packages exist to bridge UltimateAuth with a host framework.\u003C/p\u003E\n\u003Ch4 id=\u0022codebeam.ultimateauth.client.aspnetcore\u0022\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.AspNetCore\u003C/code\u003E\u003C/h4\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Client.AspNetCore\u003C/code\u003E is a lightweight ASP.NET Core compatibility layer for UltimateAuth client applications.\u003C/p\u003E\n\u003Cp\u003EIt allows client-oriented applications to integrate with ASP.NET Core authentication and authorization infrastructure without introducing the full UltimateAuth Server runtime.\u003C/p\u003E\n\u003Cp\u003EThis is particularly useful for hosting models such as Blazor Web App with Interactive WebAssembly.\u003C/p\u003E\n\u003Cp\u003EIt can enable compatibility with ASP.NET Core features such as \u003Ccode\u003E[Authorize]\u003C/code\u003E, but it does \u003Cstrong\u003Enot\u003C/strong\u003E implement authentication itself and does not turn the application into an UltimateAuth Server.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022application-composition\u0022\u003EApplication Composition\u003C/h2\u003E\n\u003Cp\u003EBecause Server and Client are independent platform sides, different applications can compose UltimateAuth differently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-only\u0022\u003EServer Only\u003C/h3\u003E\n\u003Cp\u003ETypical examples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ededicated authentication servers\u003C/li\u003E\n\u003Cli\u003Ebackend services hosting UltimateAuth\u003C/li\u003E\n\u003Cli\u003Eserver-side identity infrastructure\u003C/li\u003E\n\u003C/ul\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\u003EApplication\n|\n\u002B-- Server Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-only\u0022\u003EClient Only\u003C/h3\u003E\n\u003Cp\u003ETypical examples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Estandalone Blazor WebAssembly\u003C/li\u003E\n\u003Cli\u003Efuture MAUI applications\u003C/li\u003E\n\u003Cli\u003Eapplications using a remote UAuthHub\u003C/li\u003E\n\u003C/ul\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\u003EApplication\n|\n\u002B-- Client.Blazor\n |\n \u002B---- remote UltimateAuth Server / UAuthHub\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-client\u0022\u003EServer \u002B Client\u003C/h3\u003E\n\u003Cp\u003ESome applications host UltimateAuth and consume its client API in the same application.\u003C/p\u003E\n\u003Cp\u003EBlazor Server is a common 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\u003EBlazor Server Application\n|\n\u002B-- Server Bundle\n|\n\u002B-- Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe two sides remain architecturally distinct even when deployed in the same process.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022plugin-domains\u0022\u003EPlugin Domains\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth extends its identity platform through \u003Cstrong\u003EPlugin Domains\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe current primary Plugin Domains are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUsers\u003C/li\u003E\n\u003Cli\u003ECredentials\u003C/li\u003E\n\u003Cli\u003EAuthorization\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA Plugin Domain is more than a single assembly. It is a capability area with contracts, runtime behavior, recommended implementations, and infrastructure implementations.\u003C/p\u003E\n\u003Cp\u003EThis structure allows a domain to remain replaceable without requiring applications to redesign the rest of UltimateAuth.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022anatomy-of-a-plugin-domain\u0022\u003EAnatomy of a Plugin Domain\u003C/h3\u003E\n\u003Cp\u003EA typical Plugin Domain follows this 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\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\u003EPlugin Domain\n|\n\u002B-- Contracts\n|\n\u002B-- Domain / Runtime\n|\n\u002B-- Reference\n|\n\u002B-- Persistence\n |\n \u002B-- InMemory\n \u002B-- EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach layer has a distinct purpose.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022contracts\u0022\u003EContracts\u003C/h3\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\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\u003ECodeBeam.UltimateAuth.Users.Contracts\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EContracts define the public boundary of the domain. Server and Client can communicate through same contracts without requiring one to depend on the other.\u003C/p\u003E\n\u003Cp\u003EThey are intentionally kept close to Core and represent the smallest dependency surface for applications or extensions that only need to interact with the domain contract.\u003C/p\u003E\n\u003Cp\u003EDepending on the domain, contracts can contain concepts such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Erequests\u003C/li\u003E\n\u003Cli\u003Eresults\u003C/li\u003E\n\u003Cli\u003Epublic models\u003C/li\u003E\n\u003Cli\u003Eabstractions\u003C/li\u003E\n\u003Cli\u003Edomain-facing contracts\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis allows another implementation to integrate with UltimateAuth without depending on the entire built-in implementation.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022domain-runtime\u0022\u003EDomain / Runtime\u003C/h3\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\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\u003ECodeBeam.UltimateAuth.Users\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe main domain project provides the runtime and application behavior of that Plugin Domain.\u003C/p\u003E\n\u003Cp\u003EIt operates against abstractions rather than requiring one persistence implementation.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reference-implementations\u0022\u003EReference Implementations\u003C/h3\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\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\u003ECodeBeam.UltimateAuth.Users.Reference\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EReference packages contain the implementation UltimateAuth recommends as the standard baseline for the domain.\u003C/p\u003E\n\u003Cp\u003EThe distinction between \u003Cstrong\u003Econtract\u003C/strong\u003E and \u003Cstrong\u003Ereference implementation\u003C/strong\u003E is intentional.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth does not leave extensibility points empty and require every developer to design critical identity behavior from scratch.\u003C/p\u003E\n\u003Cp\u003EInstead, the platform provides:\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\u003EContract\n |\n \u002B---- UltimateAuth Reference Implementation\n |\n \u002B---- Your Custom Implementation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor the common path, use the UltimateAuth reference implementation.\u003C/p\u003E\n\u003Cp\u003EFor specialized requirements, replace the appropriate implementation while preserving the surrounding platform contract and security boundaries.\u003C/p\u003E\n\u003Cp\u003EThis pattern gives advanced applications extensibility without making simple applications incomplete.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022persistence-implementations\u0022\u003EPersistence Implementations\u003C/h3\u003E\n\u003Cp\u003EPlugin Domains can provide persistence-specific packages independently.\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|\n\u002B-- Users.Contracts\n\u002B-- Users\n\u002B-- Users.Reference\n\u002B-- Users.InMemory\n\u002B-- Users.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same pattern applies to other Plugin Domains where appropriate.\u003C/p\u003E\n\u003Cp\u003EA developer using a server bundle normally does not need to install these packages individually. The selected bundle composes the appropriate persistence strategy.\u003C/p\u003E\n\u003Cp\u003ETheir separation exists primarily to preserve architectural modularity and replaceability.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022current-plugin-domains\u0022\u003ECurrent Plugin Domains\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022users\u0022\u003EUsers\u003C/h3\u003E\n\u003Cp\u003EThe Users domain owns user lifecycle and user-management capabilities.\u003C/p\u003E\n\u003Cp\u003EIts package family 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\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\u003ECodeBeam.UltimateAuth.Users.Contracts\nCodeBeam.UltimateAuth.Users\nCodeBeam.UltimateAuth.Users.Reference\nCodeBeam.UltimateAuth.Users.InMemory\nCodeBeam.UltimateAuth.Users.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentials\u0022\u003ECredentials\u003C/h3\u003E\n\u003Cp\u003EThe Credentials domain owns credential-related capabilities and credential management.\u003C/p\u003E\n\u003Cp\u003EIts package family 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\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\u003ECodeBeam.UltimateAuth.Credentials.Contracts\nCodeBeam.UltimateAuth.Credentials\nCodeBeam.UltimateAuth.Credentials.Reference\nCodeBeam.UltimateAuth.Credentials.InMemory\nCodeBeam.UltimateAuth.Credentials.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization\u0022\u003EAuthorization\u003C/h3\u003E\n\u003Cp\u003EThe Authorization domain provides UltimateAuth authorization capabilities.\u003C/p\u003E\n\u003Cp\u003EIts package family 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\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\u003ECodeBeam.UltimateAuth.Authorization.Contracts\nCodeBeam.UltimateAuth.Authorization\nCodeBeam.UltimateAuth.Authorization.Reference\nCodeBeam.UltimateAuth.Authorization.InMemory\nCodeBeam.UltimateAuth.Authorization.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same architectural pattern makes these capabilities independently evolvable while keeping their public boundaries explicit.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022essential-server-capabilities\u0022\u003EEssential Server Capabilities\u003C/h2\u003E\n\u003Cp\u003EAuthentication, Sessions, and Tokens are slightly different from Plugin Domains.\u003C/p\u003E\n\u003Cp\u003EThey are fundamental parts of an UltimateAuth Server and are not treated as optional identity plugins in the same sense as Users, Credentials, or Authorization.\u003C/p\u003E\n\u003Cp\u003EHowever, they are still separated into focused projects.\u003C/p\u003E\n\u003Cp\u003EConceptually:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E UltimateAuth Server\n |\n \u002B--------------------\u002B--------------------\u002B\n | | |\n Authentication Sessions Tokens\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhy separate them if the Server needs them?\u003C/p\u003E\n\u003Cp\u003EBecause \u003Cstrong\u003Erequired does not have to mean monolithic\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EKeeping these capabilities isolated:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Eprevents the central Server project from becoming an oversized implementation assembly;\u003C/li\u003E\n\u003Cli\u003Ekeeps responsibilities and dependency boundaries explicit;\u003C/li\u003E\n\u003Cli\u003Eallows implementations to evolve independently;\u003C/li\u003E\n\u003Cli\u003Emakes persistence providers independently composable;\u003C/li\u003E\n\u003Cli\u003Ekeeps replacement and testing boundaries smaller.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThey can be thought of as \u003Cstrong\u003Emodular server subsystems\u003C/strong\u003E rather than full Plugin Domains.\u003C/p\u003E\n\u003Cp\u003EFor example, persistence implementations can still exist independently:\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\n\u002B-- Authentication.InMemory\n\u002B-- Authentication.EntityFrameworkCore\n\nSessions\n\u002B-- Sessions.InMemory\n\u002B-- Sessions.EntityFrameworkCore\n\nTokens\n\u002B-- Tokens.InMemory\n\u002B-- Tokens.EntityFrameworkCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe selected Server bundle composes the required implementations for the application.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies-and-security\u0022\u003EPolicies and Security\u003C/h2\u003E\n\u003Cp\u003ESome capabilities are deliberately isolated even though they support multiple areas of the platform.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies\u0022\u003EPolicies\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003ECodeBeam.UltimateAuth.Policies\u003C/code\u003E contains policy infrastructure used to keep security and application decisions explicit and extensible.\u003C/p\u003E\n\u003Cp\u003EPolicies are part of the server architecture rather than application-specific transport logic.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-implementations\u0022\u003ESecurity Implementations\u003C/h3\u003E\n\u003Cp\u003ESecurity algorithms can also live behind focused implementations.\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\u003ECodeBeam.UltimateAuth.Security.Argon2\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EKeeping security implementations separate avoids coupling the entire platform to one concrete algorithm or provider.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022modularity-without-an-incomplete-framework\u0022\u003EModularity Without an Incomplete Framework\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s modularity has an important design goal:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EExtensibility should not require developers to build the missing half of the framework themselves.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EEvery major extensibility boundary should have a practical UltimateAuth-provided path.\u003C/p\u003E\n\u003Cp\u003EThe common model 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\u003E UltimateAuth Contract\n |\n \u002B-----------\u002B-----------\u002B\n | |\n Reference Path Custom Path\n | |\n Works out of the box Replace when needed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis means the platform can provide strong defaults and reference implementations while still allowing advanced applications to replace individual capabilities.\u003C/p\u003E\n\u003Cp\u003EA customization should be local to the capability being replaced rather than requiring the authentication architecture to be rewritten.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022replaceability-by-design\u0022\u003EReplaceability by Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is intentionally built from small architectural boundaries.\u003C/p\u003E\n\u003Cp\u003EThe goal is not modularity for its own sake.\u003C/p\u003E\n\u003Cp\u003EThe goal is to reduce the cost of change.\u003C/p\u003E\n\u003Cp\u003EAn application may start with:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EEntityFrameworkCore.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Ewhile still having clearly separated domains and infrastructure underneath.\u003C/p\u003E\n\u003Cp\u003EIf a specialized application later needs a custom implementation for one capability, the architecture already has a boundary for it.\u003C/p\u003E\n\u003Cp\u003EThis provides a different model from choosing between two extremes:\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\u003ESimple but monolithic\n OR\nFlexible but difficult to configure\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUltimateAuth aims for:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Simple installation\n \u002B\n Modular internals\n \u002B\n Reference implementations\n \u002B\n Explicit contracts\n =\n Low-cost customization\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ESecurity-sensitive extension points still remain subject to UltimateAuth\u0027s security invariants and authority boundaries. Replaceability is intended to enable integration and specialization, not to bypass required security decisions.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-packages\u0022\u003EChoosing Packages\u003C/h2\u003E\n\u003Cp\u003EFor most developers, package selection should be simple.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-with-inmemory-persistence\u0022\u003EBlazor Server with InMemory persistence\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.InMemory.Bundle\ndotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe application both hosts UltimateAuth and consumes the Blazor client integration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-with-entity-framework-core\u0022\u003EBlazor Server with Entity Framework Core\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\ndotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-persistence\u0022\u003ECustom persistence\u003C/h3\u003E\n\u003Cp\u003EStart 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\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Reference.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Eand provide the required persistence implementations for your architecture.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022standalone-blazor-webassembly\u0022\u003EStandalone Blazor WebAssembly\u003C/h3\u003E\n\u003Cp\u003EInstall the client integration:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe authentication authority lives in a remote UltimateAuth Server or UAuthHub.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-rule-of-thumb\u0022\u003EThe Rule of Thumb\u003C/h2\u003E\n\u003Cp\u003EYou generally do not need to assemble UltimateAuth package-by-package.\u003C/p\u003E\n\u003Cp\u003EFor the \u003Cstrong\u003EServer\u003C/strong\u003E:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EChoose the Bundle that matches your infrastructure.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EFor the \u003Cstrong\u003EClient\u003C/strong\u003E:\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EChoose the Client package that matches your application platform.\u003C/p\u003E\n\u003C/blockquote\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\u003ESERVER\n\u0026quot;What infrastructure do I use?\u0026quot;\n |\n \u002B-- Reference.Bundle\n \u002B-- InMemory.Bundle\n \u002B-- EntityFrameworkCore.Bundle\n\n\nCLIENT\n\u0026quot;What kind of application am I building?\u0026quot;\n |\n \u002B-- Client.Blazor\n \u002B-- Client.Maui (future)\n \u002B-- ...\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe detailed package graph exists so that UltimateAuth remains extensible.\u003C/p\u003E\n\u003Cp\u003EThe bundles and client integrations exist so that you do not have to manage that graph for ordinary applications.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch1 id=\u0022next\u0022\u003ENext\u003C/h1\u003E\n\u003Cp\u003EYou now have the mental model needed to understand the rest of the UltimateAuth documentation:\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\u003ECore\n |\n \u002B-- Server --\u0026gt; choose a Bundle\n |\n \u002B-- Client --\u0026gt; choose your platform integration\n\nServer\n |\n \u002B-- Essential server subsystems\n |\n \u002B-- Plugin Domains\n |\n \u002B-- Contracts\n \u002B-- Runtime\n \u002B-- Reference implementations\n \u002B-- Persistence implementations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EContinue with the \u003Ca href=\u0022/docs/getting-started/quickstart\u0022\u003EQuick Start\u003C/a\u003E to build your first UltimateAuth application.\u003C/p\u003E\n\u003Cp\u003EFor persistent storage, UAuthHub, standalone WebAssembly, Resource API, and other deployment models, continue with the \u003Ca href=\u0022/docs/getting-started/real-world-setup\u0022\u003EReal-World Setup\u003C/a\u003E.\u003C/p\u003E\n", "Headings": [ { "Id": "platform-at-a-glance", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json index 75b878b9..7efe0f99 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json @@ -1,7 +1,7 @@ { "Slug": "getting-started/index", "Title": "Getting Started", - "Html": "\n\u003Cp\u003EWelcome to \u003Cstrong\u003EUltimateAuth\u003C/strong\u003E, a unified authentication and authorization framework for modern .NET applications.\u003C/p\u003E\n\u003Cp\u003EAuthentication should not become a different system every time your application gains a new client, tenant, device, or security requirement. UltimateAuth brings these concerns into \u003Cstrong\u003Eone coherent model\u003C/strong\u003E\u2014without pretending that every application needs the same authentication strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOne framework. Multiple authentication models. A consistent security lifecycle.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-ultimateauth\u0022\u003EWhat is UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is a modular, flow-driven framework for building and managing authentication in .NET. It connects concepts that are often implemented separately:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ESessions, cookies and tokens\u003C/strong\u003E \u2014 stateful session management alongside access and refresh token workflows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthentication and authorization\u003C/strong\u003E \u2014 identity establishment and access decisions within a consistent architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUsers and credentials\u003C/strong\u003E \u2014 identity, identifiers, credentials, and their security state.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevices and session continuity\u003C/strong\u003E \u2014 device-aware session chains, refresh, logout, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClients and delivery mechanisms\u003C/strong\u003E \u2014 browser, API, Blazor, and mobile scenarios with client-aware behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ETenants and isolation\u003C/strong\u003E \u2014 tenant-aware request context and authentication boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESecurity policies and execution flows\u003C/strong\u003E \u2014 explicit decisions, validation, and controlled state transitions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe goal is not to hide these concepts. It is to make them \u003Cstrong\u003Ework together predictably\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022unified-does-not-mean-one-size-fits-all\u0022\u003EUnified Does Not Mean One-Size-Fits-All\u003C/h2\u003E\n\u003Cp\u003EA server-rendered web application, a browser-based SPA, a mobile app, and a backend API do not share the same trust boundaries or credential transport requirements.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides a common foundation while allowing behavior to be shaped by the \u003Cstrong\u003Eeffective authentication mode\u003C/strong\u003E, \u003Cstrong\u003Eclient profile\u003C/strong\u003E, \u003Cstrong\u003Etenant\u003C/strong\u003E, and \u003Cstrong\u003Erequest context\u003C/strong\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcern\u003C/th\u003E\n\u003Cth\u003EWhat UltimateAuth brings together\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\u003EAuthentication models\u003C/td\u003E\n\u003Ctd\u003ESession-oriented and token-oriented workflows\u003C/td\u003E\n\u003Ctd\u003EDifferent clients can participate in one security architecture\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession lifecycle\u003C/td\u003E\n\u003Ctd\u003ERoot \u2192 Chain \u2192 Session\u003C/td\u003E\n\u003Ctd\u003EManage authentication at identity, device, and session scope\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient environments\u003C/td\u003E\n\u003Ctd\u003EASP.NET Core, Blazor, APIs, and .NET mobile clients\u003C/td\u003E\n\u003Ctd\u003EAvoid rebuilding the core authentication model for each frontend\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMulti-tenancy\u003C/td\u003E\n\u003Ctd\u003ETenant resolution and tenant-bound context\u003C/td\u003E\n\u003Ctd\u003EKeep authentication operations within the intended tenant boundary\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredentials and identities\u003C/td\u003E\n\u003Ctd\u003EUser identifiers, credential providers, and security state\u003C/td\u003E\n\u003Ctd\u003ESeparate \u003Cem\u003Ewho the user is\u003C/em\u003E from \u003Cem\u003Ehow they prove it\u003C/em\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESecurity enforcement\u003C/td\u003E\n\u003Ctd\u003EContext, validation, authority, and orchestration\u003C/td\u003E\n\u003Ctd\u003EMake decisions explicit rather than scattering checks across endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential delivery\u003C/td\u003E\n\u003Ctd\u003EMode- and client-aware response policies\u003C/td\u003E\n\u003Ctd\u003EHandle cookies, tokens, and redirects according to the scenario\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession termination\u003C/td\u003E\n\u003Ctd\u003ELogout, targeted revocation, and device management\u003C/td\u003E\n\u003Ctd\u003ERespond proportionately to ordinary sign-out and security incidents\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EUnified\u003C/strong\u003E is therefore about a shared security model\u2014not forcing all applications to use identical cookies, tokens, or endpoints.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-is-more-than-issuing-a-token\u0022\u003EAuthentication Is More Than Issuing a Token\u003C/h2\u003E\n\u003Cp\u003EIn many applications, authentication is described as a short sequence: validate credentials, generate a token, return it to the client.\u003C/p\u003E\n\u003Cp\u003EThat is only part of the story.\u003C/p\u003E\n\u003Cp\u003EA production authentication system must also answer questions such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhich tenant does this request belong to?\u003C/li\u003E\n\u003Cli\u003EWhich user and credential are being evaluated?\u003C/li\u003E\n\u003Cli\u003EIs an existing session still valid?\u003C/li\u003E\n\u003Cli\u003EIs the request coming from a known device?\u003C/li\u003E\n\u003Cli\u003ECan access be renewed, restricted, or terminated?\u003C/li\u003E\n\u003Cli\u003EWhat should happen when a credential changes or a device is no longer trusted?\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth treats authentication as a \u003Cstrong\u003Econtrolled lifecycle\u003C/strong\u003E. Login, refresh, and logout are operations within that lifecycle\u2014not unrelated endpoints that happen to exchange tokens.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessions-are-first-class\u0022\u003ESessions Are First-Class\u003C/h2\u003E\n\u003Cp\u003EWhere session-backed modes apply, UltimateAuth models authentication through a hierarchy:\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 Global session-security boundary\n \u2514\u2500\u2500 Chain Device-associated lifecycle\n \u2514\u2500\u2500 Session Individual authentication instance\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis structure gives the system a vocabulary for different security actions. A session can be invalidated, a device-associated chain can be terminated, and a broader security boundary can be revoked.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA token is not the entire authentication model.\u003C/strong\u003E It is a credential used within that model, subject to the applicable mode and validation rules.\u003C/p\u003E\n\u003Cp\u003ENot every authentication mode has identical persistence or session behavior. The session hierarchy describes the stateful lifecycle where that model is in use.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-driven-by-design\u0022\u003EFlow-Driven by Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth organizes authentication operations around explicit flows. A flow brings together the request context, operation-specific validation, security decisions, permitted state transitions, and response 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\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\u003ERequest\n \u2192 Tenant, session, and user resolution\n \u2192 AuthFlowContext\n \u2192 Flow-specific validation\n \u2192 Security decision and orchestration\n \u2192 State transition (when permitted)\n \u2192 Result and client-appropriate response\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is not a claim that pipelines or orchestration are new inventions. The value is in applying them \u003Cstrong\u003Econsistently across authentication operations\u003C/strong\u003E, so the rules are easier to reason about, test, and extend.\u003C/p\u003E\n\u003Cp\u003EFor the implementation model, see \u003Ca href=\u0022/docs/auth-flows/index\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022tenant-aware-from-the-request-boundary\u0022\u003ETenant-Aware from the Request Boundary\u003C/h2\u003E\n\u003Cp\u003EMulti-tenancy is not simply a \u003Ccode\u003ETenantId\u003C/code\u003E added to a user table. Authentication must understand \u003Cem\u003Ewhich tenant\u0027s security context\u003C/em\u003E a request is operating within.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth resolves tenant context as part of the ASP.NET Core request pipeline and carries it into the authentication flow. The flow context then combines that boundary with client settings, device information, and applicable session validation.\u003C/p\u003E\n\u003Cp\u003EThis helps teams design multi-tenant applications without treating tenant isolation as an afterthought. \u003Cstrong\u003ETenant resolution and configuration still need to be correctly integrated by the host application.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-for-different-clients-not-just-different-screens\u0022\u003EBuilt for Different Clients, Not Just Different Screens\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is designed for applications that may span several environments:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EASP.NET Core web applications\u003C/strong\u003E with server-managed authentication concerns.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EBlazor Server and Blazor WebAssembly\u003C/strong\u003E with different hosting and transport constraints.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E.NET MAUI\u003C/strong\u003E and other native clients with device-oriented authentication needs.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EHTTP APIs\u003C/strong\u003E serving browsers, applications, and other services.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EClient profiles and effective server options help adapt authentication behavior to the requesting client. Response policies determine how applicable credentials are delivered and whether a redirect belongs in the interaction.\u003C/p\u003E\n\u003Cp\u003EThe result is a common backend security vocabulary across multiple application types\u2014not a promise that every client uses the same transport or requires zero configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-is-a-lifecycle-too\u0022\u003ESecurity Is a Lifecycle, Too\u003C/h2\u003E\n\u003Cp\u003ESecurity is not finished when login succeeds. UltimateAuth\u0027s architecture accounts for what happens \u003Cem\u003Eafter\u003C/em\u003E access is established:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ESituation\u003C/th\u003E\n\u003Cth\u003ESecurity question\u003C/th\u003E\n\u003Cth\u003EUltimateAuth concept\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs in\u003C/td\u003E\n\u003Ctd\u003EShould this attempt establish authentication?\u003C/td\u003E\n\u003Ctd\u003ELogin flow and security decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA session needs renewal\u003C/td\u003E\n\u003Ctd\u003ECan continuity be preserved safely?\u003C/td\u003E\n\u003Ctd\u003ERefresh flow and session lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs out\u003C/td\u003E\n\u003Ctd\u003EWhich authentication scope should end?\u003C/td\u003E\n\u003Ctd\u003ELogout\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA device is lost\u003C/td\u003E\n\u003Ctd\u003EHow can that device\u0027s access be terminated?\u003C/td\u003E\n\u003Ctd\u003EChain-level management and revocation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA credential is compromised\u003C/td\u003E\n\u003Ctd\u003EHow should existing authentication be affected?\u003C/td\u003E\n\u003Ctd\u003ECredential security state and revocation policies\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAn administrator manages access\u003C/td\u003E\n\u003Ctd\u003EIs the operation permitted for this actor and target?\u003C/td\u003E\n\u003Ctd\u003EAuthorization and access context\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA key distinction is \u003Cstrong\u003Elogout versus revoke\u003C/strong\u003E. Ordinary logout ends authentication according to its scope; revocation is an irreversible invalidation of the targeted security state. Signing in again may establish \u003Cem\u003Enew\u003C/em\u003E authentication, but it does not restore what was revoked.\u003C/p\u003E\n\u003Cp\u003EExplore the details in \u003Ca href=\u0022/docs/auth-flows/login\u0022\u003ELogin Flow\u003C/a\u003E, \u003Ca href=\u0022/docs/auth-flows/refresh\u0022\u003ERefresh Flow\u003C/a\u003E, and \u003Ca href=\u0022/docs/auth-flows/logout\u0022\u003ELogout Flow\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022modular-and-extensible\u0022\u003EModular and Extensible\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is intended to serve both straightforward applications and systems with more demanding security requirements. Its architecture separates responsibilities such as core contracts, server execution, client integration, user management, credentials, and authorization.\u003C/p\u003E\n\u003Cp\u003EThis separation makes it possible to understand and evolve individual concerns without reducing authentication to a collection of disconnected custom handlers.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EExtensible does not mean every possible flow is already implemented.\u003C/strong\u003E Available modes, integrations, and capabilities should be evaluated against the version you are using; the documentation for each feature describes its current behavior and limitations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-should-you-choose-ultimateauth\u0022\u003EWhen Should You Choose UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is especially relevant when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need quickly-installed, consistent, secure and easy-to-use auth in your .NET application.\u003C/li\u003E\n\u003Cli\u003EYour .NET application has\u2014or will have\u2014\u003Cstrong\u003Emore than one client type\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003EYou need \u003Cstrong\u003Edevice-aware session management\u003C/strong\u003E, not only token generation.\u003C/li\u003E\n\u003Cli\u003EYou are designing a \u003Cstrong\u003Emulti-tenant\u003C/strong\u003E system with clear authentication boundaries.\u003C/li\u003E\n\u003Cli\u003EYou want \u003Cstrong\u003Eauthentication and authorization\u003C/strong\u003E to follow a consistent architectural model.\u003C/li\u003E\n\u003Cli\u003EYou need control over \u003Cstrong\u003Erefresh, logout, revocation, and credential lifecycle\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003EYou value a \u003Cstrong\u003Emodular, self-hosted, open-source\u003C/strong\u003E foundation you can understand and extend.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAlso for a small application with minimal authentication needs, UltimateAuth brings the simplest possible solution.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022open-source-first\u0022\u003EOpen Source First\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is built with a simple commitment: \u003Cstrong\u003Eself-hosted authentication should remain accessible\u003C/strong\u003E. The framework is open source, and self-hosting is intended to remain free. A hosted offering may provide an operational alternative, not a separate tier of authentication features.\u003C/p\u003E\n\u003Cp\u003EThe ambition is to build a framework that developers can adopt, inspect, contribute to, and trust\u2014not merely a service they must depend on.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022where-to-go-next\u0022\u003EWhere to Go Next\u003C/h2\u003E\n\u003Cp\u003EYou do not need to understand every internal component before getting started.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EContinue to Quick Start\u003C/strong\u003E to set up your first UltimateAuth application. Then explore \u003Ca href=\u0022/docs/auth-flows/index\u0022\u003EAuth Flows\u003C/a\u003E to understand how the pieces work together.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe idea to remember:\u003C/strong\u003E UltimateAuth unifies authentication concepts without erasing their differences. One framework, a consistent lifecycle, and room for the security model your application actually needs.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EWelcome to \u003Cstrong\u003EUltimateAuth\u003C/strong\u003E, a unified authentication and authorization framework for modern .NET applications.\u003C/p\u003E\n\u003Cp\u003EAuthentication should not become a different system every time your application gains a new client, tenant, device, or security requirement. UltimateAuth brings these concerns into \u003Cstrong\u003Eone coherent model\u003C/strong\u003E\u2014without pretending that every application needs the same authentication strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOne framework. Multiple authentication models. A consistent security lifecycle.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-ultimateauth\u0022\u003EWhat is UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is a modular, flow-driven framework for building and managing authentication in .NET. It connects concepts that are often implemented separately:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ESessions, cookies and tokens\u003C/strong\u003E \u2014 stateful session management alongside access and refresh token workflows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthentication and authorization\u003C/strong\u003E \u2014 identity establishment and access decisions within a consistent architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUsers and credentials\u003C/strong\u003E \u2014 identity, identifiers, credentials, and their security state.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevices and session continuity\u003C/strong\u003E \u2014 device-aware session chains, refresh, logout, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClients and delivery mechanisms\u003C/strong\u003E \u2014 browser, API, Blazor, and mobile scenarios with client-aware behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ETenants and isolation\u003C/strong\u003E \u2014 tenant-aware request context and authentication boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESecurity policies and execution flows\u003C/strong\u003E \u2014 explicit decisions, validation, and controlled state transitions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe goal is not to hide these concepts. It is to make them \u003Cstrong\u003Ework together predictably\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022unified-does-not-mean-one-size-fits-all\u0022\u003EUnified Does Not Mean One-Size-Fits-All\u003C/h2\u003E\n\u003Cp\u003EA server-rendered web application, a browser-based SPA, a mobile app, and a backend API do not share the same trust boundaries or credential transport requirements.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides a common foundation while allowing behavior to be shaped by the \u003Cstrong\u003Eeffective authentication mode\u003C/strong\u003E, \u003Cstrong\u003Eclient profile\u003C/strong\u003E, \u003Cstrong\u003Etenant\u003C/strong\u003E, and \u003Cstrong\u003Erequest context\u003C/strong\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcern\u003C/th\u003E\n\u003Cth\u003EWhat UltimateAuth brings together\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\u003EAuthentication models\u003C/td\u003E\n\u003Ctd\u003ESession-oriented and token-oriented workflows\u003C/td\u003E\n\u003Ctd\u003EDifferent clients can participate in one security architecture\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession lifecycle\u003C/td\u003E\n\u003Ctd\u003ERoot \u2192 Chain \u2192 Session\u003C/td\u003E\n\u003Ctd\u003EManage authentication at identity, device, and session scope\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient environments\u003C/td\u003E\n\u003Ctd\u003EASP.NET Core, Blazor, APIs, and .NET mobile clients\u003C/td\u003E\n\u003Ctd\u003EAvoid rebuilding the core authentication model for each frontend\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMulti-tenancy\u003C/td\u003E\n\u003Ctd\u003ETenant resolution and tenant-bound context\u003C/td\u003E\n\u003Ctd\u003EKeep authentication operations within the intended tenant boundary\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredentials and identities\u003C/td\u003E\n\u003Ctd\u003EUser identifiers, credential providers, and security state\u003C/td\u003E\n\u003Ctd\u003ESeparate \u003Cem\u003Ewho the user is\u003C/em\u003E from \u003Cem\u003Ehow they prove it\u003C/em\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESecurity enforcement\u003C/td\u003E\n\u003Ctd\u003EContext, validation, authority, and orchestration\u003C/td\u003E\n\u003Ctd\u003EMake decisions explicit rather than scattering checks across endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential delivery\u003C/td\u003E\n\u003Ctd\u003EMode- and client-aware response policies\u003C/td\u003E\n\u003Ctd\u003EHandle cookies, tokens, and redirects according to the scenario\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession termination\u003C/td\u003E\n\u003Ctd\u003ELogout, targeted revocation, and device management\u003C/td\u003E\n\u003Ctd\u003ERespond proportionately to ordinary sign-out and security incidents\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EUnified\u003C/strong\u003E is therefore about a shared security model\u2014not forcing all applications to use identical cookies, tokens, or endpoints.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-is-more-than-issuing-a-token\u0022\u003EAuthentication Is More Than Issuing a Token\u003C/h2\u003E\n\u003Cp\u003EIn many applications, authentication is described as a short sequence: validate credentials, generate a token, return it to the client.\u003C/p\u003E\n\u003Cp\u003EThat is only part of the story.\u003C/p\u003E\n\u003Cp\u003EA production authentication system must also answer questions such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhich tenant does this request belong to?\u003C/li\u003E\n\u003Cli\u003EWhich user and credential are being evaluated?\u003C/li\u003E\n\u003Cli\u003EIs an existing session still valid?\u003C/li\u003E\n\u003Cli\u003EIs the request coming from a known device?\u003C/li\u003E\n\u003Cli\u003ECan access be renewed, restricted, or terminated?\u003C/li\u003E\n\u003Cli\u003EWhat should happen when a credential changes or a device is no longer trusted?\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth treats authentication as a \u003Cstrong\u003Econtrolled lifecycle\u003C/strong\u003E. Login, refresh, and logout are operations within that lifecycle\u2014not unrelated endpoints that happen to exchange tokens.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessions-are-first-class\u0022\u003ESessions Are First-Class\u003C/h2\u003E\n\u003Cp\u003EWhere session-backed modes apply, UltimateAuth models authentication through a hierarchy:\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 Global session-security boundary\n \u2514\u2500\u2500 Chain Device-associated lifecycle\n \u2514\u2500\u2500 Session Individual authentication instance\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis structure gives the system a vocabulary for different security actions. A session can be invalidated, a device-associated chain can be terminated, and a broader security boundary can be revoked.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA token is not the entire authentication model.\u003C/strong\u003E It is a credential used within that model, subject to the applicable mode and validation rules.\u003C/p\u003E\n\u003Cp\u003ENot every authentication mode has identical persistence or session behavior. The session hierarchy describes the stateful lifecycle where that model is in use.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-driven-by-design\u0022\u003EFlow-Driven by Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth organizes authentication operations around explicit flows. A flow brings together the request context, operation-specific validation, security decisions, permitted state transitions, and response 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\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\u003ERequest\n \u2192 Tenant, session, and user resolution\n \u2192 AuthFlowContext\n \u2192 Flow-specific validation\n \u2192 Security decision and orchestration\n \u2192 State transition (when permitted)\n \u2192 Result and client-appropriate response\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is not a claim that pipelines or orchestration are new inventions. The value is in applying them \u003Cstrong\u003Econsistently across authentication operations\u003C/strong\u003E, so the rules are easier to reason about, test, and extend.\u003C/p\u003E\n\u003Cp\u003EFor the implementation model, see \u003Ca href=\u0022/docs/auth-flows/index\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022tenant-aware-from-the-request-boundary\u0022\u003ETenant-Aware from the Request Boundary\u003C/h2\u003E\n\u003Cp\u003EMulti-tenancy is not simply a \u003Ccode\u003ETenantId\u003C/code\u003E added to a user table. Authentication must understand \u003Cem\u003Ewhich tenant\u0027s security context\u003C/em\u003E a request is operating within.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth resolves tenant context as part of the ASP.NET Core request pipeline and carries it into the authentication flow. The flow context then combines that boundary with client settings, device information, and applicable session validation.\u003C/p\u003E\n\u003Cp\u003EThis helps teams design multi-tenant applications without treating tenant isolation as an afterthought. \u003Cstrong\u003ETenant resolution and configuration still need to be correctly integrated by the host application.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-for-different-clients-not-just-different-screens\u0022\u003EBuilt for Different Clients, Not Just Different Screens\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is designed for applications that may span several environments:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EASP.NET Core web applications\u003C/strong\u003E with server-managed authentication concerns.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EBlazor Server and Blazor WebAssembly\u003C/strong\u003E with different hosting and transport constraints.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E.NET MAUI\u003C/strong\u003E and other native clients with device-oriented authentication needs.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EHTTP APIs\u003C/strong\u003E serving browsers, applications, and other services.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EClient profiles and effective server options help adapt authentication behavior to the requesting client. Response policies determine how applicable credentials are delivered and whether a redirect belongs in the interaction.\u003C/p\u003E\n\u003Cp\u003EThe result is a common backend security vocabulary across multiple application types\u2014not a promise that every client uses the same transport or requires zero configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-is-a-lifecycle-too\u0022\u003ESecurity Is a Lifecycle, Too\u003C/h2\u003E\n\u003Cp\u003ESecurity is not finished when login succeeds. UltimateAuth\u0027s architecture accounts for what happens \u003Cem\u003Eafter\u003C/em\u003E access is established:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ESituation\u003C/th\u003E\n\u003Cth\u003ESecurity question\u003C/th\u003E\n\u003Cth\u003EUltimateAuth concept\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs in\u003C/td\u003E\n\u003Ctd\u003EShould this attempt establish authentication?\u003C/td\u003E\n\u003Ctd\u003ELogin flow and security decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA session needs renewal\u003C/td\u003E\n\u003Ctd\u003ECan continuity be preserved safely?\u003C/td\u003E\n\u003Ctd\u003ERefresh flow and session lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs out\u003C/td\u003E\n\u003Ctd\u003EWhich authentication scope should end?\u003C/td\u003E\n\u003Ctd\u003ELogout\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA device is lost\u003C/td\u003E\n\u003Ctd\u003EHow can that device\u0027s access be terminated?\u003C/td\u003E\n\u003Ctd\u003EChain-level management and revocation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA credential is compromised\u003C/td\u003E\n\u003Ctd\u003EHow should existing authentication be affected?\u003C/td\u003E\n\u003Ctd\u003ECredential security state and revocation policies\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAn administrator manages access\u003C/td\u003E\n\u003Ctd\u003EIs the operation permitted for this actor and target?\u003C/td\u003E\n\u003Ctd\u003EAuthorization and access context\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA key distinction is \u003Cstrong\u003Elogout versus revoke\u003C/strong\u003E. Ordinary logout ends authentication according to its scope; revocation is an irreversible invalidation of the targeted security state. Signing in again may establish \u003Cem\u003Enew\u003C/em\u003E authentication, but it does not restore what was revoked.\u003C/p\u003E\n\u003Cp\u003EExplore the details in \u003Ca href=\u0022/docs/auth-flows/login\u0022\u003ELogin Flow\u003C/a\u003E, \u003Ca href=\u0022/docs/auth-flows/refresh\u0022\u003ERefresh Flow\u003C/a\u003E, and \u003Ca href=\u0022/docs/auth-flows/logout\u0022\u003ELogout Flow\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022modular-and-extensible\u0022\u003EModular and Extensible\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is intended to serve both straightforward applications and systems with more demanding security requirements. Its architecture separates responsibilities such as core contracts, server execution, client integration, user management, credentials, and authorization.\u003C/p\u003E\n\u003Cp\u003EThis separation makes it possible to understand and evolve individual concerns without reducing authentication to a collection of disconnected custom handlers.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EExtensible does not mean every possible flow is already implemented.\u003C/strong\u003E Available modes, integrations, and capabilities should be evaluated against the version you are using; the documentation for each feature describes its current behavior and limitations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-should-you-choose-ultimateauth\u0022\u003EWhen Should You Choose UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is especially relevant when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need quickly-installed, consistent, secure and easy-to-use auth in your .NET application.\u003C/li\u003E\n\u003Cli\u003EYour .NET application has\u2014or will have\u2014\u003Cstrong\u003Emore than one client type\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003EYou need \u003Cstrong\u003Edevice-aware session management\u003C/strong\u003E, not only token generation.\u003C/li\u003E\n\u003Cli\u003EYou are designing a \u003Cstrong\u003Emulti-tenant\u003C/strong\u003E system with clear authentication boundaries.\u003C/li\u003E\n\u003Cli\u003EYou want \u003Cstrong\u003Eauthentication and authorization\u003C/strong\u003E to follow a consistent architectural model.\u003C/li\u003E\n\u003Cli\u003EYou need control over \u003Cstrong\u003Erefresh, logout, revocation, and credential lifecycle\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003EYou value a \u003Cstrong\u003Emodular, self-hosted, open-source\u003C/strong\u003E foundation you can understand and extend.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAlso for a small application with minimal authentication needs, UltimateAuth brings the simplest possible solution.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022open-source-first\u0022\u003EOpen Source First\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is built with a simple commitment: \u003Cstrong\u003Eself-hosted authentication should remain accessible\u003C/strong\u003E. The framework is open source, and self-hosting is intended to remain free. A hosted offering may provide an operational alternative, not a separate tier of authentication features.\u003C/p\u003E\n\u003Cp\u003EThe ambition is to build a framework that developers can adopt, inspect, contribute to, and trust\u2014not merely a service they must depend on.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022where-to-go-next\u0022\u003EWhere to Go Next\u003C/h2\u003E\n\u003Cp\u003EYou do not need to understand every internal component before getting started.\u003C/p\u003E\n\u003Cp\u003EContinue to \u003Ca href=\u0022/docs/getting-started/quickstart\u0022\u003EQuickStart\u003C/a\u003E to set up your first UltimateAuth application. Then explore \u003Ca href=\u0022/docs/auth-flows/index\u0022\u003EAuth Flows\u003C/a\u003E to understand how the pieces work together.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe idea to remember:\u003C/strong\u003E UltimateAuth unifies authentication concepts without erasing their differences. One framework, a consistent lifecycle, and room for the security model your application actually needs.\u003C/p\u003E\n", "Headings": [ { "Id": "what-is-ultimateauth", From f91f4abe3bc3fbf430f241618ca7ece13ff1755b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Fri, 16 Oct 2026 20:33:58 +0300 Subject: [PATCH 4/7] Fix Broken Links & Several Docs Issues --- .../auth-flows/{index.md => overview.md} | 2 +- .../{client => client-sdk}/authentication.md | 2 +- .../{client => client-sdk}/authorization.md | 2 +- .../{client => client-sdk}/credentials.md | 4 +- .../{client => client-sdk}/identifiers.md | 2 +- .../index.md => client-sdk/overview.md} | 2 +- .../session-management.md | 6 +- .../{client => client-sdk}/user-management.md | 0 .../configuration/advanced-configuration.md | 4 +- .../configuration/{index.md => overview.md} | 0 docs/content/fundamentals/client-profiles.md | 6 +- docs/content/fundamentals/flow-based-auth.md | 12 +- .../fundamentals/{index.md => overview.md} | 2 +- .../content/fundamentals/request-lifecycle.md | 6 +- .../fundamentals/runtime-architecture.md | 6 +- docs/content/getting-started/index.md | 2 +- docs/content/getting-started/quickstart.md | 2 +- ...thorization-domain.md => authorization.md} | 0 .../{credential-domain.md => credentials.md} | 2 +- .../plugin-domains/{index.md => overview.md} | 4 +- docs/content/plugin-domains/policies.md | 2 +- .../{users-domain.md => users.md} | 4 +- docs/content/readme.md | 67 ---- .../security/authorization-pipeline.md | 4 +- docs/content/security/refresh-rotation.md | 10 +- ...-security-model.md => session-security.md} | 8 +- docs/content/security/token-security.md | 12 +- .../sessions-devices/device-management.md | 8 +- .../sessions-devices/session-lifecycle.md | 6 +- .../Layout/DocsLayout.razor | 16 +- .../Pages/DocsLandingPage.razor | 10 +- .../Pages/DocsSidebar.razor | 6 +- .../wwwroot/docs/auth-flows/overview.json | 87 +++++ .../docs/client-sdk/authentication.json | 332 ++++++++++++++++++ .../docs/client-sdk/authorization.json | 322 +++++++++++++++++ .../wwwroot/docs/client-sdk/credentials.json | 307 ++++++++++++++++ .../wwwroot/docs/client-sdk/identifiers.json | 287 +++++++++++++++ .../wwwroot/docs/client-sdk/overview.json | 97 +++++ .../docs/client-sdk/session-management.json | 197 +++++++++++ .../docs/client-sdk/user-management.json | 312 ++++++++++++++++ .../wwwroot/docs/client/authentication.json | 2 +- .../wwwroot/docs/client/authorization.json | 2 +- .../wwwroot/docs/client/credentials.json | 2 +- .../wwwroot/docs/client/identifiers.json | 2 +- .../wwwroot/docs/client/overview.json | 97 +++++ .../docs/client/session-management.json | 2 +- .../configuration/advanced-configuration.json | 2 +- .../wwwroot/docs/configuration/overview.json | 217 ++++++++++++ .../wwwroot/docs/docs-index.json | 43 +-- .../docs/fundamentals/client-profiles.json | 2 +- .../docs/fundamentals/flow-based-auth.json | 2 +- .../wwwroot/docs/fundamentals/overview.json | 6 + .../docs/fundamentals/request-lifecycle.json | 2 +- .../fundamentals/runtime-architecture.json | 2 +- .../wwwroot/docs/getting-started/index.json | 80 +++-- .../docs/getting-started/quickstart.json | 55 +-- .../getting-started/real-world-setup.json | 293 +++++++++++++++- .../docs/plugin-domains/authorization.json | 157 +++++++++ .../docs/plugin-domains/credentials.json | 112 ++++++ .../wwwroot/docs/plugin-domains/overview.json | 42 +++ .../wwwroot/docs/plugin-domains/policies.json | 2 +- .../wwwroot/docs/plugin-domains/users.json | 82 +++++ .../docs/security/authorization-pipeline.json | 2 +- .../docs/security/refresh-rotation.json | 2 +- .../docs/security/session-security-model.json | 2 +- .../docs/security/session-security.json | 192 ++++++++++ .../wwwroot/docs/security/token-security.json | 2 +- .../sessions-devices/device-management.json | 2 +- .../sessions-devices/session-lifecycle.json | 2 +- .../wwwroot/app.css | 34 +- 70 files changed, 3353 insertions(+), 251 deletions(-) rename docs/content/auth-flows/{index.md => overview.md} (99%) rename docs/content/{client => client-sdk}/authentication.md (99%) rename docs/content/{client => client-sdk}/authorization.md (99%) rename docs/content/{client => client-sdk}/credentials.md (99%) rename docs/content/{client => client-sdk}/identifiers.md (99%) rename docs/content/{client/index.md => client-sdk/overview.md} (99%) rename docs/content/{client => client-sdk}/session-management.md (98%) rename docs/content/{client => client-sdk}/user-management.md (100%) rename docs/content/configuration/{index.md => overview.md} (100%) rename docs/content/fundamentals/{index.md => overview.md} (91%) rename docs/content/plugin-domains/{authorization-domain.md => authorization.md} (100%) rename docs/content/plugin-domains/{credential-domain.md => credentials.md} (99%) rename docs/content/plugin-domains/{index.md => overview.md} (98%) rename docs/content/plugin-domains/{users-domain.md => users.md} (99%) delete mode 100644 docs/content/readme.md rename docs/content/security/{session-security-model.md => session-security.md} (98%) create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/overview.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authentication.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authorization.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/credentials.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/identifiers.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/overview.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/session-management.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/user-management.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/overview.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/overview.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/overview.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credentials.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/overview.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/users.json create mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security.json diff --git a/docs/content/auth-flows/index.md b/docs/content/auth-flows/overview.md similarity index 99% rename from docs/content/auth-flows/index.md rename to docs/content/auth-flows/overview.md index a6980990..a3565f06 100644 --- a/docs/content/auth-flows/index.md +++ b/docs/content/auth-flows/overview.md @@ -1,5 +1,5 @@ --- -title: Auth Flows Overview +title: Auth-Flows Overview order: 1 group: auth-flows --- diff --git a/docs/content/client/authentication.md b/docs/content/client-sdk/authentication.md similarity index 99% rename from docs/content/client/authentication.md rename to docs/content/client-sdk/authentication.md index b5ce3180..88ab5fb8 100644 --- a/docs/content/client/authentication.md +++ b/docs/content/client-sdk/authentication.md @@ -1266,5 +1266,5 @@ For application-specific workflows that cannot be expressed through the componen - [Session Management](/docs/client/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/authentication-modes) β€” Compare authentication behavior across modes. +- [Authentication Modes](/docs/fundamentals/auth-modes) β€” Compare authentication behavior across modes. - [Device Management](/docs/sessions-devices/device-management) β€” Understand device-associated session chains. \ No newline at end of file diff --git a/docs/content/client/authorization.md b/docs/content/client-sdk/authorization.md similarity index 99% rename from docs/content/client/authorization.md rename to docs/content/client-sdk/authorization.md index 34448c2a..58844d42 100644 --- a/docs/content/client/authorization.md +++ b/docs/content/client-sdk/authorization.md @@ -1358,4 +1358,4 @@ This allows applications to provide flexible administrative interfaces while kee - [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. -- [Session Security Model](/docs/security/session-security-model) β€” Understand session validity and security boundaries. \ No newline at end of file +- [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/credentials.md b/docs/content/client-sdk/credentials.md similarity index 99% rename from docs/content/client/credentials.md rename to docs/content/client-sdk/credentials.md index 8d5dd9f4..0523b2a4 100644 --- a/docs/content/client/credentials.md +++ b/docs/content/client-sdk/credentials.md @@ -709,7 +709,7 @@ Successful recovery may invalidate existing sessions according to the configured By default, UltimateAuth revokes all session chains after recovery. -For further details, see [Session Security Model](/docs/security/session-security-model). +For further details, see [Session Security Model](/docs/security/session-security). ## 13. Revoke Your Credential @@ -1075,4 +1075,4 @@ Use the currently implemented credential contracts and documented authentication - [User Management](/docs/client/user-management) β€” Manage profiles and user lifecycle. - [Session Management](/docs/client/session-management) β€” Inspect and revoke sessions. - [Token Security](/docs/security/token-security) β€” Understand token validation and revocation behavior. -- [Session Security Model](/docs/security/session-security-model) β€” Understand session invalidation and account-level security boundaries. \ No newline at end of file +- [Session Security Model](/docs/security/session-security) β€” Understand session invalidation and account-level security boundaries. \ No newline at end of file diff --git a/docs/content/client/identifiers.md b/docs/content/client-sdk/identifiers.md similarity index 99% rename from docs/content/client/identifiers.md rename to docs/content/client-sdk/identifiers.md index fedcd840..4e6fbec7 100644 --- a/docs/content/client/identifiers.md +++ b/docs/content/client-sdk/identifiers.md @@ -1042,4 +1042,4 @@ Applications should distinguish identifier management from session revocation an - [Credentials](/docs/client/credentials) β€” Manage passwords, credentials, and recovery workflows. - [Authentication](/docs/client/authentication) β€” Understand login and authentication flows. - [Users](/docs/plugin-domains/users) β€” Explore the user domain architecture. -- [Session Security Model](/docs/security/session-security-model) β€” Understand authentication security boundaries. \ No newline at end of file +- [Session Security Model](/docs/security/session-security) β€” Understand authentication security boundaries. \ No newline at end of file diff --git a/docs/content/client/index.md b/docs/content/client-sdk/overview.md similarity index 99% rename from docs/content/client/index.md rename to docs/content/client-sdk/overview.md index 49d018b2..28441579 100644 --- a/docs/content/client/index.md +++ b/docs/content/client-sdk/overview.md @@ -281,4 +281,4 @@ Start with **Authentication** to configure and use login, refresh, and logout op Then explore session management, user operations, credentials, authorization, and protected resource API integration according to your application's requirements. -For the concepts behind these operations, refer to [Authentication Modes](/docs/fundamentals/authentication-modes) and [Plugin Domains](/docs/plugin-domains). \ No newline at end of file +For the concepts behind these operations, refer to [Authentication Modes](/docs/fundamentals/auth-modes) and [Plugin Domains](/docs/plugin-domains/overview). \ No newline at end of file diff --git a/docs/content/client/session-management.md b/docs/content/client-sdk/session-management.md similarity index 98% rename from docs/content/client/session-management.md rename to docs/content/client-sdk/session-management.md index ef3e2416..03ba446c 100644 --- a/docs/content/client/session-management.md +++ b/docs/content/client-sdk/session-management.md @@ -41,7 +41,7 @@ A chain is associated with a device or client context, but it is not cryptograph The available hierarchy and enforcement behavior depend on the authentication mode. -For architectural details, see [Session Model & Lifecycle](/docs/sessions-devices/session-model). +For architectural details, see [Session Model & Lifecycle](/docs/sessions-devices/session-lifecycle). ## 2. Accessing the Session Client @@ -792,6 +792,6 @@ These operations should be protected by appropriate permissions and, where requi ## Next Steps - [Authentication](/docs/client/authentication) β€” Login, logout, refresh, and authentication state validation. -- [Session Model & Lifecycle](/docs/sessions-devices/session-model) β€” Session roots, chains, lifecycle, and expiration. +- [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-model) β€” Revocation and session security boundaries. \ No newline at end of file +- [Session Security Model](/docs/security/session-security) β€” Revocation and session security boundaries. \ No newline at end of file diff --git a/docs/content/client/user-management.md b/docs/content/client-sdk/user-management.md similarity index 100% rename from docs/content/client/user-management.md rename to docs/content/client-sdk/user-management.md diff --git a/docs/content/configuration/advanced-configuration.md b/docs/content/configuration/advanced-configuration.md index 0023948a..e37a688a 100644 --- a/docs/content/configuration/advanced-configuration.md +++ b/docs/content/configuration/advanced-configuration.md @@ -1159,9 +1159,9 @@ It is to customize the smallest component necessary while retaining the guarante ## Next Steps -- [Configuration Overview](/docs/configuration/configuration) β€” Understand registration, configuration sources, and architecture. +- [Configuration Overview](/docs/configuration/overview) β€” Understand registration, configuration sources, and architecture. - [Server Options](/docs/configuration/server-options) β€” Configure authentication and server security behavior. - [Client Options](/docs/configuration/client-options) β€” Configure SDK flows and client behavior. -- [Authentication Modes](/docs/fundamentals/authentication-modes) β€” Understand supported authentication modes. +- [Authentication Modes](/docs/fundamentals/auth-modes) β€” Understand supported authentication modes. - [Authorization](/docs/client/authorization) β€” Use the client authorization API. - [Session Management](/docs/client/session-management) β€” Manage sessions and revocation. \ No newline at end of file diff --git a/docs/content/configuration/index.md b/docs/content/configuration/overview.md similarity index 100% rename from docs/content/configuration/index.md rename to docs/content/configuration/overview.md diff --git a/docs/content/fundamentals/client-profiles.md b/docs/content/fundamentals/client-profiles.md index ca3d4869..30531da9 100644 --- a/docs/content/fundamentals/client-profiles.md +++ b/docs/content/fundamentals/client-profiles.md @@ -232,7 +232,7 @@ They are not a guarantee that every listed mode is the current implementation de In particular, the `UAuthHub` profile should not be interpreted as an authentication mode. UAuthHub is the authentication host, and its supported operations may serve clients using different modes. -For the full comparison of validation and lifecycle guarantees, see [Authentication Modes](./authentication-modes.md). +For the full comparison of validation and lifecycle guarantees, see [Authentication Modes](/docs/fundamentals/auth-modes). ## Profile resolution and request context @@ -288,7 +288,7 @@ Authentication continuation must preserve the relevant security relationships es The framework-controlled `AuthFlowContext` provides a consistent boundary for these decisions. -See [Flow-Based Authentication](./flow-based-authentication.md) for the conceptual model and [Auth Flows](../auth-flows/) for the technical request pipeline. +See [Flow-Based Authentication](/docs/fundamentals/flow-based-auth) for the conceptual model and [Auth Flows](/docs/auth-flows/overview) for the technical request pipeline. ## Credential delivery and runtime capabilities @@ -396,4 +396,4 @@ Together, these concepts allow different clients to share the same UltimateAuth ## Next step -Continue to [Runtime Architecture](./runtime-architecture.md) to understand how UltimateAuth's client, server, and hosting components work together across different application environments. \ No newline at end of file +Continue to [Runtime Architecture](/docs/fundamentals/runtime-architecture) to understand how UltimateAuth's client, server, and hosting components work together across different application environments. \ 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 d7a36fea..ec1af006 100644 --- a/docs/content/fundamentals/flow-based-auth.md +++ b/docs/content/fundamentals/flow-based-auth.md @@ -71,7 +71,7 @@ In stateless JWT authentication, client-side credential removal does not automat These operations share architectural boundaries but do not perform identical work. -For their implementation details, see [Auth Flows](../auth-flows/). +For their implementation details, see [Auth Flows](/docs/auth-flows/overview). ## The flow execution model @@ -141,7 +141,7 @@ The relevant authentication evidence and security requirements must still be eva After authentication has established trusted identity, authorization can operate through the appropriate `AccessContext`. -The distinction between these contexts is explained in [Authentication Model](./authentication-model.md). +The distinction between these contexts is explained in [Authentication Model](/docs/fundamentals/auth-model). ## Flows and authentication modes @@ -174,7 +174,7 @@ A flow must never introduce a session dependency merely because another authenti In particular, PureJwt must remain independent of mandatory session persistence and validation. -For the complete mode definitions and their guarantees, see [Authentication Modes](./authentication-modes.md). +For the complete mode definitions and their guarantees, see [Authentication Modes](/docs/fundamentals/auth-modes). ## Server-controlled decisions @@ -255,7 +255,7 @@ They do not require independent implementations of the fundamental authenticatio Client Profiles provide runtime-appropriate behavior and defaults, while authentication modes define the validation and lifecycle strategy. -See [Client Profiles](./client-profiles.md) for how runtime characteristics influence authentication configuration. +See [Client Profiles](/docs/fundamentals/client-profiles) for how runtime characteristics influence authentication configuration. ## Predictable outcomes @@ -311,7 +311,7 @@ However, not every flow or mode requires persisted Root, Chain, or Session state This separation allows UltimateAuth to support both stateful and stateless authentication without maintaining two unrelated authentication architectures. -See [Authentication Model](./authentication-model.md) for the lineage model and its role in authentication. +See [Authentication Model](/docs/fundamentals/auth-model) for the lineage model and its role in authentication. ## Summary @@ -332,4 +332,4 @@ The architecture does not require every mode to use the same credentials, persis ## Next step -Continue to [Authentication Modes](./authentication-modes.md) to understand how PureOpaque, Hybrid, SemiHybrid, and PureJwt implement different authentication and session lifecycle strategies. \ No newline at end of file +Continue to [Authentication Modes](/docs/fundamentals/auth-modes) to understand how PureOpaque, Hybrid, SemiHybrid, and PureJwt implement different authentication and session lifecycle strategies. \ No newline at end of file diff --git a/docs/content/fundamentals/index.md b/docs/content/fundamentals/overview.md similarity index 91% rename from docs/content/fundamentals/index.md rename to docs/content/fundamentals/overview.md index fbe6c93b..56485b6f 100644 --- a/docs/content/fundamentals/index.md +++ b/docs/content/fundamentals/overview.md @@ -1,5 +1,5 @@ --- -title: Fundamental Overview +title: Fundamentals Overview order: 1 group: fundamentals --- diff --git a/docs/content/fundamentals/request-lifecycle.md b/docs/content/fundamentals/request-lifecycle.md index 7bb443a7..f6582ade 100644 --- a/docs/content/fundamentals/request-lifecycle.md +++ b/docs/content/fundamentals/request-lifecycle.md @@ -16,7 +16,7 @@ Understanding these differences is essential when integrating UltimateAuth into This page focuses on **how authentication is evaluated during application requests and where the security boundaries are enforced**. -For authentication flow orchestration, see [Auth Flows](/docs/auth-flows/index). +For authentication flow orchestration, see [Auth Flows](/docs/auth-flows/overview). ## Two request categories @@ -64,7 +64,7 @@ The operation may create or update sessions, issue credentials, rotate refresh t These details depend on the flow and authentication mode. -The internal execution pipeline is described in [Auth Flows](/docs/auth-flows/index), while individual operations are documented in their respective flow guides. +The internal execution pipeline is described in [Auth Flows](/docs/auth-flows/overview), while individual operations are documented in their respective flow guides. ## Protected resource operations @@ -339,4 +339,4 @@ The execution boundary depends on the runtime and deployment architecture. You have completed the Fundamentals section. -Continue to [Auth Flows](/docs/auth-flows/index) to explore the technical authentication pipeline and the individual login, refresh, and logout operations. \ No newline at end of file +Continue to [Auth Flows](/docs/auth-flows/overview) to explore the technical authentication pipeline and the individual login, refresh, and logout operations. \ No newline at end of file diff --git a/docs/content/fundamentals/runtime-architecture.md b/docs/content/fundamentals/runtime-architecture.md index 498eb8cb..6ff9c67c 100644 --- a/docs/content/fundamentals/runtime-architecture.md +++ b/docs/content/fundamentals/runtime-architecture.md @@ -112,7 +112,7 @@ For example, session lifecycle operations, credential verification, and authoriz The architecture separates orchestration from the underlying services that perform domain-specific work. -For the detailed execution pipeline, see [Auth Flows](../auth-flows/). +For the detailed execution pipeline, see [Auth Flows](/docs/auth-flows/overview). ## Client: runtime integration @@ -147,7 +147,7 @@ UltimateAuth accounts for these differences through runtime-specific integration The authentication model remains shared even when the implementation of credential transport or application state differs. -See [Client Profiles](./client-profiles.md). +See [Client Profiles](/docs/fundamentals/client-profiles). ## UAuthHub: the authentication host @@ -554,4 +554,4 @@ Authentication modes determine the required validation and state dependencies. ## Next step -Continue to [Request Lifecycle](./request-lifecycle.md) to understand how an incoming request moves through authentication validation, identity establishment, and authorization before reaching a protected operation. \ No newline at end of file +Continue to [Request Lifecycle](/docs/fundamentals/request-lifecycle) to understand how an incoming request moves through authentication validation, identity establishment, and authorization before reaching a protected operation. \ No newline at end of file diff --git a/docs/content/getting-started/index.md b/docs/content/getting-started/index.md index 8457639d..cb1e86a5 100644 --- a/docs/content/getting-started/index.md +++ b/docs/content/getting-started/index.md @@ -415,7 +415,7 @@ You do not need to understand the entire architecture before building your first | I want to… | Start here | |---|---| | Set up UltimateAuth for the first time | [QuickStart](/docs/getting-started/quickstart) | -| Understand how authentication works internally | [Auth Flows](/docs/auth-flows/index) | +| Understand how authentication works internally | [Auth Flows](/docs/auth-flows/overview) | | Configure session, token, and security behavior | [Server Options](/docs/configuration/server-options) | | Integrate authentication into a Blazor application | [Client Options](/docs/configuration/client-options) | | Understand the source code and contribute | [GitHub Repository](https://github.com/CodeBeamOrg/UltimateAuth) | diff --git a/docs/content/getting-started/quickstart.md b/docs/content/getting-started/quickstart.md index 1181cddb..1aedc442 100644 --- a/docs/content/getting-started/quickstart.md +++ b/docs/content/getting-started/quickstart.md @@ -314,5 +314,5 @@ For production use, configure durable persistence, appropriate secrets and signi - **[Ecosystem](/docs/getting-started/ecosystem)** β€” Explore packages, integrations, and supported environments. - **[Server Options](/docs/configuration/server-options)** β€” Configure sessions, tokens, login policies, and security behavior. - **[Client Options](/docs/configuration/client-options)** β€” Customize Blazor integration and authentication flows. -- **[Authentication Flows](/docs/auth-flows/index)** β€” Understand login, refresh, logout, and revocation. +- **[Authentication Flows](/docs/auth-flows/overview)** β€” Understand login, refresh, logout, and revocation. - **[Entity Framework Core Setup](/docs/getting-started/entity-framework-core)** β€” Move from in-memory storage to persistent authentication data. \ No newline at end of file diff --git a/docs/content/plugin-domains/authorization-domain.md b/docs/content/plugin-domains/authorization.md similarity index 100% rename from docs/content/plugin-domains/authorization-domain.md rename to docs/content/plugin-domains/authorization.md diff --git a/docs/content/plugin-domains/credential-domain.md b/docs/content/plugin-domains/credentials.md similarity index 99% rename from docs/content/plugin-domains/credential-domain.md rename to docs/content/plugin-domains/credentials.md index f8a50a76..c9ad7f59 100644 --- a/docs/content/plugin-domains/credential-domain.md +++ b/docs/content/plugin-domains/credentials.md @@ -246,7 +246,7 @@ In session-authoritative modes, revocation can be reflected through server-side Credential changes therefore initiate the applicable security response; they do not imply identical immediate token invalidation across every mode. -See [Authentication Modes](/docs/fundamentals/authentication-modes) for the underlying differences. +See [Authentication Modes](/docs/fundamentals/auth-modes) for the underlying differences. ## Integration with the Users Domain diff --git a/docs/content/plugin-domains/index.md b/docs/content/plugin-domains/overview.md similarity index 98% rename from docs/content/plugin-domains/index.md rename to docs/content/plugin-domains/overview.md index d29ba94c..ae3f4687 100644 --- a/docs/content/plugin-domains/index.md +++ b/docs/content/plugin-domains/overview.md @@ -1,5 +1,5 @@ --- -title: Plugin Domains +title: Plugin-Domains Overview order: 1 group: plugin-domains --- @@ -119,4 +119,4 @@ Continue with the individual domains to understand their responsibilities, publi - [Credentials](/docs/plugin-domains/credentials) β€” Credential management, verification, and recovery. - [Authorization](/docs/plugin-domains/authorization) β€” Roles, permissions, policies, and access control. -For the underlying authentication architecture, see [Runtime Architecture](/docs/fundamentals/runtime-architecture) and [Authentication Model](/docs/fundamentals/authentication-model). \ No newline at end of file +For the underlying authentication architecture, see [Runtime Architecture](/docs/fundamentals/runtime-architecture) and [Authentication Model](/docs/fundamentals/auth-model). \ No newline at end of file diff --git a/docs/content/plugin-domains/policies.md b/docs/content/plugin-domains/policies.md index d01771c4..ef74f0fe 100644 --- a/docs/content/plugin-domains/policies.md +++ b/docs/content/plugin-domains/policies.md @@ -343,7 +343,7 @@ A policy can evaluate its inputs correctly while those inputs reflect an earlier Applications must account for this when choosing token lifetimes, permission resolution strategies, and security requirements. -See [Authentication Modes](/docs/fundamentals/authentication-modes). +See [Authentication Modes](/docs/fundamentals/auth-modes). ## Application-Level Access Control diff --git a/docs/content/plugin-domains/users-domain.md b/docs/content/plugin-domains/users.md similarity index 99% rename from docs/content/plugin-domains/users-domain.md rename to docs/content/plugin-domains/users.md index f0dcbf25..43beb4a8 100644 --- a/docs/content/plugin-domains/users-domain.md +++ b/docs/content/plugin-domains/users.md @@ -96,7 +96,7 @@ For example, a locally validated JWT may remain valid until expiration unless th The Users domain participates in security-sensitive account operations, while the authentication infrastructure determines how the resulting security state is enforced. -For more information, see [Authentication Modes](/docs/fundamentals/authentication-modes). +For more information, see [Authentication Modes](/docs/fundamentals/auth-modes). ## User Identifiers @@ -254,4 +254,4 @@ The Users domain establishes identity ownership and account lifecycle, but it do - [Authorization](/docs/plugin-domains/authorization) β€” Explore how identities are associated with permissions and access policies. - [Plugin Domains](/docs/plugin-domains) β€” Return to the overall Plugin Domain architecture. -For the distinction between identity and authentication infrastructure, see [Authentication Model](/docs/fundamentals/authentication-model). \ No newline at end of file +For the distinction between identity and authentication infrastructure, see [Authentication Model](/docs/fundamentals/auth-model). \ No newline at end of file diff --git a/docs/content/readme.md b/docs/content/readme.md deleted file mode 100644 index 20daca39..00000000 --- a/docs/content/readme.md +++ /dev/null @@ -1,67 +0,0 @@ -# πŸ” UltimateAuth Docs - -The modern, unified auth framework with platform-level abilities for .NET - Reimagined. - -## Sections - -UltimateAuth docs consists of 7 sections. There are suggested read line for starters: - -### 1) Getting Started - -Start here to integrate UltimateAuth into your application: - -- Getting Started Guide -- Setup with different client profiles -- Basic usage - -### 2) Fundamentals - -Understand how UltimateAuth works internally: - -- Core Concepts -- Session Architecture (Root β†’ Chain β†’ Session) -- Token Model -- Authorization Model - -### 3) Auth Flows - -Explore authentication lifecycle: - -- Login Flow -- Refresh Flow -- Logout Flow -- Session Lifecycle -- Token Behavior -- Device Management - -### 4) Plugin Domains - -UltimateAuth is built as modular domains: - -- Users -- Credentials -- Authorization - -### 5) Configuration & Extensibility - -Customize behavior and integrate with your system: - -- Server Options -- Client Options -- Configuration Sources -- Advanced Configuration -- Extending Domains & Stores - -### 6) Client & API - -- Real world usage of UltimateAuth -- API and code examples of authentication, sessions, users, credentials and authorization - -### 7) Security & Advanced - -Deep dive into the security model: - -- Session Security Model -- Refresh Token Rotation -- Access Token Behavior -- Policy Pipeline diff --git a/docs/content/security/authorization-pipeline.md b/docs/content/security/authorization-pipeline.md index 906f726f..9964c32b 100644 --- a/docs/content/security/authorization-pipeline.md +++ b/docs/content/security/authorization-pipeline.md @@ -563,8 +563,6 @@ A custom policy should not: Custom policies should evaluate well-defined requirements using trusted context and participate in the established decision model. -Implementation and registration examples belong in [How-to Guides](/docs/how-to). - ## Architectural Perspective UltimateAuth's authorization pipeline separates the responsibilities involved in an access decision. @@ -587,5 +585,5 @@ Together, these components provide a structured foundation for context-aware, po - [Authorization](/docs/plugin-domains/authorization) β€” Actions, permissions, roles, and scopes. - [Policies & Access Control](/docs/plugin-domains/policies) β€” Policy concepts and access decision behavior. -- [Session Security Model](/docs/security/session-security-model) β€” Authentication security state and validation boundaries. +- [Session Security Model](/docs/security/session-security) β€” Authentication security state and validation boundaries. - [Token Security](/docs/security/token-security) β€” Credential validity and authorization freshness. \ No newline at end of file diff --git a/docs/content/security/refresh-rotation.md b/docs/content/security/refresh-rotation.md index 8d60b7fa..6abf54a8 100644 --- a/docs/content/security/refresh-rotation.md +++ b/docs/content/security/refresh-rotation.md @@ -80,7 +80,7 @@ Depending on the configured mode and strategy, a continuation operation may rota **Rotation guarantees must be evaluated against the selected refresh strategy, not assumed from the existence of a refresh endpoint.** -See [Authentication Modes](/docs/fundamentals/authentication-modes). +See [Authentication Modes](/docs/fundamentals/auth-modes). ## Refresh Credential State @@ -336,7 +336,7 @@ However, a chain is a logical session continuity boundary, not a cryptographical Chain-level revocation controls the associated authentication state; it does not permanently prevent the physical device from authenticating again. -See [Session Security Model](/docs/security/session-security-model). +See [Session Security Model](/docs/security/session-security). ## Invalid Refresh Conditions @@ -465,6 +465,6 @@ UltimateAuth's authentication modes provide different continuation models. Rotat ## Next Steps - [Token Security](/docs/security/token-security) β€” Access and refresh credential security fundamentals. -- [Session Security Model](/docs/security/session-security-model) β€” Hierarchical validation and revocation boundaries. -- [Session Model & Lifecycle](/docs/sessions-devices/session-model) β€” Session continuity, expiration, and revocation. -- [Authentication Modes](/docs/fundamentals/authentication-modes) β€” Mode-specific authentication and refresh behavior. \ No newline at end of file +- [Session Security Model](/docs/security/session-security) β€” Hierarchical validation and revocation boundaries. +- [Session Model & Lifecycle](/docs/sessions-devices/session-lifecycle) β€” Session continuity, expiration, and revocation. +- [Authentication Modes](/docs/fundamentals/auth-modes) β€” Mode-specific authentication and refresh behavior. \ No newline at end of file diff --git a/docs/content/security/session-security-model.md b/docs/content/security/session-security.md similarity index 98% rename from docs/content/security/session-security-model.md rename to docs/content/security/session-security.md index 90e7cb90..395e18ae 100644 --- a/docs/content/security/session-security-model.md +++ b/docs/content/security/session-security.md @@ -18,7 +18,7 @@ In session-managed authentication modes, the security hierarchy is: This page focuses on the security guarantees, validation requirements, and enforcement boundaries of that hierarchy. -For the underlying entities and their lifecycle, see [Session Model & Lifecycle](/docs/sessions-devices/session-model). +For the underlying entities and their lifecycle, see [Session Model & Lifecycle](/docs/sessions-devices/session-lifecycle). ## Security Objectives @@ -375,7 +375,7 @@ Server-authoritative validation provides stronger immediate control over active Locally validated JWTs reduce that dependency but cannot inherently provide the same immediate revocation behavior. -See [Authentication Modes](/docs/fundamentals/authentication-modes). +See [Authentication Modes](/docs/fundamentals/auth-modes). ## Threat Scenarios @@ -457,7 +457,7 @@ Their practical security guarantees depend on the authentication mode and the va ## Next Steps -- [Session Model & Lifecycle](/docs/sessions-devices/session-model) β€” Understand the underlying hierarchy and lifecycle. +- [Session Model & Lifecycle](/docs/sessions-devices/session-lifecycle) β€” Understand the underlying hierarchy and lifecycle. - [Device Management](/docs/sessions-devices/device-management) β€” Explore device context and chain association. -- [Authentication Modes](/docs/fundamentals/authentication-modes) β€” Compare enforcement across authentication modes. +- [Authentication Modes](/docs/fundamentals/auth-modes) β€” Compare enforcement across authentication modes. - [Credentials](/docs/plugin-domains/credentials) β€” Understand credential changes and session revocation policies. \ No newline at end of file diff --git a/docs/content/security/token-security.md b/docs/content/security/token-security.md index 515c8f09..a8848122 100644 --- a/docs/content/security/token-security.md +++ b/docs/content/security/token-security.md @@ -18,7 +18,7 @@ UltimateAuth supports these different mechanisms through its authentication mode This page explains the security characteristics of access and refresh credentials, including issuance, expiration, rotation, replay risks, and revocation boundaries. -For the overall authentication architecture, see [Authentication Modes](/docs/fundamentals/authentication-modes). +For the overall authentication architecture, see [Authentication Modes](/docs/fundamentals/auth-modes). ## Authentication State and Credentials @@ -100,7 +100,7 @@ Opacity alone does not prevent credential theft or replay. In a session-authoritative mode, validation can enforce the applicable session hierarchy requirements, including expiration, revocation, ownership, and security state consistency. -See [Session Security Model](/docs/security/session-security-model). +See [Session Security Model](/docs/security/session-security). ## JWT Access Tokens @@ -198,7 +198,7 @@ An optional refresh mechanism may maintain refresh-specific state. The existence of a refresh store does not make JWT access validation session-authoritative. -For more detail, see [Authentication Modes](/docs/fundamentals/authentication-modes). +For more detail, see [Authentication Modes](/docs/fundamentals/auth-modes). ## Access Token Lifetime @@ -554,7 +554,7 @@ The purpose of the token security model is to make those tradeoffs explicit. ## Next Steps -- [Session Security Model](/docs/security/session-security-model) β€” Understand authoritative session validation and hierarchical revocation. -- [Authentication Modes](/docs/fundamentals/authentication-modes) β€” Compare credential behavior across modes. -- [Session Model & Lifecycle](/docs/sessions-devices/session-model) β€” Explore session creation, continuation, and invalidation. +- [Session Security Model](/docs/security/session-security) β€” Understand authoritative session validation and hierarchical revocation. +- [Authentication Modes](/docs/fundamentals/auth-modes) β€” Compare credential behavior across modes. +- [Session Model & Lifecycle](/docs/sessions-devices/session-lifecycle) β€” Explore session creation, continuation, and invalidation. - [Credentials](/docs/plugin-domains/credentials) β€” Understand credential management and recovery security. \ No newline at end of file diff --git a/docs/content/sessions-devices/device-management.md b/docs/content/sessions-devices/device-management.md index 23e837b9..f134bb05 100644 --- a/docs/content/sessions-devices/device-management.md +++ b/docs/content/sessions-devices/device-management.md @@ -256,7 +256,7 @@ In JWT-based modes, a previously issued access token may remain valid until expi Therefore, revoking a device-associated chain must not be described as universally invalidating every previously issued access token immediately. -See [Authentication Modes](/docs/fundamentals/authentication-modes). +See [Authentication Modes](/docs/fundamentals/auth-modes). ## Session and Device Limits @@ -302,7 +302,7 @@ It also preserves an important architectural boundary: device recognition, sessi ## Next Steps -- [Session Model](/docs/sessions-devices/session-model) β€” Understand sessions, chains, and their lifecycle. -- [Session Security](/docs/sessions-devices/session-security) β€” Explore expiration, rotation, touch, and revocation. -- [Authentication Modes](/docs/fundamentals/authentication-modes) β€” Compare session validation across authentication modes. +- [Session Model](/docs/sessions-devices/session-lifecycle) β€” Understand sessions, chains, and their lifecycle. +- [Session Security](/docs/security/session-security) β€” Explore expiration, rotation, touch, and revocation. +- [Authentication Modes](/docs/fundamentals/auth-modes) β€” Compare session validation across authentication modes. - [Request Lifecycle](/docs/fundamentals/request-lifecycle) β€” Understand how protected requests are authenticated and authorized. \ No newline at end of file diff --git a/docs/content/sessions-devices/session-lifecycle.md b/docs/content/sessions-devices/session-lifecycle.md index d07c843b..24a7b465 100644 --- a/docs/content/sessions-devices/session-lifecycle.md +++ b/docs/content/sessions-devices/session-lifecycle.md @@ -277,7 +277,7 @@ A refresh mechanism may use an independent refresh store, but the server-managed This is an important distinction between the framework's common authentication concepts and the persistence requirements of individual modes. -See [Authentication Modes](/docs/fundamentals/authentication-modes). +See [Authentication Modes](/docs/fundamentals/auth-modes). ## Session Expiration @@ -419,6 +419,6 @@ The hierarchy is most directly relevant to session-managed authentication, while ## Next Steps - [Device Management](/docs/sessions-devices/device-management) β€” Device context, chain association, and device-aware session management. -- [Session Security](/docs/sessions-devices/session-security) β€” Session expiration, rotation, revocation, and security controls. -- [Authentication Modes](/docs/fundamentals/authentication-modes) β€” Understand how session behavior differs between modes. +- [Session Security](/docs/security/session-security) β€” Session expiration, rotation, revocation, and security controls. +- [Authentication Modes](/docs/fundamentals/auth-modes) β€” Understand how session behavior differs between modes. - [Request Lifecycle](/docs/fundamentals/request-lifecycle) β€” Learn how protected requests validate authentication state. \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Layout/DocsLayout.razor b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Layout/DocsLayout.razor index e0cba0bf..4de87865 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Layout/DocsLayout.razor +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Layout/DocsLayout.razor @@ -1,6 +1,7 @@ ο»Ώ@using CodeBeam.UltimateAuth.Docs.Wasm.Client.Pages @inherits LayoutComponentBase @layout MainLayout +@inject NavigationManager Nav @@ -11,8 +12,15 @@ @Body - - - - + @if (!IsDocsLandingPage) + { + + + + + } + +@code { + private bool IsDocsLandingPage => Nav.ToBaseRelativePath(Nav.Uri).Trim('/').Equals("docs", StringComparison.OrdinalIgnoreCase); +} diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsLandingPage.razor b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsLandingPage.razor index 6c71a602..830dd6cb 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsLandingPage.razor +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsLandingPage.razor @@ -57,7 +57,7 @@ auth modes, client profiles, and runtime architecture. - + Open Fundamentals
@@ -74,7 +74,7 @@ token behavior, and device-aware authentication flows. - + Open Auth Flows
@@ -91,7 +91,7 @@ Everything from basic login/logout to advanced session management should call with client. - + Open Client & API @@ -107,7 +107,7 @@ Explore the user, credential, and authorization modules that not required for core Auth, but essential. - + Open Plugin Domains @@ -128,7 +128,7 @@ + Href="/docs/security/session-security"> Open Security Docs diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsSidebar.razor b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsSidebar.razor index b8d750aa..2f207df7 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsSidebar.razor +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsSidebar.razor @@ -131,8 +131,10 @@ if (string.IsNullOrWhiteSpace(group)) return "General"; - return System.Globalization.CultureInfo.CurrentCulture.TextInfo - .ToTitleCase(group.Replace("-", " ")); + if (group == "client") + return "Client SDK"; + + return System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(group.Replace("-", " ")); } public void Dispose() 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 new file mode 100644 index 00000000..bd907ffc --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/overview.json @@ -0,0 +1,87 @@ +{ + "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", + "Headings": [ + { + "Id": "a-flow-is-more-than-an-endpoint", + "Text": "A flow is more than an endpoint", + "Level": 0 + }, + { + "Id": "the-request-to-result-pipeline", + "Text": "The request-to-result pipeline", + "Level": 0 + }, + { + "Id": "a-request-identifies-the-flow", + "Text": "1. A request identifies the flow", + "Level": 1 + }, + { + "Id": "the-server-constructs-authflowcontext", + "Text": "2. The server constructs AuthFlowContext", + "Level": 1 + }, + { + "Id": "the-context-is-made-available-to-the-handler", + "Text": "3. The context is made available to the handler", + "Level": 1 + }, + { + "Id": "flow-specific-validation-and-orchestration-begin", + "Text": "4. Flow-specific validation and orchestration begin", + "Level": 1 + }, + { + "Id": "decisions-are-evaluated-against-authoritative-state", + "Text": "5. Decisions are evaluated against authoritative state", + "Level": 1 + }, + { + "Id": "only-permitted-transitions-are-committed", + "Text": "6. Only permitted transitions are committed", + "Level": 1 + }, + { + "Id": "the-outcome-is-delivered-according-to-policy", + "Text": "7. The outcome is delivered according to policy", + "Level": 1 + }, + { + "Id": "why-authflowcontext-matters", + "Text": "Why AuthFlowContext matters", + "Level": 0 + }, + { + "Id": "source-of-truth-proof-versus-state", + "Text": "Source of truth: proof versus state", + "Level": 0 + }, + { + "Id": "built-in-flows-at-a-glance", + "Text": "Built-in flows at a glance", + "Level": 0 + }, + { + "Id": "concepts-shared-by-the-flows", + "Text": "Concepts shared by the flows", + "Level": 0 + }, + { + "Id": "what-this-design-enables-in-real-applications", + "Text": "What this design enables in real applications", + "Level": 0 + }, + { + "Id": "key-takeaways", + "Text": "Key takeaways", + "Level": 0 + }, + { + "Id": "next-step", + "Text": "Next step", + "Level": 0 + } + ] +} \ 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 new file mode 100644 index 00000000..ebbd097d --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authentication.json @@ -0,0 +1,332 @@ +{ + "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", + "Headings": [ + { + "Id": "accessing-the-authentication-client", + "Text": "1. Accessing the Authentication Client", + "Level": 0 + }, + { + "Id": "choosing-the-correct-login-flow", + "Text": "2. Choosing the Correct Login Flow", + "Level": 0 + }, + { + "Id": "direct-login", + "Text": "3. Direct Login", + "Level": 0 + }, + { + "Id": "loginrequest-properties", + "Text": "LoginRequest Properties", + "Level": 1 + }, + { + "Id": "redirect-after-login", + "Text": "Redirect After Login", + "Level": 1 + }, + { + "Id": "interactive-login-with-tryloginasync", + "Text": "4. Interactive Login with TryLoginAsync", + "Level": 0 + }, + { + "Id": "submit-modes", + "Text": "Submit Modes", + "Level": 1 + }, + { + "Id": "tryonly-validate-without-login", + "Text": "TryOnly: Validate Without Login", + "Level": 1 + }, + { + "Id": "tryandcommit-interactive-login", + "Text": "TryAndCommit: Interactive Login", + "Level": 1 + }, + { + "Id": "directcommit-single-commit-submission", + "Text": "DirectCommit: Single Commit Submission", + "Level": 1 + }, + { + "Id": "handling-login-results", + "Text": "5. Handling Login Results", + "Level": 0 + }, + { + "Id": "handling-transport-and-protocol-errors", + "Text": "Handling Transport and Protocol Errors", + "Level": 1 + }, + { + "Id": "pkce-authentication", + "Text": "6. PKCE Authentication", + "Level": 0 + }, + { + "Id": "start-pkce", + "Text": "Start PKCE", + "Level": 1 + }, + { + "Id": "configure-pkce", + "Text": "Configure PKCE", + "Level": 1 + }, + { + "Id": "observe-authorization", + "Text": "Observe Authorization", + "Level": 1 + }, + { + "Id": "disable-automatic-redirection", + "Text": "Disable Automatic Redirection", + "Level": 1 + }, + { + "Id": "complete-pkce-login", + "Text": "7. Complete PKCE Login", + "Level": 0 + }, + { + "Id": "interactive-pkce-completion", + "Text": "Interactive PKCE Completion", + "Level": 1 + }, + { + "Id": "try-pkce-without-commit", + "Text": "Try PKCE Without Commit", + "Level": 1 + }, + { + "Id": "retrywithnewpkce", + "Text": "RetryWithNewPkce", + "Level": 1 + }, + { + "Id": "direct-credential-posting-restrictions", + "Text": "8. Direct Credential Posting Restrictions", + "Level": 0 + }, + { + "Id": "trusted-server-hosted-clients", + "Text": "Trusted Server-Hosted Clients", + "Level": 1 + }, + { + "Id": "refresh-authentication", + "Text": "9. Refresh Authentication", + "Level": 0 + }, + { + "Id": "handling-refresh-results", + "Text": "Handling Refresh Results", + "Level": 1 + }, + { + "Id": "refresh-outcomes", + "Text": "Refresh Outcomes", + "Level": 1 + }, + { + "Id": "detailed-refresh-diagnostics", + "Text": "Detailed Refresh Diagnostics", + "Level": 1 + }, + { + "Id": "manual-and-automatic-refresh", + "Text": "Manual and Automatic Refresh", + "Level": 1 + }, + { + "Id": "automatic-refresh-configuration", + "Text": "10. Automatic Refresh Configuration", + "Level": 0 + }, + { + "Id": "automatic-refresh-is-not-a-security-guarantee", + "Text": "Automatic Refresh Is Not a Security Guarantee", + "Level": 1 + }, + { + "Id": "validate-current-authentication", + "Text": "11. Validate Current Authentication", + "Level": 0 + }, + { + "Id": "inspect-session-context", + "Text": "Inspect Session Context", + "Level": 1 + }, + { + "Id": "validateasync-versus-ui-authentication-state", + "Text": "ValidateAsync Versus UI Authentication State", + "Level": 1 + }, + { + "Id": "logout", + "Text": "12. Logout", + "Level": 0 + }, + { + "Id": "logout-button", + "Text": "Logout Button", + "Level": 1 + }, + { + "Id": "logout-a-specific-device", + "Text": "13. Logout a Specific Device", + "Level": 0 + }, + { + "Id": "current-device-behavior", + "Text": "Current Device Behavior", + "Level": 1 + }, + { + "Id": "logout-other-devices", + "Text": "14. Logout Other Devices", + "Level": 0 + }, + { + "Id": "logout-all-devices", + "Text": "15. Logout All Devices", + "Level": 0 + }, + { + "Id": "administrative-device-logout", + "Text": "16. Administrative Device Logout", + "Level": 0 + }, + { + "Id": "logout-one-user-device", + "Text": "Logout One User Device", + "Level": 1 + }, + { + "Id": "logout-other-devices-for-a-user", + "Text": "Logout Other Devices for a User", + "Level": 1 + }, + { + "Id": "logout-all-devices-for-a-user", + "Text": "Logout All Devices for a User", + "Level": 1 + }, + { + "Id": "building-login-forms-with-uauthloginform", + "Text": "17. Building Login Forms with UAuthLoginForm", + "Level": 0 + }, + { + "Id": "basic-usage", + "Text": "Basic Usage", + "Level": 1 + }, + { + "Id": "customizing-the-login-ui", + "Text": "Customizing the Login UI", + "Level": 1 + }, + { + "Id": "submission-modes", + "Text": "Submission Modes", + "Level": 1 + }, + { + "Id": "handling-authentication-results", + "Text": "Handling Authentication Results", + "Level": 1 + }, + { + "Id": "using-uauthloginform-with-pkce", + "Text": "Using UAuthLoginForm with PKCE", + "Level": 1 + }, + { + "Id": "return-url-handling", + "Text": "Return URL Handling", + "Level": 1 + }, + { + "Id": "custom-login-endpoints", + "Text": "Custom Login Endpoints", + "Level": 1 + }, + { + "Id": "submit-the-form-programmatically", + "Text": "Submit the Form Programmatically", + "Level": 1 + }, + { + "Id": "controlling-enter-key-submission", + "Text": "Controlling Enter-Key Submission", + "Level": 1 + }, + { + "Id": "reloading-pkce-context", + "Text": "Reloading PKCE Context", + "Level": 1 + }, + { + "Id": "component-parameters", + "Text": "Component Parameters", + "Level": 1 + }, + { + "Id": "when-to-use-uauthloginform", + "Text": "When to Use UAuthLoginForm", + "Level": 1 + }, + { + "Id": "common-integration-mistakes", + "Text": "18. Common Integration Mistakes", + "Level": 0 + }, + { + "Id": "calling-direct-login-from-a-public-client", + "Text": "Calling Direct Login from a Public Client", + "Level": 1 + }, + { + "Id": "treating-tryonly-as-authentication", + "Text": "Treating TryOnly as Authentication", + "Level": 1 + }, + { + "Id": "assuming-directcommit-skips-server-validation", + "Text": "Assuming DirectCommit Skips Server Validation", + "Level": 1 + }, + { + "Id": "assuming-every-refresh-rotates-tokens", + "Text": "Assuming Every Refresh Rotates Tokens", + "Level": 1 + }, + { + "Id": "using-a-session-id-for-device-logout", + "Text": "Using a Session ID for Device Logout", + "Level": 1 + }, + { + "Id": "assuming-logout-immediately-invalidates-all-jwts", + "Text": "Assuming Logout Immediately Invalidates All JWTs", + "Level": 1 + }, + { + "Id": "using-validateasync-on-every-render", + "Text": "Using ValidateAsync on Every Render", + "Level": 1 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ 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 new file mode 100644 index 00000000..0c229dc2 --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authorization.json @@ -0,0 +1,322 @@ +{ + "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\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=\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-another-users-roles\u0022\u003E8. 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\u003E9. 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\u003E10. 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\u003E11. 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\u003E12. 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\u003E13. 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\u003E14. 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\u003E15. 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\u003E16. 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\u003E17. 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\u003E18. 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\u003E19. 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\u003E20. 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\u003E21. 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\u003E22. 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\u0022\u003E23. 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\u003E24. 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", + "Headings": [ + { + "Id": "understanding-authorization", + "Text": "1. Understanding Authorization", + "Level": 0 + }, + { + "Id": "accessing-the-authorization-client", + "Text": "2. Accessing the Authorization Client", + "Level": 0 + }, + { + "Id": "understanding-permissions", + "Text": "3. Understanding Permissions", + "Level": 0 + }, + { + "Id": "permission-patterns", + "Text": "Permission Patterns", + "Level": 1 + }, + { + "Id": "permission-normalization", + "Text": "Permission Normalization", + "Level": 1 + }, + { + "Id": "wildcards-are-powerful", + "Text": "Wildcards Are Powerful", + "Level": 1 + }, + { + "Id": "check-authorization", + "Text": "4. Check Authorization", + "Level": 0 + }, + { + "Id": "authorizationcheckrequest", + "Text": "AuthorizationCheckRequest", + "Level": 1 + }, + { + "Id": "check-a-specific-resource", + "Text": "Check a Specific Resource", + "Level": 1 + }, + { + "Id": "understanding-authorization-results", + "Text": "5. Understanding Authorization Results", + "Level": 0 + }, + { + "Id": "handle-all-decision-outcomes", + "Text": "Handle All Decision Outcomes", + "Level": 1 + }, + { + "Id": "use-authorization-checks-in-blazor", + "Text": "6. Use Authorization Checks in Blazor", + "Level": 0 + }, + { + "Id": "why-client-checks-are-not-security-boundaries", + "Text": "Why Client Checks Are Not Security Boundaries", + "Level": 1 + }, + { + "Id": "get-my-roles", + "Text": "7. Get My Roles", + "Level": 0 + }, + { + "Id": "userrolesgetresult", + "Text": "UserRolesGetResult", + "Level": 1 + }, + { + "Id": "userroleinfo", + "Text": "UserRoleInfo", + "Level": 1 + }, + { + "Id": "pagination", + "Text": "Pagination", + "Level": 1 + }, + { + "Id": "get-another-users-roles", + "Text": "8. Get Another User\u0027s Roles", + "Level": 0 + }, + { + "Id": "query-roles", + "Text": "9. Query Roles", + "Level": 0 + }, + { + "Id": "rolequery", + "Text": "RoleQuery", + "Level": 1 + }, + { + "Id": "search-roles", + "Text": "Search Roles", + "Level": 1 + }, + { + "Id": "include-deleted-roles", + "Text": "Include Deleted Roles", + "Level": 1 + }, + { + "Id": "understanding-roleinfo", + "Text": "10. Understanding RoleInfo", + "Level": 0 + }, + { + "Id": "create-a-role", + "Text": "11. Create a Role", + "Level": 0 + }, + { + "Id": "create-a-role-with-permissions", + "Text": "Create a Role with Permissions", + "Level": 1 + }, + { + "Id": "create-a-read-only-role", + "Text": "Create a Read-Only Role", + "Level": 1 + }, + { + "Id": "rename-a-role", + "Text": "12. Rename a Role", + "Level": 0 + }, + { + "Id": "set-role-permissions", + "Text": "13. Set Role Permissions", + "Level": 0 + }, + { + "Id": "important-this-is-a-replacement-operation", + "Text": "Important: This Is a Replacement Operation", + "Level": 1 + }, + { + "Id": "safely-add-a-permission-to-an-existing-role", + "Text": "Safely Add a Permission to an Existing Role", + "Level": 1 + }, + { + "Id": "remove-a-permission", + "Text": "Remove a Permission", + "Level": 1 + }, + { + "Id": "clear-all-permissions", + "Text": "Clear All Permissions", + "Level": 1 + }, + { + "Id": "assign-a-role-to-a-user", + "Text": "14. Assign a Role to a User", + "Level": 0 + }, + { + "Id": "assign-multiple-roles", + "Text": "Assign Multiple Roles", + "Level": 1 + }, + { + "Id": "remove-a-role-from-a-user", + "Text": "15. Remove a Role from a User", + "Level": 0 + }, + { + "Id": "important-effective-permissions-may-remain", + "Text": "Important: Effective Permissions May Remain", + "Level": 1 + }, + { + "Id": "delete-a-role", + "Text": "16. Delete a Role", + "Level": 0 + }, + { + "Id": "hard-delete", + "Text": "Hard Delete", + "Level": 1 + }, + { + "Id": "roledeleteresult", + "Text": "RoleDeleteResult", + "Level": 1 + }, + { + "Id": "inspect-the-result", + "Text": "Inspect the Result", + "Level": 1 + }, + { + "Id": "build-a-role-management-page", + "Text": "17. Build a Role Management Page", + "Level": 0 + }, + { + "Id": "build-a-user-role-assignment-interface", + "Text": "18. Build a User Role Assignment Interface", + "Level": 0 + }, + { + "Id": "load-user-roles", + "Text": "Load User Roles", + "Level": 1 + }, + { + "Id": "assign-a-selected-role", + "Text": "Assign a Selected Role", + "Level": 1 + }, + { + "Id": "remove-a-selected-role", + "Text": "Remove a Selected Role", + "Level": 1 + }, + { + "Id": "authorization-state-events", + "Text": "19. Authorization State Events", + "Level": 0 + }, + { + "Id": "authorization-changes-and-active-sessions", + "Text": "20. Authorization Changes and Active Sessions", + "Level": 0 + }, + { + "Id": "roles-are-not-sessions", + "Text": "Roles Are Not Sessions", + "Level": 1 + }, + { + "Id": "multi-tenant-authorization", + "Text": "21. Multi-Tenant Authorization", + "Level": 0 + }, + { + "Id": "handling-authorization-errors", + "Text": "22. Handling Authorization Errors", + "Level": 0 + }, + { + "Id": "handle-role-creation-errors", + "Text": "Handle Role Creation Errors", + "Level": 1 + }, + { + "Id": "handle-authorization-check-failures", + "Text": "Handle Authorization Check Failures", + "Level": 1 + }, + { + "Id": "security-considerations", + "Text": "23. Security Considerations", + "Level": 0 + }, + { + "Id": "server-authorization-is-authoritative", + "Text": "Server Authorization Is Authoritative", + "Level": 1 + }, + { + "Id": "follow-least-privilege", + "Text": "Follow Least Privilege", + "Level": 1 + }, + { + "Id": "protect-administrative-operations", + "Text": "Protect Administrative Operations", + "Level": 1 + }, + { + "Id": "treat-permission-replacement-carefully", + "Text": "Treat Permission Replacement Carefully", + "Level": 1 + }, + { + "Id": "handle-reauthentication-requirements", + "Text": "Handle Reauthentication Requirements", + "Level": 1 + }, + { + "Id": "respect-tenant-boundaries", + "Text": "Respect Tenant Boundaries", + "Level": 1 + }, + { + "Id": "do-not-infer-effective-access-from-one-role", + "Text": "Do Not Infer Effective Access from One Role", + "Level": 1 + }, + { + "Id": "account-for-cached-authorization-state", + "Text": "Account for Cached Authorization State", + "Level": 1 + }, + { + "Id": "putting-everything-together", + "Text": "24. Putting Everything Together", + "Level": 0 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ 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 new file mode 100644 index 00000000..50c161bb --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/credentials.json @@ -0,0 +1,307 @@ +{ + "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=\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", + "Text": "1. Understanding Credentials", + "Level": 0 + }, + { + "Id": "accessing-the-credential-client", + "Text": "2. Accessing the Credential Client", + "Level": 0 + }, + { + "Id": "change-your-password", + "Text": "3. Change Your Password", + "Level": 0 + }, + { + "Id": "changecredentialrequest", + "Text": "ChangeCredentialRequest", + "Level": 1 + }, + { + "Id": "credentialchangeresult", + "Text": "CredentialChangeResult", + "Level": 1 + }, + { + "Id": "client-state-event", + "Text": "Client State Event", + "Level": 1 + }, + { + "Id": "build-a-password-change-form", + "Text": "4. Build a Password Change Form", + "Level": 0 + }, + { + "Id": "add-a-credential", + "Text": "5. Add a Credential", + "Level": 0 + }, + { + "Id": "addcredentialrequest", + "Text": "AddCredentialRequest", + "Level": 1 + }, + { + "Id": "credentialaddresult", + "Text": "CredentialAddResult", + "Level": 1 + }, + { + "Id": "password-change-versus-password-recovery", + "Text": "6. Password Change Versus Password Recovery", + "Level": 0 + }, + { + "Id": "begin-password-recovery", + "Text": "7. Begin Password Recovery", + "Level": 0 + }, + { + "Id": "beginresetcredentialrequest", + "Text": "BeginResetCredentialRequest", + "Level": 1 + }, + { + "Id": "reset-code-types", + "Text": "Reset Code Types", + "Level": 1 + }, + { + "Id": "credentialbeginresetresult", + "Text": "CredentialBeginResetResult", + "Level": 1 + }, + { + "Id": "complete-password-recovery", + "Text": "8. Complete Password Recovery", + "Level": 0 + }, + { + "Id": "completeresetcredentialrequest", + "Text": "CompleteResetCredentialRequest", + "Level": 1 + }, + { + "Id": "credentialactionresult", + "Text": "CredentialActionResult", + "Level": 1 + }, + { + "Id": "complete-recovery-using-a-numeric-code", + "Text": "9. Complete Recovery Using a Numeric Code", + "Level": 0 + }, + { + "Id": "build-a-forgot-password-form", + "Text": "10. Build a Forgot Password Form", + "Level": 0 + }, + { + "Id": "step-one-request-recovery", + "Text": "Step One: Request Recovery", + "Level": 1 + }, + { + "Id": "step-two-complete-recovery", + "Text": "Step Two: Complete Recovery", + "Level": 1 + }, + { + "Id": "configure-reset-notifications", + "Text": "11. Configure Reset Notifications", + "Level": 0 + }, + { + "Id": "register-the-notifier", + "Text": "Register the Notifier", + "Level": 1 + }, + { + "Id": "missing-notifier-configuration", + "Text": "Missing Notifier Configuration", + "Level": 1 + }, + { + "Id": "recovery-security-behavior", + "Text": "12. Recovery Security Behavior", + "Level": 0 + }, + { + "Id": "account-enumeration-resistance", + "Text": "Account Enumeration Resistance", + "Level": 1 + }, + { + "Id": "proof-expiration", + "Text": "Proof Expiration", + "Level": 1 + }, + { + "Id": "single-use-proofs", + "Text": "Single-Use Proofs", + "Level": 1 + }, + { + "Id": "identifier-and-target-binding", + "Text": "Identifier and Target Binding", + "Level": 1 + }, + { + "Id": "session-revocation", + "Text": "Session Revocation", + "Level": 1 + }, + { + "Id": "revoke-your-credential", + "Text": "13. Revoke Your Credential", + "Level": 0 + }, + { + "Id": "state-event", + "Text": "State Event", + "Level": 1 + }, + { + "Id": "administrative-credential-management", + "Text": "14. Administrative Credential Management", + "Level": 0 + }, + { + "Id": "add-a-credential-1", + "Text": "Add a Credential", + "Level": 1 + }, + { + "Id": "change-a-users-password", + "Text": "Change a User\u0027s Password", + "Level": 1 + }, + { + "Id": "revoke-a-users-credential", + "Text": "Revoke a User\u0027s Credential", + "Level": 1 + }, + { + "Id": "begin-administrative-recovery", + "Text": "Begin Administrative Recovery", + "Level": 1 + }, + { + "Id": "complete-administrative-recovery", + "Text": "Complete Administrative Recovery", + "Level": 1 + }, + { + "Id": "delete-a-users-credential", + "Text": "Delete a User\u0027s Credential", + "Level": 1 + }, + { + "Id": "hard-delete", + "Text": "Hard Delete", + "Level": 1 + }, + { + "Id": "credential-changes-and-session-invalidation", + "Text": "15. Credential Changes and Session Invalidation", + "Level": 0 + }, + { + "Id": "credentialsessionbehavior", + "Text": "CredentialSessionBehavior", + "Level": 1 + }, + { + "Id": "configure-the-policy", + "Text": "Configure the Policy", + "Level": 1 + }, + { + "Id": "recovery-and-preservecurrent", + "Text": "Recovery and PreserveCurrent", + "Level": 1 + }, + { + "Id": "authentication-mode-considerations", + "Text": "Authentication Mode Considerations", + "Level": 1 + }, + { + "Id": "handling-credential-results", + "Text": "16. Handling Credential Results", + "Level": 0 + }, + { + "Id": "credential-creation", + "Text": "Credential Creation", + "Level": 1 + }, + { + "Id": "credential-recovery", + "Text": "Credential Recovery", + "Level": 1 + }, + { + "Id": "http-status-helpers", + "Text": "HTTP Status Helpers", + "Level": 1 + }, + { + "Id": "credential-state-events", + "Text": "17. Credential State Events", + "Level": 0 + }, + { + "Id": "security-considerations", + "Text": "18. Security Considerations", + "Level": 0 + }, + { + "Id": "never-expose-stored-secrets", + "Text": "Never Expose Stored Secrets", + "Level": 1 + }, + { + "Id": "use-secure-transport", + "Text": "Use Secure Transport", + "Level": 1 + }, + { + "Id": "protect-recovery-proofs", + "Text": "Protect Recovery Proofs", + "Level": 1 + }, + { + "Id": "keep-self-service-and-administrative-operations-separate", + "Text": "Keep Self-Service and Administrative Operations Separate", + "Level": 1 + }, + { + "Id": "preserve-account-recovery-safety", + "Text": "Preserve Account Recovery Safety", + "Level": 1 + }, + { + "Id": "consider-session-consequences", + "Text": "Consider Session Consequences", + "Level": 1 + }, + { + "Id": "do-not-assume-every-credential-type-is-implemented", + "Text": "Do Not Assume Every Credential Type Is Implemented", + "Level": 1 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ 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 new file mode 100644 index 00000000..f2fe60ac --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/identifiers.json @@ -0,0 +1,287 @@ +{ + "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\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", + "Headings": [ + { + "Id": "understanding-user-identifiers", + "Text": "1. Understanding User Identifiers", + "Level": 0 + }, + { + "Id": "accessing-the-identifier-client", + "Text": "2. Accessing the Identifier Client", + "Level": 0 + }, + { + "Id": "get-my-identifiers", + "Text": "3. Get My Identifiers", + "Level": 0 + }, + { + "Id": "useridentifierinfo", + "Text": "UserIdentifierInfo", + "Level": 1 + }, + { + "Id": "filter-identifiers-in-the-ui", + "Text": "Filter Identifiers in the UI", + "Level": 1 + }, + { + "Id": "pagination", + "Text": "4. Pagination", + "Level": 0 + }, + { + "Id": "add-an-identifier", + "Text": "5. Add an Identifier", + "Level": 0 + }, + { + "Id": "add-an-email-address", + "Text": "Add an Email Address", + "Level": 1 + }, + { + "Id": "add-a-phone-number", + "Text": "Add a Phone Number", + "Level": 1 + }, + { + "Id": "add-a-username", + "Text": "Add a Username", + "Level": 1 + }, + { + "Id": "add-a-custom-identifier", + "Text": "Add a Custom Identifier", + "Level": 1 + }, + { + "Id": "add-as-primary", + "Text": "Add as Primary", + "Level": 1 + }, + { + "Id": "identifier-uniqueness-and-normalization", + "Text": "6. Identifier Uniqueness and Normalization", + "Level": 0 + }, + { + "Id": "use-server-results-as-authoritative", + "Text": "Use Server Results as Authoritative", + "Level": 1 + }, + { + "Id": "check-identifier-availability", + "Text": "7. Check Identifier Availability", + "Level": 0 + }, + { + "Id": "check-a-username", + "Text": "Check a Username", + "Level": 1 + }, + { + "Id": "check-an-email-address", + "Text": "Check an Email Address", + "Level": 1 + }, + { + "Id": "availability-result", + "Text": "Availability Result", + "Level": 1 + }, + { + "Id": "display-validation-feedback", + "Text": "Display Validation Feedback", + "Level": 1 + }, + { + "Id": "update-an-identifier", + "Text": "8. Update an Identifier", + "Level": 0 + }, + { + "Id": "verification-after-an-update", + "Text": "Verification After an Update", + "Level": 1 + }, + { + "Id": "understanding-primary-identifiers", + "Text": "9. Understanding Primary Identifiers", + "Level": 0 + }, + { + "Id": "set-a-primary-identifier", + "Text": "Set a Primary Identifier", + "Level": 1 + }, + { + "Id": "unset-a-primary-identifier", + "Text": "Unset a Primary Identifier", + "Level": 1 + }, + { + "Id": "display-primary-identifiers", + "Text": "Display Primary Identifiers", + "Level": 1 + }, + { + "Id": "verify-identifier-ownership", + "Text": "10. Verify Identifier Ownership", + "Level": 0 + }, + { + "Id": "verify-an-identifier", + "Text": "Verify an Identifier", + "Level": 1 + }, + { + "Id": "verification-flow", + "Text": "Verification Flow", + "Level": 1 + }, + { + "Id": "server-side-verification-with-iuseridentifierverifier", + "Text": "11. Server-Side Verification with IUserIdentifierVerifier", + "Level": 0 + }, + { + "Id": "register-your-verifier", + "Text": "Register Your Verifier", + "Level": 1 + }, + { + "Id": "delete-an-identifier", + "Text": "12. Delete an Identifier", + "Level": 0 + }, + { + "Id": "hard-delete", + "Text": "Hard Delete", + "Level": 1 + }, + { + "Id": "deleting-primary-identifiers", + "Text": "Deleting Primary Identifiers", + "Level": 1 + }, + { + "Id": "deleting-login-identifiers", + "Text": "Deleting Login Identifiers", + "Level": 1 + }, + { + "Id": "build-an-identifier-management-page", + "Text": "13. Build an Identifier Management Page", + "Level": 0 + }, + { + "Id": "administrative-identifier-management", + "Text": "14. Administrative Identifier Management", + "Level": 0 + }, + { + "Id": "get-a-users-identifiers", + "Text": "Get a User\u0027s Identifiers", + "Level": 1 + }, + { + "Id": "add-an-identifier-1", + "Text": "Add an Identifier", + "Level": 1 + }, + { + "Id": "update-an-identifier-1", + "Text": "Update an Identifier", + "Level": 1 + }, + { + "Id": "set-primary", + "Text": "Set Primary", + "Level": 1 + }, + { + "Id": "unset-primary", + "Text": "Unset Primary", + "Level": 1 + }, + { + "Id": "verify-an-identifier-as-administrator", + "Text": "Verify an Identifier as Administrator", + "Level": 1 + }, + { + "Id": "delete-an-identifier-1", + "Text": "Delete an Identifier", + "Level": 1 + }, + { + "Id": "handling-identifier-operation-results", + "Text": "15. Handling Identifier Operation Results", + "Level": 0 + }, + { + "Id": "handle-success-and-failure", + "Text": "Handle Success and Failure", + "Level": 1 + }, + { + "Id": "identifier-state-events", + "Text": "16. Identifier State Events", + "Level": 0 + }, + { + "Id": "refresh-identifier-data-after-changes", + "Text": "Refresh Identifier Data After Changes", + "Level": 1 + }, + { + "Id": "security-considerations", + "Text": "17. Security Considerations", + "Level": 0 + }, + { + "Id": "verification-requires-proof", + "Text": "Verification Requires Proof", + "Level": 1 + }, + { + "Id": "primary-does-not-mean-verified", + "Text": "Primary Does Not Mean Verified", + "Level": 1 + }, + { + "Id": "login-eligibility-is-separate", + "Text": "Login Eligibility Is Separate", + "Level": 1 + }, + { + "Id": "tenant-boundaries-matter", + "Text": "Tenant Boundaries Matter", + "Level": 1 + }, + { + "Id": "availability-checks-are-not-reservations", + "Text": "Availability Checks Are Not Reservations", + "Level": 1 + }, + { + "Id": "administrative-verification-is-privileged", + "Text": "Administrative Verification Is Privileged", + "Level": 1 + }, + { + "Id": "identifier-changes-may-affect-authentication", + "Text": "Identifier Changes May Affect Authentication", + "Level": 1 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/overview.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/overview.json new file mode 100644 index 00000000..6c3172b0 --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/overview.json @@ -0,0 +1,97 @@ +{ + "Slug": "client-sdk/overview", + "Title": "Client SDK Overview", + "Html": "\n\u003Cp\u003EThe UltimateAuth Client SDK provides a unified way to integrate authentication, session management, user operations, credentials, and authorization into .NET applications.\u003C/p\u003E\n\u003Cp\u003EInstead of manually constructing requests for every authentication endpoint, applications can use dedicated client services that understand UltimateAuth\u0027s API contracts and authentication workflows.\u003C/p\u003E\n\u003Cp\u003EThe SDK is designed for applications that need to interact with an UltimateAuth server, including Blazor WebAssembly, Blazor Server, and other supported .NET client environments.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThis section is the practical developer guide to using UltimateAuth from application code.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-the-client-sdk-provides\u0022\u003EWhat the Client SDK Provides\u003C/h2\u003E\n\u003Cp\u003EThe Client SDK is more than a collection of HTTP methods.\u003C/p\u003E\n\u003Cp\u003EIt brings together several responsibilities:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthentication workflows\u003C/strong\u003E \u2014 Start login, refresh, logout, and other supported authentication operations.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession management\u003C/strong\u003E \u2014 Interact with available session and device management capabilities.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUser management\u003C/strong\u003E \u2014 Retrieve and manage user information through the appropriate API contracts.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ECredential operations\u003C/strong\u003E \u2014 Manage credentials and initiate supported recovery workflows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthorization management\u003C/strong\u003E \u2014 Work with roles, permissions, and other exposed authorization operations.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EStructured results\u003C/strong\u003E \u2014 Receive operation results through the SDK\u0027s response contracts.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClient state integration\u003C/strong\u003E \u2014 Coordinate supported authentication state changes and notifications.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe SDK helps applications interact with UltimateAuth consistently while keeping the server responsible for authentication and authorization enforcement.\u003C/p\u003E\n\u003Cp\u003EIt does not replace server-side security checks.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-client-architecture\u0022\u003EThe Client Architecture\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates client responsibilities into specialized services.\u003C/p\u003E\n\u003Cp\u003EThe main client entry point is \u003Ccode\u003EUAuthClient\u003C/code\u003E, which provides access to the SDK\u0027s functional areas.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EApplication\n |\n v\nUAuthClient\n |\n \u002B-- Flows\n | Login, logout, refresh, PKCE\n |\n \u002B-- Sessions\n | Sessions and device-associated chains\n |\n \u002B-- Users\n | User information and lifecycle\n |\n \u002B-- Identifiers\n | User identifiers and verification\n |\n \u002B-- Credentials\n | Credential management and recovery\n |\n \u002B-- Authorization\n Roles, permissions, and access management\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese represent functional responsibilities. The precise available members and operations depend on the installed packages and supported SDK contracts.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-specialized-clients\u0022\u003EWhy Specialized Clients?\u003C/h3\u003E\n\u003Cp\u003EAuthentication-related operations have different requirements.\u003C/p\u003E\n\u003Cp\u003EA login flow may establish authentication state and involve redirects. A user management operation may retrieve or update account information. A credential recovery operation may require additional verification and security handling.\u003C/p\u003E\n\u003Cp\u003EKeeping these responsibilities separate provides a clearer programming model and makes individual APIs easier to discover.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022getting-started-with-uauthclient\u0022\u003EGetting Started with UAuthClient\u003C/h2\u003E\n\u003Cp\u003EIn a supported dependency injection environment, application components can consume the configured UltimateAuth client.\u003C/p\u003E\n\u003Cp\u003EFor example, a Blazor component can receive the client through dependency injection:\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@inject UAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOnce configured, the application uses the appropriate client service for the operation it needs.\u003C/p\u003E\n\u003Cp\u003EThe following illustrates the intended programming 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\u003Evar user = await UAuthClient.Users.GetMeAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe SDK handles the corresponding client operation and returns its structured result.\u003C/p\u003E\n\u003Cp\u003EBefore using the client, the application must register the appropriate UltimateAuth services and configure its connection to the authentication server.\u003C/p\u003E\n\u003Cp\u003EFor a complete setup, follow the Getting Started guide for your application environment.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choose-your-application-environment\u0022\u003EChoose Your Application Environment\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports different application architectures, but their authentication and transport requirements are not identical.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-webassembly\u0022\u003EBlazor WebAssembly\u003C/h3\u003E\n\u003Cp\u003EBlazor WebAssembly executes application code in the browser.\u003C/p\u003E\n\u003Cp\u003EThe client communicates with the authentication server and protected APIs through browser-compatible HTTP requests.\u003C/p\u003E\n\u003Cp\u003EBrowser security rules, credential handling, CORS configuration, and authentication state synchronization are relevant to this environment.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server\u0022\u003EBlazor Server\u003C/h3\u003E\n\u003Cp\u003EBlazor Server executes application code on the server while maintaining an interactive connection to the browser.\u003C/p\u003E\n\u003Cp\u003EAuthentication state, cookie behavior, and server-side execution require different considerations from a browser-hosted WebAssembly application.\u003C/p\u003E\n\u003Cp\u003EServer-side code may also invoke application services directly rather than making an HTTP request.\u003C/p\u003E\n\u003Cp\u003ESuch operations still require appropriate server-side authorization enforcement.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022interactive-web-applications\u0022\u003EInteractive Web Applications\u003C/h3\u003E\n\u003Cp\u003EApplications combining server rendering and WebAssembly interactivity may have multiple execution environments.\u003C/p\u003E\n\u003Cp\u003EDevelopers should distinguish between operations performed on the server and operations performed in the browser.\u003C/p\u003E\n\u003Cp\u003EShared UI components do not automatically imply identical transport or credential handling.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022other.net-clients\u0022\u003EOther .NET Clients\u003C/h3\u003E\n\u003Cp\u003EOther supported .NET application environments can use the relevant client SDK capabilities according to their platform requirements.\u003C/p\u003E\n\u003Cp\u003ECredential storage, HTTP transport, redirect handling, and authentication state integration may differ from browser applications.\u003C/p\u003E\n\u003Cp\u003EThe SDK\u0027s shared contracts help maintain consistency, while platform-specific integration handles the environment\u0027s requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022working-with-authentication-state\u0022\u003EWorking with Authentication State\u003C/h2\u003E\n\u003Cp\u003EAuthentication is not simply a method call that returns a token.\u003C/p\u003E\n\u003Cp\u003EThe client may need to coordinate several related concerns:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECompleting the selected authentication flow.\u003C/li\u003E\n\u003Cli\u003EHandling the resulting authentication state.\u003C/li\u003E\n\u003Cli\u003ERenewing authentication when continuation is supported.\u003C/li\u003E\n\u003Cli\u003EReacting to logout or session invalidation.\u003C/li\u003E\n\u003Cli\u003EUpdating application components when relevant state changes.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth provides client-side abstractions and notifications for supported operations.\u003C/p\u003E\n\u003Cp\u003EFor example, user or credential changes may publish SDK events after a successful operation.\u003C/p\u003E\n\u003Cp\u003EApplications can use the available state integration mechanisms to update their UI without treating every API response as a complete authentication state transition.\u003C/p\u003E\n\u003Cp\u003EThe exact behavior depends on the client environment and configured integration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-sdk-results\u0022\u003EUnderstanding SDK Results\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth client operations use structured response contracts.\u003C/p\u003E\n\u003Cp\u003EApplications should inspect the result of an operation rather than assuming that completing an asynchronous call means the requested action succeeded.\u003C/p\u003E\n\u003Cp\u003EA typical application workflow 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\u003ECall SDK operation\n |\n v\nReceive structured result\n |\n \u002B-- Success\n | |\n | v\n | Continue application workflow\n |\n \u002B-- Failure\n |\n v\n Handle error\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDifferent operations may return different result types.\u003C/p\u003E\n\u003Cp\u003EThe relevant guide for each client service explains its request and response contracts, including how to handle failures.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-events-and-ui-updates\u0022\u003EClient Events and UI Updates\u003C/h2\u003E\n\u003Cp\u003ESome SDK operations can publish notifications when a successful operation changes application-relevant state.\u003C/p\u003E\n\u003Cp\u003EFor example, credential or user management operations may notify subscribers that related information has changed.\u003C/p\u003E\n\u003Cp\u003EThese notifications help decouple an operation from the UI components that react to it.\u003C/p\u003E\n\u003Cp\u003EA component can respond by refreshing its data rather than requiring the caller to manually update every affected view.\u003C/p\u003E\n\u003Cp\u003EHowever, client events are not a security authority.\u003C/p\u003E\n\u003Cp\u003EAn event does not replace server-side validation, and the absence of an event does not prove that the underlying security state has remained unchanged.\u003C/p\u003E\n\u003Cp\u003EApplications should continue to treat the server as authoritative wherever the selected authentication mode requires it.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022calling-protected-resource-apis\u0022\u003ECalling Protected Resource APIs\u003C/h2\u003E\n\u003Cp\u003EAuthentication is often only the first step.\u003C/p\u003E\n\u003Cp\u003EApplications also need to call protected business APIs after the user has authenticated.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports integration with protected resource requests, allowing the client application to use the authentication mechanisms appropriate to its configured environment.\u003C/p\u003E\n\u003Cp\u003EA protected resource request involves two distinct responsibilities:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EThe client sends the request with the appropriate authentication material.\u003C/li\u003E\n\u003Cli\u003EThe receiving server validates authentication and enforces authorization.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe Client SDK can help with the client-side responsibility.\u003C/p\u003E\n\u003Cp\u003EIt cannot grant access to an operation that the receiving server does not authorize.\u003C/p\u003E\n\u003Cp\u003EWhen building applications with separate resource APIs, developers must configure the relevant API integration and server-side authentication enforcement.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-sdk-guides\u0022\u003EClient SDK Guides\u003C/h2\u003E\n\u003Cp\u003EThe following guides cover the major client responsibilities with practical C# examples.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EGuide\u003C/th\u003E\n\u003Cth\u003EWhat you will learn\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAuthentication\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin, logout, refresh, PKCE, and authentication state handling\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESession Management\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERetrieve, inspect, and manage sessions and device-associated chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EUser Management\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERetrieve user information and perform supported user lifecycle operations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EIdentifiers\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EManage email addresses, usernames, phone numbers, and verification workflows\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAdd, change, revoke, and recover credentials\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAuthorization\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EWork with roles, permissions, and exposed authorization management operations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EResource API Requests\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECall protected application APIs using the appropriate authentication integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EEach guide focuses on a specific developer task and includes the relevant SDK methods, request models, response handling, and implementation considerations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-learning-path\u0022\u003ERecommended Learning Path\u003C/h3\u003E\n\u003Cp\u003EIf you are integrating UltimateAuth for the first time, start with \u003Cstrong\u003EAuthentication\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EOnce login and authentication state handling work correctly, continue with \u003Cstrong\u003EResource API Requests\u003C/strong\u003E to connect authentication to your application\u0027s protected functionality.\u003C/p\u003E\n\u003Cp\u003EAfter that, use the domain-specific guides for the features your application needs.\u003C/p\u003E\n\u003Cp\u003EFor example, an account settings page may require the User Management, Identifiers, and Credentials guides, while an administration panel may additionally require Authorization and Session Management.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-sdk-and-server-responsibilities\u0022\u003EClient SDK and Server Responsibilities\u003C/h2\u003E\n\u003Cp\u003EThe Client SDK simplifies integration, but it does not independently enforce the security of server-side operations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EClient responsibility\u003C/th\u003E\n\u003Cth\u003EServer responsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESubmit authentication requests\u003C/td\u003E\n\u003Ctd\u003EValidate authentication requirements\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EManage supported client state\u003C/td\u003E\n\u003Ctd\u003EEstablish and validate authoritative session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESend protected resource requests\u003C/td\u003E\n\u003Ctd\u003EAuthenticate and authorize protected operations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EHandle structured responses\u003C/td\u003E\n\u003Ctd\u003EEnforce domain rules and security policies\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReact to state notifications\u003C/td\u003E\n\u003Ctd\u003EApply revocation and account security controls\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis separation is particularly important for applications that share UI components across different hosting models.\u003C/p\u003E\n\u003Cp\u003EA shared component can use the same SDK abstractions, while the hosting environment and server determine how authentication material is transported and validated.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-to-use-the-client-sdk\u0022\u003EWhen to Use the Client SDK\u003C/h2\u003E\n\u003Cp\u003EUse the Client SDK when building an application that interacts with UltimateAuth\u0027s exposed authentication and management APIs.\u003C/p\u003E\n\u003Cp\u003EIt is generally preferable to manually reproducing the corresponding HTTP contracts because it provides a consistent integration surface and reduces duplicated request-handling code.\u003C/p\u003E\n\u003Cp\u003EDirect endpoint integration may still be appropriate for clients that cannot use the .NET SDK or for specialized interoperability requirements.\u003C/p\u003E\n\u003Cp\u003ERegardless of the client implementation, the same server-side authentication and authorization requirements apply.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cp\u003EContinue with the Client SDK guides to implement real application workflows.\u003C/p\u003E\n\u003Cp\u003EStart with \u003Cstrong\u003EAuthentication\u003C/strong\u003E to configure and use login, refresh, and logout operations.\u003C/p\u003E\n\u003Cp\u003EThen explore session management, user operations, credentials, authorization, and protected resource API integration according to your application\u0027s requirements.\u003C/p\u003E\n\u003Cp\u003EFor the concepts behind these operations, refer to \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E and \u003Ca href=\u0022/docs/plugin-domains/overview\u0022\u003EPlugin Domains\u003C/a\u003E.\u003C/p\u003E\n", + "Headings": [ + { + "Id": "what-the-client-sdk-provides", + "Text": "What the Client SDK Provides", + "Level": 0 + }, + { + "Id": "the-client-architecture", + "Text": "The Client Architecture", + "Level": 0 + }, + { + "Id": "why-specialized-clients", + "Text": "Why Specialized Clients?", + "Level": 1 + }, + { + "Id": "getting-started-with-uauthclient", + "Text": "Getting Started with UAuthClient", + "Level": 0 + }, + { + "Id": "choose-your-application-environment", + "Text": "Choose Your Application Environment", + "Level": 0 + }, + { + "Id": "blazor-webassembly", + "Text": "Blazor WebAssembly", + "Level": 1 + }, + { + "Id": "blazor-server", + "Text": "Blazor Server", + "Level": 1 + }, + { + "Id": "interactive-web-applications", + "Text": "Interactive Web Applications", + "Level": 1 + }, + { + "Id": "other.net-clients", + "Text": "Other .NET Clients", + "Level": 1 + }, + { + "Id": "working-with-authentication-state", + "Text": "Working with Authentication State", + "Level": 0 + }, + { + "Id": "understanding-sdk-results", + "Text": "Understanding SDK Results", + "Level": 0 + }, + { + "Id": "client-events-and-ui-updates", + "Text": "Client Events and UI Updates", + "Level": 0 + }, + { + "Id": "calling-protected-resource-apis", + "Text": "Calling Protected Resource APIs", + "Level": 0 + }, + { + "Id": "client-sdk-guides", + "Text": "Client SDK Guides", + "Level": 0 + }, + { + "Id": "recommended-learning-path", + "Text": "Recommended Learning Path", + "Level": 1 + }, + { + "Id": "client-sdk-and-server-responsibilities", + "Text": "Client SDK and Server Responsibilities", + "Level": 0 + }, + { + "Id": "when-to-use-the-client-sdk", + "Text": "When to Use the Client SDK", + "Level": 0 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ 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 new file mode 100644 index 00000000..6beb6297 --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/session-management.json @@ -0,0 +1,197 @@ +{ + "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\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", + "Headings": [ + { + "Id": "understanding-the-session-hierarchy", + "Text": "1. Understanding the Session Hierarchy", + "Level": 0 + }, + { + "Id": "accessing-the-session-client", + "Text": "2. Accessing the Session Client", + "Level": 0 + }, + { + "Id": "list-my-devices-and-session-chains", + "Text": "3. List My Devices and Session Chains", + "Level": 0 + }, + { + "Id": "sessionchainsummary", + "Text": "SessionChainSummary", + "Level": 1 + }, + { + "Id": "identify-the-current-device", + "Text": "Identify the Current Device", + "Level": 1 + }, + { + "Id": "pagination", + "Text": "4. Pagination", + "Level": 0 + }, + { + "Id": "navigating-pages", + "Text": "Navigating Pages", + "Level": 1 + }, + { + "Id": "inspect-a-session-chain", + "Text": "5. Inspect a Session Chain", + "Level": 0 + }, + { + "Id": "sessionchaindetail", + "Text": "SessionChainDetail", + "Level": 1 + }, + { + "Id": "inspect-session-history", + "Text": "Inspect Session History", + "Level": 1 + }, + { + "Id": "understanding-chain-states", + "Text": "6. Understanding Chain States", + "Level": 0 + }, + { + "Id": "revoke-one-of-my-devices", + "Text": "7. Revoke One of My Devices", + "Level": 0 + }, + { + "Id": "detect-revocation-of-the-current-chain", + "Text": "Detect Revocation of the Current Chain", + "Level": 1 + }, + { + "Id": "revoke-all-other-devices", + "Text": "8. Revoke All Other Devices", + "Level": 0 + }, + { + "Id": "revoke-all-my-devices", + "Text": "9. Revoke All My Devices", + "Level": 0 + }, + { + "Id": "security-considerations", + "Text": "Security Considerations", + "Level": 1 + }, + { + "Id": "logout-versus-session-revocation", + "Text": "10. Logout Versus Session Revocation", + "Level": 0 + }, + { + "Id": "which-api-should-you-use", + "Text": "Which API Should You Use?", + "Level": 1 + }, + { + "Id": "logout-a-device-through-flows", + "Text": "Logout a Device Through Flows", + "Level": 1 + }, + { + "Id": "build-a-device-management-page", + "Text": "11. Build a Device Management Page", + "Level": 0 + }, + { + "Id": "administrative-session-management", + "Text": "12. Administrative Session Management", + "Level": 0 + }, + { + "Id": "list-a-users-devices", + "Text": "List a User\u0027s Devices", + "Level": 1 + }, + { + "Id": "inspect-a-users-chain", + "Text": "Inspect a User\u0027s Chain", + "Level": 1 + }, + { + "Id": "revoke-an-individual-session", + "Text": "Revoke an Individual Session", + "Level": 1 + }, + { + "Id": "revoke-a-users-device-chain", + "Text": "Revoke a User\u0027s Device Chain", + "Level": 1 + }, + { + "Id": "revoke-all-user-chains", + "Text": "Revoke All User Chains", + "Level": 1 + }, + { + "Id": "revoke-the-users-session-root", + "Text": "Revoke the User\u0027s Session Root", + "Level": 1 + }, + { + "Id": "choosing-the-correct-revocation-scope", + "Text": "13. Choosing the Correct Revocation Scope", + "Level": 0 + }, + { + "Id": "handling-session-operation-results", + "Text": "14. Handling Session Operation Results", + "Level": 0 + }, + { + "Id": "administrative-authorization-failures", + "Text": "Administrative Authorization Failures", + "Level": 1 + }, + { + "Id": "session-state-events", + "Text": "15. Session State Events", + "Level": 0 + }, + { + "Id": "security-and-operational-considerations", + "Text": "16. Security and Operational Considerations", + "Level": 0 + }, + { + "Id": "device-metadata-is-descriptive", + "Text": "Device Metadata Is Descriptive", + "Level": 1 + }, + { + "Id": "session-chains-are-not-permanent-device-identities", + "Text": "Session Chains Are Not Permanent Device Identities", + "Level": 1 + }, + { + "Id": "revocation-is-server-controlled", + "Text": "Revocation Is Server-Controlled", + "Level": 1 + }, + { + "Id": "authentication-modes-matter", + "Text": "Authentication Modes Matter", + "Level": 1 + }, + { + "Id": "protect-administrative-operations", + "Text": "Protect Administrative Operations", + "Level": 1 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ 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 new file mode 100644 index 00000000..86a8c9ea --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/user-management.json @@ -0,0 +1,312 @@ +{ + "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=\u0022get-a-specific-user\u0022\u003E13. 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\u003E14. 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\u003E15. 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\u003E16. 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\u003E17. 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\u003E18. 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\u003E19. 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\u003E20. 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\u003E21. 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\u003E22. 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\u003E23. 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\u003E24. 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", + "Headings": [ + { + "Id": "accessing-the-user-client", + "Text": "1. Accessing the User Client", + "Level": 0 + }, + { + "Id": "get-the-current-user", + "Text": "2. Get the Current User", + "Level": 0 + }, + { + "Id": "userview", + "Text": "UserView", + "Level": 1 + }, + { + "Id": "update-the-current-users-profile", + "Text": "3. Update the Current User\u0027s Profile", + "Level": 0 + }, + { + "Id": "important-profile-updates-replace-editable-fields", + "Text": "Important: Profile Updates Replace Editable Fields", + "Level": 1 + }, + { + "Id": "recommended-read-modify-and-submit", + "Text": "Recommended: Read, Modify, and Submit", + "Level": 1 + }, + { + "Id": "profile-state-events", + "Text": "Profile State Events", + "Level": 1 + }, + { + "Id": "understanding-multiple-profiles", + "Text": "4. Understanding Multiple Profiles", + "Level": 0 + }, + { + "Id": "default-profile", + "Text": "Default Profile", + "Level": 1 + }, + { + "Id": "profile-keys", + "Text": "Profile Keys", + "Level": 1 + }, + { + "Id": "retrieve-a-specific-profile", + "Text": "5. Retrieve a Specific Profile", + "Level": 0 + }, + { + "Id": "create-an-additional-profile", + "Text": "6. Create an Additional Profile", + "Level": 0 + }, + { + "Id": "clone-an-existing-profile", + "Text": "Clone an Existing Profile", + "Level": 1 + }, + { + "Id": "profile-creation-events", + "Text": "Profile Creation Events", + "Level": 1 + }, + { + "Id": "update-a-specific-profile", + "Text": "7. Update a Specific Profile", + "Level": 0 + }, + { + "Id": "delete-a-profile", + "Text": "8. Delete a Profile", + "Level": 0 + }, + { + "Id": "create-a-user", + "Text": "9. Create a User", + "Level": 0 + }, + { + "Id": "createuserrequest", + "Text": "CreateUserRequest", + "Level": 1 + }, + { + "Id": "usercreateresult", + "Text": "UserCreateResult", + "Level": 1 + }, + { + "Id": "administrative-user-creation", + "Text": "10. Administrative User Creation", + "Level": 0 + }, + { + "Id": "query-users", + "Text": "11. Query Users", + "Level": 0 + }, + { + "Id": "userquery-filters", + "Text": "UserQuery Filters", + "Level": 1 + }, + { + "Id": "search-behavior", + "Text": "Search Behavior", + "Level": 1 + }, + { + "Id": "filter-by-status", + "Text": "Filter by Status", + "Level": 1 + }, + { + "Id": "include-deleted-users", + "Text": "Include Deleted Users", + "Level": 1 + }, + { + "Id": "usersummary-and-pagination", + "Text": "12. UserSummary and Pagination", + "Level": 0 + }, + { + "Id": "navigating-pages", + "Text": "Navigating Pages", + "Level": 1 + }, + { + "Id": "get-a-specific-user", + "Text": "13. Get a Specific User", + "Level": 0 + }, + { + "Id": "retrieve-a-specific-user-profile", + "Text": "Retrieve a Specific User Profile", + "Level": 1 + }, + { + "Id": "update-another-users-profile", + "Text": "14. Update Another User\u0027s Profile", + "Level": 0 + }, + { + "Id": "update-a-named-profile", + "Text": "Update a Named Profile", + "Level": 1 + }, + { + "Id": "manage-another-users-profiles", + "Text": "15. Manage Another User\u0027s Profiles", + "Level": 0 + }, + { + "Id": "create-a-profile", + "Text": "Create a Profile", + "Level": 1 + }, + { + "Id": "delete-a-profile-1", + "Text": "Delete a Profile", + "Level": 1 + }, + { + "Id": "understanding-user-status", + "Text": "16. Understanding User Status", + "Level": 0 + }, + { + "Id": "self-service-and-administrative-statuses", + "Text": "Self-Service and Administrative Statuses", + "Level": 1 + }, + { + "Id": "change-your-own-status", + "Text": "17. Change Your Own Status", + "Level": 0 + }, + { + "Id": "userstatuschangeresult", + "Text": "UserStatusChangeResult", + "Level": 1 + }, + { + "Id": "state-event", + "Text": "State Event", + "Level": 1 + }, + { + "Id": "change-another-users-status", + "Text": "18. Change Another User\u0027s Status", + "Level": 0 + }, + { + "Id": "example-disable-an-account", + "Text": "Example: Disable an Account", + "Level": 1 + }, + { + "Id": "example-restore-an-active-status", + "Text": "Example: Restore an Active Status", + "Level": 1 + }, + { + "Id": "delete-the-current-user", + "Text": "19. Delete the Current User", + "Level": 0 + }, + { + "Id": "important-considerations", + "Text": "Important Considerations", + "Level": 1 + }, + { + "Id": "administrative-user-deletion", + "Text": "20. Administrative User Deletion", + "Level": 0 + }, + { + "Id": "soft-delete", + "Text": "Soft Delete", + "Level": 1 + }, + { + "Id": "hard-delete", + "Text": "Hard Delete", + "Level": 1 + }, + { + "Id": "deletemode", + "Text": "DeleteMode", + "Level": 1 + }, + { + "Id": "userdeleteresult", + "Text": "UserDeleteResult", + "Level": 1 + }, + { + "Id": "build-a-user-administration-table", + "Text": "21. Build a User Administration Table", + "Level": 0 + }, + { + "Id": "handling-user-operation-results", + "Text": "22. Handling User Operation Results", + "Level": 0 + }, + { + "Id": "transport-level-versus-operation-level-success", + "Text": "Transport-Level Versus Operation-Level Success", + "Level": 1 + }, + { + "Id": "http-status-helpers", + "Text": "HTTP Status Helpers", + "Level": 1 + }, + { + "Id": "client-state-events", + "Text": "23. Client State Events", + "Level": 0 + }, + { + "Id": "security-and-lifecycle-considerations", + "Text": "24. Security and Lifecycle Considerations", + "Level": 0 + }, + { + "Id": "self-service-and-administrative-boundaries", + "Text": "Self-Service and Administrative Boundaries", + "Level": 1 + }, + { + "Id": "status-changes-and-existing-sessions", + "Text": "Status Changes and Existing Sessions", + "Level": 1 + }, + { + "Id": "profile-data-is-not-authorization-data", + "Text": "Profile Data Is Not Authorization Data", + "Level": 1 + }, + { + "Id": "profile-replacement-can-cause-data-loss", + "Text": "Profile Replacement Can Cause Data Loss", + "Level": 1 + }, + { + "Id": "deletion-is-a-lifecycle-operation", + "Text": "Deletion Is a Lifecycle Operation", + "Level": 1 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json index f92b4715..7c5594ef 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json @@ -1,7 +1,7 @@ { "Slug": "client/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/authentication-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/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", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json index 6959e991..959ef349 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json @@ -1,7 +1,7 @@ { "Slug": "client/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\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=\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-another-users-roles\u0022\u003E8. 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\u003E9. 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\u003E10. 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\u003E11. 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\u003E12. 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\u003E13. 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\u003E14. 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\u003E15. 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\u003E16. 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\u003E17. 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\u003E18. 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\u003E19. 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\u003E20. 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\u003E21. 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\u003E22. 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\u0022\u003E23. 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\u003E24. 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-model\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\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=\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-another-users-roles\u0022\u003E8. 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\u003E9. 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\u003E10. 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\u003E11. 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\u003E12. 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\u003E13. 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\u003E14. 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\u003E15. 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\u003E16. 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\u003E17. 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\u003E18. 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\u003E19. 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\u003E20. 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\u003E21. 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\u003E22. 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\u0022\u003E23. 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\u003E24. 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", "Headings": [ { "Id": "understanding-authorization", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json index e8c5e2c6..b61389b9 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json @@ -1,7 +1,7 @@ { "Slug": "client/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-model\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=\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-model\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=\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", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json index d1abf661..3bffd6b0 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json @@ -1,7 +1,7 @@ { "Slug": "client/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\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-model\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/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\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", "Headings": [ { "Id": "understanding-user-identifiers", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/overview.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/overview.json new file mode 100644 index 00000000..66047fef --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/overview.json @@ -0,0 +1,97 @@ +{ + "Slug": "client/overview", + "Title": "Client SDK Overview", + "Html": "\n\u003Cp\u003EThe UltimateAuth Client SDK provides a unified way to integrate authentication, session management, user operations, credentials, and authorization into .NET applications.\u003C/p\u003E\n\u003Cp\u003EInstead of manually constructing requests for every authentication endpoint, applications can use dedicated client services that understand UltimateAuth\u0027s API contracts and authentication workflows.\u003C/p\u003E\n\u003Cp\u003EThe SDK is designed for applications that need to interact with an UltimateAuth server, including Blazor WebAssembly, Blazor Server, and other supported .NET client environments.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThis section is the practical developer guide to using UltimateAuth from application code.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-the-client-sdk-provides\u0022\u003EWhat the Client SDK Provides\u003C/h2\u003E\n\u003Cp\u003EThe Client SDK is more than a collection of HTTP methods.\u003C/p\u003E\n\u003Cp\u003EIt brings together several responsibilities:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthentication workflows\u003C/strong\u003E \u2014 Start login, refresh, logout, and other supported authentication operations.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession management\u003C/strong\u003E \u2014 Interact with available session and device management capabilities.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUser management\u003C/strong\u003E \u2014 Retrieve and manage user information through the appropriate API contracts.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ECredential operations\u003C/strong\u003E \u2014 Manage credentials and initiate supported recovery workflows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthorization management\u003C/strong\u003E \u2014 Work with roles, permissions, and other exposed authorization operations.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EStructured results\u003C/strong\u003E \u2014 Receive operation results through the SDK\u0027s response contracts.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClient state integration\u003C/strong\u003E \u2014 Coordinate supported authentication state changes and notifications.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe SDK helps applications interact with UltimateAuth consistently while keeping the server responsible for authentication and authorization enforcement.\u003C/p\u003E\n\u003Cp\u003EIt does not replace server-side security checks.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-client-architecture\u0022\u003EThe Client Architecture\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates client responsibilities into specialized services.\u003C/p\u003E\n\u003Cp\u003EThe main client entry point is \u003Ccode\u003EUAuthClient\u003C/code\u003E, which provides access to the SDK\u0027s functional areas.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EApplication\n |\n v\nUAuthClient\n |\n \u002B-- Flows\n | Login, logout, refresh, PKCE\n |\n \u002B-- Sessions\n | Sessions and device-associated chains\n |\n \u002B-- Users\n | User information and lifecycle\n |\n \u002B-- Identifiers\n | User identifiers and verification\n |\n \u002B-- Credentials\n | Credential management and recovery\n |\n \u002B-- Authorization\n Roles, permissions, and access management\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese represent functional responsibilities. The precise available members and operations depend on the installed packages and supported SDK contracts.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-specialized-clients\u0022\u003EWhy Specialized Clients?\u003C/h3\u003E\n\u003Cp\u003EAuthentication-related operations have different requirements.\u003C/p\u003E\n\u003Cp\u003EA login flow may establish authentication state and involve redirects. A user management operation may retrieve or update account information. A credential recovery operation may require additional verification and security handling.\u003C/p\u003E\n\u003Cp\u003EKeeping these responsibilities separate provides a clearer programming model and makes individual APIs easier to discover.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022getting-started-with-uauthclient\u0022\u003EGetting Started with UAuthClient\u003C/h2\u003E\n\u003Cp\u003EIn a supported dependency injection environment, application components can consume the configured UltimateAuth client.\u003C/p\u003E\n\u003Cp\u003EFor example, a Blazor component can receive the client through dependency injection:\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@inject UAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOnce configured, the application uses the appropriate client service for the operation it needs.\u003C/p\u003E\n\u003Cp\u003EThe following illustrates the intended programming 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\u003Evar user = await UAuthClient.Users.GetMeAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe SDK handles the corresponding client operation and returns its structured result.\u003C/p\u003E\n\u003Cp\u003EBefore using the client, the application must register the appropriate UltimateAuth services and configure its connection to the authentication server.\u003C/p\u003E\n\u003Cp\u003EFor a complete setup, follow the Getting Started guide for your application environment.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choose-your-application-environment\u0022\u003EChoose Your Application Environment\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports different application architectures, but their authentication and transport requirements are not identical.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-webassembly\u0022\u003EBlazor WebAssembly\u003C/h3\u003E\n\u003Cp\u003EBlazor WebAssembly executes application code in the browser.\u003C/p\u003E\n\u003Cp\u003EThe client communicates with the authentication server and protected APIs through browser-compatible HTTP requests.\u003C/p\u003E\n\u003Cp\u003EBrowser security rules, credential handling, CORS configuration, and authentication state synchronization are relevant to this environment.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server\u0022\u003EBlazor Server\u003C/h3\u003E\n\u003Cp\u003EBlazor Server executes application code on the server while maintaining an interactive connection to the browser.\u003C/p\u003E\n\u003Cp\u003EAuthentication state, cookie behavior, and server-side execution require different considerations from a browser-hosted WebAssembly application.\u003C/p\u003E\n\u003Cp\u003EServer-side code may also invoke application services directly rather than making an HTTP request.\u003C/p\u003E\n\u003Cp\u003ESuch operations still require appropriate server-side authorization enforcement.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022interactive-web-applications\u0022\u003EInteractive Web Applications\u003C/h3\u003E\n\u003Cp\u003EApplications combining server rendering and WebAssembly interactivity may have multiple execution environments.\u003C/p\u003E\n\u003Cp\u003EDevelopers should distinguish between operations performed on the server and operations performed in the browser.\u003C/p\u003E\n\u003Cp\u003EShared UI components do not automatically imply identical transport or credential handling.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022other.net-clients\u0022\u003EOther .NET Clients\u003C/h3\u003E\n\u003Cp\u003EOther supported .NET application environments can use the relevant client SDK capabilities according to their platform requirements.\u003C/p\u003E\n\u003Cp\u003ECredential storage, HTTP transport, redirect handling, and authentication state integration may differ from browser applications.\u003C/p\u003E\n\u003Cp\u003EThe SDK\u0027s shared contracts help maintain consistency, while platform-specific integration handles the environment\u0027s requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022working-with-authentication-state\u0022\u003EWorking with Authentication State\u003C/h2\u003E\n\u003Cp\u003EAuthentication is not simply a method call that returns a token.\u003C/p\u003E\n\u003Cp\u003EThe client may need to coordinate several related concerns:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECompleting the selected authentication flow.\u003C/li\u003E\n\u003Cli\u003EHandling the resulting authentication state.\u003C/li\u003E\n\u003Cli\u003ERenewing authentication when continuation is supported.\u003C/li\u003E\n\u003Cli\u003EReacting to logout or session invalidation.\u003C/li\u003E\n\u003Cli\u003EUpdating application components when relevant state changes.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth provides client-side abstractions and notifications for supported operations.\u003C/p\u003E\n\u003Cp\u003EFor example, user or credential changes may publish SDK events after a successful operation.\u003C/p\u003E\n\u003Cp\u003EApplications can use the available state integration mechanisms to update their UI without treating every API response as a complete authentication state transition.\u003C/p\u003E\n\u003Cp\u003EThe exact behavior depends on the client environment and configured integration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-sdk-results\u0022\u003EUnderstanding SDK Results\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth client operations use structured response contracts.\u003C/p\u003E\n\u003Cp\u003EApplications should inspect the result of an operation rather than assuming that completing an asynchronous call means the requested action succeeded.\u003C/p\u003E\n\u003Cp\u003EA typical application workflow 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\u003ECall SDK operation\n |\n v\nReceive structured result\n |\n \u002B-- Success\n | |\n | v\n | Continue application workflow\n |\n \u002B-- Failure\n |\n v\n Handle error\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDifferent operations may return different result types.\u003C/p\u003E\n\u003Cp\u003EThe relevant guide for each client service explains its request and response contracts, including how to handle failures.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-events-and-ui-updates\u0022\u003EClient Events and UI Updates\u003C/h2\u003E\n\u003Cp\u003ESome SDK operations can publish notifications when a successful operation changes application-relevant state.\u003C/p\u003E\n\u003Cp\u003EFor example, credential or user management operations may notify subscribers that related information has changed.\u003C/p\u003E\n\u003Cp\u003EThese notifications help decouple an operation from the UI components that react to it.\u003C/p\u003E\n\u003Cp\u003EA component can respond by refreshing its data rather than requiring the caller to manually update every affected view.\u003C/p\u003E\n\u003Cp\u003EHowever, client events are not a security authority.\u003C/p\u003E\n\u003Cp\u003EAn event does not replace server-side validation, and the absence of an event does not prove that the underlying security state has remained unchanged.\u003C/p\u003E\n\u003Cp\u003EApplications should continue to treat the server as authoritative wherever the selected authentication mode requires it.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022calling-protected-resource-apis\u0022\u003ECalling Protected Resource APIs\u003C/h2\u003E\n\u003Cp\u003EAuthentication is often only the first step.\u003C/p\u003E\n\u003Cp\u003EApplications also need to call protected business APIs after the user has authenticated.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports integration with protected resource requests, allowing the client application to use the authentication mechanisms appropriate to its configured environment.\u003C/p\u003E\n\u003Cp\u003EA protected resource request involves two distinct responsibilities:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EThe client sends the request with the appropriate authentication material.\u003C/li\u003E\n\u003Cli\u003EThe receiving server validates authentication and enforces authorization.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe Client SDK can help with the client-side responsibility.\u003C/p\u003E\n\u003Cp\u003EIt cannot grant access to an operation that the receiving server does not authorize.\u003C/p\u003E\n\u003Cp\u003EWhen building applications with separate resource APIs, developers must configure the relevant API integration and server-side authentication enforcement.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-sdk-guides\u0022\u003EClient SDK Guides\u003C/h2\u003E\n\u003Cp\u003EThe following guides cover the major client responsibilities with practical C# examples.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EGuide\u003C/th\u003E\n\u003Cth\u003EWhat you will learn\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAuthentication\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ELogin, logout, refresh, PKCE, and authentication state handling\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESession Management\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERetrieve, inspect, and manage sessions and device-associated chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EUser Management\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERetrieve user information and perform supported user lifecycle operations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EIdentifiers\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EManage email addresses, usernames, phone numbers, and verification workflows\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAdd, change, revoke, and recover credentials\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAuthorization\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EWork with roles, permissions, and exposed authorization management operations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EResource API Requests\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECall protected application APIs using the appropriate authentication integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EEach guide focuses on a specific developer task and includes the relevant SDK methods, request models, response handling, and implementation considerations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-learning-path\u0022\u003ERecommended Learning Path\u003C/h3\u003E\n\u003Cp\u003EIf you are integrating UltimateAuth for the first time, start with \u003Cstrong\u003EAuthentication\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EOnce login and authentication state handling work correctly, continue with \u003Cstrong\u003EResource API Requests\u003C/strong\u003E to connect authentication to your application\u0027s protected functionality.\u003C/p\u003E\n\u003Cp\u003EAfter that, use the domain-specific guides for the features your application needs.\u003C/p\u003E\n\u003Cp\u003EFor example, an account settings page may require the User Management, Identifiers, and Credentials guides, while an administration panel may additionally require Authorization and Session Management.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-sdk-and-server-responsibilities\u0022\u003EClient SDK and Server Responsibilities\u003C/h2\u003E\n\u003Cp\u003EThe Client SDK simplifies integration, but it does not independently enforce the security of server-side operations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EClient responsibility\u003C/th\u003E\n\u003Cth\u003EServer responsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESubmit authentication requests\u003C/td\u003E\n\u003Ctd\u003EValidate authentication requirements\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EManage supported client state\u003C/td\u003E\n\u003Ctd\u003EEstablish and validate authoritative session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESend protected resource requests\u003C/td\u003E\n\u003Ctd\u003EAuthenticate and authorize protected operations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EHandle structured responses\u003C/td\u003E\n\u003Ctd\u003EEnforce domain rules and security policies\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReact to state notifications\u003C/td\u003E\n\u003Ctd\u003EApply revocation and account security controls\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis separation is particularly important for applications that share UI components across different hosting models.\u003C/p\u003E\n\u003Cp\u003EA shared component can use the same SDK abstractions, while the hosting environment and server determine how authentication material is transported and validated.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-to-use-the-client-sdk\u0022\u003EWhen to Use the Client SDK\u003C/h2\u003E\n\u003Cp\u003EUse the Client SDK when building an application that interacts with UltimateAuth\u0027s exposed authentication and management APIs.\u003C/p\u003E\n\u003Cp\u003EIt is generally preferable to manually reproducing the corresponding HTTP contracts because it provides a consistent integration surface and reduces duplicated request-handling code.\u003C/p\u003E\n\u003Cp\u003EDirect endpoint integration may still be appropriate for clients that cannot use the .NET SDK or for specialized interoperability requirements.\u003C/p\u003E\n\u003Cp\u003ERegardless of the client implementation, the same server-side authentication and authorization requirements apply.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cp\u003EContinue with the Client SDK guides to implement real application workflows.\u003C/p\u003E\n\u003Cp\u003EStart with \u003Cstrong\u003EAuthentication\u003C/strong\u003E to configure and use login, refresh, and logout operations.\u003C/p\u003E\n\u003Cp\u003EThen explore session management, user operations, credentials, authorization, and protected resource API integration according to your application\u0027s requirements.\u003C/p\u003E\n\u003Cp\u003EFor the concepts behind these operations, refer to \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E and \u003Ca href=\u0022/docs/plugin-domains/overview\u0022\u003EPlugin Domains\u003C/a\u003E.\u003C/p\u003E\n", + "Headings": [ + { + "Id": "what-the-client-sdk-provides", + "Text": "What the Client SDK Provides", + "Level": 0 + }, + { + "Id": "the-client-architecture", + "Text": "The Client Architecture", + "Level": 0 + }, + { + "Id": "why-specialized-clients", + "Text": "Why Specialized Clients?", + "Level": 1 + }, + { + "Id": "getting-started-with-uauthclient", + "Text": "Getting Started with UAuthClient", + "Level": 0 + }, + { + "Id": "choose-your-application-environment", + "Text": "Choose Your Application Environment", + "Level": 0 + }, + { + "Id": "blazor-webassembly", + "Text": "Blazor WebAssembly", + "Level": 1 + }, + { + "Id": "blazor-server", + "Text": "Blazor Server", + "Level": 1 + }, + { + "Id": "interactive-web-applications", + "Text": "Interactive Web Applications", + "Level": 1 + }, + { + "Id": "other.net-clients", + "Text": "Other .NET Clients", + "Level": 1 + }, + { + "Id": "working-with-authentication-state", + "Text": "Working with Authentication State", + "Level": 0 + }, + { + "Id": "understanding-sdk-results", + "Text": "Understanding SDK Results", + "Level": 0 + }, + { + "Id": "client-events-and-ui-updates", + "Text": "Client Events and UI Updates", + "Level": 0 + }, + { + "Id": "calling-protected-resource-apis", + "Text": "Calling Protected Resource APIs", + "Level": 0 + }, + { + "Id": "client-sdk-guides", + "Text": "Client SDK Guides", + "Level": 0 + }, + { + "Id": "recommended-learning-path", + "Text": "Recommended Learning Path", + "Level": 1 + }, + { + "Id": "client-sdk-and-server-responsibilities", + "Text": "Client SDK and Server Responsibilities", + "Level": 0 + }, + { + "Id": "when-to-use-the-client-sdk", + "Text": "When to Use the Client SDK", + "Level": 0 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/session-management.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/session-management.json index 4c834885..1bdf75ba 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/session-management.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/session-management.json @@ -1,7 +1,7 @@ { "Slug": "client/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-model\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\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-model\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-model\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\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", "Headings": [ { "Id": "understanding-the-session-hierarchy", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/advanced-configuration.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/advanced-configuration.json index 8576cd4b..e775f62d 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/advanced-configuration.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/advanced-configuration.json @@ -1,7 +1,7 @@ { "Slug": "configuration/advanced-configuration", "Title": "Advanced Configuration", - "Html": "\n\u003Cp\u003EUltimateAuth supports advanced customization of authentication behavior through effective server options, authentication mode resolution, dependency injection, custom infrastructure, and lifecycle events.\u003C/p\u003E\n\u003Cp\u003EThese mechanisms serve different purposes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EOptions\u003C/strong\u003E define configurable behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EEffective options\u003C/strong\u003E adapt that behavior to the selected authentication mode.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EResolvers\u003C/strong\u003E determine information required by authentication flows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthorities and validators\u003C/strong\u003E participate in security decisions.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EStores\u003C/strong\u003E persist authentication and application state.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EEvents\u003C/strong\u003E allow applications to react to completed operations.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EMost applications should begin with the standard Server and Client options. Advanced customization becomes useful when an application needs specialized authentication behavior, custom infrastructure, or integration with existing systems.\u003C/p\u003E\n\u003Cp\u003EThis guide explains the supported extension mechanisms, their execution order, and the security boundaries developers must preserve.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-versus-extensibility\u0022\u003E1. Configuration Versus Extensibility\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between configuring existing behavior and replacing an implementation.\u003C/p\u003E\n\u003Cp\u003EFor example, changing a session lifetime is 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.Session.Lifetime = TimeSpan.FromHours(8);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EProviding a custom clock is an infrastructure extension:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddSingleton\u0026lt;IClock, ApplicationClock\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationClock\u003C/code\u003E is an implementation supplied by your application.\u003C/p\u003E\n\u003Cp\u003EThese approaches are not interchangeable.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ERequirement\u003C/th\u003E\n\u003Cth\u003ERecommended mechanism\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EChange session lifetime\u003C/td\u003E\n\u003Ctd\u003EServer options\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EChange token lifetime for a specific mode\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EConfigureMode()\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESelect authentication mode differently\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIEffectiveAuthModeResolver\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EResolve tenants from a custom source\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EITenantIdResolver\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECustomize login decisions\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EILoginAuthority\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESupply signing keys\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIJwtSigningKeyProvider\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPersist sessions in another backend\u003C/td\u003E\n\u003Ctd\u003EStore implementation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReact to successful login\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthEvents\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAdd application-specific access rules\u003C/td\u003E\n\u003Ctd\u003EPolicies\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EPrefer the most specific supported mechanism rather than replacing an entire authentication component.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-effective-server-options\u0022\u003E2. Understanding Effective Server Options\u003C/h2\u003E\n\u003Cp\u003EThe server does not necessarily use the original \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E instance unchanged for every authentication flow.\u003C/p\u003E\n\u003Cp\u003EInstead, UltimateAuth can resolve an effective configuration based on the client profile and authentication mode.\u003C/p\u003E\n\u003Cp\u003EThe relevant 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\u003EIEffectiveServerOptionsProvider\nEffectiveUAuthServerOptions\nIEffectiveAuthModeResolver\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configured-options\u0022\u003EConfigured Options\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E represents the application\u0027s configured server settings.\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 options.Token.Issuer = \u0026quot;MyApplication\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese settings form the base configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022effective-options\u0022\u003EEffective Options\u003C/h3\u003E\n\u003Cp\u003EThe provider produces an \u003Ccode\u003EEffectiveUAuthServerOptions\u003C/code\u003E instance containing:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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 class EffectiveUAuthServerOptions\n{\n public UAuthMode Mode { get; init; }\n public UAuthServerOptions Options { get; init; } = default!;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EMode\u003C/code\u003E identifies the resolved authentication mode.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EOptions\u003C/code\u003E contains the effective server configuration for that mode.\u003C/p\u003E\n\u003Cp\u003EThe effective configuration is derived from the original settings rather than replacing the application\u0027s registered options globally.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022effective-configuration-resolution\u0022\u003E3. Effective Configuration Resolution\u003C/h2\u003E\n\u003Cp\u003EThe current \u003Ccode\u003EEffectiveServerOptionsProvider\u003C/code\u003E follows this sequence:\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\u003EConfigured UAuthServerOptions\n |\n v\n Resolve mode\n |\n v\n Clone base options\n |\n v\n Apply mode defaults\n |\n v\n Apply ConfigureMode override\n |\n v\n EffectiveUAuthServerOptions\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn simplified form:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar original = baseOptions.Value;\n\nvar mode = modeResolver.Resolve(clientProfile);\n\nvar options = original.Clone();\n\nConfigureDefaults.ApplyModeDefaults(mode, options);\n\nif (original.ModeConfigurations.TryGetValue(mode, out var configure))\n{\n configure(options);\n}\n\nreturn new EffectiveUAuthServerOptions\n{\n Mode = mode,\n Options = options\n};\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis execution order is important.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EMode defaults are applied after the original configuration is cloned.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EConsequently, certain values configured globally may be replaced by mode-specific defaults.\u003C/p\u003E\n\u003Cp\u003EExplicit mode overrides are applied last.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-changes-between-requests\u0022\u003EWhat Changes Between Requests?\u003C/h3\u003E\n\u003Cp\u003EDifferent client profiles can resolve to different authentication modes.\u003C/p\u003E\n\u003Cp\u003EThe provider therefore can produce different effective configurations for different client profiles.\u003C/p\u003E\n\u003Cp\u003EHowever, this does not mean every setting is independently recalculated from arbitrary request data.\u003C/p\u003E\n\u003Cp\u003EIn the current implementation, mode selection is based on the supplied \u003Ccode\u003EUAuthClientProfile\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe provider also accepts tenant context, but the default effective-options construction does not apply an additional tenant-specific configuration override.\u003C/p\u003E\n\u003Cp\u003EApplications should not assume that per-tenant options customization exists automatically.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022default-authentication-mode-resolution\u0022\u003E4. Default Authentication Mode Resolution\u003C/h2\u003E\n\u003Cp\u003EThe default \u003Ccode\u003EEffectiveAuthModeResolver\u003C/code\u003E maps client profiles to authentication modes.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EClient profile\u003C/th\u003E\n\u003Cth\u003EResolved mode\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EWebServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorWasm\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMaui\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EApi\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureJwt\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOther / unknown\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese mappings describe the current default resolver.\u003C/p\u003E\n\u003Cp\u003EThey do not imply that every resolved mode is fully supported in the current release.\u003C/p\u003E\n\u003Cp\u003EIn particular, the current server options validator rejects explicit activation of \u003Ccode\u003ESemiHybrid\u003C/code\u003E and \u003Ccode\u003EPureJwt\u003C/code\u003E through \u003Ccode\u003EAllowedModes\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EApplications should verify that their client profiles resolve to a supported authentication mode before deploying.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022allowedmodes-is-not-a-mode-selector\u0022\u003EAllowedModes Is Not a Mode Selector\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\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\u003EThis restricts which modes are allowed.\u003C/p\u003E\n\u003Cp\u003EIt does not instruct the resolver to choose \u003Ccode\u003EHybrid\u003C/code\u003E for every client.\u003C/p\u003E\n\u003Cp\u003EMode selection and mode restrictions are separate responsibilities.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-authentication-modes\u0022\u003E5. Configure Authentication Modes\u003C/h2\u003E\n\u003Cp\u003EUse the public \u003Ccode\u003EConfigureMode()\u003C/code\u003E extension to customize settings for a particular authentication 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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.ConfigureMode(UAuthMode.Hybrid, mode =\u0026gt;\n {\n mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(5);\n });\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis configuration changes the effective access token lifetime for \u003Ccode\u003EHybrid\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIt does not change the effective \u003Ccode\u003EPureOpaque\u003C/code\u003E configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-configuremode-matters\u0022\u003EWhy ConfigureMode Matters\u003C/h3\u003E\n\u003Cp\u003EConsider this 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.Token.AccessTokenLifetime = TimeSpan.FromMinutes(30);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe current \u003Ccode\u003EHybrid\u003C/code\u003E mode defaults explicitly set:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EToken.AccessTokenLifetime = TimeSpan.FromMinutes(10);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EBecause mode defaults are applied after the base options are cloned, the effective \u003Ccode\u003EHybrid\u003C/code\u003E access token lifetime becomes ten minutes rather than thirty.\u003C/p\u003E\n\u003Cp\u003ETo override that behavior, use:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.ConfigureMode(UAuthMode.Hybrid, mode =\u0026gt;\n {\n mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(30);\n });\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe explicit mode override is applied after the mode defaults.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-multiple-modes\u0022\u003EConfigure Multiple 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.ConfigureMode(UAuthMode.Hybrid, mode =\u0026gt;\n {\n mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(10);\n });\n\n options.ConfigureMode(UAuthMode.PureOpaque, mode =\u0026gt;\n {\n mode.Session.IdleTimeout = TimeSpan.FromDays(3);\n });\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach override is associated with its specified mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-limitations\u0022\u003EImportant Limitations\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EConfigureMode()\u003C/code\u003E configures the effective options for a mode.\u003C/p\u003E\n\u003Cp\u003EIt does not:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnable an unsupported authentication mode.\u003C/li\u003E\n\u003Cli\u003EBypass the server\u0027s allowed-mode restrictions.\u003C/li\u003E\n\u003Cli\u003EReplace the authentication mode resolver.\u003C/li\u003E\n\u003Cli\u003EAutomatically create a tenant-specific configuration.\u003C/li\u003E\n\u003Cli\u003EGuarantee that every combination of effective settings is safe.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe current implementation applies mode overrides during effective-options resolution.\u003C/p\u003E\n\u003Cp\u003EDo not assume that every mode-specific combination receives the same startup validation as the original \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E instance.\u003C/p\u003E\n\u003Cp\u003ETest mode overrides carefully, especially when changing token issuance, session lifetimes, or credential delivery.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-defaults\u0022\u003E6. Mode Defaults\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides mode-specific defaults for authentication behavior.\u003C/p\u003E\n\u003Cp\u003EThe current implementation includes templates for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ESemiHybrid\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EPureJwt\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese templates configure relevant token, session, cookie, and response behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque\u0022\u003EPureOpaque\u003C/h3\u003E\n\u003Cp\u003EThe \u003Ccode\u003EPureOpaque\u003C/code\u003E defaults 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\u003EToken.IssueJwt = false;\nToken.IssueOpaque = false;\nToken.IssueRefresh = false;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn this mode, the session identifier is the primary opaque authentication credential.\u003C/p\u003E\n\u003Cp\u003EThe defaults also configure a longer idle timeout and session touch interval where no explicit values were supplied.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003EHybrid\u003C/h3\u003E\n\u003Cp\u003EThe \u003Ccode\u003EHybrid\u003C/code\u003E defaults 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\u003EToken.IssueJwt = true;\nToken.IssueOpaque = true;\nToken.AccessTokenLifetime = TimeSpan.FromMinutes(10);\nToken.RefreshTokenLifetime = TimeSpan.FromDays(7);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EHybrid also configures refresh-token delivery through a cookie.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022unsupported-modes\u0022\u003EUnsupported Modes\u003C/h3\u003E\n\u003Cp\u003EAlthough default templates exist for \u003Ccode\u003ESemiHybrid\u003C/code\u003E and \u003Ccode\u003EPureJwt\u003C/code\u003E, their presence does not mean those modes are ready for production use.\u003C/p\u003E\n\u003Cp\u003EThe server\u0027s current validator explicitly rejects these modes when they are enabled through \u003Ccode\u003EAllowedModes\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EOnly use modes supported by the current release.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-effective-options\u0022\u003E7. Inspect Effective Options\u003C/h2\u003E\n\u003Cp\u003EAdvanced integrations can use \u003Ccode\u003EIEffectiveServerOptionsProvider\u003C/code\u003E to inspect configured and effective server options.\u003C/p\u003E\n\u003Cp\u003EThe interface 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\u003Epublic interface IEffectiveServerOptionsProvider\n{\n UAuthServerOptions GetOriginal(HttpContext context);\n EffectiveUAuthServerOptions GetEffective(HttpContext context, UAuthClientProfile clientProfile);\n EffectiveUAuthServerOptions GetEffective(TenantKey tenant, UAuthClientProfile clientProfile);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-the-original-configuration\u0022\u003EInspect the Original 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\u003Evar original = provider.GetOriginal(httpContext);\nvar configuredLifetime = original.Token.AccessTokenLifetime;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-the-effective-configuration\u0022\u003EInspect the Effective 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\u003Evar effective = provider.GetEffective(httpContext, UAuthClientProfile.BlazorWasm);\nvar mode = effective.Mode;\nvar lifetime = effective.Options.Token.AccessTokenLifetime;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is useful when diagnosing unexpected differences between configured and runtime behavior.\u003C/p\u003E\n\u003Cp\u003EFor example, a globally configured access token lifetime may differ from the effective value because a mode template overrides it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-not-mutate-the-original-options\u0022\u003EDo Not Mutate the Original Options\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EGetOriginal()\u003C/code\u003E returns the original options instance.\u003C/p\u003E\n\u003Cp\u003ETreat it as read-only application configuration.\u003C/p\u003E\n\u003Cp\u003ERuntime customization should use the supported configuration mechanisms rather than mutating registered options during requests.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-authentication-mode-resolution\u0022\u003E8. Custom Authentication Mode Resolution\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth 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\u003EIEffectiveAuthModeResolver\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe interface 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 interface IEffectiveAuthModeResolver\n{\n UAuthMode Resolve(UAuthClientProfile clientProfile);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe default implementation selects the mode using the client profile.\u003C/p\u003E\n\u003Cp\u003EAn application can provide its own resolver when the default mapping does not meet its requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-resolver-example\u0022\u003ECustom Resolver Example\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 class ApplicationAuthModeResolver : IEffectiveAuthModeResolver\n{\n public UAuthMode Resolve(UAuthClientProfile clientProfile)\n {\n return clientProfile switch\n {\n UAuthClientProfile.BlazorServer =\u0026gt; UAuthMode.PureOpaque,\n UAuthClientProfile.BlazorWasm =\u0026gt; UAuthMode.Hybrid,\n UAuthClientProfile.Maui =\u0026gt; UAuthMode.Hybrid,\n\n _ =\u0026gt; UAuthMode.Hybrid\n };\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERegister the 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.AddSingleton\u0026lt;IEffectiveAuthModeResolver, ApplicationAuthModeResolver\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server registers its default resolver using \u003Ccode\u003ETryAddSingleton\u003C/code\u003E, so an existing registration can take precedence.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003ESecurity Considerations\u003C/h3\u003E\n\u003Cp\u003EThe custom resolver must return a mode compatible with the server\u0027s supported modes and security requirements.\u003C/p\u003E\n\u003Cp\u003EDo not choose authentication modes based solely on untrusted client-provided values without appropriate validation.\u003C/p\u003E\n\u003Cp\u003EAlso note that the current resolver interface receives a client profile, not an arbitrary HTTP request context.\u003C/p\u003E\n\u003Cp\u003EFor more complex context-dependent selection, additional application integration may be necessary.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022dependency-injection-extension-points\u0022\u003E9. Dependency Injection Extension Points\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth registers many internal services through dependency injection.\u003C/p\u003E\n\u003Cp\u003ESeveral default registrations use \u003Ccode\u003ETryAddScoped\u003C/code\u003E or \u003Ccode\u003ETryAddSingleton\u003C/code\u003E, allowing applications to supply an implementation before the default registration is added.\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.AddScoped\u0026lt;ISessionValidator, ApplicationSessionValidator\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe application-defined validator is registered before the UltimateAuth server\u0027s default registration.\u003C/p\u003E\n\u003Cp\u003EBecause the server uses \u003Ccode\u003ETryAddScoped\u003C/code\u003E for this service, it does not add its default implementation when an existing registration is present.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022registration-order-matters\u0022\u003ERegistration Order Matters\u003C/h3\u003E\n\u003Cp\u003EFor services registered using \u003Ccode\u003ETryAdd...\u003C/code\u003E, prefer:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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;ISessionValidator, ApplicationSessionValidator\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDo not assume that registering a replacement after the server will always produce the same behavior.\u003C/p\u003E\n\u003Cp\u003EThe result depends on the service\u0027s registration pattern and how it is resolved.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022not-every-service-is-replaceable-in-the-same-way\u0022\u003ENot Every Service Is Replaceable in the Same Way\u003C/h3\u003E\n\u003Cp\u003ESome UltimateAuth services use ordinary \u003Ccode\u003EAddSingleton\u003C/code\u003E or \u003Ccode\u003EAddScoped\u003C/code\u003E registrations.\u003C/p\u003E\n\u003Cp\u003EOthers use \u003Ccode\u003ETryAdd...\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ESome are registered as collections.\u003C/p\u003E\n\u003Cp\u003EThese patterns have different override semantics.\u003C/p\u003E\n\u003Cp\u003ETherefore, do not assume that every internal service can be replaced safely or that every replacement preserves the intended security behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-login-authority\u0022\u003E10. Custom Login Authority\u003C/h2\u003E\n\u003Cp\u003EThe login authority participates in deciding whether a login attempt is permitted.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth 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\u003EILoginAuthority\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe interface 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 interface ILoginAuthority\n{\n LoginDecision Decide(LoginDecisionContext context);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn application can implement its own login decision logic.\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\u003Epublic sealed class ApplicationLoginAuthority : ILoginAuthority\n{\n public LoginDecision Decide(LoginDecisionContext context)\n {\n // Evaluate application-specific login rules.\n // Return a valid LoginDecision.\n throw new NotImplementedException();\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe example illustrates the extension contract; it is not a usable implementation.\u003C/p\u003E\n\u003Cp\u003ERegister the 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;ILoginAuthority, ApplicationLoginAuthority\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-security-boundary\u0022\u003EImportant Security Boundary\u003C/h3\u003E\n\u003Cp\u003EReplacing the login authority changes a security decision component.\u003C/p\u003E\n\u003Cp\u003EThe replacement must preserve required authentication checks and correctly handle all decision outcomes.\u003C/p\u003E\n\u003Cp\u003EDo not replace the authority merely to add logging, notifications, or metrics.\u003C/p\u003E\n\u003Cp\u003EUse lifecycle events for those purposes.\u003C/p\u003E\n\u003Cp\u003EIf the application only needs additional access rules, consider the policy system before replacing a core authority.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorities-policies-and-invariants\u0022\u003E11. Authorities, Policies, and Invariants\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates different responsibilities within its security architecture.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorities\u0022\u003EAuthorities\u003C/h3\u003E\n\u003Cp\u003EAuthorities evaluate security decisions.\u003C/p\u003E\n\u003Cp\u003EExamples 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\u003EIAuthAuthority\nIAccessAuthority\nILoginAuthority\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies\u0022\u003EPolicies\u003C/h3\u003E\n\u003Cp\u003EPolicies define application-specific rules used by the authorization system.\u003C/p\u003E\n\u003Cp\u003EThey should be the preferred extension mechanism when the requirement is to add or refine access rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022invariants\u0022\u003EInvariants\u003C/h3\u003E\n\u003Cp\u003EInvariants enforce foundational security requirements.\u003C/p\u003E\n\u003Cp\u003EThe server registers invariants including:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EDeviceRequiredInvariant\nExpiredSessionInvariant\nInvalidOrRevokedSessionInvariant\nTenantResolvedInvariant\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese are registered as a collection of \u003Ccode\u003EIAuthorityInvariant\u003C/code\u003E implementations.\u003C/p\u003E\n\u003Cp\u003EUnlike ordinary application policies, security invariants protect fundamental authentication requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not remove or bypass invariants to make a custom authentication flow succeed.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EFor example, an expired or revoked session should not become valid merely because a custom access policy grants permission.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-tenant-resolution\u0022\u003E12. Custom Tenant Resolution\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports route, header, and host-based tenant resolution.\u003C/p\u003E\n\u003Cp\u003EThese mechanisms are controlled through server options.\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.MultiTenant.EnableHeader = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor applications with a different tenant identification strategy, UltimateAuth 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\u003EITenantIdResolver\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server\u0027s default registration uses \u003Ccode\u003ETryAddSingleton\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA custom resolver can therefore be registered before 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\u003Ebuilder.Services.AddSingleton\u0026lt;ITenantIdResolver, ApplicationTenantIdResolver\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationTenantIdResolver\u003C/code\u003E represents an application-defined implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-to-use-a-custom-resolver\u0022\u003EWhen to Use a Custom Resolver\u003C/h3\u003E\n\u003Cp\u003EA custom tenant resolver may be appropriate when tenants are identified through:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn application-specific host naming convention.\u003C/li\u003E\n\u003Cli\u003EA trusted gateway-provided tenant identifier.\u003C/li\u003E\n\u003Cli\u003EA custom routing structure.\u003C/li\u003E\n\u003Cli\u003EAn existing tenant management system.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations-1\u0022\u003ESecurity Considerations\u003C/h3\u003E\n\u003Cp\u003ETenant identification is not the same as tenant authorization.\u003C/p\u003E\n\u003Cp\u003EThe server must still verify that the authenticated user or application is allowed to access the resolved tenant.\u003C/p\u003E\n\u003Cp\u003ENever trust arbitrary tenant identifiers supplied by clients without applying the required validation and isolation rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-session-and-device-resolution\u0022\u003E13. Custom Session and Device Resolution\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates session identifier resolution from session validation.\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\u003EISessionIdResolver\nISessionValidator\nIDeviceResolver\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese interfaces have different responsibilities.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EComponent\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\u003EISessionIdResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDetermine which session identifier is supplied\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EISessionValidator\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EValidate the session\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIDeviceResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve device information\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe server supports multiple session identifier sources through internal resolvers, including cookies, bearer credentials, headers, and query parameters.\u003C/p\u003E\n\u003Cp\u003EThe active resolution behavior is controlled by the configured session resolution options.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-session-validator\u0022\u003ECustom Session Validator\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;ISessionValidator, ApplicationSessionValidator\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA custom validator must preserve the relevant expiration, revocation, and security-state checks.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-device-resolver\u0022\u003ECustom Device Resolver\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;IDeviceResolver, ApplicationDeviceResolver\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDevice resolution may affect session identification and security decisions.\u003C/p\u003E\n\u003Cp\u003ECustom implementations should produce consistent, trustworthy device information.\u003C/p\u003E\n\u003Cp\u003EAvoid replacing these services unless the application\u0027s requirements cannot be met through standard options.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-stores\u0022\u003E14. Custom Stores\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates domain behavior from persistence through store interfaces.\u003C/p\u003E\n\u003Cp\u003EExamples 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\u003EISessionStore\nIRefreshTokenStore\nIRoleStore\nIUserRoleStore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ESome stores are resolved through corresponding factory interfaces.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides persistence implementations in separate packages, including in-memory and Entity Framework Core implementations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-a-store-implementation\u0022\u003EChoosing a Store Implementation\u003C/h3\u003E\n\u003Cp\u003EUse in-memory implementations for suitable development, testing, or demonstration scenarios.\u003C/p\u003E\n\u003Cp\u003EUse durable persistence for production authentication state that must survive process restarts or be shared across application instances.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-store-requirements\u0022\u003ECustom Store Requirements\u003C/h3\u003E\n\u003Cp\u003EA custom store must preserve the semantics expected by the authentication services.\u003C/p\u003E\n\u003Cp\u003EDepending on the store, these may include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECorrect session expiration handling.\u003C/li\u003E\n\u003Cli\u003EReliable revocation state.\u003C/li\u003E\n\u003Cli\u003ERefresh token consumption and replay protection.\u003C/li\u003E\n\u003Cli\u003EConsistent user and tenant scoping.\u003C/li\u003E\n\u003Cli\u003EAppropriate concurrency behavior.\u003C/li\u003E\n\u003Cli\u003EAtomic state transitions where required.\u003C/li\u003E\n\u003Cli\u003ECorrect deletion and lifecycle behavior.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, refresh token rotation may depend on preventing two concurrent requests from successfully consuming the same token.\u003C/p\u003E\n\u003Cp\u003EA store implementation that does not enforce the required concurrency semantics can undermine the security of the refresh flow.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022store-registration\u0022\u003EStore Registration\u003C/h3\u003E\n\u003Cp\u003ECustom store registration depends on the specific store interface and its factory.\u003C/p\u003E\n\u003Cp\u003EDo not assume that replacing one store interface is sufficient when the runtime resolves stores through a factory.\u003C/p\u003E\n\u003Cp\u003EUse the relevant domain package\u0027s documented registration pattern.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-lifecycle-events\u0022\u003E15. Authentication Lifecycle Events\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides application-wide authentication event hooks 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\u003EUAuthEvents\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese hooks allow applications to react to authentication lifecycle events without replacing the authentication flow.\u003C/p\u003E\n\u003Cp\u003EThe current event options expose:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EOnAnyEvent\nOnUserLoggedIn\nOnUserLoggedOut\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022login-event\u0022\u003ELogin Event\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.Events.OnUserLoggedIn = context =\u0026gt;\n {\n // Record a successful login.\n return Task.CompletedTask;\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-event\u0022\u003ELogout Event\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.Events.OnUserLoggedOut = context =\u0026gt;\n {\n // Record a logout event.\n return Task.CompletedTask;\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022global-event-hook\u0022\u003EGlobal Event Hook\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.Events.OnAnyEvent = context =\u0026gt;\n {\n // Process an authentication event.\n return Task.CompletedTask;\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe global hook receives an \u003Ccode\u003EIAuthEventContext\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022appropriate-event-uses\u0022\u003EAppropriate Event Uses\u003C/h3\u003E\n\u003Cp\u003ELifecycle events are suitable for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAudit logging.\u003C/li\u003E\n\u003Cli\u003EApplication metrics.\u003C/li\u003E\n\u003Cli\u003EMonitoring.\u003C/li\u003E\n\u003Cli\u003ENotifications.\u003C/li\u003E\n\u003Cli\u003ELightweight post-operation integration.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThey should not be used to implement critical authentication checks.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022events-are-not-decision-hooks\u0022\u003EEvents Are Not Decision Hooks\u003C/h3\u003E\n\u003Cp\u003EThe documented event delegates run after their corresponding successful operations.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EOnUserLoggedIn\u003C/code\u003E is not the place to decide whether the login should have been permitted.\u003C/p\u003E\n\u003Cp\u003EUse an appropriate authority or policy for security decisions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022avoid-complex-work-inside-events\u0022\u003EAvoid Complex Work Inside Events\u003C/h3\u003E\n\u003Cp\u003ELong-running or failure-prone external operations should generally be delegated to a reliable application service or message-processing mechanism.\u003C/p\u003E\n\u003Cp\u003EDo not make critical authentication behavior depend on an unreliable external notification system.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-jwt-signing-keys\u0022\u003E16. Custom JWT Signing Keys\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth 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\u003EIJwtSigningKeyProvider\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server registers a development signing key provider as its default implementation.\u003C/p\u003E\n\u003Cp\u003EProduction applications should supply an appropriate signing key strategy.\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.AddSingleton\u0026lt;IJwtSigningKeyProvider, ApplicationJwtSigningKeyProvider\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationJwtSigningKeyProvider\u003C/code\u003E is an implementation supplied by the application.\u003C/p\u003E\n\u003Cp\u003EA production implementation should use appropriately protected signing keys and support the application\u0027s operational requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022signing-key-responsibilities\u0022\u003ESigning Key Responsibilities\u003C/h3\u003E\n\u003Cp\u003EConsider:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EKey confidentiality.\u003C/li\u003E\n\u003Cli\u003EKey rotation.\u003C/li\u003E\n\u003Cli\u003EKey identification.\u003C/li\u003E\n\u003Cli\u003EConsistent validation across resource servers.\u003C/li\u003E\n\u003Cli\u003ESecure key storage.\u003C/li\u003E\n\u003Cli\u003ERecovery and deployment procedures.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EDo not use development signing keys as a production security strategy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoint-customization\u0022\u003E17. Endpoint Customization\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides 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:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EIEndpointRouteBuilder\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.OnConfigureEndpoints = endpoints =\u0026gt;\n {\n // Apply supported application-specific\n // endpoint configuration here.\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe hook is invoked after UltimateAuth registers its default endpoints.\u003C/p\u003E\n\u003Cp\u003EIt can support advanced endpoint integration, but it should not be used to remove, remap, or weaken the security requirements of UltimateAuth endpoints.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022prefer-endpoint-options-first\u0022\u003EPrefer Endpoint Options First\u003C/h3\u003E\n\u003Cp\u003EFor ordinary endpoint configuration, use:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Authentication = true;\n options.Endpoints.Credentials = true;\n options.Endpoints.Authorization = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe advanced endpoint hook is intended for cases that cannot be addressed through the supported endpoint options.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-sources-and-precedence\u0022\u003E18. Configuration Sources and Precedence\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth integrates with the standard ASP.NET Core configuration system.\u003C/p\u003E\n\u003Cp\u003EFor the server, the 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:Server\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor the client:\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 Resource APIs:\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\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022registration-order\u0022\u003ERegistration Order\u003C/h3\u003E\n\u003Cp\u003EThe standard server registration applies:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EOption type defaults.\u003C/li\u003E\n\u003Cli\u003EThe \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E configuration delegate.\u003C/li\u003E\n\u003Cli\u003EValues bound from \u003Ccode\u003EUltimateAuth:Server\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EPost-configuration processing.\u003C/li\u003E\n\u003Cli\u003ERegistered options validation.\u003C/li\u003E\n\u003C/ol\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.Login.MaxFailedAttempts = 5;\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 configured value is \u003Ccode\u003E3\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe configuration binding occurs after the registration delegate.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022environment-variables\u0022\u003EEnvironment Variables\u003C/h3\u003E\n\u003Cp\u003EASP.NET Core supports hierarchical configuration keys through double underscores.\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 corresponds 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\u003EEnvironment-specific configuration can therefore override values without requiring changes to application code.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022environment-specific-files\u0022\u003EEnvironment-Specific Files\u003C/h3\u003E\n\u003Cp\u003EStandard ASP.NET Core applications commonly use:\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\u003Eappsettings.json\nappsettings.Development.json\nappsettings.Staging.json\nappsettings.Production.json\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe active environment determines which environment-specific file is loaded.\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\u003EASPNETCORE_ENVIRONMENT=Production\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe final value depends on the configured ASP.NET Core configuration providers and their precedence.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-binding-does-not-replace-mode-resolution\u0022\u003EConfiguration Binding Does Not Replace Mode Resolution\u003C/h3\u003E\n\u003Cp\u003EA value supplied through \u003Ccode\u003Eappsettings.json\u003C/code\u003E becomes part of the configured options.\u003C/p\u003E\n\u003Cp\u003EIt may still be replaced later when UltimateAuth applies mode defaults.\u003C/p\u003E\n\u003Cp\u003EFor settings explicitly controlled by mode templates, use \u003Ccode\u003EConfigureMode()\u003C/code\u003E when the desired behavior must apply to a specific mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022startup-validation-and-runtime-safety\u0022\u003E19. Startup Validation and Runtime Safety\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth validates server options through registered \u003Ccode\u003EIValidateOptions\u0026lt;UAuthServerOptions\u0026gt;\u003C/code\u003E implementations.\u003C/p\u003E\n\u003Cp\u003EThe server uses:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EValidateOnStart()\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows many invalid configurations to fail during application startup.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalid endpoint base paths.\u003C/li\u003E\n\u003Cli\u003EInvalid or unsupported allowed modes.\u003C/li\u003E\n\u003Cli\u003EInvalid session lifetimes.\u003C/li\u003E\n\u003Cli\u003EIncompatible token and session lifetime constraints.\u003C/li\u003E\n\u003Cli\u003EInvalid login settings.\u003C/li\u003E\n\u003Cli\u003EInvalid tenant configuration.\u003C/li\u003E\n\u003Cli\u003EInvalid pagination settings.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validation-has-boundaries\u0022\u003EValidation Has Boundaries\u003C/h3\u003E\n\u003Cp\u003EStartup validation applies to the configured options according to the registered validators.\u003C/p\u003E\n\u003Cp\u003EIt does not automatically prove that every custom dependency injection replacement is safe.\u003C/p\u003E\n\u003Cp\u003EIt also does not guarantee that every mode-specific override has been validated against all possible security invariants.\u003C/p\u003E\n\u003Cp\u003ECustom implementations must preserve the relevant contracts and security requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-validation-strategy\u0022\u003ERecommended Validation Strategy\u003C/h3\u003E\n\u003Cp\u003EFor advanced configurations:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EVerify that the application starts successfully.\u003C/li\u003E\n\u003Cli\u003ETest each enabled authentication mode.\u003C/li\u003E\n\u003Cli\u003ETest login, refresh, validation, and logout.\u003C/li\u003E\n\u003Cli\u003ETest session expiration and revocation.\u003C/li\u003E\n\u003Cli\u003ETest tenant isolation.\u003C/li\u003E\n\u003Cli\u003ETest authorization denial paths.\u003C/li\u003E\n\u003Cli\u003ETest concurrent refresh requests where applicable.\u003C/li\u003E\n\u003Cli\u003EVerify that effective options contain the intended values.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThese checks are especially important when customizing authorities, validators, stores, or authentication mode behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-configuration-mistakes\u0022\u003E20. Common Configuration Mistakes\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuring-core-options-separately-from-server-options\u0022\u003EConfiguring Core Options Separately from Server Options\u003C/h3\u003E\n\u003Cp\u003EWhen using \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E, configure Core-defined behavior through \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EDo not expect a separate \u003Ccode\u003EUAuthOptions\u003C/code\u003E configuration to override the server\u0027s composed options.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-global-values-always-survive-mode-defaults\u0022\u003EAssuming Global Values Always Survive Mode Defaults\u003C/h3\u003E\n\u003Cp\u003EMode templates can overwrite values such as access token lifetime.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003EConfigureMode()\u003C/code\u003E for explicit mode-specific customization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022treating-allowedmodes-as-mode-selection\u0022\u003ETreating AllowedModes as Mode Selection\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EAllowedModes\u003C/code\u003E restricts permitted modes.\u003C/p\u003E\n\u003Cp\u003EIt does not replace \u003Ccode\u003EIEffectiveAuthModeResolver\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022enabling-an-unsupported-mode\u0022\u003EEnabling an Unsupported Mode\u003C/h3\u003E\n\u003Cp\u003EAn enum value or default template does not establish that a mode is ready for production.\u003C/p\u003E\n\u003Cp\u003EUse only modes supported by the current implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022replacing-security-components-unnecessarily\u0022\u003EReplacing Security Components Unnecessarily\u003C/h3\u003E\n\u003Cp\u003EReplacing a validator or authority can change fundamental authentication behavior.\u003C/p\u003E\n\u003Cp\u003EPrefer options and policies when they satisfy the requirement.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-every-di-service-is-replaceable\u0022\u003EAssuming Every DI Service Is Replaceable\u003C/h3\u003E\n\u003Cp\u003EDifferent services use different registration patterns.\u003C/p\u003E\n\u003Cp\u003EReview the actual registration behavior before replacing a service.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-events-for-authorization-decisions\u0022\u003EUsing Events for Authorization Decisions\u003C/h3\u003E\n\u003Cp\u003ELifecycle events are for post-operation reactions.\u003C/p\u003E\n\u003Cp\u003EUse authorities and policies for decisions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022ignoring-store-concurrency\u0022\u003EIgnoring Store Concurrency\u003C/h3\u003E\n\u003Cp\u003EAuthentication stores must preserve the required consistency and atomicity semantics.\u003C/p\u003E\n\u003Cp\u003EThis is particularly important for refresh token rotation and revocation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-runtime-overrides-receive-full-startup-validation\u0022\u003EAssuming Runtime Overrides Receive Full Startup Validation\u003C/h3\u003E\n\u003Cp\u003EMode-specific overrides are applied during effective-options construction.\u003C/p\u003E\n\u003Cp\u003EValidate their results through tests and appropriate application safeguards.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022advanced-configuration-reference\u0022\u003E21. Advanced Configuration Reference\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EMechanism\u003C/th\u003E\n\u003Cth\u003EAPI / Type\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\u003EServer configuration\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBase server settings\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMode customization\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EConfigureMode()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EApply mode-specific overrides\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EEffective configuration\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIEffectiveServerOptionsProvider\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve effective server settings\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EEffective result\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EEffectiveUAuthServerOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolved mode and options\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMode selection\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIEffectiveAuthModeResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve mode from client profile\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ELogin decisions\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EILoginAuthority\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEvaluate login decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthentication authority\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIAuthAuthority\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication security decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAccess authority\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIAccessAuthority\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAccess decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ETenant identification\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EITenantIdResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve tenant identifiers\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession validation\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EISessionValidator\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EValidate sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession identification\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EISessionIdResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve session identifiers\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDevice identification\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIDeviceResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve device information\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESigning keys\u003C/td\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\u003Ctr\u003E\n\u003Ctd\u003ELifecycle events\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthEvents\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EObserve completed authentication operations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EEndpoint customization\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnConfigureEndpoints\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAdvanced endpoint configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession persistence\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EISessionStore\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession storage\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERefresh persistence\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIRefreshTokenStore\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERefresh token storage\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=\u0022recommended-extension-strategy\u0022\u003E22. Recommended Extension Strategy\u003C/h2\u003E\n\u003Cp\u003EWhen an application requires customization, follow this order:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse existing options.\u003C/strong\u003E Prefer supported configuration properties.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse mode overrides.\u003C/strong\u003E Apply \u003Ccode\u003EConfigureMode()\u003C/code\u003E when behavior must differ by authentication mode.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse application policies.\u003C/strong\u003E Extend authorization rules without replacing the authorization pipeline.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse lifecycle events.\u003C/strong\u003E Implement logging, monitoring, and notifications without changing authentication decisions.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse focused resolvers or providers.\u003C/strong\u003E Customize tenant identification, signing keys, or other specific infrastructure concerns.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EReplace authorities, validators, or stores only when necessary.\u003C/strong\u003E Preserve all relevant security invariants and verify the implementation through tests.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe objective is not to replace as much of UltimateAuth as possible.\u003C/p\u003E\n\u003Cp\u003EIt is to customize the smallest component necessary while retaining the guarantees of the surrounding 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/configuration/configuration\u0022\u003EConfiguration Overview\u003C/a\u003E \u2014 Understand registration, configuration sources, and architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E \u2014 Configure authentication and server security behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E \u2014 Configure SDK flows and client behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Understand supported authentication modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Use the client authorization API.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Manage sessions and revocation.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth supports advanced customization of authentication behavior through effective server options, authentication mode resolution, dependency injection, custom infrastructure, and lifecycle events.\u003C/p\u003E\n\u003Cp\u003EThese mechanisms serve different purposes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EOptions\u003C/strong\u003E define configurable behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EEffective options\u003C/strong\u003E adapt that behavior to the selected authentication mode.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EResolvers\u003C/strong\u003E determine information required by authentication flows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthorities and validators\u003C/strong\u003E participate in security decisions.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EStores\u003C/strong\u003E persist authentication and application state.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EEvents\u003C/strong\u003E allow applications to react to completed operations.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EMost applications should begin with the standard Server and Client options. Advanced customization becomes useful when an application needs specialized authentication behavior, custom infrastructure, or integration with existing systems.\u003C/p\u003E\n\u003Cp\u003EThis guide explains the supported extension mechanisms, their execution order, and the security boundaries developers must preserve.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-versus-extensibility\u0022\u003E1. Configuration Versus Extensibility\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between configuring existing behavior and replacing an implementation.\u003C/p\u003E\n\u003Cp\u003EFor example, changing a session lifetime is 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.Session.Lifetime = TimeSpan.FromHours(8);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EProviding a custom clock is an infrastructure extension:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddSingleton\u0026lt;IClock, ApplicationClock\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationClock\u003C/code\u003E is an implementation supplied by your application.\u003C/p\u003E\n\u003Cp\u003EThese approaches are not interchangeable.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ERequirement\u003C/th\u003E\n\u003Cth\u003ERecommended mechanism\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EChange session lifetime\u003C/td\u003E\n\u003Ctd\u003EServer options\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EChange token lifetime for a specific mode\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EConfigureMode()\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESelect authentication mode differently\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIEffectiveAuthModeResolver\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EResolve tenants from a custom source\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EITenantIdResolver\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECustomize login decisions\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EILoginAuthority\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESupply signing keys\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIJwtSigningKeyProvider\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPersist sessions in another backend\u003C/td\u003E\n\u003Ctd\u003EStore implementation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReact to successful login\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthEvents\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAdd application-specific access rules\u003C/td\u003E\n\u003Ctd\u003EPolicies\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EPrefer the most specific supported mechanism rather than replacing an entire authentication component.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-effective-server-options\u0022\u003E2. Understanding Effective Server Options\u003C/h2\u003E\n\u003Cp\u003EThe server does not necessarily use the original \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E instance unchanged for every authentication flow.\u003C/p\u003E\n\u003Cp\u003EInstead, UltimateAuth can resolve an effective configuration based on the client profile and authentication mode.\u003C/p\u003E\n\u003Cp\u003EThe relevant 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\u003EIEffectiveServerOptionsProvider\nEffectiveUAuthServerOptions\nIEffectiveAuthModeResolver\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configured-options\u0022\u003EConfigured Options\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E represents the application\u0027s configured server settings.\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 options.Token.Issuer = \u0026quot;MyApplication\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese settings form the base configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022effective-options\u0022\u003EEffective Options\u003C/h3\u003E\n\u003Cp\u003EThe provider produces an \u003Ccode\u003EEffectiveUAuthServerOptions\u003C/code\u003E instance containing:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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 class EffectiveUAuthServerOptions\n{\n public UAuthMode Mode { get; init; }\n public UAuthServerOptions Options { get; init; } = default!;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EMode\u003C/code\u003E identifies the resolved authentication mode.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EOptions\u003C/code\u003E contains the effective server configuration for that mode.\u003C/p\u003E\n\u003Cp\u003EThe effective configuration is derived from the original settings rather than replacing the application\u0027s registered options globally.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022effective-configuration-resolution\u0022\u003E3. Effective Configuration Resolution\u003C/h2\u003E\n\u003Cp\u003EThe current \u003Ccode\u003EEffectiveServerOptionsProvider\u003C/code\u003E follows this sequence:\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\u003EConfigured UAuthServerOptions\n |\n v\n Resolve mode\n |\n v\n Clone base options\n |\n v\n Apply mode defaults\n |\n v\n Apply ConfigureMode override\n |\n v\n EffectiveUAuthServerOptions\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn simplified form:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar original = baseOptions.Value;\n\nvar mode = modeResolver.Resolve(clientProfile);\n\nvar options = original.Clone();\n\nConfigureDefaults.ApplyModeDefaults(mode, options);\n\nif (original.ModeConfigurations.TryGetValue(mode, out var configure))\n{\n configure(options);\n}\n\nreturn new EffectiveUAuthServerOptions\n{\n Mode = mode,\n Options = options\n};\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis execution order is important.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EMode defaults are applied after the original configuration is cloned.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EConsequently, certain values configured globally may be replaced by mode-specific defaults.\u003C/p\u003E\n\u003Cp\u003EExplicit mode overrides are applied last.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-changes-between-requests\u0022\u003EWhat Changes Between Requests?\u003C/h3\u003E\n\u003Cp\u003EDifferent client profiles can resolve to different authentication modes.\u003C/p\u003E\n\u003Cp\u003EThe provider therefore can produce different effective configurations for different client profiles.\u003C/p\u003E\n\u003Cp\u003EHowever, this does not mean every setting is independently recalculated from arbitrary request data.\u003C/p\u003E\n\u003Cp\u003EIn the current implementation, mode selection is based on the supplied \u003Ccode\u003EUAuthClientProfile\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe provider also accepts tenant context, but the default effective-options construction does not apply an additional tenant-specific configuration override.\u003C/p\u003E\n\u003Cp\u003EApplications should not assume that per-tenant options customization exists automatically.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022default-authentication-mode-resolution\u0022\u003E4. Default Authentication Mode Resolution\u003C/h2\u003E\n\u003Cp\u003EThe default \u003Ccode\u003EEffectiveAuthModeResolver\u003C/code\u003E maps client profiles to authentication modes.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EClient profile\u003C/th\u003E\n\u003Cth\u003EResolved mode\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EWebServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorWasm\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMaui\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EApi\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureJwt\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOther / unknown\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese mappings describe the current default resolver.\u003C/p\u003E\n\u003Cp\u003EThey do not imply that every resolved mode is fully supported in the current release.\u003C/p\u003E\n\u003Cp\u003EIn particular, the current server options validator rejects explicit activation of \u003Ccode\u003ESemiHybrid\u003C/code\u003E and \u003Ccode\u003EPureJwt\u003C/code\u003E through \u003Ccode\u003EAllowedModes\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EApplications should verify that their client profiles resolve to a supported authentication mode before deploying.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022allowedmodes-is-not-a-mode-selector\u0022\u003EAllowedModes Is Not a Mode Selector\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\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\u003EThis restricts which modes are allowed.\u003C/p\u003E\n\u003Cp\u003EIt does not instruct the resolver to choose \u003Ccode\u003EHybrid\u003C/code\u003E for every client.\u003C/p\u003E\n\u003Cp\u003EMode selection and mode restrictions are separate responsibilities.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-authentication-modes\u0022\u003E5. Configure Authentication Modes\u003C/h2\u003E\n\u003Cp\u003EUse the public \u003Ccode\u003EConfigureMode()\u003C/code\u003E extension to customize settings for a particular authentication 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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.ConfigureMode(UAuthMode.Hybrid, mode =\u0026gt;\n {\n mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(5);\n });\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis configuration changes the effective access token lifetime for \u003Ccode\u003EHybrid\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIt does not change the effective \u003Ccode\u003EPureOpaque\u003C/code\u003E configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-configuremode-matters\u0022\u003EWhy ConfigureMode Matters\u003C/h3\u003E\n\u003Cp\u003EConsider this 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.Token.AccessTokenLifetime = TimeSpan.FromMinutes(30);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe current \u003Ccode\u003EHybrid\u003C/code\u003E mode defaults explicitly set:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EToken.AccessTokenLifetime = TimeSpan.FromMinutes(10);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EBecause mode defaults are applied after the base options are cloned, the effective \u003Ccode\u003EHybrid\u003C/code\u003E access token lifetime becomes ten minutes rather than thirty.\u003C/p\u003E\n\u003Cp\u003ETo override that behavior, use:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.ConfigureMode(UAuthMode.Hybrid, mode =\u0026gt;\n {\n mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(30);\n });\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe explicit mode override is applied after the mode defaults.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-multiple-modes\u0022\u003EConfigure Multiple 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.ConfigureMode(UAuthMode.Hybrid, mode =\u0026gt;\n {\n mode.Token.AccessTokenLifetime = TimeSpan.FromMinutes(10);\n });\n\n options.ConfigureMode(UAuthMode.PureOpaque, mode =\u0026gt;\n {\n mode.Session.IdleTimeout = TimeSpan.FromDays(3);\n });\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach override is associated with its specified mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-limitations\u0022\u003EImportant Limitations\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EConfigureMode()\u003C/code\u003E configures the effective options for a mode.\u003C/p\u003E\n\u003Cp\u003EIt does not:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnable an unsupported authentication mode.\u003C/li\u003E\n\u003Cli\u003EBypass the server\u0027s allowed-mode restrictions.\u003C/li\u003E\n\u003Cli\u003EReplace the authentication mode resolver.\u003C/li\u003E\n\u003Cli\u003EAutomatically create a tenant-specific configuration.\u003C/li\u003E\n\u003Cli\u003EGuarantee that every combination of effective settings is safe.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe current implementation applies mode overrides during effective-options resolution.\u003C/p\u003E\n\u003Cp\u003EDo not assume that every mode-specific combination receives the same startup validation as the original \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E instance.\u003C/p\u003E\n\u003Cp\u003ETest mode overrides carefully, especially when changing token issuance, session lifetimes, or credential delivery.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-defaults\u0022\u003E6. Mode Defaults\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides mode-specific defaults for authentication behavior.\u003C/p\u003E\n\u003Cp\u003EThe current implementation includes templates for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ESemiHybrid\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EPureJwt\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese templates configure relevant token, session, cookie, and response behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque\u0022\u003EPureOpaque\u003C/h3\u003E\n\u003Cp\u003EThe \u003Ccode\u003EPureOpaque\u003C/code\u003E defaults 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\u003EToken.IssueJwt = false;\nToken.IssueOpaque = false;\nToken.IssueRefresh = false;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn this mode, the session identifier is the primary opaque authentication credential.\u003C/p\u003E\n\u003Cp\u003EThe defaults also configure a longer idle timeout and session touch interval where no explicit values were supplied.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003EHybrid\u003C/h3\u003E\n\u003Cp\u003EThe \u003Ccode\u003EHybrid\u003C/code\u003E defaults 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\u003EToken.IssueJwt = true;\nToken.IssueOpaque = true;\nToken.AccessTokenLifetime = TimeSpan.FromMinutes(10);\nToken.RefreshTokenLifetime = TimeSpan.FromDays(7);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EHybrid also configures refresh-token delivery through a cookie.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022unsupported-modes\u0022\u003EUnsupported Modes\u003C/h3\u003E\n\u003Cp\u003EAlthough default templates exist for \u003Ccode\u003ESemiHybrid\u003C/code\u003E and \u003Ccode\u003EPureJwt\u003C/code\u003E, their presence does not mean those modes are ready for production use.\u003C/p\u003E\n\u003Cp\u003EThe server\u0027s current validator explicitly rejects these modes when they are enabled through \u003Ccode\u003EAllowedModes\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EOnly use modes supported by the current release.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-effective-options\u0022\u003E7. Inspect Effective Options\u003C/h2\u003E\n\u003Cp\u003EAdvanced integrations can use \u003Ccode\u003EIEffectiveServerOptionsProvider\u003C/code\u003E to inspect configured and effective server options.\u003C/p\u003E\n\u003Cp\u003EThe interface 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\u003Epublic interface IEffectiveServerOptionsProvider\n{\n UAuthServerOptions GetOriginal(HttpContext context);\n EffectiveUAuthServerOptions GetEffective(HttpContext context, UAuthClientProfile clientProfile);\n EffectiveUAuthServerOptions GetEffective(TenantKey tenant, UAuthClientProfile clientProfile);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-the-original-configuration\u0022\u003EInspect the Original 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\u003Evar original = provider.GetOriginal(httpContext);\nvar configuredLifetime = original.Token.AccessTokenLifetime;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022inspect-the-effective-configuration\u0022\u003EInspect the Effective 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\u003Evar effective = provider.GetEffective(httpContext, UAuthClientProfile.BlazorWasm);\nvar mode = effective.Mode;\nvar lifetime = effective.Options.Token.AccessTokenLifetime;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is useful when diagnosing unexpected differences between configured and runtime behavior.\u003C/p\u003E\n\u003Cp\u003EFor example, a globally configured access token lifetime may differ from the effective value because a mode template overrides it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-not-mutate-the-original-options\u0022\u003EDo Not Mutate the Original Options\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EGetOriginal()\u003C/code\u003E returns the original options instance.\u003C/p\u003E\n\u003Cp\u003ETreat it as read-only application configuration.\u003C/p\u003E\n\u003Cp\u003ERuntime customization should use the supported configuration mechanisms rather than mutating registered options during requests.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-authentication-mode-resolution\u0022\u003E8. Custom Authentication Mode Resolution\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth 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\u003EIEffectiveAuthModeResolver\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe interface 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 interface IEffectiveAuthModeResolver\n{\n UAuthMode Resolve(UAuthClientProfile clientProfile);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe default implementation selects the mode using the client profile.\u003C/p\u003E\n\u003Cp\u003EAn application can provide its own resolver when the default mapping does not meet its requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-resolver-example\u0022\u003ECustom Resolver Example\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 class ApplicationAuthModeResolver : IEffectiveAuthModeResolver\n{\n public UAuthMode Resolve(UAuthClientProfile clientProfile)\n {\n return clientProfile switch\n {\n UAuthClientProfile.BlazorServer =\u0026gt; UAuthMode.PureOpaque,\n UAuthClientProfile.BlazorWasm =\u0026gt; UAuthMode.Hybrid,\n UAuthClientProfile.Maui =\u0026gt; UAuthMode.Hybrid,\n\n _ =\u0026gt; UAuthMode.Hybrid\n };\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERegister the 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.AddSingleton\u0026lt;IEffectiveAuthModeResolver, ApplicationAuthModeResolver\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server registers its default resolver using \u003Ccode\u003ETryAddSingleton\u003C/code\u003E, so an existing registration can take precedence.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003ESecurity Considerations\u003C/h3\u003E\n\u003Cp\u003EThe custom resolver must return a mode compatible with the server\u0027s supported modes and security requirements.\u003C/p\u003E\n\u003Cp\u003EDo not choose authentication modes based solely on untrusted client-provided values without appropriate validation.\u003C/p\u003E\n\u003Cp\u003EAlso note that the current resolver interface receives a client profile, not an arbitrary HTTP request context.\u003C/p\u003E\n\u003Cp\u003EFor more complex context-dependent selection, additional application integration may be necessary.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022dependency-injection-extension-points\u0022\u003E9. Dependency Injection Extension Points\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth registers many internal services through dependency injection.\u003C/p\u003E\n\u003Cp\u003ESeveral default registrations use \u003Ccode\u003ETryAddScoped\u003C/code\u003E or \u003Ccode\u003ETryAddSingleton\u003C/code\u003E, allowing applications to supply an implementation before the default registration is added.\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.AddScoped\u0026lt;ISessionValidator, ApplicationSessionValidator\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe application-defined validator is registered before the UltimateAuth server\u0027s default registration.\u003C/p\u003E\n\u003Cp\u003EBecause the server uses \u003Ccode\u003ETryAddScoped\u003C/code\u003E for this service, it does not add its default implementation when an existing registration is present.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022registration-order-matters\u0022\u003ERegistration Order Matters\u003C/h3\u003E\n\u003Cp\u003EFor services registered using \u003Ccode\u003ETryAdd...\u003C/code\u003E, prefer:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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;ISessionValidator, ApplicationSessionValidator\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDo not assume that registering a replacement after the server will always produce the same behavior.\u003C/p\u003E\n\u003Cp\u003EThe result depends on the service\u0027s registration pattern and how it is resolved.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022not-every-service-is-replaceable-in-the-same-way\u0022\u003ENot Every Service Is Replaceable in the Same Way\u003C/h3\u003E\n\u003Cp\u003ESome UltimateAuth services use ordinary \u003Ccode\u003EAddSingleton\u003C/code\u003E or \u003Ccode\u003EAddScoped\u003C/code\u003E registrations.\u003C/p\u003E\n\u003Cp\u003EOthers use \u003Ccode\u003ETryAdd...\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ESome are registered as collections.\u003C/p\u003E\n\u003Cp\u003EThese patterns have different override semantics.\u003C/p\u003E\n\u003Cp\u003ETherefore, do not assume that every internal service can be replaced safely or that every replacement preserves the intended security behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-login-authority\u0022\u003E10. Custom Login Authority\u003C/h2\u003E\n\u003Cp\u003EThe login authority participates in deciding whether a login attempt is permitted.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth 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\u003EILoginAuthority\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe interface 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 interface ILoginAuthority\n{\n LoginDecision Decide(LoginDecisionContext context);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn application can implement its own login decision logic.\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\u003Epublic sealed class ApplicationLoginAuthority : ILoginAuthority\n{\n public LoginDecision Decide(LoginDecisionContext context)\n {\n // Evaluate application-specific login rules.\n // Return a valid LoginDecision.\n throw new NotImplementedException();\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe example illustrates the extension contract; it is not a usable implementation.\u003C/p\u003E\n\u003Cp\u003ERegister the 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;ILoginAuthority, ApplicationLoginAuthority\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-security-boundary\u0022\u003EImportant Security Boundary\u003C/h3\u003E\n\u003Cp\u003EReplacing the login authority changes a security decision component.\u003C/p\u003E\n\u003Cp\u003EThe replacement must preserve required authentication checks and correctly handle all decision outcomes.\u003C/p\u003E\n\u003Cp\u003EDo not replace the authority merely to add logging, notifications, or metrics.\u003C/p\u003E\n\u003Cp\u003EUse lifecycle events for those purposes.\u003C/p\u003E\n\u003Cp\u003EIf the application only needs additional access rules, consider the policy system before replacing a core authority.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorities-policies-and-invariants\u0022\u003E11. Authorities, Policies, and Invariants\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates different responsibilities within its security architecture.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorities\u0022\u003EAuthorities\u003C/h3\u003E\n\u003Cp\u003EAuthorities evaluate security decisions.\u003C/p\u003E\n\u003Cp\u003EExamples 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\u003EIAuthAuthority\nIAccessAuthority\nILoginAuthority\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies\u0022\u003EPolicies\u003C/h3\u003E\n\u003Cp\u003EPolicies define application-specific rules used by the authorization system.\u003C/p\u003E\n\u003Cp\u003EThey should be the preferred extension mechanism when the requirement is to add or refine access rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022invariants\u0022\u003EInvariants\u003C/h3\u003E\n\u003Cp\u003EInvariants enforce foundational security requirements.\u003C/p\u003E\n\u003Cp\u003EThe server registers invariants including:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EDeviceRequiredInvariant\nExpiredSessionInvariant\nInvalidOrRevokedSessionInvariant\nTenantResolvedInvariant\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese are registered as a collection of \u003Ccode\u003EIAuthorityInvariant\u003C/code\u003E implementations.\u003C/p\u003E\n\u003Cp\u003EUnlike ordinary application policies, security invariants protect fundamental authentication requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not remove or bypass invariants to make a custom authentication flow succeed.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EFor example, an expired or revoked session should not become valid merely because a custom access policy grants permission.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-tenant-resolution\u0022\u003E12. Custom Tenant Resolution\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports route, header, and host-based tenant resolution.\u003C/p\u003E\n\u003Cp\u003EThese mechanisms are controlled through server options.\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.MultiTenant.EnableHeader = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor applications with a different tenant identification strategy, UltimateAuth 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\u003EITenantIdResolver\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server\u0027s default registration uses \u003Ccode\u003ETryAddSingleton\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA custom resolver can therefore be registered before 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\u003Ebuilder.Services.AddSingleton\u0026lt;ITenantIdResolver, ApplicationTenantIdResolver\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationTenantIdResolver\u003C/code\u003E represents an application-defined implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-to-use-a-custom-resolver\u0022\u003EWhen to Use a Custom Resolver\u003C/h3\u003E\n\u003Cp\u003EA custom tenant resolver may be appropriate when tenants are identified through:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn application-specific host naming convention.\u003C/li\u003E\n\u003Cli\u003EA trusted gateway-provided tenant identifier.\u003C/li\u003E\n\u003Cli\u003EA custom routing structure.\u003C/li\u003E\n\u003Cli\u003EAn existing tenant management system.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations-1\u0022\u003ESecurity Considerations\u003C/h3\u003E\n\u003Cp\u003ETenant identification is not the same as tenant authorization.\u003C/p\u003E\n\u003Cp\u003EThe server must still verify that the authenticated user or application is allowed to access the resolved tenant.\u003C/p\u003E\n\u003Cp\u003ENever trust arbitrary tenant identifiers supplied by clients without applying the required validation and isolation rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-session-and-device-resolution\u0022\u003E13. Custom Session and Device Resolution\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates session identifier resolution from session validation.\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\u003EISessionIdResolver\nISessionValidator\nIDeviceResolver\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese interfaces have different responsibilities.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EComponent\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\u003EISessionIdResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDetermine which session identifier is supplied\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EISessionValidator\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EValidate the session\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIDeviceResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve device information\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe server supports multiple session identifier sources through internal resolvers, including cookies, bearer credentials, headers, and query parameters.\u003C/p\u003E\n\u003Cp\u003EThe active resolution behavior is controlled by the configured session resolution options.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-session-validator\u0022\u003ECustom Session Validator\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;ISessionValidator, ApplicationSessionValidator\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA custom validator must preserve the relevant expiration, revocation, and security-state checks.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-device-resolver\u0022\u003ECustom Device Resolver\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;IDeviceResolver, ApplicationDeviceResolver\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDevice resolution may affect session identification and security decisions.\u003C/p\u003E\n\u003Cp\u003ECustom implementations should produce consistent, trustworthy device information.\u003C/p\u003E\n\u003Cp\u003EAvoid replacing these services unless the application\u0027s requirements cannot be met through standard options.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-stores\u0022\u003E14. Custom Stores\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates domain behavior from persistence through store interfaces.\u003C/p\u003E\n\u003Cp\u003EExamples 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\u003EISessionStore\nIRefreshTokenStore\nIRoleStore\nIUserRoleStore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ESome stores are resolved through corresponding factory interfaces.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides persistence implementations in separate packages, including in-memory and Entity Framework Core implementations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-a-store-implementation\u0022\u003EChoosing a Store Implementation\u003C/h3\u003E\n\u003Cp\u003EUse in-memory implementations for suitable development, testing, or demonstration scenarios.\u003C/p\u003E\n\u003Cp\u003EUse durable persistence for production authentication state that must survive process restarts or be shared across application instances.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-store-requirements\u0022\u003ECustom Store Requirements\u003C/h3\u003E\n\u003Cp\u003EA custom store must preserve the semantics expected by the authentication services.\u003C/p\u003E\n\u003Cp\u003EDepending on the store, these may include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECorrect session expiration handling.\u003C/li\u003E\n\u003Cli\u003EReliable revocation state.\u003C/li\u003E\n\u003Cli\u003ERefresh token consumption and replay protection.\u003C/li\u003E\n\u003Cli\u003EConsistent user and tenant scoping.\u003C/li\u003E\n\u003Cli\u003EAppropriate concurrency behavior.\u003C/li\u003E\n\u003Cli\u003EAtomic state transitions where required.\u003C/li\u003E\n\u003Cli\u003ECorrect deletion and lifecycle behavior.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, refresh token rotation may depend on preventing two concurrent requests from successfully consuming the same token.\u003C/p\u003E\n\u003Cp\u003EA store implementation that does not enforce the required concurrency semantics can undermine the security of the refresh flow.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022store-registration\u0022\u003EStore Registration\u003C/h3\u003E\n\u003Cp\u003ECustom store registration depends on the specific store interface and its factory.\u003C/p\u003E\n\u003Cp\u003EDo not assume that replacing one store interface is sufficient when the runtime resolves stores through a factory.\u003C/p\u003E\n\u003Cp\u003EUse the relevant domain package\u0027s documented registration pattern.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-lifecycle-events\u0022\u003E15. Authentication Lifecycle Events\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides application-wide authentication event hooks 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\u003EUAuthEvents\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese hooks allow applications to react to authentication lifecycle events without replacing the authentication flow.\u003C/p\u003E\n\u003Cp\u003EThe current event options expose:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EOnAnyEvent\nOnUserLoggedIn\nOnUserLoggedOut\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022login-event\u0022\u003ELogin Event\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.Events.OnUserLoggedIn = context =\u0026gt;\n {\n // Record a successful login.\n return Task.CompletedTask;\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-event\u0022\u003ELogout Event\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.Events.OnUserLoggedOut = context =\u0026gt;\n {\n // Record a logout event.\n return Task.CompletedTask;\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022global-event-hook\u0022\u003EGlobal Event Hook\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.Events.OnAnyEvent = context =\u0026gt;\n {\n // Process an authentication event.\n return Task.CompletedTask;\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe global hook receives an \u003Ccode\u003EIAuthEventContext\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022appropriate-event-uses\u0022\u003EAppropriate Event Uses\u003C/h3\u003E\n\u003Cp\u003ELifecycle events are suitable for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAudit logging.\u003C/li\u003E\n\u003Cli\u003EApplication metrics.\u003C/li\u003E\n\u003Cli\u003EMonitoring.\u003C/li\u003E\n\u003Cli\u003ENotifications.\u003C/li\u003E\n\u003Cli\u003ELightweight post-operation integration.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThey should not be used to implement critical authentication checks.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022events-are-not-decision-hooks\u0022\u003EEvents Are Not Decision Hooks\u003C/h3\u003E\n\u003Cp\u003EThe documented event delegates run after their corresponding successful operations.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EOnUserLoggedIn\u003C/code\u003E is not the place to decide whether the login should have been permitted.\u003C/p\u003E\n\u003Cp\u003EUse an appropriate authority or policy for security decisions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022avoid-complex-work-inside-events\u0022\u003EAvoid Complex Work Inside Events\u003C/h3\u003E\n\u003Cp\u003ELong-running or failure-prone external operations should generally be delegated to a reliable application service or message-processing mechanism.\u003C/p\u003E\n\u003Cp\u003EDo not make critical authentication behavior depend on an unreliable external notification system.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-jwt-signing-keys\u0022\u003E16. Custom JWT Signing Keys\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth 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\u003EIJwtSigningKeyProvider\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server registers a development signing key provider as its default implementation.\u003C/p\u003E\n\u003Cp\u003EProduction applications should supply an appropriate signing key strategy.\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.AddSingleton\u0026lt;IJwtSigningKeyProvider, ApplicationJwtSigningKeyProvider\u0026gt;();\nbuilder.Services.AddUltimateAuthServer();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationJwtSigningKeyProvider\u003C/code\u003E is an implementation supplied by the application.\u003C/p\u003E\n\u003Cp\u003EA production implementation should use appropriately protected signing keys and support the application\u0027s operational requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022signing-key-responsibilities\u0022\u003ESigning Key Responsibilities\u003C/h3\u003E\n\u003Cp\u003EConsider:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EKey confidentiality.\u003C/li\u003E\n\u003Cli\u003EKey rotation.\u003C/li\u003E\n\u003Cli\u003EKey identification.\u003C/li\u003E\n\u003Cli\u003EConsistent validation across resource servers.\u003C/li\u003E\n\u003Cli\u003ESecure key storage.\u003C/li\u003E\n\u003Cli\u003ERecovery and deployment procedures.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EDo not use development signing keys as a production security strategy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022endpoint-customization\u0022\u003E17. Endpoint Customization\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides 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:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EIEndpointRouteBuilder\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.OnConfigureEndpoints = endpoints =\u0026gt;\n {\n // Apply supported application-specific\n // endpoint configuration here.\n };\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe hook is invoked after UltimateAuth registers its default endpoints.\u003C/p\u003E\n\u003Cp\u003EIt can support advanced endpoint integration, but it should not be used to remove, remap, or weaken the security requirements of UltimateAuth endpoints.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022prefer-endpoint-options-first\u0022\u003EPrefer Endpoint Options First\u003C/h3\u003E\n\u003Cp\u003EFor ordinary endpoint configuration, use:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.Authentication = true;\n options.Endpoints.Credentials = true;\n options.Endpoints.Authorization = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe advanced endpoint hook is intended for cases that cannot be addressed through the supported endpoint options.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-sources-and-precedence\u0022\u003E18. Configuration Sources and Precedence\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth integrates with the standard ASP.NET Core configuration system.\u003C/p\u003E\n\u003Cp\u003EFor the server, the 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:Server\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor the client:\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 Resource APIs:\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\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022registration-order\u0022\u003ERegistration Order\u003C/h3\u003E\n\u003Cp\u003EThe standard server registration applies:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EOption type defaults.\u003C/li\u003E\n\u003Cli\u003EThe \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E configuration delegate.\u003C/li\u003E\n\u003Cli\u003EValues bound from \u003Ccode\u003EUltimateAuth:Server\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EPost-configuration processing.\u003C/li\u003E\n\u003Cli\u003ERegistered options validation.\u003C/li\u003E\n\u003C/ol\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.Login.MaxFailedAttempts = 5;\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 configured value is \u003Ccode\u003E3\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe configuration binding occurs after the registration delegate.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022environment-variables\u0022\u003EEnvironment Variables\u003C/h3\u003E\n\u003Cp\u003EASP.NET Core supports hierarchical configuration keys through double underscores.\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 corresponds 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\u003EEnvironment-specific configuration can therefore override values without requiring changes to application code.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022environment-specific-files\u0022\u003EEnvironment-Specific Files\u003C/h3\u003E\n\u003Cp\u003EStandard ASP.NET Core applications commonly use:\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\u003Eappsettings.json\nappsettings.Development.json\nappsettings.Staging.json\nappsettings.Production.json\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe active environment determines which environment-specific file is loaded.\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\u003EASPNETCORE_ENVIRONMENT=Production\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe final value depends on the configured ASP.NET Core configuration providers and their precedence.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-binding-does-not-replace-mode-resolution\u0022\u003EConfiguration Binding Does Not Replace Mode Resolution\u003C/h3\u003E\n\u003Cp\u003EA value supplied through \u003Ccode\u003Eappsettings.json\u003C/code\u003E becomes part of the configured options.\u003C/p\u003E\n\u003Cp\u003EIt may still be replaced later when UltimateAuth applies mode defaults.\u003C/p\u003E\n\u003Cp\u003EFor settings explicitly controlled by mode templates, use \u003Ccode\u003EConfigureMode()\u003C/code\u003E when the desired behavior must apply to a specific mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022startup-validation-and-runtime-safety\u0022\u003E19. Startup Validation and Runtime Safety\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth validates server options through registered \u003Ccode\u003EIValidateOptions\u0026lt;UAuthServerOptions\u0026gt;\u003C/code\u003E implementations.\u003C/p\u003E\n\u003Cp\u003EThe server uses:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EValidateOnStart()\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows many invalid configurations to fail during application startup.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInvalid endpoint base paths.\u003C/li\u003E\n\u003Cli\u003EInvalid or unsupported allowed modes.\u003C/li\u003E\n\u003Cli\u003EInvalid session lifetimes.\u003C/li\u003E\n\u003Cli\u003EIncompatible token and session lifetime constraints.\u003C/li\u003E\n\u003Cli\u003EInvalid login settings.\u003C/li\u003E\n\u003Cli\u003EInvalid tenant configuration.\u003C/li\u003E\n\u003Cli\u003EInvalid pagination settings.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022validation-has-boundaries\u0022\u003EValidation Has Boundaries\u003C/h3\u003E\n\u003Cp\u003EStartup validation applies to the configured options according to the registered validators.\u003C/p\u003E\n\u003Cp\u003EIt does not automatically prove that every custom dependency injection replacement is safe.\u003C/p\u003E\n\u003Cp\u003EIt also does not guarantee that every mode-specific override has been validated against all possible security invariants.\u003C/p\u003E\n\u003Cp\u003ECustom implementations must preserve the relevant contracts and security requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-validation-strategy\u0022\u003ERecommended Validation Strategy\u003C/h3\u003E\n\u003Cp\u003EFor advanced configurations:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EVerify that the application starts successfully.\u003C/li\u003E\n\u003Cli\u003ETest each enabled authentication mode.\u003C/li\u003E\n\u003Cli\u003ETest login, refresh, validation, and logout.\u003C/li\u003E\n\u003Cli\u003ETest session expiration and revocation.\u003C/li\u003E\n\u003Cli\u003ETest tenant isolation.\u003C/li\u003E\n\u003Cli\u003ETest authorization denial paths.\u003C/li\u003E\n\u003Cli\u003ETest concurrent refresh requests where applicable.\u003C/li\u003E\n\u003Cli\u003EVerify that effective options contain the intended values.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThese checks are especially important when customizing authorities, validators, stores, or authentication mode behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-configuration-mistakes\u0022\u003E20. Common Configuration Mistakes\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuring-core-options-separately-from-server-options\u0022\u003EConfiguring Core Options Separately from Server Options\u003C/h3\u003E\n\u003Cp\u003EWhen using \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E, configure Core-defined behavior through \u003Ccode\u003EUAuthServerOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EDo not expect a separate \u003Ccode\u003EUAuthOptions\u003C/code\u003E configuration to override the server\u0027s composed options.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-global-values-always-survive-mode-defaults\u0022\u003EAssuming Global Values Always Survive Mode Defaults\u003C/h3\u003E\n\u003Cp\u003EMode templates can overwrite values such as access token lifetime.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003EConfigureMode()\u003C/code\u003E for explicit mode-specific customization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022treating-allowedmodes-as-mode-selection\u0022\u003ETreating AllowedModes as Mode Selection\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EAllowedModes\u003C/code\u003E restricts permitted modes.\u003C/p\u003E\n\u003Cp\u003EIt does not replace \u003Ccode\u003EIEffectiveAuthModeResolver\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022enabling-an-unsupported-mode\u0022\u003EEnabling an Unsupported Mode\u003C/h3\u003E\n\u003Cp\u003EAn enum value or default template does not establish that a mode is ready for production.\u003C/p\u003E\n\u003Cp\u003EUse only modes supported by the current implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022replacing-security-components-unnecessarily\u0022\u003EReplacing Security Components Unnecessarily\u003C/h3\u003E\n\u003Cp\u003EReplacing a validator or authority can change fundamental authentication behavior.\u003C/p\u003E\n\u003Cp\u003EPrefer options and policies when they satisfy the requirement.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-every-di-service-is-replaceable\u0022\u003EAssuming Every DI Service Is Replaceable\u003C/h3\u003E\n\u003Cp\u003EDifferent services use different registration patterns.\u003C/p\u003E\n\u003Cp\u003EReview the actual registration behavior before replacing a service.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-events-for-authorization-decisions\u0022\u003EUsing Events for Authorization Decisions\u003C/h3\u003E\n\u003Cp\u003ELifecycle events are for post-operation reactions.\u003C/p\u003E\n\u003Cp\u003EUse authorities and policies for decisions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022ignoring-store-concurrency\u0022\u003EIgnoring Store Concurrency\u003C/h3\u003E\n\u003Cp\u003EAuthentication stores must preserve the required consistency and atomicity semantics.\u003C/p\u003E\n\u003Cp\u003EThis is particularly important for refresh token rotation and revocation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022assuming-runtime-overrides-receive-full-startup-validation\u0022\u003EAssuming Runtime Overrides Receive Full Startup Validation\u003C/h3\u003E\n\u003Cp\u003EMode-specific overrides are applied during effective-options construction.\u003C/p\u003E\n\u003Cp\u003EValidate their results through tests and appropriate application safeguards.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022advanced-configuration-reference\u0022\u003E21. Advanced Configuration Reference\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EMechanism\u003C/th\u003E\n\u003Cth\u003EAPI / Type\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\u003EServer configuration\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthServerOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBase server settings\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMode customization\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EConfigureMode()\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EApply mode-specific overrides\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EEffective configuration\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIEffectiveServerOptionsProvider\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve effective server settings\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EEffective result\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EEffectiveUAuthServerOptions\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolved mode and options\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMode selection\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIEffectiveAuthModeResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve mode from client profile\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ELogin decisions\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EILoginAuthority\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEvaluate login decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthentication authority\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIAuthAuthority\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication security decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAccess authority\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIAccessAuthority\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAccess decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ETenant identification\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EITenantIdResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve tenant identifiers\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession validation\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EISessionValidator\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EValidate sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession identification\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EISessionIdResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve session identifiers\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDevice identification\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIDeviceResolver\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EResolve device information\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESigning keys\u003C/td\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\u003Ctr\u003E\n\u003Ctd\u003ELifecycle events\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthEvents\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EObserve completed authentication operations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EEndpoint customization\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnConfigureEndpoints\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAdvanced endpoint configuration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession persistence\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EISessionStore\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESession storage\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERefresh persistence\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EIRefreshTokenStore\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERefresh token storage\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=\u0022recommended-extension-strategy\u0022\u003E22. Recommended Extension Strategy\u003C/h2\u003E\n\u003Cp\u003EWhen an application requires customization, follow this order:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse existing options.\u003C/strong\u003E Prefer supported configuration properties.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse mode overrides.\u003C/strong\u003E Apply \u003Ccode\u003EConfigureMode()\u003C/code\u003E when behavior must differ by authentication mode.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse application policies.\u003C/strong\u003E Extend authorization rules without replacing the authorization pipeline.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse lifecycle events.\u003C/strong\u003E Implement logging, monitoring, and notifications without changing authentication decisions.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUse focused resolvers or providers.\u003C/strong\u003E Customize tenant identification, signing keys, or other specific infrastructure concerns.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EReplace authorities, validators, or stores only when necessary.\u003C/strong\u003E Preserve all relevant security invariants and verify the implementation through tests.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe objective is not to replace as much of UltimateAuth as possible.\u003C/p\u003E\n\u003Cp\u003EIt is to customize the smallest component necessary while retaining the guarantees of the surrounding 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/configuration/overview\u0022\u003EConfiguration Overview\u003C/a\u003E \u2014 Understand registration, configuration sources, and architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E \u2014 Configure authentication and server security behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E \u2014 Configure SDK flows and client behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Understand supported authentication modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Use the client authorization API.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Manage sessions and revocation.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "configuration-versus-extensibility", 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 new file mode 100644 index 00000000..c74f4be5 --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/overview.json @@ -0,0 +1,217 @@ +{ + "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", + "Headings": [ + { + "Id": "configuration-architecture", + "Text": "1. Configuration Architecture", + "Level": 0 + }, + { + "Id": "configure-the-server", + "Text": "2. Configure the Server", + "Level": 0 + }, + { + "Id": "example-authentication-and-session-settings", + "Text": "Example: Authentication and Session Settings", + "Level": 1 + }, + { + "Id": "core-options-are-composed-into-server-options", + "Text": "3. Core Options Are Composed into Server Options", + "Level": 0 + }, + { + "Id": "recommended", + "Text": "Recommended", + "Level": 1 + }, + { + "Id": "not-recommended-for-server-configuration", + "Text": "Not Recommended for Server Configuration", + "Level": 1 + }, + { + "Id": "server-configuration-groups", + "Text": "4. Server Configuration Groups", + "Level": 0 + }, + { + "Id": "configure-the-client", + "Text": "5. Configure the Client", + "Level": 0 + }, + { + "Id": "client-configuration-groups", + "Text": "Client Configuration Groups", + "Level": 1 + }, + { + "Id": "server-and-client-configuration-are-independent", + "Text": "6. Server and Client Configuration Are Independent", + "Level": 0 + }, + { + "Id": "configuration-sources", + "Text": "7. Configuration Sources", + "Level": 0 + }, + { + "Id": "code-based-configuration", + "Text": "Code-Based Configuration", + "Level": 1 + }, + { + "Id": "configuration-based-setup", + "Text": "Configuration-Based Setup", + "Level": 1 + }, + { + "Id": "using-both-sources", + "Text": "Using Both Sources", + "Level": 1 + }, + { + "Id": "configuration-precedence", + "Text": "8. Configuration Precedence", + "Level": 0 + }, + { + "Id": "important-distinction", + "Text": "Important Distinction", + "Level": 1 + }, + { + "Id": "environment-variable-example", + "Text": "Environment Variable Example", + "Level": 1 + }, + { + "Id": "authentication-mode-configuration", + "Text": "9. Authentication Mode Configuration", + "Level": 0 + }, + { + "Id": "restrict-allowed-modes", + "Text": "Restrict Allowed Modes", + "Level": 1 + }, + { + "Id": "unsupported-modes", + "Text": "Unsupported Modes", + "Level": 1 + }, + { + "Id": "configure-credential-session-policies", + "Text": "10. Configure Credential Session Policies", + "Level": 0 + }, + { + "Id": "configure-endpoint-exposure", + "Text": "11. Configure Endpoint Exposure", + "Level": 0 + }, + { + "Id": "multi-tenant-configuration", + "Text": "12. Multi-Tenant Configuration", + "Level": 0 + }, + { + "Id": "resource-api-configuration", + "Text": "13. Resource API Configuration", + "Level": 0 + }, + { + "Id": "important-resource-api-cors-configuration", + "Text": "Important: Resource API CORS Configuration", + "Level": 1 + }, + { + "Id": "validation-and-safe-configuration", + "Text": "14. Validation and Safe Configuration", + "Level": 0 + }, + { + "Id": "example-invalid-session-lifetime", + "Text": "Example: Invalid Session Lifetime", + "Level": 1 + }, + { + "Id": "example-invalid-endpoint-path", + "Text": "Example: Invalid Endpoint Path", + "Level": 1 + }, + { + "Id": "validation-timing", + "Text": "Validation Timing", + "Level": 1 + }, + { + "Id": "extensibility-through-dependency-injection", + "Text": "15. Extensibility Through Dependency Injection", + "Level": 0 + }, + { + "Id": "configuration-versus-extension", + "Text": "Configuration Versus Extension", + "Level": 1 + }, + { + "Id": "endpoint-customization", + "Text": "16. Endpoint Customization", + "Level": 0 + }, + { + "Id": "recommended-configuration-strategy", + "Text": "17. Recommended Configuration Strategy", + "Level": 0 + }, + { + "Id": "keep-server-security-settings-on-the-server", + "Text": "Keep Server Security Settings on the Server", + "Level": 1 + }, + { + "Id": "keep-client-behavior-in-client-options", + "Text": "Keep Client Behavior in Client Options", + "Level": 1 + }, + { + "Id": "use-configuration-files-for-environment-differences", + "Text": "Use Configuration Files for Environment Differences", + "Level": 1 + }, + { + "Id": "prefer-strongly-typed-configuration", + "Text": "Prefer Strongly Typed Configuration", + "Level": 1 + }, + { + "Id": "preserve-secure-defaults", + "Text": "Preserve Secure Defaults", + "Level": 1 + }, + { + "Id": "avoid-duplicate-core-configuration", + "Text": "Avoid Duplicate Core Configuration", + "Level": 1 + }, + { + "Id": "validate-production-configuration", + "Text": "Validate Production Configuration", + "Level": 1 + }, + { + "Id": "configuration-reference", + "Text": "18. Configuration Reference", + "Level": 0 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json index 9f55f4e8..12c6baeb 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json @@ -28,8 +28,8 @@ "GroupOrder": 1 }, { - "Title": "Fundamental Overview", - "Slug": "fundamentals/index", + "Title": "Fundamentals Overview", + "Slug": "fundamentals/overview", "Order": 1, "Group": "fundamentals", "GroupOrder": 2 @@ -77,8 +77,8 @@ "GroupOrder": 2 }, { - "Title": "Auth Flows Overview", - "Slug": "auth-flows/index", + "Title": "Auth-Flows Overview", + "Slug": "auth-flows/overview", "Order": 1, "Group": "auth-flows", "GroupOrder": 3 @@ -119,29 +119,29 @@ "GroupOrder": 4 }, { - "Title": "Plugin Domains", - "Slug": "plugin-domains/index", + "Title": "Plugin-Domains Overview", + "Slug": "plugin-domains/overview", "Order": 1, "Group": "plugin-domains", "GroupOrder": 5 }, { "Title": "Users", - "Slug": "plugin-domains/users-domain", + "Slug": "plugin-domains/users", "Order": 2, "Group": "plugin-domains", "GroupOrder": 5 }, { "Title": "Credentials", - "Slug": "plugin-domains/credential-domain", + "Slug": "plugin-domains/credentials", "Order": 3, "Group": "plugin-domains", "GroupOrder": 5 }, { "Title": "Authorization", - "Slug": "plugin-domains/authorization-domain", + "Slug": "plugin-domains/authorization", "Order": 4, "Group": "plugin-domains", "GroupOrder": 5 @@ -155,56 +155,56 @@ }, { "Title": "Client SDK Overview", - "Slug": "client/index", + "Slug": "client-sdk/overview", "Order": 1, "Group": "client", "GroupOrder": 6 }, { "Title": "Authentication", - "Slug": "client/authentication", + "Slug": "client-sdk/authentication", "Order": 2, "Group": "client", "GroupOrder": 6 }, { "Title": "Session Management", - "Slug": "client/session-management", + "Slug": "client-sdk/session-management", "Order": 3, "Group": "client", "GroupOrder": 6 }, { "Title": "User Management", - "Slug": "client/user-management", + "Slug": "client-sdk/user-management", "Order": 4, "Group": "client", "GroupOrder": 6 }, { "Title": "Identifiers", - "Slug": "client/identifiers", + "Slug": "client-sdk/identifiers", "Order": 5, "Group": "client", "GroupOrder": 6 }, { "Title": "Credentials", - "Slug": "client/credentials", + "Slug": "client-sdk/credentials", "Order": 6, "Group": "client", "GroupOrder": 6 }, { "Title": "Authorization", - "Slug": "client/authorization", + "Slug": "client-sdk/authorization", "Order": 7, "Group": "client", "GroupOrder": 6 }, { "Title": "Configuration Overview", - "Slug": "configuration/index", + "Slug": "configuration/overview", "Order": 1, "Group": "configuration", "GroupOrder": 7 @@ -232,7 +232,7 @@ }, { "Title": "Session Security Model", - "Slug": "security/session-security-model", + "Slug": "security/session-security", "Order": 1, "Group": "security", "GroupOrder": 8 @@ -257,12 +257,5 @@ "Order": 4, "Group": "security", "GroupOrder": 8 - }, - { - "Title": "\uD83D\uDD10 UltimateAuth Docs", - "Slug": "readme", - "Order": 999, - "Group": "", - "GroupOrder": 999 } ] \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/client-profiles.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/client-profiles.json index 3a37f6a1..6ab377c3 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/client-profiles.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/client-profiles.json @@ -1,7 +1,7 @@ { "Slug": "fundamentals/client-profiles", "Title": "Client Profiles", - "Html": "\n\u003Cp\u003EUltimateAuth is designed to support different application environments through a unified authentication architecture.\u003C/p\u003E\n\u003Cp\u003EA Blazor Server application, a browser-based WebAssembly application, a native mobile client, and a server-side application do not interact with authentication credentials in exactly the same way.\u003C/p\u003E\n\u003Cp\u003EThey may differ in how credentials are stored, how requests are sent, whether browser cookies are available, and how authentication results are delivered.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient Profiles allow UltimateAuth to account for these runtime differences without requiring separate authentication architectures for each client type.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA Client Profile describes the environment in which a client operates. It helps the framework resolve suitable authentication behavior and defaults, while the server remains responsible for enforcing the effective security policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-a-client-profile\u0022\u003EWhat is a Client Profile?\u003C/h2\u003E\n\u003Cp\u003EA Client Profile identifies a supported client environment and its relevant runtime characteristics.\u003C/p\u003E\n\u003Cp\u003EIt can influence how UltimateAuth resolves:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDefault authentication mode.\u003C/li\u003E\n\u003Cli\u003ECredential delivery and transport.\u003C/li\u003E\n\u003Cli\u003EClient-specific authentication flow behavior.\u003C/li\u003E\n\u003Cli\u003EBrowser and non-browser integration requirements.\u003C/li\u003E\n\u003Cli\u003EApplicable server configuration and compatibility constraints.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA profile does not authenticate the client or grant permissions.\u003C/p\u003E\n\u003Cp\u003EFor example, identifying a request as originating from a Blazor WebAssembly application does not establish the identity of its user.\u003C/p\u003E\n\u003Cp\u003ELikewise, a client declaring itself to be a server-side application must not automatically receive privileges associated with a trusted server.\u003C/p\u003E\n\u003Cp\u003EThe distinction is fundamental:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient Profile describes the environment. Authentication Mode defines the authentication strategy. Server policy determines what is permitted.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-client-profiles-exist\u0022\u003EWhy Client Profiles exist\u003C/h2\u003E\n\u003Cp\u003EConsider an application that includes a browser frontend, a native mobile application, and a server-rendered administrative interface.\u003C/p\u003E\n\u003Cp\u003EAll three clients may authenticate against the same UAuthHub.\u003C/p\u003E\n\u003Cp\u003EHowever, their runtime characteristics differ.\u003C/p\u003E\n\u003Cp\u003EA browser application can use cookies managed by the browser. A native application may need to manage credentials through platform-specific secure storage. A server-rendered application may be able to maintain authentication through a server-controlled session without introducing a separate refresh-token lifecycle.\u003C/p\u003E\n\u003Cp\u003EWithout a client abstraction, these differences tend to spread throughout application code.\u003C/p\u003E\n\u003Cp\u003EDevelopers may need separate configuration paths, credential-handling logic, and authentication integration code for every runtime.\u003C/p\u003E\n\u003Cp\u003EClient Profiles provide a consistent way to describe those differences.\u003C/p\u003E\n\u003Cp\u003EThey allow UltimateAuth to select appropriate integration behavior while preserving the same underlying authentication and authorization architecture.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-in-client-profiles\u0022\u003EBuilt-in Client Profiles\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth defines profiles for its supported client environments.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProfile\u003C/th\u003E\n\u003Cth\u003EEnvironment\u003C/th\u003E\n\u003Cth\u003ETypical characteristics\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBlazor Server applications\u003C/td\u003E\n\u003Ctd\u003EServer-side execution, browser connection, server-managed authentication context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorWasm\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBlazor WebAssembly applications\u003C/td\u003E\n\u003Ctd\u003EBrowser execution, HTTP-based API communication, browser credential constraints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMaui\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E.NET MAUI applications\u003C/td\u003E\n\u003Ctd\u003ENative execution, platform-specific credential storage and HTTP communication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EWebServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EASP.NET Core MVC, Razor Pages, and other server-hosted web applications\u003C/td\u003E\n\u003Ctd\u003EServer-side execution with browser-oriented authentication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EApi\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAPI-oriented integrations\u003C/td\u003E\n\u003Ctd\u003EHTTP request authentication without assuming a browser UI\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthHub\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUltimateAuth authentication host\u003C/td\u003E\n\u003Ctd\u003EAuthentication-host-specific runtime integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENotSpecified\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo explicit profile resolved\u003C/td\u003E\n\u003Ctd\u003ERequires the applicable server-side fallback or resolution policy\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese profiles describe runtime environments rather than independent authentication systems.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EBlazorWasm\u003C/code\u003E does not inherently mean JWT authentication, and \u003Ccode\u003EBlazorServer\u003C/code\u003E does not inherently prohibit access tokens.\u003C/p\u003E\n\u003Cp\u003EA profile may provide a recommended default, but the authentication mode remains a separate decision.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022api-profile-versus-resource-api\u0022\u003EAPI profile versus resource API\u003C/h3\u003E\n\u003Cp\u003EAn API-oriented client and an API acting as a protected resource are not necessarily the same thing.\u003C/p\u003E\n\u003Cp\u003EA protected resource API may receive requests from browser applications, mobile applications, or other services.\u003C/p\u003E\n\u003Cp\u003EThe calling client\u0027s profile should not be confused with the hosting environment of the resource API.\u003C/p\u003E\n\u003Cp\u003EResource authentication must follow the applicable server-side configuration and token-validation requirements, regardless of what an untrusted caller claims its runtime to be.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-profile-detection\u0022\u003EAutomatic profile detection\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports automatic Client Profile detection in compatible client integrations.\u003C/p\u003E\n\u003Cp\u003EDetection is intended to reduce configuration requirements for common application environments.\u003C/p\u003E\n\u003Cp\u003EDepending on the client implementation, the runtime can provide information such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe current execution environment.\u003C/li\u003E\n\u003Cli\u003EAvailable framework capabilities.\u003C/li\u003E\n\u003Cli\u003EHosting-specific registrations.\u003C/li\u003E\n\u003Cli\u003ERuntime and platform characteristics.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, a Blazor WebAssembly client can identify that it is executing in a browser environment, while a native MAUI client can identify its native application context.\u003C/p\u003E\n\u003Cp\u003EThis resolution occurs within the supported client integration.\u003C/p\u003E\n\u003Cp\u003EIt should not be confused with a server independently discovering the true identity or trust level of a remote application.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022detection-is-a-convenience-not-a-security-boundary\u0022\u003EDetection is a convenience, not a security boundary\u003C/h3\u003E\n\u003Cp\u003ERuntime detection helps choose appropriate defaults.\u003C/p\u003E\n\u003Cp\u003EIt is not an authentication proof.\u003C/p\u003E\n\u003Cp\u003EA malicious HTTP client can imitate headers, request parameters, and other client-provided metadata.\u003C/p\u003E\n\u003Cp\u003EConsequently, the server must not grant additional authentication privileges solely because a request claims a particular Client Profile.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for validating the profile against its own configuration and security requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-to-server-propagation\u0022\u003EClient-to-server propagation\u003C/h2\u003E\n\u003Cp\u003EFor supported operations, the client can communicate its resolved profile to the authentication server.\u003C/p\u003E\n\u003Cp\u003EThe transport may depend on the integration and operation, including request metadata such as headers or flow-specific form fields.\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 Runtime\n |\n v\nClient Profile Detection\n |\n v\nProfile Metadata\n |\n v\nUAuthHub\n |\n v\nServer-Side Profile Resolution\n |\n v\nEffective Authentication Context\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe important distinction is between a \u003Cstrong\u003Edeclared profile\u003C/strong\u003E and an \u003Cstrong\u003Eeffective profile\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EA declared profile is information supplied by the client.\u003C/p\u003E\n\u003Cp\u003EAn effective profile is the result accepted or resolved by the server under its configured rules.\u003C/p\u003E\n\u003Cp\u003EThe server must not treat arbitrary client-provided profile values as authority to weaken authentication requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022missing-profile-information\u0022\u003EMissing profile information\u003C/h3\u003E\n\u003Cp\u003ESome requests may not provide an explicit Client Profile.\u003C/p\u003E\n\u003Cp\u003EIn those cases, UltimateAuth can represent the unresolved client profile as \u003Ccode\u003ENotSpecified\u003C/code\u003E and apply the relevant server-side resolution or fallback behavior.\u003C/p\u003E\n\u003Cp\u003EA missing profile must not automatically imply a privileged or less restrictive environment.\u003C/p\u003E\n\u003Cp\u003EIf the server cannot establish a permitted configuration for the operation, it should reject the operation rather than inventing an unsafe authentication context.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-and-explicit-configuration\u0022\u003EAutomatic and explicit configuration\u003C/h2\u003E\n\u003Cp\u003EClient Profile resolution can be automatic or explicitly configured, depending on the supported client integration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-configuration\u0022\u003EAutomatic configuration\u003C/h3\u003E\n\u003Cp\u003EFor supported Blazor client registrations, automatic detection can reduce the need to specify the runtime manually.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client integration can resolve the appropriate profile according to its runtime detection configuration.\u003C/p\u003E\n\u003Cp\u003EThis is generally the preferred approach when the application uses a standard, supported hosting environment.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022explicit-configuration\u0022\u003EExplicit configuration\u003C/h3\u003E\n\u003Cp\u003EApplications can also configure the profile directly when their integration supports 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\u003Ebuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.ClientProfile = UAuthClientProfile.BlazorWasm;\n options.AutoDetectClientProfile = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EExplicit configuration may be useful when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn application uses a custom hosting environment.\u003C/li\u003E\n\u003Cli\u003ERuntime detection is ambiguous.\u003C/li\u003E\n\u003Cli\u003EA shared integration requires a specific client profile.\u003C/li\u003E\n\u003Cli\u003EThe application needs predictable profile selection across environments.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EExplicitly configuring a client profile does not override the server\u0027s authentication policies.\u003C/p\u003E\n\u003Cp\u003EIt only determines what profile the client integration declares or uses locally.\u003C/p\u003E\n\u003Cp\u003EThe server must still resolve and enforce the effective authentication context.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-profiles-and-authentication-modes\u0022\u003EClient Profiles and Authentication Modes\u003C/h2\u003E\n\u003Cp\u003EClient Profiles and Authentication Modes are related but independent concepts.\u003C/p\u003E\n\u003Cp\u003EA Client Profile describes the execution environment and its integration requirements.\u003C/p\u003E\n\u003Cp\u003EAn Authentication Mode defines how authentication credentials are issued, validated, continued, and revoked.\u003C/p\u003E\n\u003Cp\u003EFor example, a Blazor Server application commonly benefits from PureOpaque because it can use server-controlled sessions without managing separate access and refresh tokens.\u003C/p\u003E\n\u003Cp\u003EA Blazor WebAssembly application communicating with a separate API may benefit from Hybrid because it can use access and refresh tokens while retaining server-authoritative session validation.\u003C/p\u003E\n\u003Cp\u003EThese are architectural recommendations, not inherent limitations of the runtimes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-defaults\u0022\u003ERecommended defaults\u003C/h3\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 mode\u003C/th\u003E\n\u003Cth\u003EReason\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\u003EPureOpaque\u003C/td\u003E\n\u003Ctd\u003ESimple session-based authentication without separate token rotation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor WebAssembly with API\u003C/td\u003E\n\u003Ctd\u003EHybrid\u003C/td\u003E\n\u003Ctd\u003EAccess/refresh credentials with authoritative session control\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E.NET MAUI\u003C/td\u003E\n\u003Ctd\u003EHybrid\u003C/td\u003E\n\u003Ctd\u003EToken lifecycle suitable for native clients\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMVC / Razor Pages\u003C/td\u003E\n\u003Ctd\u003EPureOpaque\u003C/td\u003E\n\u003Ctd\u003ENatural integration with secure browser cookies and server-side sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAPI-oriented service client\u003C/td\u003E\n\u003Ctd\u003EPureJwt, when stateless authentication is appropriate\u003C/td\u003E\n\u003Ctd\u003EIndependent JWT validation without mandatory session infrastructure\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUAuthHub\u003C/td\u003E\n\u003Ctd\u003EDepends on its configured role and operation\u003C/td\u003E\n\u003Ctd\u003EHosting the authentication system is not itself an authentication mode\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese recommendations describe suitable architectural choices.\u003C/p\u003E\n\u003Cp\u003EThey are not a guarantee that every listed mode is the current implementation default for that profile.\u003C/p\u003E\n\u003Cp\u003EIn particular, the \u003Ccode\u003EUAuthHub\u003C/code\u003E profile should not be interpreted as an authentication mode. UAuthHub is the authentication host, and its supported operations may serve clients using different modes.\u003C/p\u003E\n\u003Cp\u003EFor the full comparison of validation and lifecycle guarantees, see \u003Ca href=\u0022./authentication-modes.md\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-resolution-and-request-context\u0022\u003EProfile resolution and request context\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth can serve clients with different profiles through the same authentication infrastructure.\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\u003EBlazor Server Client\n |\n | Profile: BlazorServer\n |\n \u002B--------------------\u002B\n |\nBlazor WASM Client |\n | |\n | Profile: BlazorWasm |\n | v\n \u002B---------------\u0026gt; UAuthHub\n |\nMAUI Client |\n | |\n | Profile: Maui |\n | |\n \u002B--------------------\u002B\n |\n v\n AuthFlowContext\n |\n v\n Effective Mode\n Effective Policy\n Response Behavior\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server resolves the effective authentication context for each applicable operation.\u003C/p\u003E\n\u003Cp\u003EThis can include the Client Profile, authentication mode, tenant, security options, and credential delivery requirements.\u003C/p\u003E\n\u003Cp\u003EDifferent clients can therefore operate under different authentication strategies without requiring separate authentication architectures.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022per-request-resolution-does-not-mean-arbitrary-mode-switching\u0022\u003EPer-request resolution does not mean arbitrary mode switching\u003C/h3\u003E\n\u003Cp\u003EThe effective profile and mode may differ between requests from different clients.\u003C/p\u003E\n\u003Cp\u003EHowever, they must remain consistent throughout an individual authentication operation.\u003C/p\u003E\n\u003Cp\u003EA request must not begin under one authentication policy and complete under another because different components interpreted its profile differently.\u003C/p\u003E\n\u003Cp\u003ELikewise, a client must not be allowed to change its declared profile to bypass validation requirements associated with an existing credential or session.\u003C/p\u003E\n\u003Cp\u003EAuthentication continuation must preserve the relevant security relationships established by the original authentication context.\u003C/p\u003E\n\u003Cp\u003EThe framework-controlled \u003Ccode\u003EAuthFlowContext\u003C/code\u003E provides a consistent boundary for these decisions.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022./flow-based-authentication.md\u0022\u003EFlow-Based Authentication\u003C/a\u003E for the conceptual model and \u003Ca href=\u0022../auth-flows/\u0022\u003EAuth Flows\u003C/a\u003E for the technical request pipeline.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-delivery-and-runtime-capabilities\u0022\u003ECredential delivery and runtime capabilities\u003C/h2\u003E\n\u003Cp\u003EDifferent clients have different capabilities for receiving and storing authentication credentials.\u003C/p\u003E\n\u003Cp\u003EClient Profiles help the framework account for those differences.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022browser-environments\u0022\u003EBrowser environments\u003C/h3\u003E\n\u003Cp\u003EBrowser applications can use cookies for authentication.\u003C/p\u003E\n\u003Cp\u003EWhen cookies carry sensitive authentication credentials, appropriate \u003Ccode\u003EHttpOnly\u003C/code\u003E, \u003Ccode\u003ESecure\u003C/code\u003E, and \u003Ccode\u003ESameSite\u003C/code\u003E policies should be applied.\u003C/p\u003E\n\u003Cp\u003ECookie-based authentication also requires appropriate CSRF protection.\u003C/p\u003E\n\u003Cp\u003EIn browser environments, a credential stored in an HttpOnly cookie is not directly readable by JavaScript or WebAssembly application code, although the browser can attach it to applicable requests.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022native-applications\u0022\u003ENative applications\u003C/h3\u003E\n\u003Cp\u003ENative applications generally communicate through HTTP clients and can use platform-specific secure credential storage.\u003C/p\u003E\n\u003Cp\u003ETheir credential transport and storage requirements differ from those of a browser.\u003C/p\u003E\n\u003Cp\u003EA native Client Profile can help select appropriate integration behavior, but secure storage remains an application and platform responsibility.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-hosted-applications\u0022\u003EServer-hosted applications\u003C/h3\u003E\n\u003Cp\u003EServer-hosted applications can manage authentication state without exposing all authentication implementation details to browser application code.\u003C/p\u003E\n\u003Cp\u003EThis often makes session-oriented authentication attractive.\u003C/p\u003E\n\u003Cp\u003EHowever, server execution alone does not guarantee security. Cookie protection, session validation, authorization, and long-lived connection behavior must still be handled correctly.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-apis\u0022\u003EResource APIs\u003C/h3\u003E\n\u003Cp\u003EResource APIs validate the credentials presented by callers.\u003C/p\u003E\n\u003Cp\u003ETheir authentication requirements must be enforced by trusted server configuration.\u003C/p\u003E\n\u003Cp\u003EA caller\u0027s declared Client Profile must not determine whether token signatures, session validity, tenant boundaries, or authorization requirements can be bypassed.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-and-trust-boundaries\u0022\u003ESecurity and trust boundaries\u003C/h2\u003E\n\u003Cp\u003EClient Profiles must preserve a clear separation between runtime adaptation and security authority.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-declarations-are-not-trusted-identity\u0022\u003EProfile declarations are not trusted identity\u003C/h3\u003E\n\u003Cp\u003EA Client Profile does not prove that the caller is a particular application, device, or user.\u003C/p\u003E\n\u003Cp\u003EAdditional authentication mechanisms are required when an application needs to establish client identity.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-selection-is-server-controlled\u0022\u003EMode selection is server-controlled\u003C/h3\u003E\n\u003Cp\u003EA client may provide information relevant to mode resolution, but the server determines which authentication modes are permitted.\u003C/p\u003E\n\u003Cp\u003EA profile must not provide a path to downgrade authentication requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-validity-is-independent-of-profile-labels\u0022\u003ECredential validity is independent of profile labels\u003C/h3\u003E\n\u003Cp\u003EA valid-looking credential must still satisfy the validation requirements of its authentication mode.\u003C/p\u003E\n\u003Cp\u003EChanging a profile label must not turn an invalid credential into a valid one.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022transport-configuration-is-not-authorization\u0022\u003ETransport configuration is not authorization\u003C/h3\u003E\n\u003Cp\u003ESelecting cookies, headers, or another supported delivery mechanism does not grant access to protected resources.\u003C/p\u003E\n\u003Cp\u003EAuthentication and authorization decisions remain separate from credential transport.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022defaults-are-not-universal-guarantees\u0022\u003EDefaults are not universal guarantees\u003C/h3\u003E\n\u003Cp\u003EA secure default depends on the application\u0027s deployment, runtime, authentication mode, and security requirements.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth can provide suitable defaults, but developers remain responsible for selecting and configuring the policies appropriate to their applications.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-environments-and-extensibility\u0022\u003ECustom environments and extensibility\u003C/h2\u003E\n\u003Cp\u003ENot every application fits a predefined runtime category.\u003C/p\u003E\n\u003Cp\u003EA custom host, embedded client, hybrid application, or specialized integration may need explicit profile configuration or additional integration behavior.\u003C/p\u003E\n\u003Cp\u003EThe purpose of Client Profiles is to keep these differences at a defined architectural boundary.\u003C/p\u003E\n\u003Cp\u003ENew integrations should reuse the existing authentication model and flow infrastructure wherever possible, rather than introducing independent authentication implementations.\u003C/p\u003E\n\u003Cp\u003ECustom profile resolution and integration behavior must continue to respect server-controlled mode selection and authentication security policies.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EClient Profiles allow UltimateAuth to adapt authentication integration to different application environments.\u003C/p\u003E\n\u003Cp\u003EThey provide a consistent way to describe runtime characteristics and influence suitable defaults without turning client-provided metadata into a security authority.\u003C/p\u003E\n\u003Cp\u003EThe fundamental distinctions are:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient Profile\u003C/strong\u003E describes the runtime environment and integration requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication Mode\u003C/strong\u003E defines credential validation, session dependencies, and authentication lifecycle guarantees.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthFlowContext\u003C/strong\u003E establishes the effective security context for an authentication operation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EServer policy\u003C/strong\u003E determines which behavior is permitted.\u003C/p\u003E\n\u003Cp\u003ETogether, these concepts allow different clients to share the same UltimateAuth infrastructure while preserving explicit authentication boundaries.\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./runtime-architecture.md\u0022\u003ERuntime Architecture\u003C/a\u003E to understand how UltimateAuth\u0027s client, server, and hosting components work together across different application environments.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth is designed to support different application environments through a unified authentication architecture.\u003C/p\u003E\n\u003Cp\u003EA Blazor Server application, a browser-based WebAssembly application, a native mobile client, and a server-side application do not interact with authentication credentials in exactly the same way.\u003C/p\u003E\n\u003Cp\u003EThey may differ in how credentials are stored, how requests are sent, whether browser cookies are available, and how authentication results are delivered.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient Profiles allow UltimateAuth to account for these runtime differences without requiring separate authentication architectures for each client type.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA Client Profile describes the environment in which a client operates. It helps the framework resolve suitable authentication behavior and defaults, while the server remains responsible for enforcing the effective security policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-a-client-profile\u0022\u003EWhat is a Client Profile?\u003C/h2\u003E\n\u003Cp\u003EA Client Profile identifies a supported client environment and its relevant runtime characteristics.\u003C/p\u003E\n\u003Cp\u003EIt can influence how UltimateAuth resolves:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDefault authentication mode.\u003C/li\u003E\n\u003Cli\u003ECredential delivery and transport.\u003C/li\u003E\n\u003Cli\u003EClient-specific authentication flow behavior.\u003C/li\u003E\n\u003Cli\u003EBrowser and non-browser integration requirements.\u003C/li\u003E\n\u003Cli\u003EApplicable server configuration and compatibility constraints.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA profile does not authenticate the client or grant permissions.\u003C/p\u003E\n\u003Cp\u003EFor example, identifying a request as originating from a Blazor WebAssembly application does not establish the identity of its user.\u003C/p\u003E\n\u003Cp\u003ELikewise, a client declaring itself to be a server-side application must not automatically receive privileges associated with a trusted server.\u003C/p\u003E\n\u003Cp\u003EThe distinction is fundamental:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient Profile describes the environment. Authentication Mode defines the authentication strategy. Server policy determines what is permitted.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-client-profiles-exist\u0022\u003EWhy Client Profiles exist\u003C/h2\u003E\n\u003Cp\u003EConsider an application that includes a browser frontend, a native mobile application, and a server-rendered administrative interface.\u003C/p\u003E\n\u003Cp\u003EAll three clients may authenticate against the same UAuthHub.\u003C/p\u003E\n\u003Cp\u003EHowever, their runtime characteristics differ.\u003C/p\u003E\n\u003Cp\u003EA browser application can use cookies managed by the browser. A native application may need to manage credentials through platform-specific secure storage. A server-rendered application may be able to maintain authentication through a server-controlled session without introducing a separate refresh-token lifecycle.\u003C/p\u003E\n\u003Cp\u003EWithout a client abstraction, these differences tend to spread throughout application code.\u003C/p\u003E\n\u003Cp\u003EDevelopers may need separate configuration paths, credential-handling logic, and authentication integration code for every runtime.\u003C/p\u003E\n\u003Cp\u003EClient Profiles provide a consistent way to describe those differences.\u003C/p\u003E\n\u003Cp\u003EThey allow UltimateAuth to select appropriate integration behavior while preserving the same underlying authentication and authorization architecture.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-in-client-profiles\u0022\u003EBuilt-in Client Profiles\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth defines profiles for its supported client environments.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProfile\u003C/th\u003E\n\u003Cth\u003EEnvironment\u003C/th\u003E\n\u003Cth\u003ETypical characteristics\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBlazor Server applications\u003C/td\u003E\n\u003Ctd\u003EServer-side execution, browser connection, server-managed authentication context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EBlazorWasm\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBlazor WebAssembly applications\u003C/td\u003E\n\u003Ctd\u003EBrowser execution, HTTP-based API communication, browser credential constraints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMaui\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E.NET MAUI applications\u003C/td\u003E\n\u003Ctd\u003ENative execution, platform-specific credential storage and HTTP communication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EWebServer\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EASP.NET Core MVC, Razor Pages, and other server-hosted web applications\u003C/td\u003E\n\u003Ctd\u003EServer-side execution with browser-oriented authentication\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EApi\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAPI-oriented integrations\u003C/td\u003E\n\u003Ctd\u003EHTTP request authentication without assuming a browser UI\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthHub\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUltimateAuth authentication host\u003C/td\u003E\n\u003Ctd\u003EAuthentication-host-specific runtime integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENotSpecified\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENo explicit profile resolved\u003C/td\u003E\n\u003Ctd\u003ERequires the applicable server-side fallback or resolution policy\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese profiles describe runtime environments rather than independent authentication systems.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EBlazorWasm\u003C/code\u003E does not inherently mean JWT authentication, and \u003Ccode\u003EBlazorServer\u003C/code\u003E does not inherently prohibit access tokens.\u003C/p\u003E\n\u003Cp\u003EA profile may provide a recommended default, but the authentication mode remains a separate decision.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022api-profile-versus-resource-api\u0022\u003EAPI profile versus resource API\u003C/h3\u003E\n\u003Cp\u003EAn API-oriented client and an API acting as a protected resource are not necessarily the same thing.\u003C/p\u003E\n\u003Cp\u003EA protected resource API may receive requests from browser applications, mobile applications, or other services.\u003C/p\u003E\n\u003Cp\u003EThe calling client\u0027s profile should not be confused with the hosting environment of the resource API.\u003C/p\u003E\n\u003Cp\u003EResource authentication must follow the applicable server-side configuration and token-validation requirements, regardless of what an untrusted caller claims its runtime to be.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-profile-detection\u0022\u003EAutomatic profile detection\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports automatic Client Profile detection in compatible client integrations.\u003C/p\u003E\n\u003Cp\u003EDetection is intended to reduce configuration requirements for common application environments.\u003C/p\u003E\n\u003Cp\u003EDepending on the client implementation, the runtime can provide information such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe current execution environment.\u003C/li\u003E\n\u003Cli\u003EAvailable framework capabilities.\u003C/li\u003E\n\u003Cli\u003EHosting-specific registrations.\u003C/li\u003E\n\u003Cli\u003ERuntime and platform characteristics.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, a Blazor WebAssembly client can identify that it is executing in a browser environment, while a native MAUI client can identify its native application context.\u003C/p\u003E\n\u003Cp\u003EThis resolution occurs within the supported client integration.\u003C/p\u003E\n\u003Cp\u003EIt should not be confused with a server independently discovering the true identity or trust level of a remote application.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022detection-is-a-convenience-not-a-security-boundary\u0022\u003EDetection is a convenience, not a security boundary\u003C/h3\u003E\n\u003Cp\u003ERuntime detection helps choose appropriate defaults.\u003C/p\u003E\n\u003Cp\u003EIt is not an authentication proof.\u003C/p\u003E\n\u003Cp\u003EA malicious HTTP client can imitate headers, request parameters, and other client-provided metadata.\u003C/p\u003E\n\u003Cp\u003EConsequently, the server must not grant additional authentication privileges solely because a request claims a particular Client Profile.\u003C/p\u003E\n\u003Cp\u003EThe server remains responsible for validating the profile against its own configuration and security requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-to-server-propagation\u0022\u003EClient-to-server propagation\u003C/h2\u003E\n\u003Cp\u003EFor supported operations, the client can communicate its resolved profile to the authentication server.\u003C/p\u003E\n\u003Cp\u003EThe transport may depend on the integration and operation, including request metadata such as headers or flow-specific form fields.\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 Runtime\n |\n v\nClient Profile Detection\n |\n v\nProfile Metadata\n |\n v\nUAuthHub\n |\n v\nServer-Side Profile Resolution\n |\n v\nEffective Authentication Context\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe important distinction is between a \u003Cstrong\u003Edeclared profile\u003C/strong\u003E and an \u003Cstrong\u003Eeffective profile\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EA declared profile is information supplied by the client.\u003C/p\u003E\n\u003Cp\u003EAn effective profile is the result accepted or resolved by the server under its configured rules.\u003C/p\u003E\n\u003Cp\u003EThe server must not treat arbitrary client-provided profile values as authority to weaken authentication requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022missing-profile-information\u0022\u003EMissing profile information\u003C/h3\u003E\n\u003Cp\u003ESome requests may not provide an explicit Client Profile.\u003C/p\u003E\n\u003Cp\u003EIn those cases, UltimateAuth can represent the unresolved client profile as \u003Ccode\u003ENotSpecified\u003C/code\u003E and apply the relevant server-side resolution or fallback behavior.\u003C/p\u003E\n\u003Cp\u003EA missing profile must not automatically imply a privileged or less restrictive environment.\u003C/p\u003E\n\u003Cp\u003EIf the server cannot establish a permitted configuration for the operation, it should reject the operation rather than inventing an unsafe authentication context.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-and-explicit-configuration\u0022\u003EAutomatic and explicit configuration\u003C/h2\u003E\n\u003Cp\u003EClient Profile resolution can be automatic or explicitly configured, depending on the supported client integration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022automatic-configuration\u0022\u003EAutomatic configuration\u003C/h3\u003E\n\u003Cp\u003EFor supported Blazor client registrations, automatic detection can reduce the need to specify the runtime manually.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client integration can resolve the appropriate profile according to its runtime detection configuration.\u003C/p\u003E\n\u003Cp\u003EThis is generally the preferred approach when the application uses a standard, supported hosting environment.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022explicit-configuration\u0022\u003EExplicit configuration\u003C/h3\u003E\n\u003Cp\u003EApplications can also configure the profile directly when their integration supports 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\u003Ebuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.ClientProfile = UAuthClientProfile.BlazorWasm;\n options.AutoDetectClientProfile = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EExplicit configuration may be useful when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn application uses a custom hosting environment.\u003C/li\u003E\n\u003Cli\u003ERuntime detection is ambiguous.\u003C/li\u003E\n\u003Cli\u003EA shared integration requires a specific client profile.\u003C/li\u003E\n\u003Cli\u003EThe application needs predictable profile selection across environments.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EExplicitly configuring a client profile does not override the server\u0027s authentication policies.\u003C/p\u003E\n\u003Cp\u003EIt only determines what profile the client integration declares or uses locally.\u003C/p\u003E\n\u003Cp\u003EThe server must still resolve and enforce the effective authentication context.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-profiles-and-authentication-modes\u0022\u003EClient Profiles and Authentication Modes\u003C/h2\u003E\n\u003Cp\u003EClient Profiles and Authentication Modes are related but independent concepts.\u003C/p\u003E\n\u003Cp\u003EA Client Profile describes the execution environment and its integration requirements.\u003C/p\u003E\n\u003Cp\u003EAn Authentication Mode defines how authentication credentials are issued, validated, continued, and revoked.\u003C/p\u003E\n\u003Cp\u003EFor example, a Blazor Server application commonly benefits from PureOpaque because it can use server-controlled sessions without managing separate access and refresh tokens.\u003C/p\u003E\n\u003Cp\u003EA Blazor WebAssembly application communicating with a separate API may benefit from Hybrid because it can use access and refresh tokens while retaining server-authoritative session validation.\u003C/p\u003E\n\u003Cp\u003EThese are architectural recommendations, not inherent limitations of the runtimes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-defaults\u0022\u003ERecommended defaults\u003C/h3\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 mode\u003C/th\u003E\n\u003Cth\u003EReason\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\u003EPureOpaque\u003C/td\u003E\n\u003Ctd\u003ESimple session-based authentication without separate token rotation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor WebAssembly with API\u003C/td\u003E\n\u003Ctd\u003EHybrid\u003C/td\u003E\n\u003Ctd\u003EAccess/refresh credentials with authoritative session control\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E.NET MAUI\u003C/td\u003E\n\u003Ctd\u003EHybrid\u003C/td\u003E\n\u003Ctd\u003EToken lifecycle suitable for native clients\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMVC / Razor Pages\u003C/td\u003E\n\u003Ctd\u003EPureOpaque\u003C/td\u003E\n\u003Ctd\u003ENatural integration with secure browser cookies and server-side sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAPI-oriented service client\u003C/td\u003E\n\u003Ctd\u003EPureJwt, when stateless authentication is appropriate\u003C/td\u003E\n\u003Ctd\u003EIndependent JWT validation without mandatory session infrastructure\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUAuthHub\u003C/td\u003E\n\u003Ctd\u003EDepends on its configured role and operation\u003C/td\u003E\n\u003Ctd\u003EHosting the authentication system is not itself an authentication mode\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese recommendations describe suitable architectural choices.\u003C/p\u003E\n\u003Cp\u003EThey are not a guarantee that every listed mode is the current implementation default for that profile.\u003C/p\u003E\n\u003Cp\u003EIn particular, the \u003Ccode\u003EUAuthHub\u003C/code\u003E profile should not be interpreted as an authentication mode. UAuthHub is the authentication host, and its supported operations may serve clients using different modes.\u003C/p\u003E\n\u003Cp\u003EFor the full comparison of validation and lifecycle 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=\u0022profile-resolution-and-request-context\u0022\u003EProfile resolution and request context\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth can serve clients with different profiles through the same authentication infrastructure.\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\u003EBlazor Server Client\n |\n | Profile: BlazorServer\n |\n \u002B--------------------\u002B\n |\nBlazor WASM Client |\n | |\n | Profile: BlazorWasm |\n | v\n \u002B---------------\u0026gt; UAuthHub\n |\nMAUI Client |\n | |\n | Profile: Maui |\n | |\n \u002B--------------------\u002B\n |\n v\n AuthFlowContext\n |\n v\n Effective Mode\n Effective Policy\n Response Behavior\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server resolves the effective authentication context for each applicable operation.\u003C/p\u003E\n\u003Cp\u003EThis can include the Client Profile, authentication mode, tenant, security options, and credential delivery requirements.\u003C/p\u003E\n\u003Cp\u003EDifferent clients can therefore operate under different authentication strategies without requiring separate authentication architectures.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022per-request-resolution-does-not-mean-arbitrary-mode-switching\u0022\u003EPer-request resolution does not mean arbitrary mode switching\u003C/h3\u003E\n\u003Cp\u003EThe effective profile and mode may differ between requests from different clients.\u003C/p\u003E\n\u003Cp\u003EHowever, they must remain consistent throughout an individual authentication operation.\u003C/p\u003E\n\u003Cp\u003EA request must not begin under one authentication policy and complete under another because different components interpreted its profile differently.\u003C/p\u003E\n\u003Cp\u003ELikewise, a client must not be allowed to change its declared profile to bypass validation requirements associated with an existing credential or session.\u003C/p\u003E\n\u003Cp\u003EAuthentication continuation must preserve the relevant security relationships established by the original authentication context.\u003C/p\u003E\n\u003Cp\u003EThe framework-controlled \u003Ccode\u003EAuthFlowContext\u003C/code\u003E provides a consistent boundary for these decisions.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/flow-based-auth\u0022\u003EFlow-Based Authentication\u003C/a\u003E for the conceptual model and \u003Ca href=\u0022/docs/auth-flows/overview\u0022\u003EAuth Flows\u003C/a\u003E for the technical request pipeline.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-delivery-and-runtime-capabilities\u0022\u003ECredential delivery and runtime capabilities\u003C/h2\u003E\n\u003Cp\u003EDifferent clients have different capabilities for receiving and storing authentication credentials.\u003C/p\u003E\n\u003Cp\u003EClient Profiles help the framework account for those differences.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022browser-environments\u0022\u003EBrowser environments\u003C/h3\u003E\n\u003Cp\u003EBrowser applications can use cookies for authentication.\u003C/p\u003E\n\u003Cp\u003EWhen cookies carry sensitive authentication credentials, appropriate \u003Ccode\u003EHttpOnly\u003C/code\u003E, \u003Ccode\u003ESecure\u003C/code\u003E, and \u003Ccode\u003ESameSite\u003C/code\u003E policies should be applied.\u003C/p\u003E\n\u003Cp\u003ECookie-based authentication also requires appropriate CSRF protection.\u003C/p\u003E\n\u003Cp\u003EIn browser environments, a credential stored in an HttpOnly cookie is not directly readable by JavaScript or WebAssembly application code, although the browser can attach it to applicable requests.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022native-applications\u0022\u003ENative applications\u003C/h3\u003E\n\u003Cp\u003ENative applications generally communicate through HTTP clients and can use platform-specific secure credential storage.\u003C/p\u003E\n\u003Cp\u003ETheir credential transport and storage requirements differ from those of a browser.\u003C/p\u003E\n\u003Cp\u003EA native Client Profile can help select appropriate integration behavior, but secure storage remains an application and platform responsibility.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-hosted-applications\u0022\u003EServer-hosted applications\u003C/h3\u003E\n\u003Cp\u003EServer-hosted applications can manage authentication state without exposing all authentication implementation details to browser application code.\u003C/p\u003E\n\u003Cp\u003EThis often makes session-oriented authentication attractive.\u003C/p\u003E\n\u003Cp\u003EHowever, server execution alone does not guarantee security. Cookie protection, session validation, authorization, and long-lived connection behavior must still be handled correctly.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-apis\u0022\u003EResource APIs\u003C/h3\u003E\n\u003Cp\u003EResource APIs validate the credentials presented by callers.\u003C/p\u003E\n\u003Cp\u003ETheir authentication requirements must be enforced by trusted server configuration.\u003C/p\u003E\n\u003Cp\u003EA caller\u0027s declared Client Profile must not determine whether token signatures, session validity, tenant boundaries, or authorization requirements can be bypassed.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-and-trust-boundaries\u0022\u003ESecurity and trust boundaries\u003C/h2\u003E\n\u003Cp\u003EClient Profiles must preserve a clear separation between runtime adaptation and security authority.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022profile-declarations-are-not-trusted-identity\u0022\u003EProfile declarations are not trusted identity\u003C/h3\u003E\n\u003Cp\u003EA Client Profile does not prove that the caller is a particular application, device, or user.\u003C/p\u003E\n\u003Cp\u003EAdditional authentication mechanisms are required when an application needs to establish client identity.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022mode-selection-is-server-controlled\u0022\u003EMode selection is server-controlled\u003C/h3\u003E\n\u003Cp\u003EA client may provide information relevant to mode resolution, but the server determines which authentication modes are permitted.\u003C/p\u003E\n\u003Cp\u003EA profile must not provide a path to downgrade authentication requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-validity-is-independent-of-profile-labels\u0022\u003ECredential validity is independent of profile labels\u003C/h3\u003E\n\u003Cp\u003EA valid-looking credential must still satisfy the validation requirements of its authentication mode.\u003C/p\u003E\n\u003Cp\u003EChanging a profile label must not turn an invalid credential into a valid one.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022transport-configuration-is-not-authorization\u0022\u003ETransport configuration is not authorization\u003C/h3\u003E\n\u003Cp\u003ESelecting cookies, headers, or another supported delivery mechanism does not grant access to protected resources.\u003C/p\u003E\n\u003Cp\u003EAuthentication and authorization decisions remain separate from credential transport.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022defaults-are-not-universal-guarantees\u0022\u003EDefaults are not universal guarantees\u003C/h3\u003E\n\u003Cp\u003EA secure default depends on the application\u0027s deployment, runtime, authentication mode, and security requirements.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth can provide suitable defaults, but developers remain responsible for selecting and configuring the policies appropriate to their applications.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-environments-and-extensibility\u0022\u003ECustom environments and extensibility\u003C/h2\u003E\n\u003Cp\u003ENot every application fits a predefined runtime category.\u003C/p\u003E\n\u003Cp\u003EA custom host, embedded client, hybrid application, or specialized integration may need explicit profile configuration or additional integration behavior.\u003C/p\u003E\n\u003Cp\u003EThe purpose of Client Profiles is to keep these differences at a defined architectural boundary.\u003C/p\u003E\n\u003Cp\u003ENew integrations should reuse the existing authentication model and flow infrastructure wherever possible, rather than introducing independent authentication implementations.\u003C/p\u003E\n\u003Cp\u003ECustom profile resolution and integration behavior must continue to respect server-controlled mode selection and authentication security policies.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EClient Profiles allow UltimateAuth to adapt authentication integration to different application environments.\u003C/p\u003E\n\u003Cp\u003EThey provide a consistent way to describe runtime characteristics and influence suitable defaults without turning client-provided metadata into a security authority.\u003C/p\u003E\n\u003Cp\u003EThe fundamental distinctions are:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient Profile\u003C/strong\u003E describes the runtime environment and integration requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication Mode\u003C/strong\u003E defines credential validation, session dependencies, and authentication lifecycle guarantees.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthFlowContext\u003C/strong\u003E establishes the effective security context for an authentication operation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EServer policy\u003C/strong\u003E determines which behavior is permitted.\u003C/p\u003E\n\u003Cp\u003ETogether, these concepts allow different clients to share the same UltimateAuth infrastructure while preserving explicit authentication boundaries.\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/runtime-architecture\u0022\u003ERuntime Architecture\u003C/a\u003E to understand how UltimateAuth\u0027s client, server, and hosting components work together across different application environments.\u003C/p\u003E\n", "Headings": [ { "Id": "what-is-a-client-profile", 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 bd7474e3..44faf37a 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../auth-flows/\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./authentication-model.md\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./authentication-modes.md\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./client-profiles.md\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./authentication-model.md\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./authentication-modes.md\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../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", "Headings": [ { "Id": "why-authentication-flows-matter", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/overview.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/overview.json new file mode 100644 index 00000000..6c2c249f --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/overview.json @@ -0,0 +1,6 @@ +{ + "Slug": "fundamentals/overview", + "Title": "Fundamentals Overview", + "Html": "\n\u003Cp\u003EThis section explains how UltimateAuth works internally.\u003C/p\u003E\n\u003Ch1 id=\u0022section-order\u0022\u003ESection Order\u003C/h1\u003E\n\u003Cp\u003EIf you are new, follow this order:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication Model\u003C/li\u003E\n\u003Cli\u003EFlow-Based Authentication\u003C/li\u003E\n\u003Cli\u003EAuthentication Modes\u003C/li\u003E\n\u003Cli\u003EClient Profiles\u003C/li\u003E\n\u003Cli\u003ERuntime Architecture\u003C/li\u003E\n\u003Cli\u003ERequest Lifecycle\u003C/li\u003E\n\u003C/ul\u003E\n", + "Headings": [] +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/request-lifecycle.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/request-lifecycle.json index 19f894a6..c9d67fc4 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/request-lifecycle.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/request-lifecycle.json @@ -1,7 +1,7 @@ { "Slug": "fundamentals/request-lifecycle", "Title": "Request Lifecycle", - "Html": "\n\u003Cp\u003EUltimateAuth distinguishes between establishing authentication, validating an existing identity, and authorizing access to protected operations.\u003C/p\u003E\n\u003Cp\u003EThese responsibilities are related, but they do not necessarily execute through the same pipeline.\u003C/p\u003E\n\u003Cp\u003EA login request may establish a new authentication context. A protected API request may only need to validate an existing credential. A server-side application operation may execute without making an additional HTTP request.\u003C/p\u003E\n\u003Cp\u003EUnderstanding these differences is essential when integrating UltimateAuth into real applications.\u003C/p\u003E\n\u003Cp\u003EThis page focuses on \u003Cstrong\u003Ehow authentication is evaluated during application requests and where the security boundaries are enforced\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EFor authentication flow orchestration, see \u003Ca href=\u0022/docs/auth-flows/index\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022two-request-categories\u0022\u003ETwo request categories\u003C/h2\u003E\n\u003Cp\u003EAt a conceptual level, requests interacting with UltimateAuth fall into two categories.\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\u003Cth\u003ETypical examples\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthentication operations\u003C/td\u003E\n\u003Ctd\u003EEstablish, continue, or terminate authentication\u003C/td\u003E\n\u003Ctd\u003ELogin, refresh, logout\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EProtected resource operations\u003C/td\u003E\n\u003Ctd\u003EAccess application functionality using an existing identity\u003C/td\u003E\n\u003Ctd\u003EAPI calls, protected pages, application commands\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe distinction matters because an authentication operation may change authentication state, while a protected resource operation generally consumes an existing authentication context.\u003C/p\u003E\n\u003Cp\u003EBoth require appropriate security checks, but they do not need to execute identical flows.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-operations\u0022\u003EAuthentication operations\u003C/h2\u003E\n\u003Cp\u003EAuthentication operations are explicitly initiated through supported authentication endpoints or application integrations.\u003C/p\u003E\n\u003Cp\u003EExamples include login, refresh, and logout.\u003C/p\u003E\n\u003Cp\u003EFor HTTP authentication endpoints, UltimateAuth uses its flow infrastructure to resolve the effective authentication context and execute the requested operation.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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 Request\n |\n v\nAuthentication Flow Boundary\n |\n v\nEffective AuthFlowContext\n |\n v\nOperation-Specific Validation\n |\n v\nAuthentication Operation\n |\n v\nAuthentication Result\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe operation may create or update sessions, issue credentials, rotate refresh tokens, revoke authentication state, or produce a result without persistent changes.\u003C/p\u003E\n\u003Cp\u003EThese details depend on the flow and authentication mode.\u003C/p\u003E\n\u003Cp\u003EThe internal execution pipeline is described in \u003Ca href=\u0022/docs/auth-flows/index\u0022\u003EAuth Flows\u003C/a\u003E, while individual operations are documented in their respective flow guides.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022protected-resource-operations\u0022\u003EProtected resource operations\u003C/h2\u003E\n\u003Cp\u003EMost application requests do not initiate a login, refresh, or logout flow.\u003C/p\u003E\n\u003Cp\u003EInstead, they attempt to access a protected resource using an existing authentication credential or established server-side authentication context.\u003C/p\u003E\n\u003Cp\u003EA typical protected HTTP request follows this conceptual lifecycle:\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 Request\n |\n v\nResolve Applicable Authentication Scheme\n |\n v\nExtract Authentication Credential\n |\n v\nValidate Credential and Required State\n |\n v\nEstablish Trusted Identity\n |\n v\nEvaluate Authorization\n |\n \u002B---- Denied\n |\n v\nExecute Protected Operation\n |\n v\nReturn Response\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe exact framework components and ordering depend on the application integration.\u003C/p\u003E\n\u003Cp\u003EHowever, the security distinction remains the same:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication establishes identity. Authorization determines whether that identity may perform the requested operation.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA successfully authenticated request is not automatically authorized.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-validation\u0022\u003ECredential validation\u003C/h2\u003E\n\u003Cp\u003EThe most important mode-dependent difference in the request lifecycle is how authentication credentials are validated.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports different authentication strategies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque\u0022\u003EPureOpaque\u003C/h3\u003E\n\u003Cp\u003EThe client presents an opaque session credential.\u003C/p\u003E\n\u003Cp\u003EThe server resolves the associated session and evaluates the applicable session, chain, and security requirements.\u003C/p\u003E\n\u003Cp\u003EThe session remains authoritative for authentication.\u003C/p\u003E\n\u003Cp\u003EA revoked or expired session must not continue to authenticate subsequent requests.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003EHybrid\u003C/h3\u003E\n\u003Cp\u003EThe client presents an opaque access token.\u003C/p\u003E\n\u003Cp\u003EThe server resolves the token and validates its relationship to the authoritative server-side session.\u003C/p\u003E\n\u003Cp\u003EThe access token and session are separate concepts.\u003C/p\u003E\n\u003Cp\u003EAuthentication requires both a valid access credential and the applicable session validation requirements to succeed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid\u0022\u003ESemiHybrid\u003C/h3\u003E\n\u003Cp\u003EThe client presents a JWT access token.\u003C/p\u003E\n\u003Cp\u003ENormal resource authentication validates the JWT cryptographically and applies the relevant token-validation rules.\u003C/p\u003E\n\u003Cp\u003EA server-side session remains relevant for lifecycle operations such as refresh and session management, but normal JWT validation does not require a session lookup.\u003C/p\u003E\n\u003Cp\u003ERevoking a session therefore does not automatically invalidate an already issued JWT.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022purejwt\u0022\u003EPureJwt\u003C/h3\u003E\n\u003Cp\u003EThe client presents a JWT access token.\u003C/p\u003E\n\u003Cp\u003EThe resource validates the token cryptographically and applies the configured issuer, audience, lifetime, and other relevant validation requirements.\u003C/p\u003E\n\u003Cp\u003EAuthentication does not require a server-side session.\u003C/p\u003E\n\u003Cp\u003EPreviously issued JWTs may remain valid until expiration unless an additional invalidation mechanism is explicitly configured.\u003C/p\u003E\n\u003Cp\u003EFor the complete guarantees and tradeoffs, 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=\u0022when-identity-becomes-trusted\u0022\u003EWhen identity becomes trusted\u003C/h2\u003E\n\u003Cp\u003EAn incoming request may contain user identifiers, claims, tokens, cookies, device information, or tenant-related metadata.\u003C/p\u003E\n\u003Cp\u003EThese values must not automatically be treated as a trusted identity.\u003C/p\u003E\n\u003Cp\u003EThe relevant authentication mechanism must validate the presented evidence before the application relies on it.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, this includes the required server-side session checks.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, it includes the applicable cryptographic and token-validation checks.\u003C/p\u003E\n\u003Cp\u003EOnly after successful authentication should the application treat the resulting identity as authenticated.\u003C/p\u003E\n\u003Cp\u003EA failed authentication attempt must not leave behind an identity that appears successfully authenticated to downstream application code.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022anonymous-requests\u0022\u003EAnonymous requests\u003C/h3\u003E\n\u003Cp\u003ENot every application request requires authentication.\u003C/p\u003E\n\u003Cp\u003EPublic endpoints may allow anonymous access.\u003C/p\u003E\n\u003Cp\u003EA request without valid authentication credentials can remain anonymous when the endpoint permits it.\u003C/p\u003E\n\u003Cp\u003EFor protected endpoints, missing or invalid authentication must prevent access according to the applicable authentication and authorization configuration.\u003C/p\u003E\n\u003Cp\u003EAn anonymous request is not necessarily an error. It becomes an access problem when the requested operation requires an authenticated identity.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-context-and-accesscontext\u0022\u003EAuthentication context and AccessContext\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates authentication-flow execution from authorization context.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlowContext\u003C/code\u003E is associated with authentication operations and describes the effective environment in which those operations execute.\u003C/p\u003E\n\u003Cp\u003EIt includes the applicable mode, client profile, tenant, and other flow-related information.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E represents the trusted actor and contextual information used for access and authorization decisions.\u003C/p\u003E\n\u003Cp\u003EThese contexts serve different purposes.\u003C/p\u003E\n\u003Cp\u003EAn application request does not need to execute a login flow merely to establish an identity from an already valid authentication credential.\u003C/p\u003E\n\u003Cp\u003ELikewise, the existence of an \u003Ccode\u003EAuthFlowContext\u003C/code\u003E does not mean the operation has already been authorized.\u003C/p\u003E\n\u003Cp\u003EThe authentication model and these trust boundaries are introduced 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=\u0022authorization-boundary\u0022\u003EAuthorization boundary\u003C/h2\u003E\n\u003Cp\u003EAfter authentication establishes the caller\u0027s identity, the application must determine whether the requested operation is permitted.\u003C/p\u003E\n\u003Cp\u003EAuthorization may depend on:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe authenticated actor.\u003C/li\u003E\n\u003Cli\u003EThe requested resource or operation.\u003C/li\u003E\n\u003Cli\u003ETenant boundaries.\u003C/li\u003E\n\u003Cli\u003ERoles and permissions.\u003C/li\u003E\n\u003Cli\u003EApplicable application policies.\u003C/li\u003E\n\u003Cli\u003EResource ownership or other domain-specific requirements.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth integrates with the relevant server-side authorization mechanisms, but authentication and authorization remain separate responsibilities.\u003C/p\u003E\n\u003Cp\u003EFor ASP.NET Core applications, endpoint and policy-based authorization can protect HTTP resources.\u003C/p\u003E\n\u003Cp\u003EHowever, an authorization attribute applied to an HTTP endpoint does not automatically protect arbitrary direct calls to the underlying application service.\u003C/p\u003E\n\u003Cp\u003ESecurity requirements must be enforced at the boundary through which the operation is actually accessible.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-requests-and-in-process-operations\u0022\u003EHTTP requests and in-process operations\u003C/h2\u003E\n\u003Cp\u003ENot every protected application operation crosses an HTTP boundary.\u003C/p\u003E\n\u003Cp\u003EThis distinction is especially important for server-hosted applications.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-resource-requests\u0022\u003EHTTP resource requests\u003C/h3\u003E\n\u003Cp\u003EA remote client sends an HTTP request to a protected application or resource API.\u003C/p\u003E\n\u003Cp\u003EThe receiving application must authenticate the request and enforce authorization before performing the protected operation.\u003C/p\u003E\n\u003Cp\u003EClient-side authentication state alone is insufficient.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022in-process-application-operations\u0022\u003EIn-process application operations\u003C/h3\u003E\n\u003Cp\u003EA server-hosted application may invoke application services directly without making an HTTP request.\u003C/p\u003E\n\u003Cp\u003EFor example, a Blazor Server component may call a registered service within the same process.\u003C/p\u003E\n\u003Cp\u003EIn this case, the application must not assume that HTTP middleware or endpoint authorization filters execute automatically.\u003C/p\u003E\n\u003Cp\u003EThe service invocation must operate with an appropriately established identity and enforce the relevant authorization requirements.\u003C/p\u003E\n\u003Cp\u003EThis is an architectural distinction, not a separate authentication mode.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/runtime-architecture\u0022\u003ERuntime Architecture\u003C/a\u003E for integrated and distributed deployment models.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022long-lived-connections\u0022\u003ELong-lived connections\u003C/h2\u003E\n\u003Cp\u003ESome application environments maintain connections or execution contexts that outlive individual HTTP requests.\u003C/p\u003E\n\u003Cp\u003EBlazor Server is an important example.\u003C/p\u003E\n\u003Cp\u003EAn interactive circuit may remain active after its initial authentication state was established.\u003C/p\u003E\n\u003Cp\u003EDuring that time, the underlying authentication state may change.\u003C/p\u003E\n\u003Cp\u003EA session may expire, be revoked, or become invalid because of a security-sensitive operation.\u003C/p\u003E\n\u003Cp\u003EConsequently, applications must account for authentication-state revalidation and authorization during long-lived interactions.\u003C/p\u003E\n\u003Cp\u003EAn initially authenticated connection must not be treated as permanent proof that every later operation remains authorized.\u003C/p\u003E\n\u003Cp\u003EThe appropriate mechanism depends on the runtime integration and authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-failures-and-authorization-failures\u0022\u003EAuthentication failures and authorization failures\u003C/h2\u003E\n\u003Cp\u003EAuthentication and authorization failures represent different conditions.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECondition\u003C/th\u003E\n\u003Cth\u003EMeaning\u003C/th\u003E\n\u003Cth\u003ETypical HTTP outcome\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMissing required authentication\u003C/td\u003E\n\u003Ctd\u003ENo acceptable authenticated identity was established\u003C/td\u003E\n\u003Ctd\u003E401 Unauthorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInvalid or expired credential\u003C/td\u003E\n\u003Ctd\u003EPresented evidence failed authentication\u003C/td\u003E\n\u003Ctd\u003E401 Unauthorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthenticated but insufficient permission\u003C/td\u003E\n\u003Ctd\u003EIdentity is valid, but access is denied\u003C/td\u003E\n\u003Ctd\u003E403 Forbidden\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPublic endpoint accessed anonymously\u003C/td\u003E\n\u003Ctd\u003EAuthentication is not required\u003C/td\u003E\n\u003Ctd\u003ERequest proceeds\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese are typical HTTP semantics, not guarantees that every integration returns identical status codes.\u003C/p\u003E\n\u003Cp\u003EBrowser applications may use redirects or other configured challenge behavior.\u003C/p\u003E\n\u003Cp\u003EApplication services operating outside HTTP may express failures through typed results or exceptions rather than HTTP responses.\u003C/p\u003E\n\u003Cp\u003EThe essential distinction is that authentication failure and authorization denial must not be confused.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022request-isolation\u0022\u003ERequest isolation\u003C/h2\u003E\n\u003Cp\u003EAuthentication state must be associated with the correct execution context.\u003C/p\u003E\n\u003Cp\u003EA request must not inherit another request\u0027s authenticated identity, tenant context, or authorization state.\u003C/p\u003E\n\u003Cp\u003EThis matters in multi-tenant systems, applications with concurrent requests, and server-side environments where services may have different lifetimes.\u003C/p\u003E\n\u003Cp\u003ERequest-specific context must be resolved and propagated through the appropriate framework boundaries.\u003C/p\u003E\n\u003Cp\u003ELong-lived or singleton services must not accidentally retain request-specific authentication information.\u003C/p\u003E\n\u003Cp\u003EThe same principle applies to direct application service calls and background operations.\u003C/p\u003E\n\u003Cp\u003EA background operation without an authenticated HTTP request must establish its execution and authorization context through an explicitly supported mechanism rather than assuming a user identity exists.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-the-request-lifecycle-does-not-guarantee\u0022\u003EWhat the request lifecycle does not guarantee\u003C/h2\u003E\n\u003Cp\u003EThe request lifecycle should not be interpreted as a promise that every UltimateAuth operation:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECreates an \u003Ccode\u003EAuthFlowContext\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EResolves a server-side session.\u003C/li\u003E\n\u003Cli\u003EQueries authentication persistence.\u003C/li\u003E\n\u003Cli\u003ERotates or issues tokens.\u003C/li\u003E\n\u003Cli\u003EExecutes the same orchestrator or authority method.\u003C/li\u003E\n\u003Cli\u003EPasses through an HTTP endpoint.\u003C/li\u003E\n\u003Cli\u003EUses identical authorization mechanisms.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThose requirements depend on the operation, authentication mode, and runtime integration.\u003C/p\u003E\n\u003Cp\u003EThe shared guarantee is architectural: authentication evidence must be validated according to the applicable security model, and protected operations must enforce their authorization requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between authentication operations and requests that consume an existing authenticated identity.\u003C/p\u003E\n\u003Cp\u003EAuthentication operations establish, continue, or terminate authentication through controlled flows.\u003C/p\u003E\n\u003Cp\u003EProtected resource operations validate existing authentication evidence, establish trusted identity, and enforce authorization before allowing access.\u003C/p\u003E\n\u003Cp\u003EThe validation strategy depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EThe execution boundary depends on the runtime and deployment architecture.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA request is not trusted because it contains a credential. It becomes authenticated only when the required validation succeeds, and it becomes authorized only when the applicable access rules permit the operation.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003ENext step\u003C/h2\u003E\n\u003Cp\u003EYou have completed the Fundamentals section.\u003C/p\u003E\n\u003Cp\u003EContinue to \u003Ca href=\u0022/docs/auth-flows/index\u0022\u003EAuth Flows\u003C/a\u003E to explore the technical authentication pipeline and the individual login, refresh, and logout operations.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth distinguishes between establishing authentication, validating an existing identity, and authorizing access to protected operations.\u003C/p\u003E\n\u003Cp\u003EThese responsibilities are related, but they do not necessarily execute through the same pipeline.\u003C/p\u003E\n\u003Cp\u003EA login request may establish a new authentication context. A protected API request may only need to validate an existing credential. A server-side application operation may execute without making an additional HTTP request.\u003C/p\u003E\n\u003Cp\u003EUnderstanding these differences is essential when integrating UltimateAuth into real applications.\u003C/p\u003E\n\u003Cp\u003EThis page focuses on \u003Cstrong\u003Ehow authentication is evaluated during application requests and where the security boundaries are enforced\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EFor authentication flow orchestration, 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=\u0022two-request-categories\u0022\u003ETwo request categories\u003C/h2\u003E\n\u003Cp\u003EAt a conceptual level, requests interacting with UltimateAuth fall into two categories.\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\u003Cth\u003ETypical examples\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthentication operations\u003C/td\u003E\n\u003Ctd\u003EEstablish, continue, or terminate authentication\u003C/td\u003E\n\u003Ctd\u003ELogin, refresh, logout\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EProtected resource operations\u003C/td\u003E\n\u003Ctd\u003EAccess application functionality using an existing identity\u003C/td\u003E\n\u003Ctd\u003EAPI calls, protected pages, application commands\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe distinction matters because an authentication operation may change authentication state, while a protected resource operation generally consumes an existing authentication context.\u003C/p\u003E\n\u003Cp\u003EBoth require appropriate security checks, but they do not need to execute identical flows.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-operations\u0022\u003EAuthentication operations\u003C/h2\u003E\n\u003Cp\u003EAuthentication operations are explicitly initiated through supported authentication endpoints or application integrations.\u003C/p\u003E\n\u003Cp\u003EExamples include login, refresh, and logout.\u003C/p\u003E\n\u003Cp\u003EFor HTTP authentication endpoints, UltimateAuth uses its flow infrastructure to resolve the effective authentication context and execute the requested operation.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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 Request\n |\n v\nAuthentication Flow Boundary\n |\n v\nEffective AuthFlowContext\n |\n v\nOperation-Specific Validation\n |\n v\nAuthentication Operation\n |\n v\nAuthentication Result\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe operation may create or update sessions, issue credentials, rotate refresh tokens, revoke authentication state, or produce a result without persistent changes.\u003C/p\u003E\n\u003Cp\u003EThese details depend on the flow and authentication mode.\u003C/p\u003E\n\u003Cp\u003EThe internal execution pipeline is described in \u003Ca href=\u0022/docs/auth-flows/overview\u0022\u003EAuth Flows\u003C/a\u003E, while individual operations are documented in their respective flow guides.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022protected-resource-operations\u0022\u003EProtected resource operations\u003C/h2\u003E\n\u003Cp\u003EMost application requests do not initiate a login, refresh, or logout flow.\u003C/p\u003E\n\u003Cp\u003EInstead, they attempt to access a protected resource using an existing authentication credential or established server-side authentication context.\u003C/p\u003E\n\u003Cp\u003EA typical protected HTTP request follows this conceptual lifecycle:\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 Request\n |\n v\nResolve Applicable Authentication Scheme\n |\n v\nExtract Authentication Credential\n |\n v\nValidate Credential and Required State\n |\n v\nEstablish Trusted Identity\n |\n v\nEvaluate Authorization\n |\n \u002B---- Denied\n |\n v\nExecute Protected Operation\n |\n v\nReturn Response\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe exact framework components and ordering depend on the application integration.\u003C/p\u003E\n\u003Cp\u003EHowever, the security distinction remains the same:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication establishes identity. Authorization determines whether that identity may perform the requested operation.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA successfully authenticated request is not automatically authorized.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-validation\u0022\u003ECredential validation\u003C/h2\u003E\n\u003Cp\u003EThe most important mode-dependent difference in the request lifecycle is how authentication credentials are validated.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports different authentication strategies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque\u0022\u003EPureOpaque\u003C/h3\u003E\n\u003Cp\u003EThe client presents an opaque session credential.\u003C/p\u003E\n\u003Cp\u003EThe server resolves the associated session and evaluates the applicable session, chain, and security requirements.\u003C/p\u003E\n\u003Cp\u003EThe session remains authoritative for authentication.\u003C/p\u003E\n\u003Cp\u003EA revoked or expired session must not continue to authenticate subsequent requests.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003EHybrid\u003C/h3\u003E\n\u003Cp\u003EThe client presents an opaque access token.\u003C/p\u003E\n\u003Cp\u003EThe server resolves the token and validates its relationship to the authoritative server-side session.\u003C/p\u003E\n\u003Cp\u003EThe access token and session are separate concepts.\u003C/p\u003E\n\u003Cp\u003EAuthentication requires both a valid access credential and the applicable session validation requirements to succeed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid\u0022\u003ESemiHybrid\u003C/h3\u003E\n\u003Cp\u003EThe client presents a JWT access token.\u003C/p\u003E\n\u003Cp\u003ENormal resource authentication validates the JWT cryptographically and applies the relevant token-validation rules.\u003C/p\u003E\n\u003Cp\u003EA server-side session remains relevant for lifecycle operations such as refresh and session management, but normal JWT validation does not require a session lookup.\u003C/p\u003E\n\u003Cp\u003ERevoking a session therefore does not automatically invalidate an already issued JWT.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022purejwt\u0022\u003EPureJwt\u003C/h3\u003E\n\u003Cp\u003EThe client presents a JWT access token.\u003C/p\u003E\n\u003Cp\u003EThe resource validates the token cryptographically and applies the configured issuer, audience, lifetime, and other relevant validation requirements.\u003C/p\u003E\n\u003Cp\u003EAuthentication does not require a server-side session.\u003C/p\u003E\n\u003Cp\u003EPreviously issued JWTs may remain valid until expiration unless an additional invalidation mechanism is explicitly configured.\u003C/p\u003E\n\u003Cp\u003EFor the complete guarantees and tradeoffs, 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=\u0022when-identity-becomes-trusted\u0022\u003EWhen identity becomes trusted\u003C/h2\u003E\n\u003Cp\u003EAn incoming request may contain user identifiers, claims, tokens, cookies, device information, or tenant-related metadata.\u003C/p\u003E\n\u003Cp\u003EThese values must not automatically be treated as a trusted identity.\u003C/p\u003E\n\u003Cp\u003EThe relevant authentication mechanism must validate the presented evidence before the application relies on it.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, this includes the required server-side session checks.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, it includes the applicable cryptographic and token-validation checks.\u003C/p\u003E\n\u003Cp\u003EOnly after successful authentication should the application treat the resulting identity as authenticated.\u003C/p\u003E\n\u003Cp\u003EA failed authentication attempt must not leave behind an identity that appears successfully authenticated to downstream application code.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022anonymous-requests\u0022\u003EAnonymous requests\u003C/h3\u003E\n\u003Cp\u003ENot every application request requires authentication.\u003C/p\u003E\n\u003Cp\u003EPublic endpoints may allow anonymous access.\u003C/p\u003E\n\u003Cp\u003EA request without valid authentication credentials can remain anonymous when the endpoint permits it.\u003C/p\u003E\n\u003Cp\u003EFor protected endpoints, missing or invalid authentication must prevent access according to the applicable authentication and authorization configuration.\u003C/p\u003E\n\u003Cp\u003EAn anonymous request is not necessarily an error. It becomes an access problem when the requested operation requires an authenticated identity.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-context-and-accesscontext\u0022\u003EAuthentication context and AccessContext\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates authentication-flow execution from authorization context.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlowContext\u003C/code\u003E is associated with authentication operations and describes the effective environment in which those operations execute.\u003C/p\u003E\n\u003Cp\u003EIt includes the applicable mode, client profile, tenant, and other flow-related information.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E represents the trusted actor and contextual information used for access and authorization decisions.\u003C/p\u003E\n\u003Cp\u003EThese contexts serve different purposes.\u003C/p\u003E\n\u003Cp\u003EAn application request does not need to execute a login flow merely to establish an identity from an already valid authentication credential.\u003C/p\u003E\n\u003Cp\u003ELikewise, the existence of an \u003Ccode\u003EAuthFlowContext\u003C/code\u003E does not mean the operation has already been authorized.\u003C/p\u003E\n\u003Cp\u003EThe authentication model and these trust boundaries are introduced 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=\u0022authorization-boundary\u0022\u003EAuthorization boundary\u003C/h2\u003E\n\u003Cp\u003EAfter authentication establishes the caller\u0027s identity, the application must determine whether the requested operation is permitted.\u003C/p\u003E\n\u003Cp\u003EAuthorization may depend on:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe authenticated actor.\u003C/li\u003E\n\u003Cli\u003EThe requested resource or operation.\u003C/li\u003E\n\u003Cli\u003ETenant boundaries.\u003C/li\u003E\n\u003Cli\u003ERoles and permissions.\u003C/li\u003E\n\u003Cli\u003EApplicable application policies.\u003C/li\u003E\n\u003Cli\u003EResource ownership or other domain-specific requirements.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth integrates with the relevant server-side authorization mechanisms, but authentication and authorization remain separate responsibilities.\u003C/p\u003E\n\u003Cp\u003EFor ASP.NET Core applications, endpoint and policy-based authorization can protect HTTP resources.\u003C/p\u003E\n\u003Cp\u003EHowever, an authorization attribute applied to an HTTP endpoint does not automatically protect arbitrary direct calls to the underlying application service.\u003C/p\u003E\n\u003Cp\u003ESecurity requirements must be enforced at the boundary through which the operation is actually accessible.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-requests-and-in-process-operations\u0022\u003EHTTP requests and in-process operations\u003C/h2\u003E\n\u003Cp\u003ENot every protected application operation crosses an HTTP boundary.\u003C/p\u003E\n\u003Cp\u003EThis distinction is especially important for server-hosted applications.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-resource-requests\u0022\u003EHTTP resource requests\u003C/h3\u003E\n\u003Cp\u003EA remote client sends an HTTP request to a protected application or resource API.\u003C/p\u003E\n\u003Cp\u003EThe receiving application must authenticate the request and enforce authorization before performing the protected operation.\u003C/p\u003E\n\u003Cp\u003EClient-side authentication state alone is insufficient.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022in-process-application-operations\u0022\u003EIn-process application operations\u003C/h3\u003E\n\u003Cp\u003EA server-hosted application may invoke application services directly without making an HTTP request.\u003C/p\u003E\n\u003Cp\u003EFor example, a Blazor Server component may call a registered service within the same process.\u003C/p\u003E\n\u003Cp\u003EIn this case, the application must not assume that HTTP middleware or endpoint authorization filters execute automatically.\u003C/p\u003E\n\u003Cp\u003EThe service invocation must operate with an appropriately established identity and enforce the relevant authorization requirements.\u003C/p\u003E\n\u003Cp\u003EThis is an architectural distinction, not a separate authentication mode.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/runtime-architecture\u0022\u003ERuntime Architecture\u003C/a\u003E for integrated and distributed deployment models.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022long-lived-connections\u0022\u003ELong-lived connections\u003C/h2\u003E\n\u003Cp\u003ESome application environments maintain connections or execution contexts that outlive individual HTTP requests.\u003C/p\u003E\n\u003Cp\u003EBlazor Server is an important example.\u003C/p\u003E\n\u003Cp\u003EAn interactive circuit may remain active after its initial authentication state was established.\u003C/p\u003E\n\u003Cp\u003EDuring that time, the underlying authentication state may change.\u003C/p\u003E\n\u003Cp\u003EA session may expire, be revoked, or become invalid because of a security-sensitive operation.\u003C/p\u003E\n\u003Cp\u003EConsequently, applications must account for authentication-state revalidation and authorization during long-lived interactions.\u003C/p\u003E\n\u003Cp\u003EAn initially authenticated connection must not be treated as permanent proof that every later operation remains authorized.\u003C/p\u003E\n\u003Cp\u003EThe appropriate mechanism depends on the runtime integration and authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-failures-and-authorization-failures\u0022\u003EAuthentication failures and authorization failures\u003C/h2\u003E\n\u003Cp\u003EAuthentication and authorization failures represent different conditions.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECondition\u003C/th\u003E\n\u003Cth\u003EMeaning\u003C/th\u003E\n\u003Cth\u003ETypical HTTP outcome\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMissing required authentication\u003C/td\u003E\n\u003Ctd\u003ENo acceptable authenticated identity was established\u003C/td\u003E\n\u003Ctd\u003E401 Unauthorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInvalid or expired credential\u003C/td\u003E\n\u003Ctd\u003EPresented evidence failed authentication\u003C/td\u003E\n\u003Ctd\u003E401 Unauthorized\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAuthenticated but insufficient permission\u003C/td\u003E\n\u003Ctd\u003EIdentity is valid, but access is denied\u003C/td\u003E\n\u003Ctd\u003E403 Forbidden\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPublic endpoint accessed anonymously\u003C/td\u003E\n\u003Ctd\u003EAuthentication is not required\u003C/td\u003E\n\u003Ctd\u003ERequest proceeds\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese are typical HTTP semantics, not guarantees that every integration returns identical status codes.\u003C/p\u003E\n\u003Cp\u003EBrowser applications may use redirects or other configured challenge behavior.\u003C/p\u003E\n\u003Cp\u003EApplication services operating outside HTTP may express failures through typed results or exceptions rather than HTTP responses.\u003C/p\u003E\n\u003Cp\u003EThe essential distinction is that authentication failure and authorization denial must not be confused.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022request-isolation\u0022\u003ERequest isolation\u003C/h2\u003E\n\u003Cp\u003EAuthentication state must be associated with the correct execution context.\u003C/p\u003E\n\u003Cp\u003EA request must not inherit another request\u0027s authenticated identity, tenant context, or authorization state.\u003C/p\u003E\n\u003Cp\u003EThis matters in multi-tenant systems, applications with concurrent requests, and server-side environments where services may have different lifetimes.\u003C/p\u003E\n\u003Cp\u003ERequest-specific context must be resolved and propagated through the appropriate framework boundaries.\u003C/p\u003E\n\u003Cp\u003ELong-lived or singleton services must not accidentally retain request-specific authentication information.\u003C/p\u003E\n\u003Cp\u003EThe same principle applies to direct application service calls and background operations.\u003C/p\u003E\n\u003Cp\u003EA background operation without an authenticated HTTP request must establish its execution and authorization context through an explicitly supported mechanism rather than assuming a user identity exists.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-the-request-lifecycle-does-not-guarantee\u0022\u003EWhat the request lifecycle does not guarantee\u003C/h2\u003E\n\u003Cp\u003EThe request lifecycle should not be interpreted as a promise that every UltimateAuth operation:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECreates an \u003Ccode\u003EAuthFlowContext\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EResolves a server-side session.\u003C/li\u003E\n\u003Cli\u003EQueries authentication persistence.\u003C/li\u003E\n\u003Cli\u003ERotates or issues tokens.\u003C/li\u003E\n\u003Cli\u003EExecutes the same orchestrator or authority method.\u003C/li\u003E\n\u003Cli\u003EPasses through an HTTP endpoint.\u003C/li\u003E\n\u003Cli\u003EUses identical authorization mechanisms.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThose requirements depend on the operation, authentication mode, and runtime integration.\u003C/p\u003E\n\u003Cp\u003EThe shared guarantee is architectural: authentication evidence must be validated according to the applicable security model, and protected operations must enforce their authorization requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between authentication operations and requests that consume an existing authenticated identity.\u003C/p\u003E\n\u003Cp\u003EAuthentication operations establish, continue, or terminate authentication through controlled flows.\u003C/p\u003E\n\u003Cp\u003EProtected resource operations validate existing authentication evidence, establish trusted identity, and enforce authorization before allowing access.\u003C/p\u003E\n\u003Cp\u003EThe validation strategy depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EThe execution boundary depends on the runtime and deployment architecture.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA request is not trusted because it contains a credential. It becomes authenticated only when the required validation succeeds, and it becomes authorized only when the applicable access rules permit the operation.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-step\u0022\u003ENext step\u003C/h2\u003E\n\u003Cp\u003EYou have completed the Fundamentals section.\u003C/p\u003E\n\u003Cp\u003EContinue to \u003Ca href=\u0022/docs/auth-flows/overview\u0022\u003EAuth Flows\u003C/a\u003E to explore the technical authentication pipeline and the individual login, refresh, and logout operations.\u003C/p\u003E\n", "Headings": [ { "Id": "two-request-categories", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/runtime-architecture.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/runtime-architecture.json index d371d924..129bacea 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/runtime-architecture.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/runtime-architecture.json @@ -1,7 +1,7 @@ { "Slug": "fundamentals/runtime-architecture", "Title": "Runtime Architecture", - "Html": "\n\u003Cp\u003EUltimateAuth is designed to provide a unified authentication architecture across different .NET application environments.\u003C/p\u003E\n\u003Cp\u003EA Blazor Server application, a standalone Blazor WebAssembly application, a native MAUI application, and a resource API may have different execution models, but they should not require entirely separate authentication systems.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth addresses this through a separation between \u003Cstrong\u003Eauthentication infrastructure, client integration, hosting, and protected resources\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThis separation allows authentication behavior to remain consistent while adapting to the capabilities and responsibilities of each runtime.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-overview\u0022\u003EArchitectural overview\u003C/h2\u003E\n\u003Cp\u003EAt a high level, UltimateAuth consists of several cooperating layers:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ECore:\u003C/strong\u003E Shared authentication contracts, models, and foundational abstractions.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EServer:\u003C/strong\u003E Authentication operations, security decisions, credential management, and persistence coordination.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClient:\u003C/strong\u003E Client-side integration, authentication requests, and credential lifecycle handling.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUAuthHub:\u003C/strong\u003E The application hosting UltimateAuth\u0027s authentication endpoints and services.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EResource applications:\u003C/strong\u003E Applications and APIs that consume authenticated identity and enforce authorization.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese are architectural responsibilities, not necessarily separate processes or deployments.\u003C/p\u003E\n\u003Cp\u003EA single application may host multiple responsibilities. Alternatively, they can be distributed across independently deployed services.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Application Clients\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502 \u2502\n Blazor WASM MAUI Other Clients\n \u2502 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u2502 HTTP\n \u25BC\n UAuthHub\n \u2502\n UltimateAuth Server\n \u2502\n Authentication Services\n \u2502\n Stores / Providers\n \u2502\n \u25BC\n Persistent State\n\n\n Protected Resources\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502 \u2502\n Resource API Web App Other Services\n \u2502 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n Authentication Validation\n \u2502\n Authorization\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis diagram illustrates the separation of responsibilities.\u003C/p\u003E\n\u003Cp\u003EIt does not imply that every authenticated resource request must pass through UAuthHub.\u003C/p\u003E\n\u003Cp\u003EWhether a protected resource needs to consult server-side authentication infrastructure depends on the effective authentication mode and its validation requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-shared-authentication-foundations\u0022\u003ECore: shared authentication foundations\u003C/h2\u003E\n\u003Cp\u003EThe Core layer defines the common concepts used throughout UltimateAuth.\u003C/p\u003E\n\u003Cp\u003EIts responsibility is to provide stable contracts and abstractions without depending on a particular client runtime or application host.\u003C/p\u003E\n\u003Cp\u003EThese foundations include concepts such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication modes.\u003C/li\u003E\n\u003Cli\u003EAuthentication and security contexts.\u003C/li\u003E\n\u003Cli\u003EAuthentication results and contracts.\u003C/li\u003E\n\u003Cli\u003EDomain models and shared abstractions.\u003C/li\u003E\n\u003Cli\u003ESecurity-related options and definitions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe Core layer provides the common vocabulary used by client integrations, server services, and other framework components.\u003C/p\u003E\n\u003Cp\u003EIt should not contain assumptions that authentication always occurs through an HTTP endpoint, a browser cookie, or a particular hosting model.\u003C/p\u003E\n\u003Cp\u003EThis separation is important because UltimateAuth supports both stateful and stateless authentication strategies.\u003C/p\u003E\n\u003Cp\u003EA shared authentication contract must not implicitly require session persistence when the selected mode does not use sessions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-authentication-execution-and-lifecycle\u0022\u003EServer: authentication execution and lifecycle\u003C/h2\u003E\n\u003Cp\u003EThe Server layer implements the server-side authentication capabilities.\u003C/p\u003E\n\u003Cp\u003EIt coordinates the operations required to establish, continue, validate, and terminate authentication according to the effective mode and security policies.\u003C/p\u003E\n\u003Cp\u003EIts responsibilities include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication flow execution.\u003C/li\u003E\n\u003Cli\u003ECredential validation and management.\u003C/li\u003E\n\u003Cli\u003ESession and token lifecycle operations.\u003C/li\u003E\n\u003Cli\u003EAuthentication security decisions.\u003C/li\u003E\n\u003Cli\u003ECoordination with persistence providers.\u003C/li\u003E\n\u003Cli\u003ECredential issuance and validation.\u003C/li\u003E\n\u003Cli\u003EIntegration with server-side authorization infrastructure.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe Server layer contains multiple cooperating services rather than one universal authentication engine responsible for every decision.\u003C/p\u003E\n\u003Cp\u003EFor example, session lifecycle operations, credential verification, and authorization may follow different execution paths while sharing common security contracts.\u003C/p\u003E\n\u003Cp\u003EThe architecture separates orchestration from the underlying services that perform domain-specific work.\u003C/p\u003E\n\u003Cp\u003EFor the detailed execution pipeline, see \u003Ca href=\u0022../auth-flows/\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-runtime-integration\u0022\u003EClient: runtime integration\u003C/h2\u003E\n\u003Cp\u003EThe Client layer provides the integration between an application and UltimateAuth\u0027s authentication services.\u003C/p\u003E\n\u003Cp\u003EIts purpose is to reduce the amount of authentication infrastructure that application developers need to implement themselves.\u003C/p\u003E\n\u003Cp\u003EDepending on the supported runtime and configuration, client integration may handle:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInitiating authentication flows.\u003C/li\u003E\n\u003Cli\u003ECommunicating with UAuthHub.\u003C/li\u003E\n\u003Cli\u003EProcessing authentication outcomes.\u003C/li\u003E\n\u003Cli\u003EManaging supported credential delivery mechanisms.\u003C/li\u003E\n\u003Cli\u003ECoordinating authentication state with the application.\u003C/li\u003E\n\u003Cli\u003EIntegrating authenticated requests with protected resources.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe client SDK does not replace server-side authentication validation.\u003C/p\u003E\n\u003Cp\u003EIt provides an application-facing interface to the authentication infrastructure.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-integration-is-runtime-aware\u0022\u003EClient integration is runtime-aware\u003C/h3\u003E\n\u003Cp\u003EDifferent client runtimes have different capabilities.\u003C/p\u003E\n\u003Cp\u003EA browser-based WebAssembly application executes within browser security restrictions.\u003C/p\u003E\n\u003Cp\u003EA native MAUI application has access to platform-specific capabilities.\u003C/p\u003E\n\u003Cp\u003EA Blazor Server application executes application logic on the server while maintaining an interactive connection with the browser.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth accounts for these differences through runtime-specific integrations and Client Profiles.\u003C/p\u003E\n\u003Cp\u003EThe authentication model remains shared even when the implementation of credential transport or application state differs.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022./client-profiles.md\u0022\u003EClient Profiles\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022uauthhub-the-authentication-host\u0022\u003EUAuthHub: the authentication host\u003C/h2\u003E\n\u003Cp\u003EUAuthHub is the application hosting UltimateAuth\u0027s server-side authentication infrastructure.\u003C/p\u003E\n\u003Cp\u003EIt provides the environment in which authentication endpoints and the required server services are available.\u003C/p\u003E\n\u003Cp\u003EDepending on the deployment, UAuthHub can be responsible for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAccepting authentication requests.\u003C/li\u003E\n\u003Cli\u003EResolving the applicable tenant and authentication context.\u003C/li\u003E\n\u003Cli\u003EExecuting authentication flows.\u003C/li\u003E\n\u003Cli\u003EManaging credentials and sessions.\u003C/li\u003E\n\u003Cli\u003EIssuing authentication credentials.\u003C/li\u003E\n\u003Cli\u003EApplying configured authentication policies.\u003C/li\u003E\n\u003Cli\u003EExposing the supported authentication endpoints.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUAuthHub is a hosting role, not an authentication mode.\u003C/p\u003E\n\u003Cp\u003EA single UAuthHub can support multiple client environments and authentication strategies, subject to its configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022centralized-hosting\u0022\u003ECentralized hosting\u003C/h3\u003E\n\u003Cp\u003EIn a centralized deployment, several applications can use the same UAuthHub.\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\u003EBlazor WASM Application \u2500\u2500\u2510\n \u2502\nMAUI Application \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u25BA UAuthHub\n \u2502 \u2502\nWeb Application \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n \u25BC\n Authentication Services\n \u2502\n \u25BC\n Authentication Storage\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows applications to share authentication infrastructure without requiring each application to implement its own credential management and authentication lifecycle.\u003C/p\u003E\n\u003Cp\u003EHowever, centralized authentication hosting does not mean that all protected resource requests must be routed through UAuthHub.\u003C/p\u003E\n\u003Cp\u003EThe resource authentication strategy remains mode-dependent.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-applications-and-apis\u0022\u003EResource applications and APIs\u003C/h2\u003E\n\u003Cp\u003EA resource application is an application or service that exposes functionality requiring authentication and authorization.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA protected Web API.\u003C/li\u003E\n\u003Cli\u003EAn administrative web application.\u003C/li\u003E\n\u003Cli\u003EA business application backend.\u003C/li\u003E\n\u003Cli\u003EAn independently deployed service.\u003C/li\u003E\n\u003Cli\u003EA server-rendered application with protected operations.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA resource application must establish the caller\u0027s authenticated identity before allowing access to protected functionality.\u003C/p\u003E\n\u003Cp\u003EIt must then enforce the relevant authorization policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-validation-depends-on-the-mode\u0022\u003EAuthentication validation depends on the mode\u003C/h3\u003E\n\u003Cp\u003EIn PureOpaque and Hybrid, authentication requires server-side session authority.\u003C/p\u003E\n\u003Cp\u003EThe resource authentication path must therefore have access to the infrastructure necessary to enforce the relevant session validation requirements.\u003C/p\u003E\n\u003Cp\u003EIn SemiHybrid and PureJwt, normal access-token authentication uses JWT validation without a mandatory session lookup.\u003C/p\u003E\n\u003Cp\u003EThis can allow a resource API to validate access tokens locally when it has the required verification keys and token-validation configuration.\u003C/p\u003E\n\u003Cp\u003EThe authentication mode therefore affects the resource application\u0027s infrastructure dependencies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-remains-local-to-the-protected-operation\u0022\u003EAuthorization remains local to the protected operation\u003C/h3\u003E\n\u003Cp\u003EAuthentication establishes who the caller is.\u003C/p\u003E\n\u003Cp\u003EAuthorization determines whether that caller may perform the requested operation.\u003C/p\u003E\n\u003Cp\u003EA valid authentication credential does not automatically grant permission to every resource.\u003C/p\u003E\n\u003Cp\u003EResource applications must continue to enforce their authorization requirements through the applicable framework and application policies.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s authentication integration should preserve these boundaries rather than replacing resource authorization with client-side assumptions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022deployment-topology\u0022\u003EDeployment topology\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates architectural responsibilities from physical deployment boundaries.\u003C/p\u003E\n\u003Cp\u003EThis means the same authentication architecture can support different deployment topologies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022integrated-deployment\u0022\u003EIntegrated deployment\u003C/h3\u003E\n\u003Cp\u003EAn integrated deployment hosts authentication infrastructure and application functionality within the same server application.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\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\u003EASP.NET Core Application\n \u2502\n \u251C\u2500\u2500 UltimateAuth Server\n \u2502\n \u251C\u2500\u2500 Authentication Services\n \u2502\n \u251C\u2500\u2500 Application Services\n \u2502\n \u2514\u2500\u2500 Protected Endpoints\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn this model, authentication services and protected application functionality may share the same dependency injection container and process.\u003C/p\u003E\n\u003Cp\u003EWhere supported, internal operations can interact with server-side services directly without requiring an HTTP call to the same application.\u003C/p\u003E\n\u003Cp\u003EThis can simplify deployment and avoid unnecessary network communication.\u003C/p\u003E\n\u003Cp\u003EHowever, direct service access must still respect the required authentication and authorization boundaries.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022distributed-deployment\u0022\u003EDistributed deployment\u003C/h3\u003E\n\u003Cp\u003EA distributed deployment separates the authentication host from one or more resource applications.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E UAuthHub\n \u2502\n \u2502 Authentication\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502\n Web Application Mobile Client\n \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u25BC\n Resource API\n \u2502\n \u25BC\n Application Services\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn this model, clients communicate with UAuthHub for the applicable authentication operations and access resource APIs using the supported credentials.\u003C/p\u003E\n\u003Cp\u003EResource APIs validate those credentials according to the configured authentication mode.\u003C/p\u003E\n\u003Cp\u003EThis architecture supports independently deployed applications while maintaining shared authentication policies and identity infrastructure.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-a-topology\u0022\u003EChoosing a topology\u003C/h3\u003E\n\u003Cp\u003EIntegrated and distributed deployments are not different authentication modes.\u003C/p\u003E\n\u003Cp\u003EThey describe where application components execute and how they communicate.\u003C/p\u003E\n\u003Cp\u003EThe appropriate topology depends on:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EApplication boundaries.\u003C/li\u003E\n\u003Cli\u003EDeployment and operational requirements.\u003C/li\u003E\n\u003Cli\u003ENetwork and infrastructure constraints.\u003C/li\u003E\n\u003Cli\u003EAuthentication mode dependencies.\u003C/li\u003E\n\u003Cli\u003EService ownership and scaling requirements.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA distributed deployment is not inherently more secure or more scalable than an integrated deployment.\u003C/p\u003E\n\u003Cp\u003EBoth require correct authentication validation, authorization enforcement, and infrastructure configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-architecture\u0022\u003EBlazor Server architecture\u003C/h2\u003E\n\u003Cp\u003EBlazor Server has a distinct execution model.\u003C/p\u003E\n\u003Cp\u003EThe browser maintains an interactive connection to an application whose component logic executes on the server.\u003C/p\u003E\n\u003Cp\u003EThis makes it possible to keep many application operations and authentication-related services within the server environment.\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\u003EBrowser\n \u2502\n \u2502 Interactive Connection\n \u25BC\nBlazor Server Application\n \u2502\n \u251C\u2500\u2500 UI Components\n \u2502\n \u251C\u2500\u2500 Application Services\n \u2502\n \u251C\u2500\u2500 UltimateAuth Integration\n \u2502\n \u2514\u2500\u2500 Protected Operations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EPureOpaque is a natural default for this environment because it provides server-controlled authentication without requiring a separate access/refresh token lifecycle.\u003C/p\u003E\n\u003Cp\u003EHowever, the hosting model does not require PureOpaque exclusively.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022direct-service-access\u0022\u003EDirect service access\u003C/h3\u003E\n\u003Cp\u003EWhen authentication infrastructure and application services are hosted in the same process, server-side code may be able to use registered services directly.\u003C/p\u003E\n\u003Cp\u003EThis can avoid unnecessary HTTP requests between components of the same application.\u003C/p\u003E\n\u003Cp\u003EThe distinction is between an application service call and a remote API request.\u003C/p\u003E\n\u003Cp\u003EA direct service call must not bypass security checks merely because it avoids HTTP.\u003C/p\u003E\n\u003Cp\u003EAuthorization requirements must still be enforced at the appropriate trusted boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022long-lived-interactive-connections\u0022\u003ELong-lived interactive connections\u003C/h3\u003E\n\u003Cp\u003EBlazor Server introduces an additional consideration: authentication state may change while an interactive circuit remains active.\u003C/p\u003E\n\u003Cp\u003EA session can expire or be revoked after the circuit was established.\u003C/p\u003E\n\u003Cp\u003EApplications must therefore account for authentication-state revalidation and authorization of sensitive operations.\u003C/p\u003E\n\u003Cp\u003EAn authenticated circuit must not be treated as permanently authorized solely because its initial connection succeeded.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-webassembly-architecture\u0022\u003EBlazor WebAssembly architecture\u003C/h2\u003E\n\u003Cp\u003EBlazor WebAssembly executes application code in the browser.\u003C/p\u003E\n\u003Cp\u003EUnlike Blazor Server, it cannot directly access private server-side authentication services.\u003C/p\u003E\n\u003Cp\u003EAuthentication and protected application operations therefore cross an HTTP boundary.\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\u003EBrowser\n \u2502\n \u2514\u2500\u2500 Blazor WebAssembly\n \u2502\n \u251C\u2500\u2500 UltimateAuth Client\n \u2502 \u2502\n \u2502 \u25BC\n \u2502 UAuthHub\n \u2502\n \u2514\u2500\u2500 Application API Client\n \u2502\n \u25BC\n Resource API\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EHybrid is a suitable authentication strategy for applications that need separate access and refresh tokens while retaining server-authoritative session validation.\u003C/p\u003E\n\u003Cp\u003EHowever, a browser application can also use other modes when the corresponding transport and security architecture supports them.\u003C/p\u003E\n\u003Cp\u003EFor example, a backend-for-frontend architecture may use secure session cookies rather than exposing bearer tokens to browser application code.\u003C/p\u003E\n\u003Cp\u003EThe runtime determines which integration techniques are available; the authentication mode determines the security and lifecycle model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022native-application-architecture\u0022\u003ENative application architecture\u003C/h2\u003E\n\u003Cp\u003ENative applications, including .NET MAUI applications, execute outside the browser environment.\u003C/p\u003E\n\u003Cp\u003EThey generally communicate with authentication hosts and resource APIs through HTTP.\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\u003EMAUI Application\n \u2502\n \u251C\u2500\u2500 UltimateAuth Client\n \u2502 \u2502\n \u2502 \u25BC\n \u2502 UAuthHub\n \u2502\n \u251C\u2500\u2500 Secure Credential Storage\n \u2502\n \u2514\u2500\u2500 Resource API Client\n \u2502\n \u25BC\n Resource API\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ENative applications can use platform-specific secure storage for sensitive credentials.\u003C/p\u003E\n\u003Cp\u003ETheir credential delivery and persistence requirements differ from browser applications.\u003C/p\u003E\n\u003Cp\u003EHybrid can provide a suitable model for applications requiring refresh-token continuity and centralized session control.\u003C/p\u003E\n\u003Cp\u003EThe choice remains configurable according to the supported authentication modes and application requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022shared-components-across-runtimes\u0022\u003EShared components across runtimes\u003C/h2\u003E\n\u003Cp\u003EA unified authentication architecture should minimize unnecessary duplication between client environments.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth separates shared authentication behavior from runtime-specific integration.\u003C/p\u003E\n\u003Cp\u003EFor example, Blazor Server and Blazor WebAssembly applications may share:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication-related UI components.\u003C/li\u003E\n\u003Cli\u003ELogin and logout interaction patterns.\u003C/li\u003E\n\u003Cli\u003ERequest and response contracts.\u003C/li\u003E\n\u003Cli\u003EApplication-facing client abstractions.\u003C/li\u003E\n\u003Cli\u003EAuthentication-state presentation logic.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAt the same time, they may require different implementations for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredential storage.\u003C/li\u003E\n\u003Cli\u003EHTTP communication.\u003C/li\u003E\n\u003Cli\u003EBrowser interaction.\u003C/li\u003E\n\u003Cli\u003EServer-side service access.\u003C/li\u003E\n\u003Cli\u003EAuthentication-state synchronization.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe goal is not to force every runtime to execute identical code.\u003C/p\u003E\n\u003Cp\u003EThe goal is to share behavior and contracts where appropriate while preserving each runtime\u0027s security and execution requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-boundaries-and-direct-execution\u0022\u003EHTTP boundaries and direct execution\u003C/h2\u003E\n\u003Cp\u003EAn important architectural distinction is whether an operation crosses an HTTP boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remote-execution\u0022\u003ERemote execution\u003C/h3\u003E\n\u003Cp\u003EA remote client communicates with an authentication host or resource API through HTTP.\u003C/p\u003E\n\u003Cp\u003EThe server must independently validate the request, credentials, and authorization requirements.\u003C/p\u003E\n\u003Cp\u003EClient-side checks are not sufficient.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022in-process-execution\u0022\u003EIn-process execution\u003C/h3\u003E\n\u003Cp\u003EAn application may host UltimateAuth services and protected application services within the same process.\u003C/p\u003E\n\u003Cp\u003EIn that case, some operations may use direct service calls instead of HTTP.\u003C/p\u003E\n\u003Cp\u003EThis can reduce serialization and network overhead.\u003C/p\u003E\n\u003Cp\u003EHowever, HTTP middleware and endpoint authorization attributes do not automatically execute during arbitrary direct service calls.\u003C/p\u003E\n\u003Cp\u003EThe application must ensure that equivalent security requirements are enforced through the appropriate service or authorization boundary.\u003C/p\u003E\n\u003Cp\u003EDirect execution is an optimization and integration choice, not permission to bypass authentication or authorization.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022infrastructure-and-persistence\u0022\u003EInfrastructure and persistence\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses abstractions to separate authentication behavior from infrastructure implementations.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured mode and services, the system may require storage for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUser and credential information.\u003C/li\u003E\n\u003Cli\u003EAuthentication security state.\u003C/li\u003E\n\u003Cli\u003ESession lineage.\u003C/li\u003E\n\u003Cli\u003ERefresh-token lifecycle.\u003C/li\u003E\n\u003Cli\u003EDevice-associated authentication state.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese dependencies vary by authentication mode.\u003C/p\u003E\n\u003Cp\u003EFor example, PureOpaque and Hybrid require session infrastructure for authentication.\u003C/p\u003E\n\u003Cp\u003ESemiHybrid requires session infrastructure for lifecycle management but not for normal JWT validation.\u003C/p\u003E\n\u003Cp\u003EPureJwt does not require server-side session infrastructure. Optional refresh-token functionality may introduce independent renewal-state requirements.\u003C/p\u003E\n\u003Cp\u003EThe architecture must preserve these distinctions.\u003C/p\u003E\n\u003Cp\u003EInfrastructure abstractions allow the authentication services to remain independent of a particular persistence implementation, while the selected mode determines which capabilities are required.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-boundaries\u0022\u003EArchitectural boundaries\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s runtime architecture depends on several boundaries remaining explicit.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-and-server\u0022\u003EClient and server\u003C/h3\u003E\n\u003Cp\u003EClients initiate operations and present credentials.\u003C/p\u003E\n\u003Cp\u003EServers establish trusted authentication state and enforce security requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-and-authorization\u0022\u003EAuthentication and authorization\u003C/h3\u003E\n\u003Cp\u003EAuthentication determines identity.\u003C/p\u003E\n\u003Cp\u003EAuthorization determines whether an operation is permitted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hosting-and-authentication-mode\u0022\u003EHosting and authentication mode\u003C/h3\u003E\n\u003Cp\u003EUAuthHub and resource applications describe hosting responsibilities.\u003C/p\u003E\n\u003Cp\u003EPureOpaque, Hybrid, SemiHybrid, and PureJwt describe authentication strategies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022shared-contracts-and-runtime-integration\u0022\u003EShared contracts and runtime integration\u003C/h3\u003E\n\u003Cp\u003EShared contracts define consistent behavior.\u003C/p\u003E\n\u003Cp\u003ERuntime integrations adapt that behavior to browser, native, and server-side environments.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022orchestration-and-infrastructure\u0022\u003EOrchestration and infrastructure\u003C/h3\u003E\n\u003Cp\u003EOrchestration coordinates authentication operations.\u003C/p\u003E\n\u003Cp\u003EInfrastructure services provide persistence, token operations, and other required capabilities.\u003C/p\u003E\n\u003Cp\u003EThese boundaries allow UltimateAuth to evolve without requiring every client integration or authentication mode to duplicate the complete authentication implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s runtime architecture separates authentication responsibilities from application hosting and client execution environments.\u003C/p\u003E\n\u003Cp\u003EIts main architectural components are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ECore\u003C/strong\u003E, which defines shared contracts and authentication foundations.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EServer\u003C/strong\u003E, which implements authentication operations and lifecycle management.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClient\u003C/strong\u003E, which provides application-facing authentication integration.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUAuthHub\u003C/strong\u003E, which hosts the authentication infrastructure.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EResource applications\u003C/strong\u003E, which authenticate callers and enforce authorization.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese components can operate within an integrated application or across distributed services.\u003C/p\u003E\n\u003Cp\u003EDifferent client runtimes can share authentication contracts and behavior while using integration mechanisms appropriate to their environments.\u003C/p\u003E\n\u003Cp\u003EAuthentication modes determine the required validation and state dependencies.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe architecture is unified because its security contracts and responsibilities remain consistent\u2014not because every runtime, mode, or deployment follows an identical execution path.\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./request-lifecycle.md\u0022\u003ERequest Lifecycle\u003C/a\u003E to understand how an incoming request moves through authentication validation, identity establishment, and authorization before reaching a protected operation.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth is designed to provide a unified authentication architecture across different .NET application environments.\u003C/p\u003E\n\u003Cp\u003EA Blazor Server application, a standalone Blazor WebAssembly application, a native MAUI application, and a resource API may have different execution models, but they should not require entirely separate authentication systems.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth addresses this through a separation between \u003Cstrong\u003Eauthentication infrastructure, client integration, hosting, and protected resources\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThis separation allows authentication behavior to remain consistent while adapting to the capabilities and responsibilities of each runtime.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-overview\u0022\u003EArchitectural overview\u003C/h2\u003E\n\u003Cp\u003EAt a high level, UltimateAuth consists of several cooperating layers:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ECore:\u003C/strong\u003E Shared authentication contracts, models, and foundational abstractions.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EServer:\u003C/strong\u003E Authentication operations, security decisions, credential management, and persistence coordination.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClient:\u003C/strong\u003E Client-side integration, authentication requests, and credential lifecycle handling.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUAuthHub:\u003C/strong\u003E The application hosting UltimateAuth\u0027s authentication endpoints and services.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EResource applications:\u003C/strong\u003E Applications and APIs that consume authenticated identity and enforce authorization.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese are architectural responsibilities, not necessarily separate processes or deployments.\u003C/p\u003E\n\u003Cp\u003EA single application may host multiple responsibilities. Alternatively, they can be distributed across independently deployed services.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Application Clients\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502 \u2502\n Blazor WASM MAUI Other Clients\n \u2502 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u2502 HTTP\n \u25BC\n UAuthHub\n \u2502\n UltimateAuth Server\n \u2502\n Authentication Services\n \u2502\n Stores / Providers\n \u2502\n \u25BC\n Persistent State\n\n\n Protected Resources\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502 \u2502\n Resource API Web App Other Services\n \u2502 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n Authentication Validation\n \u2502\n Authorization\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis diagram illustrates the separation of responsibilities.\u003C/p\u003E\n\u003Cp\u003EIt does not imply that every authenticated resource request must pass through UAuthHub.\u003C/p\u003E\n\u003Cp\u003EWhether a protected resource needs to consult server-side authentication infrastructure depends on the effective authentication mode and its validation requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022core-shared-authentication-foundations\u0022\u003ECore: shared authentication foundations\u003C/h2\u003E\n\u003Cp\u003EThe Core layer defines the common concepts used throughout UltimateAuth.\u003C/p\u003E\n\u003Cp\u003EIts responsibility is to provide stable contracts and abstractions without depending on a particular client runtime or application host.\u003C/p\u003E\n\u003Cp\u003EThese foundations include concepts such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication modes.\u003C/li\u003E\n\u003Cli\u003EAuthentication and security contexts.\u003C/li\u003E\n\u003Cli\u003EAuthentication results and contracts.\u003C/li\u003E\n\u003Cli\u003EDomain models and shared abstractions.\u003C/li\u003E\n\u003Cli\u003ESecurity-related options and definitions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe Core layer provides the common vocabulary used by client integrations, server services, and other framework components.\u003C/p\u003E\n\u003Cp\u003EIt should not contain assumptions that authentication always occurs through an HTTP endpoint, a browser cookie, or a particular hosting model.\u003C/p\u003E\n\u003Cp\u003EThis separation is important because UltimateAuth supports both stateful and stateless authentication strategies.\u003C/p\u003E\n\u003Cp\u003EA shared authentication contract must not implicitly require session persistence when the selected mode does not use sessions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-authentication-execution-and-lifecycle\u0022\u003EServer: authentication execution and lifecycle\u003C/h2\u003E\n\u003Cp\u003EThe Server layer implements the server-side authentication capabilities.\u003C/p\u003E\n\u003Cp\u003EIt coordinates the operations required to establish, continue, validate, and terminate authentication according to the effective mode and security policies.\u003C/p\u003E\n\u003Cp\u003EIts responsibilities include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication flow execution.\u003C/li\u003E\n\u003Cli\u003ECredential validation and management.\u003C/li\u003E\n\u003Cli\u003ESession and token lifecycle operations.\u003C/li\u003E\n\u003Cli\u003EAuthentication security decisions.\u003C/li\u003E\n\u003Cli\u003ECoordination with persistence providers.\u003C/li\u003E\n\u003Cli\u003ECredential issuance and validation.\u003C/li\u003E\n\u003Cli\u003EIntegration with server-side authorization infrastructure.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe Server layer contains multiple cooperating services rather than one universal authentication engine responsible for every decision.\u003C/p\u003E\n\u003Cp\u003EFor example, session lifecycle operations, credential verification, and authorization may follow different execution paths while sharing common security contracts.\u003C/p\u003E\n\u003Cp\u003EThe architecture separates orchestration from the underlying services that perform domain-specific work.\u003C/p\u003E\n\u003Cp\u003EFor the detailed execution pipeline, 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=\u0022client-runtime-integration\u0022\u003EClient: runtime integration\u003C/h2\u003E\n\u003Cp\u003EThe Client layer provides the integration between an application and UltimateAuth\u0027s authentication services.\u003C/p\u003E\n\u003Cp\u003EIts purpose is to reduce the amount of authentication infrastructure that application developers need to implement themselves.\u003C/p\u003E\n\u003Cp\u003EDepending on the supported runtime and configuration, client integration may handle:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInitiating authentication flows.\u003C/li\u003E\n\u003Cli\u003ECommunicating with UAuthHub.\u003C/li\u003E\n\u003Cli\u003EProcessing authentication outcomes.\u003C/li\u003E\n\u003Cli\u003EManaging supported credential delivery mechanisms.\u003C/li\u003E\n\u003Cli\u003ECoordinating authentication state with the application.\u003C/li\u003E\n\u003Cli\u003EIntegrating authenticated requests with protected resources.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe client SDK does not replace server-side authentication validation.\u003C/p\u003E\n\u003Cp\u003EIt provides an application-facing interface to the authentication infrastructure.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-integration-is-runtime-aware\u0022\u003EClient integration is runtime-aware\u003C/h3\u003E\n\u003Cp\u003EDifferent client runtimes have different capabilities.\u003C/p\u003E\n\u003Cp\u003EA browser-based WebAssembly application executes within browser security restrictions.\u003C/p\u003E\n\u003Cp\u003EA native MAUI application has access to platform-specific capabilities.\u003C/p\u003E\n\u003Cp\u003EA Blazor Server application executes application logic on the server while maintaining an interactive connection with the browser.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth accounts for these differences through runtime-specific integrations and Client Profiles.\u003C/p\u003E\n\u003Cp\u003EThe authentication model remains shared even when the implementation of credential transport or application state differs.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/client-profiles\u0022\u003EClient Profiles\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022uauthhub-the-authentication-host\u0022\u003EUAuthHub: the authentication host\u003C/h2\u003E\n\u003Cp\u003EUAuthHub is the application hosting UltimateAuth\u0027s server-side authentication infrastructure.\u003C/p\u003E\n\u003Cp\u003EIt provides the environment in which authentication endpoints and the required server services are available.\u003C/p\u003E\n\u003Cp\u003EDepending on the deployment, UAuthHub can be responsible for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAccepting authentication requests.\u003C/li\u003E\n\u003Cli\u003EResolving the applicable tenant and authentication context.\u003C/li\u003E\n\u003Cli\u003EExecuting authentication flows.\u003C/li\u003E\n\u003Cli\u003EManaging credentials and sessions.\u003C/li\u003E\n\u003Cli\u003EIssuing authentication credentials.\u003C/li\u003E\n\u003Cli\u003EApplying configured authentication policies.\u003C/li\u003E\n\u003Cli\u003EExposing the supported authentication endpoints.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUAuthHub is a hosting role, not an authentication mode.\u003C/p\u003E\n\u003Cp\u003EA single UAuthHub can support multiple client environments and authentication strategies, subject to its configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022centralized-hosting\u0022\u003ECentralized hosting\u003C/h3\u003E\n\u003Cp\u003EIn a centralized deployment, several applications can use the same UAuthHub.\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\u003EBlazor WASM Application \u2500\u2500\u2510\n \u2502\nMAUI Application \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u25BA UAuthHub\n \u2502 \u2502\nWeb Application \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n \u25BC\n Authentication Services\n \u2502\n \u25BC\n Authentication Storage\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows applications to share authentication infrastructure without requiring each application to implement its own credential management and authentication lifecycle.\u003C/p\u003E\n\u003Cp\u003EHowever, centralized authentication hosting does not mean that all protected resource requests must be routed through UAuthHub.\u003C/p\u003E\n\u003Cp\u003EThe resource authentication strategy remains mode-dependent.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-applications-and-apis\u0022\u003EResource applications and APIs\u003C/h2\u003E\n\u003Cp\u003EA resource application is an application or service that exposes functionality requiring authentication and authorization.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA protected Web API.\u003C/li\u003E\n\u003Cli\u003EAn administrative web application.\u003C/li\u003E\n\u003Cli\u003EA business application backend.\u003C/li\u003E\n\u003Cli\u003EAn independently deployed service.\u003C/li\u003E\n\u003Cli\u003EA server-rendered application with protected operations.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA resource application must establish the caller\u0027s authenticated identity before allowing access to protected functionality.\u003C/p\u003E\n\u003Cp\u003EIt must then enforce the relevant authorization policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-validation-depends-on-the-mode\u0022\u003EAuthentication validation depends on the mode\u003C/h3\u003E\n\u003Cp\u003EIn PureOpaque and Hybrid, authentication requires server-side session authority.\u003C/p\u003E\n\u003Cp\u003EThe resource authentication path must therefore have access to the infrastructure necessary to enforce the relevant session validation requirements.\u003C/p\u003E\n\u003Cp\u003EIn SemiHybrid and PureJwt, normal access-token authentication uses JWT validation without a mandatory session lookup.\u003C/p\u003E\n\u003Cp\u003EThis can allow a resource API to validate access tokens locally when it has the required verification keys and token-validation configuration.\u003C/p\u003E\n\u003Cp\u003EThe authentication mode therefore affects the resource application\u0027s infrastructure dependencies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-remains-local-to-the-protected-operation\u0022\u003EAuthorization remains local to the protected operation\u003C/h3\u003E\n\u003Cp\u003EAuthentication establishes who the caller is.\u003C/p\u003E\n\u003Cp\u003EAuthorization determines whether that caller may perform the requested operation.\u003C/p\u003E\n\u003Cp\u003EA valid authentication credential does not automatically grant permission to every resource.\u003C/p\u003E\n\u003Cp\u003EResource applications must continue to enforce their authorization requirements through the applicable framework and application policies.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s authentication integration should preserve these boundaries rather than replacing resource authorization with client-side assumptions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022deployment-topology\u0022\u003EDeployment topology\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates architectural responsibilities from physical deployment boundaries.\u003C/p\u003E\n\u003Cp\u003EThis means the same authentication architecture can support different deployment topologies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022integrated-deployment\u0022\u003EIntegrated deployment\u003C/h3\u003E\n\u003Cp\u003EAn integrated deployment hosts authentication infrastructure and application functionality within the same server application.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\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\u003EASP.NET Core Application\n \u2502\n \u251C\u2500\u2500 UltimateAuth Server\n \u2502\n \u251C\u2500\u2500 Authentication Services\n \u2502\n \u251C\u2500\u2500 Application Services\n \u2502\n \u2514\u2500\u2500 Protected Endpoints\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn this model, authentication services and protected application functionality may share the same dependency injection container and process.\u003C/p\u003E\n\u003Cp\u003EWhere supported, internal operations can interact with server-side services directly without requiring an HTTP call to the same application.\u003C/p\u003E\n\u003Cp\u003EThis can simplify deployment and avoid unnecessary network communication.\u003C/p\u003E\n\u003Cp\u003EHowever, direct service access must still respect the required authentication and authorization boundaries.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022distributed-deployment\u0022\u003EDistributed deployment\u003C/h3\u003E\n\u003Cp\u003EA distributed deployment separates the authentication host from one or more resource applications.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E UAuthHub\n \u2502\n \u2502 Authentication\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502\n Web Application Mobile Client\n \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u25BC\n Resource API\n \u2502\n \u25BC\n Application Services\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn this model, clients communicate with UAuthHub for the applicable authentication operations and access resource APIs using the supported credentials.\u003C/p\u003E\n\u003Cp\u003EResource APIs validate those credentials according to the configured authentication mode.\u003C/p\u003E\n\u003Cp\u003EThis architecture supports independently deployed applications while maintaining shared authentication policies and identity infrastructure.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-a-topology\u0022\u003EChoosing a topology\u003C/h3\u003E\n\u003Cp\u003EIntegrated and distributed deployments are not different authentication modes.\u003C/p\u003E\n\u003Cp\u003EThey describe where application components execute and how they communicate.\u003C/p\u003E\n\u003Cp\u003EThe appropriate topology depends on:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EApplication boundaries.\u003C/li\u003E\n\u003Cli\u003EDeployment and operational requirements.\u003C/li\u003E\n\u003Cli\u003ENetwork and infrastructure constraints.\u003C/li\u003E\n\u003Cli\u003EAuthentication mode dependencies.\u003C/li\u003E\n\u003Cli\u003EService ownership and scaling requirements.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA distributed deployment is not inherently more secure or more scalable than an integrated deployment.\u003C/p\u003E\n\u003Cp\u003EBoth require correct authentication validation, authorization enforcement, and infrastructure configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-architecture\u0022\u003EBlazor Server architecture\u003C/h2\u003E\n\u003Cp\u003EBlazor Server has a distinct execution model.\u003C/p\u003E\n\u003Cp\u003EThe browser maintains an interactive connection to an application whose component logic executes on the server.\u003C/p\u003E\n\u003Cp\u003EThis makes it possible to keep many application operations and authentication-related services within the server environment.\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\u003EBrowser\n \u2502\n \u2502 Interactive Connection\n \u25BC\nBlazor Server Application\n \u2502\n \u251C\u2500\u2500 UI Components\n \u2502\n \u251C\u2500\u2500 Application Services\n \u2502\n \u251C\u2500\u2500 UltimateAuth Integration\n \u2502\n \u2514\u2500\u2500 Protected Operations\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EPureOpaque is a natural default for this environment because it provides server-controlled authentication without requiring a separate access/refresh token lifecycle.\u003C/p\u003E\n\u003Cp\u003EHowever, the hosting model does not require PureOpaque exclusively.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022direct-service-access\u0022\u003EDirect service access\u003C/h3\u003E\n\u003Cp\u003EWhen authentication infrastructure and application services are hosted in the same process, server-side code may be able to use registered services directly.\u003C/p\u003E\n\u003Cp\u003EThis can avoid unnecessary HTTP requests between components of the same application.\u003C/p\u003E\n\u003Cp\u003EThe distinction is between an application service call and a remote API request.\u003C/p\u003E\n\u003Cp\u003EA direct service call must not bypass security checks merely because it avoids HTTP.\u003C/p\u003E\n\u003Cp\u003EAuthorization requirements must still be enforced at the appropriate trusted boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022long-lived-interactive-connections\u0022\u003ELong-lived interactive connections\u003C/h3\u003E\n\u003Cp\u003EBlazor Server introduces an additional consideration: authentication state may change while an interactive circuit remains active.\u003C/p\u003E\n\u003Cp\u003EA session can expire or be revoked after the circuit was established.\u003C/p\u003E\n\u003Cp\u003EApplications must therefore account for authentication-state revalidation and authorization of sensitive operations.\u003C/p\u003E\n\u003Cp\u003EAn authenticated circuit must not be treated as permanently authorized solely because its initial connection succeeded.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-webassembly-architecture\u0022\u003EBlazor WebAssembly architecture\u003C/h2\u003E\n\u003Cp\u003EBlazor WebAssembly executes application code in the browser.\u003C/p\u003E\n\u003Cp\u003EUnlike Blazor Server, it cannot directly access private server-side authentication services.\u003C/p\u003E\n\u003Cp\u003EAuthentication and protected application operations therefore cross an HTTP boundary.\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\u003EBrowser\n \u2502\n \u2514\u2500\u2500 Blazor WebAssembly\n \u2502\n \u251C\u2500\u2500 UltimateAuth Client\n \u2502 \u2502\n \u2502 \u25BC\n \u2502 UAuthHub\n \u2502\n \u2514\u2500\u2500 Application API Client\n \u2502\n \u25BC\n Resource API\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EHybrid is a suitable authentication strategy for applications that need separate access and refresh tokens while retaining server-authoritative session validation.\u003C/p\u003E\n\u003Cp\u003EHowever, a browser application can also use other modes when the corresponding transport and security architecture supports them.\u003C/p\u003E\n\u003Cp\u003EFor example, a backend-for-frontend architecture may use secure session cookies rather than exposing bearer tokens to browser application code.\u003C/p\u003E\n\u003Cp\u003EThe runtime determines which integration techniques are available; the authentication mode determines the security and lifecycle model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022native-application-architecture\u0022\u003ENative application architecture\u003C/h2\u003E\n\u003Cp\u003ENative applications, including .NET MAUI applications, execute outside the browser environment.\u003C/p\u003E\n\u003Cp\u003EThey generally communicate with authentication hosts and resource APIs through HTTP.\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\u003EMAUI Application\n \u2502\n \u251C\u2500\u2500 UltimateAuth Client\n \u2502 \u2502\n \u2502 \u25BC\n \u2502 UAuthHub\n \u2502\n \u251C\u2500\u2500 Secure Credential Storage\n \u2502\n \u2514\u2500\u2500 Resource API Client\n \u2502\n \u25BC\n Resource API\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ENative applications can use platform-specific secure storage for sensitive credentials.\u003C/p\u003E\n\u003Cp\u003ETheir credential delivery and persistence requirements differ from browser applications.\u003C/p\u003E\n\u003Cp\u003EHybrid can provide a suitable model for applications requiring refresh-token continuity and centralized session control.\u003C/p\u003E\n\u003Cp\u003EThe choice remains configurable according to the supported authentication modes and application requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022shared-components-across-runtimes\u0022\u003EShared components across runtimes\u003C/h2\u003E\n\u003Cp\u003EA unified authentication architecture should minimize unnecessary duplication between client environments.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth separates shared authentication behavior from runtime-specific integration.\u003C/p\u003E\n\u003Cp\u003EFor example, Blazor Server and Blazor WebAssembly applications may share:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication-related UI components.\u003C/li\u003E\n\u003Cli\u003ELogin and logout interaction patterns.\u003C/li\u003E\n\u003Cli\u003ERequest and response contracts.\u003C/li\u003E\n\u003Cli\u003EApplication-facing client abstractions.\u003C/li\u003E\n\u003Cli\u003EAuthentication-state presentation logic.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAt the same time, they may require different implementations for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredential storage.\u003C/li\u003E\n\u003Cli\u003EHTTP communication.\u003C/li\u003E\n\u003Cli\u003EBrowser interaction.\u003C/li\u003E\n\u003Cli\u003EServer-side service access.\u003C/li\u003E\n\u003Cli\u003EAuthentication-state synchronization.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe goal is not to force every runtime to execute identical code.\u003C/p\u003E\n\u003Cp\u003EThe goal is to share behavior and contracts where appropriate while preserving each runtime\u0027s security and execution requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-boundaries-and-direct-execution\u0022\u003EHTTP boundaries and direct execution\u003C/h2\u003E\n\u003Cp\u003EAn important architectural distinction is whether an operation crosses an HTTP boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022remote-execution\u0022\u003ERemote execution\u003C/h3\u003E\n\u003Cp\u003EA remote client communicates with an authentication host or resource API through HTTP.\u003C/p\u003E\n\u003Cp\u003EThe server must independently validate the request, credentials, and authorization requirements.\u003C/p\u003E\n\u003Cp\u003EClient-side checks are not sufficient.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022in-process-execution\u0022\u003EIn-process execution\u003C/h3\u003E\n\u003Cp\u003EAn application may host UltimateAuth services and protected application services within the same process.\u003C/p\u003E\n\u003Cp\u003EIn that case, some operations may use direct service calls instead of HTTP.\u003C/p\u003E\n\u003Cp\u003EThis can reduce serialization and network overhead.\u003C/p\u003E\n\u003Cp\u003EHowever, HTTP middleware and endpoint authorization attributes do not automatically execute during arbitrary direct service calls.\u003C/p\u003E\n\u003Cp\u003EThe application must ensure that equivalent security requirements are enforced through the appropriate service or authorization boundary.\u003C/p\u003E\n\u003Cp\u003EDirect execution is an optimization and integration choice, not permission to bypass authentication or authorization.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022infrastructure-and-persistence\u0022\u003EInfrastructure and persistence\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses abstractions to separate authentication behavior from infrastructure implementations.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured mode and services, the system may require storage for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUser and credential information.\u003C/li\u003E\n\u003Cli\u003EAuthentication security state.\u003C/li\u003E\n\u003Cli\u003ESession lineage.\u003C/li\u003E\n\u003Cli\u003ERefresh-token lifecycle.\u003C/li\u003E\n\u003Cli\u003EDevice-associated authentication state.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese dependencies vary by authentication mode.\u003C/p\u003E\n\u003Cp\u003EFor example, PureOpaque and Hybrid require session infrastructure for authentication.\u003C/p\u003E\n\u003Cp\u003ESemiHybrid requires session infrastructure for lifecycle management but not for normal JWT validation.\u003C/p\u003E\n\u003Cp\u003EPureJwt does not require server-side session infrastructure. Optional refresh-token functionality may introduce independent renewal-state requirements.\u003C/p\u003E\n\u003Cp\u003EThe architecture must preserve these distinctions.\u003C/p\u003E\n\u003Cp\u003EInfrastructure abstractions allow the authentication services to remain independent of a particular persistence implementation, while the selected mode determines which capabilities are required.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-boundaries\u0022\u003EArchitectural boundaries\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s runtime architecture depends on several boundaries remaining explicit.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-and-server\u0022\u003EClient and server\u003C/h3\u003E\n\u003Cp\u003EClients initiate operations and present credentials.\u003C/p\u003E\n\u003Cp\u003EServers establish trusted authentication state and enforce security requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-and-authorization\u0022\u003EAuthentication and authorization\u003C/h3\u003E\n\u003Cp\u003EAuthentication determines identity.\u003C/p\u003E\n\u003Cp\u003EAuthorization determines whether an operation is permitted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hosting-and-authentication-mode\u0022\u003EHosting and authentication mode\u003C/h3\u003E\n\u003Cp\u003EUAuthHub and resource applications describe hosting responsibilities.\u003C/p\u003E\n\u003Cp\u003EPureOpaque, Hybrid, SemiHybrid, and PureJwt describe authentication strategies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022shared-contracts-and-runtime-integration\u0022\u003EShared contracts and runtime integration\u003C/h3\u003E\n\u003Cp\u003EShared contracts define consistent behavior.\u003C/p\u003E\n\u003Cp\u003ERuntime integrations adapt that behavior to browser, native, and server-side environments.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022orchestration-and-infrastructure\u0022\u003EOrchestration and infrastructure\u003C/h3\u003E\n\u003Cp\u003EOrchestration coordinates authentication operations.\u003C/p\u003E\n\u003Cp\u003EInfrastructure services provide persistence, token operations, and other required capabilities.\u003C/p\u003E\n\u003Cp\u003EThese boundaries allow UltimateAuth to evolve without requiring every client integration or authentication mode to duplicate the complete authentication implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s runtime architecture separates authentication responsibilities from application hosting and client execution environments.\u003C/p\u003E\n\u003Cp\u003EIts main architectural components are:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ECore\u003C/strong\u003E, which defines shared contracts and authentication foundations.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EServer\u003C/strong\u003E, which implements authentication operations and lifecycle management.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClient\u003C/strong\u003E, which provides application-facing authentication integration.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUAuthHub\u003C/strong\u003E, which hosts the authentication infrastructure.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EResource applications\u003C/strong\u003E, which authenticate callers and enforce authorization.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese components can operate within an integrated application or across distributed services.\u003C/p\u003E\n\u003Cp\u003EDifferent client runtimes can share authentication contracts and behavior while using integration mechanisms appropriate to their environments.\u003C/p\u003E\n\u003Cp\u003EAuthentication modes determine the required validation and state dependencies.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe architecture is unified because its security contracts and responsibilities remain consistent\u2014not because every runtime, mode, or deployment follows an identical execution path.\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/request-lifecycle\u0022\u003ERequest Lifecycle\u003C/a\u003E to understand how an incoming request moves through authentication validation, identity establishment, and authorization before reaching a protected operation.\u003C/p\u003E\n", "Headings": [ { "Id": "architectural-overview", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json index 7efe0f99..16a5c545 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json @@ -1,67 +1,97 @@ { "Slug": "getting-started/index", "Title": "Getting Started", - "Html": "\n\u003Cp\u003EWelcome to \u003Cstrong\u003EUltimateAuth\u003C/strong\u003E, a unified authentication and authorization framework for modern .NET applications.\u003C/p\u003E\n\u003Cp\u003EAuthentication should not become a different system every time your application gains a new client, tenant, device, or security requirement. UltimateAuth brings these concerns into \u003Cstrong\u003Eone coherent model\u003C/strong\u003E\u2014without pretending that every application needs the same authentication strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOne framework. Multiple authentication models. A consistent security lifecycle.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-is-ultimateauth\u0022\u003EWhat is UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is a modular, flow-driven framework for building and managing authentication in .NET. It connects concepts that are often implemented separately:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ESessions, cookies and tokens\u003C/strong\u003E \u2014 stateful session management alongside access and refresh token workflows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthentication and authorization\u003C/strong\u003E \u2014 identity establishment and access decisions within a consistent architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUsers and credentials\u003C/strong\u003E \u2014 identity, identifiers, credentials, and their security state.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevices and session continuity\u003C/strong\u003E \u2014 device-aware session chains, refresh, logout, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EClients and delivery mechanisms\u003C/strong\u003E \u2014 browser, API, Blazor, and mobile scenarios with client-aware behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ETenants and isolation\u003C/strong\u003E \u2014 tenant-aware request context and authentication boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESecurity policies and execution flows\u003C/strong\u003E \u2014 explicit decisions, validation, and controlled state transitions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe goal is not to hide these concepts. It is to make them \u003Cstrong\u003Ework together predictably\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022unified-does-not-mean-one-size-fits-all\u0022\u003EUnified Does Not Mean One-Size-Fits-All\u003C/h2\u003E\n\u003Cp\u003EA server-rendered web application, a browser-based SPA, a mobile app, and a backend API do not share the same trust boundaries or credential transport requirements.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides a common foundation while allowing behavior to be shaped by the \u003Cstrong\u003Eeffective authentication mode\u003C/strong\u003E, \u003Cstrong\u003Eclient profile\u003C/strong\u003E, \u003Cstrong\u003Etenant\u003C/strong\u003E, and \u003Cstrong\u003Erequest context\u003C/strong\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcern\u003C/th\u003E\n\u003Cth\u003EWhat UltimateAuth brings together\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\u003EAuthentication models\u003C/td\u003E\n\u003Ctd\u003ESession-oriented and token-oriented workflows\u003C/td\u003E\n\u003Ctd\u003EDifferent clients can participate in one security architecture\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession lifecycle\u003C/td\u003E\n\u003Ctd\u003ERoot \u2192 Chain \u2192 Session\u003C/td\u003E\n\u003Ctd\u003EManage authentication at identity, device, and session scope\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClient environments\u003C/td\u003E\n\u003Ctd\u003EASP.NET Core, Blazor, APIs, and .NET mobile clients\u003C/td\u003E\n\u003Ctd\u003EAvoid rebuilding the core authentication model for each frontend\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMulti-tenancy\u003C/td\u003E\n\u003Ctd\u003ETenant resolution and tenant-bound context\u003C/td\u003E\n\u003Ctd\u003EKeep authentication operations within the intended tenant boundary\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredentials and identities\u003C/td\u003E\n\u003Ctd\u003EUser identifiers, credential providers, and security state\u003C/td\u003E\n\u003Ctd\u003ESeparate \u003Cem\u003Ewho the user is\u003C/em\u003E from \u003Cem\u003Ehow they prove it\u003C/em\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESecurity enforcement\u003C/td\u003E\n\u003Ctd\u003EContext, validation, authority, and orchestration\u003C/td\u003E\n\u003Ctd\u003EMake decisions explicit rather than scattering checks across endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential delivery\u003C/td\u003E\n\u003Ctd\u003EMode- and client-aware response policies\u003C/td\u003E\n\u003Ctd\u003EHandle cookies, tokens, and redirects according to the scenario\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession termination\u003C/td\u003E\n\u003Ctd\u003ELogout, targeted revocation, and device management\u003C/td\u003E\n\u003Ctd\u003ERespond proportionately to ordinary sign-out and security incidents\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EUnified\u003C/strong\u003E is therefore about a shared security model\u2014not forcing all applications to use identical cookies, tokens, or endpoints.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-is-more-than-issuing-a-token\u0022\u003EAuthentication Is More Than Issuing a Token\u003C/h2\u003E\n\u003Cp\u003EIn many applications, authentication is described as a short sequence: validate credentials, generate a token, return it to the client.\u003C/p\u003E\n\u003Cp\u003EThat is only part of the story.\u003C/p\u003E\n\u003Cp\u003EA production authentication system must also answer questions such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhich tenant does this request belong to?\u003C/li\u003E\n\u003Cli\u003EWhich user and credential are being evaluated?\u003C/li\u003E\n\u003Cli\u003EIs an existing session still valid?\u003C/li\u003E\n\u003Cli\u003EIs the request coming from a known device?\u003C/li\u003E\n\u003Cli\u003ECan access be renewed, restricted, or terminated?\u003C/li\u003E\n\u003Cli\u003EWhat should happen when a credential changes or a device is no longer trusted?\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth treats authentication as a \u003Cstrong\u003Econtrolled lifecycle\u003C/strong\u003E. Login, refresh, and logout are operations within that lifecycle\u2014not unrelated endpoints that happen to exchange tokens.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessions-are-first-class\u0022\u003ESessions Are First-Class\u003C/h2\u003E\n\u003Cp\u003EWhere session-backed modes apply, UltimateAuth models authentication through a hierarchy:\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 Global session-security boundary\n \u2514\u2500\u2500 Chain Device-associated lifecycle\n \u2514\u2500\u2500 Session Individual authentication instance\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis structure gives the system a vocabulary for different security actions. A session can be invalidated, a device-associated chain can be terminated, and a broader security boundary can be revoked.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA token is not the entire authentication model.\u003C/strong\u003E It is a credential used within that model, subject to the applicable mode and validation rules.\u003C/p\u003E\n\u003Cp\u003ENot every authentication mode has identical persistence or session behavior. The session hierarchy describes the stateful lifecycle where that model is in use.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022flow-driven-by-design\u0022\u003EFlow-Driven by Design\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth organizes authentication operations around explicit flows. A flow brings together the request context, operation-specific validation, security decisions, permitted state transitions, and response 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\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\u003ERequest\n \u2192 Tenant, session, and user resolution\n \u2192 AuthFlowContext\n \u2192 Flow-specific validation\n \u2192 Security decision and orchestration\n \u2192 State transition (when permitted)\n \u2192 Result and client-appropriate response\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is not a claim that pipelines or orchestration are new inventions. The value is in applying them \u003Cstrong\u003Econsistently across authentication operations\u003C/strong\u003E, so the rules are easier to reason about, test, and extend.\u003C/p\u003E\n\u003Cp\u003EFor the implementation model, see \u003Ca href=\u0022/docs/auth-flows/index\u0022\u003EAuth Flows\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022tenant-aware-from-the-request-boundary\u0022\u003ETenant-Aware from the Request Boundary\u003C/h2\u003E\n\u003Cp\u003EMulti-tenancy is not simply a \u003Ccode\u003ETenantId\u003C/code\u003E added to a user table. Authentication must understand \u003Cem\u003Ewhich tenant\u0027s security context\u003C/em\u003E a request is operating within.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth resolves tenant context as part of the ASP.NET Core request pipeline and carries it into the authentication flow. The flow context then combines that boundary with client settings, device information, and applicable session validation.\u003C/p\u003E\n\u003Cp\u003EThis helps teams design multi-tenant applications without treating tenant isolation as an afterthought. \u003Cstrong\u003ETenant resolution and configuration still need to be correctly integrated by the host application.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-for-different-clients-not-just-different-screens\u0022\u003EBuilt for Different Clients, Not Just Different Screens\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is designed for applications that may span several environments:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EASP.NET Core web applications\u003C/strong\u003E with server-managed authentication concerns.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EBlazor Server and Blazor WebAssembly\u003C/strong\u003E with different hosting and transport constraints.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E.NET MAUI\u003C/strong\u003E and other native clients with device-oriented authentication needs.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EHTTP APIs\u003C/strong\u003E serving browsers, applications, and other services.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EClient profiles and effective server options help adapt authentication behavior to the requesting client. Response policies determine how applicable credentials are delivered and whether a redirect belongs in the interaction.\u003C/p\u003E\n\u003Cp\u003EThe result is a common backend security vocabulary across multiple application types\u2014not a promise that every client uses the same transport or requires zero configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-is-a-lifecycle-too\u0022\u003ESecurity Is a Lifecycle, Too\u003C/h2\u003E\n\u003Cp\u003ESecurity is not finished when login succeeds. UltimateAuth\u0027s architecture accounts for what happens \u003Cem\u003Eafter\u003C/em\u003E access is established:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ESituation\u003C/th\u003E\n\u003Cth\u003ESecurity question\u003C/th\u003E\n\u003Cth\u003EUltimateAuth concept\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs in\u003C/td\u003E\n\u003Ctd\u003EShould this attempt establish authentication?\u003C/td\u003E\n\u003Ctd\u003ELogin flow and security decisions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA session needs renewal\u003C/td\u003E\n\u003Ctd\u003ECan continuity be preserved safely?\u003C/td\u003E\n\u003Ctd\u003ERefresh flow and session lifecycle\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs out\u003C/td\u003E\n\u003Ctd\u003EWhich authentication scope should end?\u003C/td\u003E\n\u003Ctd\u003ELogout\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA device is lost\u003C/td\u003E\n\u003Ctd\u003EHow can that device\u0027s access be terminated?\u003C/td\u003E\n\u003Ctd\u003EChain-level management and revocation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA credential is compromised\u003C/td\u003E\n\u003Ctd\u003EHow should existing authentication be affected?\u003C/td\u003E\n\u003Ctd\u003ECredential security state and revocation policies\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAn administrator manages access\u003C/td\u003E\n\u003Ctd\u003EIs the operation permitted for this actor and target?\u003C/td\u003E\n\u003Ctd\u003EAuthorization and access context\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA key distinction is \u003Cstrong\u003Elogout versus revoke\u003C/strong\u003E. Ordinary logout ends authentication according to its scope; revocation is an irreversible invalidation of the targeted security state. Signing in again may establish \u003Cem\u003Enew\u003C/em\u003E authentication, but it does not restore what was revoked.\u003C/p\u003E\n\u003Cp\u003EExplore the details in \u003Ca href=\u0022/docs/auth-flows/login\u0022\u003ELogin Flow\u003C/a\u003E, \u003Ca href=\u0022/docs/auth-flows/refresh\u0022\u003ERefresh Flow\u003C/a\u003E, and \u003Ca href=\u0022/docs/auth-flows/logout\u0022\u003ELogout Flow\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022modular-and-extensible\u0022\u003EModular and Extensible\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is intended to serve both straightforward applications and systems with more demanding security requirements. Its architecture separates responsibilities such as core contracts, server execution, client integration, user management, credentials, and authorization.\u003C/p\u003E\n\u003Cp\u003EThis separation makes it possible to understand and evolve individual concerns without reducing authentication to a collection of disconnected custom handlers.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EExtensible does not mean every possible flow is already implemented.\u003C/strong\u003E Available modes, integrations, and capabilities should be evaluated against the version you are using; the documentation for each feature describes its current behavior and limitations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022when-should-you-choose-ultimateauth\u0022\u003EWhen Should You Choose UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is especially relevant when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou need quickly-installed, consistent, secure and easy-to-use auth in your .NET application.\u003C/li\u003E\n\u003Cli\u003EYour .NET application has\u2014or will have\u2014\u003Cstrong\u003Emore than one client type\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003EYou need \u003Cstrong\u003Edevice-aware session management\u003C/strong\u003E, not only token generation.\u003C/li\u003E\n\u003Cli\u003EYou are designing a \u003Cstrong\u003Emulti-tenant\u003C/strong\u003E system with clear authentication boundaries.\u003C/li\u003E\n\u003Cli\u003EYou want \u003Cstrong\u003Eauthentication and authorization\u003C/strong\u003E to follow a consistent architectural model.\u003C/li\u003E\n\u003Cli\u003EYou need control over \u003Cstrong\u003Erefresh, logout, revocation, and credential lifecycle\u003C/strong\u003E.\u003C/li\u003E\n\u003Cli\u003EYou value a \u003Cstrong\u003Emodular, self-hosted, open-source\u003C/strong\u003E foundation you can understand and extend.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAlso for a small application with minimal authentication needs, UltimateAuth brings the simplest possible solution.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022open-source-first\u0022\u003EOpen Source First\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is built with a simple commitment: \u003Cstrong\u003Eself-hosted authentication should remain accessible\u003C/strong\u003E. The framework is open source, and self-hosting is intended to remain free. A hosted offering may provide an operational alternative, not a separate tier of authentication features.\u003C/p\u003E\n\u003Cp\u003EThe ambition is to build a framework that developers can adopt, inspect, contribute to, and trust\u2014not merely a service they must depend on.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022where-to-go-next\u0022\u003EWhere to Go Next\u003C/h2\u003E\n\u003Cp\u003EYou do not need to understand every internal component before getting started.\u003C/p\u003E\n\u003Cp\u003EContinue to \u003Ca href=\u0022/docs/getting-started/quickstart\u0022\u003EQuickStart\u003C/a\u003E to set up your first UltimateAuth application. Then explore \u003Ca href=\u0022/docs/auth-flows/index\u0022\u003EAuth Flows\u003C/a\u003E to understand how the pieces work together.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe idea to remember:\u003C/strong\u003E UltimateAuth unifies authentication concepts without erasing their differences. One framework, a consistent lifecycle, and room for the security model your application actually needs.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003E\u003Cstrong\u003EModern authentication and authorization for .NET \u2014 built around a unified security lifecycle.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAuthentication starts with a login. But it doesn\u0027t end there.\u003C/p\u003E\n\u003Cp\u003EAs applications grow, they introduce new challenges: multiple clients, refresh tokens, device sessions, credential recovery, tenant isolation, permissions, and revocation. What begins as a simple login system can gradually become a collection of disconnected security mechanisms.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth takes a different approach.\u003C/p\u003E\n\u003Cp\u003EIt brings authentication, authorization, users, credentials, sessions, and client integration into \u003Cstrong\u003Eone coherent framework\u003C/strong\u003E, with a shared model for how security decisions are made and how authentication state changes over time.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOne framework. Multiple client environments. A consistent security lifecycle.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003E\u003Ca href=\u0022https://github.com/CodeBeamOrg/UltimateAuth\u0022\u003EExplore the Source\u003C/a\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-ultimateauth\u0022\u003EWhy UltimateAuth?\u003C/h2\u003E\n\u003Cp\u003EMost applications don\u0027t struggle with generating an access token.\u003C/p\u003E\n\u003Cp\u003EThey struggle with everything that comes afterward.\u003C/p\u003E\n\u003Cp\u003EConsider an application that starts with a single web frontend. Eventually, it adds a mobile application, an administration panel, multiple organizations, and external APIs.\u003C/p\u003E\n\u003Cp\u003ESuddenly, authentication raises questions that a basic login implementation doesn\u0027t answer:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EHow should browser sessions and API tokens coexist?\u003C/li\u003E\n\u003Cli\u003ECan users see and revoke sessions associated with their devices?\u003C/li\u003E\n\u003Cli\u003EWhat happens to existing sessions after a password reset?\u003C/li\u003E\n\u003Cli\u003EHow do we prevent refresh token replay?\u003C/li\u003E\n\u003Cli\u003EHow do we enforce tenant boundaries consistently?\u003C/li\u003E\n\u003Cli\u003EHow do we keep authorization decisions separate from authentication?\u003C/li\u003E\n\u003Cli\u003ECan multiple application types use the same backend security model?\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese concerns are related. Implementing each independently makes the system harder to maintain, audit, and extend.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EUltimateAuth is designed to manage these concerns as parts of the same security lifecycle.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-different-way-to-think-about-authentication\u0022\u003EA Different Way to Think About Authentication\u003C/h2\u003E\n\u003Cp\u003ETraditional authentication integration is often organized around credentials and their immediate 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\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\u003ECredentials\n \u2193\nValidate\n \u2193\nIssue Token\n \u2193\nAuthenticated\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThat sequence is necessary, but incomplete.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth approaches authentication as a longer-running lifecycle:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E UltimateAuth\n \u2502\n \u25BC\n Request Context\n Client \u00B7 Tenant \u00B7 Device\n \u2502\n \u25BC\n Authentication\n Validation \u00B7 Decisions\n \u2502\n \u25BC\n Security State\n User \u00B7 Root \u00B7 Chain \u00B7 Session\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u25BC \u25BC \u25BC\n Refresh Logout Revoke\n \u2502 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u25BC\n Controlled State\n Transitions\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis model makes it possible to reason about authentication beyond a single request.\u003C/p\u003E\n\u003Cp\u003ELogin establishes authentication. Refresh evaluates whether it may continue. Logout and revocation terminate authentication at the appropriate scope. Credential changes and security policies can affect existing sessions.\u003C/p\u003E\n\u003Cp\u003EThe result is a framework where these operations share a consistent security vocabulary.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-makes-ultimateauth-different\u0022\u003EWhat Makes UltimateAuth Different?\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003E\u003Cescape\u003E\u003C/escape\u003E\u003C/th\u003E\n\u003Cth\u003ECapability\u003C/th\u003E\n\u003Cth\u003EUltimateAuth\u0027s approach\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EUnified architecture\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication, sessions, users, credentials, and authorization share a common foundation\u003C/td\u003E\n\u003Ctd\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EClient-aware behavior\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication can adapt to the client profile and effective server configuration\u003C/td\u003E\n\u003Ctd\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESession lifecycle\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERoot, chain, and session concepts support different scopes of session management\u003C/td\u003E\n\u003Ctd\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EFlow-driven execution\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication operations use explicit context, validation, decisions, and state transitions\u003C/td\u003E\n\u003Ctd\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredential lifecycle\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential changes, recovery, and session invalidation are related security operations\u003C/td\u003E\n\u003Ctd\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ETenant awareness\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ETenant context participates in authentication and access decisions\u003C/td\u003E\n\u003Ctd\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EModularity\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECore, Server, Client, and domain packages separate responsibilities\u003C/td\u003E\n\u003Ctd\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESelf-hosting\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EApplications can operate their own authentication infrastructure\u003C/td\u003E\n\u003Ctd\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\n\u003Cp\u003EUltimateAuth does not claim that tokens, sessions, PKCE, or policy-based authorization are new concepts.\u003C/p\u003E\n\u003Cp\u003EIts value is in \u003Cstrong\u003Ebringing established security concepts together under a consistent architecture\u003C/strong\u003E, rather than requiring every application to connect them independently.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022one-security-model-across-different-clients\u0022\u003EOne Security Model Across Different Clients\u003C/h2\u003E\n\u003Cp\u003EA browser application, a server-rendered application, and a native client have different security constraints.\u003C/p\u003E\n\u003Cp\u003EThey should not necessarily use identical credential storage, transport, or authentication flows.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth separates the common authentication model from client-specific integration.\u003C/p\u003E\n\u003Cp\u003E\u003Cescape\u003E\u0060\u0060\u0060text\nYour .NET Applications\u003C/escape\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\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\u003EBlazor Server Blazor WebAssembly\n \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n UltimateAuth Client\n \u2502\n \u25BC\n UltimateAuth Server\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u25BC \u25BC \u25BC\n Users Sessions Authorization\n \u2502 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u25BC\n Shared Security\n Lifecycle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003E\u0026LT;/ESCAPE\u0026GT;\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-\u0026lt;/escape\u0026gt;\u0022\u003E\nThe architecture also provides foundations for .NET MAUI, traditional ASP.NET Core applications, and API-oriented integrations.\n\n**Support depends on the client adapter and authentication modes available in the installed version.** A supported architectural profile should not be confused with a fully released platform adapter.\n\nThe goal is to share authentication concepts without pretending that all clients have the same trust boundaries.\n\n## Sessions Are More Than Tokens\n\nOne of UltimateAuth\u0027s central concepts is its stateful session hierarchy.\n\n\u0060\u0060\u0060text\nUser\n \u2514\u2500\u2500 Root\n \u251C\u2500\u2500 Chain \u2014 Browser A\n \u2502 \u251C\u2500\u2500 Session\n \u2502 \u2514\u2500\u2500 Session\n \u2502\n \u2514\u2500\u2500 Chain \u2014 Device B\n \u251C\u2500\u2500 Session\n \u2514\u2500\u2500 Session\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach level represents a different security scope.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERoot\u003C/strong\u003E represents a broader authentication security boundary.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EChain\u003C/strong\u003E represents the continuity of authentication associated with a device or client context.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESession\u003C/strong\u003E represents an individual authentication instance within that chain.\u003C/p\u003E\n\u003Cp\u003EThis distinction makes several operations easier to express:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEnd the current session.\u003C/li\u003E\n\u003Cli\u003ERevoke a particular device-associated chain.\u003C/li\u003E\n\u003Cli\u003ERevoke other active chains while preserving the current one.\u003C/li\u003E\n\u003Cli\u003ERevoke a broader authentication scope after a security-sensitive event.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA token is a credential used to access the system. It is not necessarily the complete record of the user\u0027s authentication lifecycle.\u003C/p\u003E\n\u003Cp\u003EThe hierarchy applies to session-backed authentication modes; different modes can have different persistence and validation characteristics.\u003C/p\u003E\n\u003Cp\u003E\u003Ca href=\u0022https://github.com/CodeBeamOrg/UltimateAuth\u0022\u003EExplore Session Management\u003C/a\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-flows-not-disconnected-endpoints\u0022\u003EAuthentication Flows, Not Disconnected Endpoints\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth organizes security-sensitive operations around explicit flows.\u003C/p\u003E\n\u003Cp\u003EA flow can combine:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003ERequest and tenant context.\u003C/li\u003E\n\u003Cli\u003EUser, credential, and session resolution.\u003C/li\u003E\n\u003Cli\u003EInput and security validation.\u003C/li\u003E\n\u003Cli\u003EAuthorization and authority decisions.\u003C/li\u003E\n\u003Cli\u003EControlled state transitions.\u003C/li\u003E\n\u003Cli\u003EClient-appropriate results and responses.\u003C/li\u003E\n\u003C/ol\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\u003ERequest\n \u2193\nAuthFlowContext\n \u2193\nValidation\n \u2193\nSecurity Decision\n \u2193\nExecution\n \u2193\nState Transition\n \u2193\nResponse\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis approach provides a consistent place for security rules to be evaluated.\u003C/p\u003E\n\u003Cp\u003EIt also makes individual operations easier to test and inspect because the important decisions are part of an explicit execution model.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe purpose is not abstraction for its own sake. It is to make authentication behavior understandable and predictable.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-continues-after-login\u0022\u003ESecurity Continues After Login\u003C/h2\u003E\n\u003Cp\u003EA secure authentication system must account for events that happen after a user signs in.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EEvent\u003C/th\u003E\n\u003Cth\u003EUltimateAuth concept\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs in\u003C/td\u003E\n\u003Ctd\u003ELogin flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA session needs renewal\u003C/td\u003E\n\u003Ctd\u003ERefresh flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user signs out\u003C/td\u003E\n\u003Ctd\u003ELogout flow\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA device should lose access\u003C/td\u003E\n\u003Ctd\u003EChain revocation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA credential is changed\u003C/td\u003E\n\u003Ctd\u003ECredential lifecycle and session policy\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAn account is recovered\u003C/td\u003E\n\u003Ctd\u003ERecovery proof and session invalidation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA user requests a protected resource\u003C/td\u003E\n\u003Ctd\u003EAuthorization decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA request targets a tenant\u003C/td\u003E\n\u003Ctd\u003ETenant-aware security context\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis is why UltimateAuth treats session management, credential management, and authorization as connected parts of the system.\u003C/p\u003E\n\u003Cp\u003EFor example, a password reset is not simply a database update. It can also affect the validity of existing authentication sessions according to the configured credential session policy.\u003C/p\u003E\n\u003Cp\u003ESimilarly, logout and revocation are not interchangeable operations. They have different scopes and consequences.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-and-authorization-together\u0022\u003EAuthentication and Authorization Together\u003C/h2\u003E\n\u003Cp\u003EKnowing who a user is does not determine what that user is allowed to do.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth separates authentication from authorization while keeping them within the same overall security architecture.\u003C/p\u003E\n\u003Cp\u003EAuthentication establishes identity and applicable security context.\u003C/p\u003E\n\u003Cp\u003EAuthorization evaluates whether an actor may perform an action on a resource.\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\u003EActor: Authenticated user\nAction: update\nResource: products\nResult: Allowed or Denied\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERoles and permissions can be managed through the authorization domain, while the server remains responsible for enforcing access decisions.\u003C/p\u003E\n\u003Cp\u003EThis separation helps applications avoid spreading authorization logic across UI components, controllers, and unrelated service methods.\u003C/p\u003E\n\u003Cp\u003EClient-side authorization checks can improve the interface, but \u003Cstrong\u003Eserver-side enforcement remains the security boundary\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022tenant-aware-by-design\u0022\u003ETenant-Aware by Design\u003C/h2\u003E\n\u003Cp\u003EMulti-tenant authentication involves more than adding a tenant identifier to a user record.\u003C/p\u003E\n\u003Cp\u003EA request must be interpreted within the correct tenant context, and security operations must respect that boundary.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides tenant resolution and propagation mechanisms that participate in its request and flow infrastructure.\u003C/p\u003E\n\u003Cp\u003EThis allows applications to build tenant-aware authentication and authorization behavior without treating tenancy as an unrelated afterthought.\u003C/p\u003E\n\u003Cp\u003ETenant context must still be configured correctly, and a client-supplied tenant identifier is never sufficient proof of authorization.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022modular-without-becoming-fragmented\u0022\u003EModular Without Becoming Fragmented\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is divided into packages with distinct responsibilities.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPackage or domain\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\u003EShared contracts, domain concepts, and authentication foundations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EServer\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication execution, runtime configuration, and server integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EClient\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EPlatform-independent authentication and domain APIs\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EClient.Blazor\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EBlazor-specific transport, storage, and authentication state integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EUsers\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUser lifecycle, profiles, and identifiers\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential operations, changes, and recovery\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAuthorization\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERoles, permissions, and access checks\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPolicies\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EApplication access policy integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis structure supports both a straightforward integration experience and deeper customization where the application requires it.\u003C/p\u003E\n\u003Cp\u003EDevelopers can begin with the public registration and client APIs without first learning every internal service.\u003C/p\u003E\n\u003Cp\u003EWhen necessary, they can explore the underlying flows, policies, stores, and extension points.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022designed-for-practical.net-development\u0022\u003EDesigned for Practical .NET Development\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is intended to fit naturally into .NET applications.\u003C/p\u003E\n\u003Cp\u003EA server application begins by registering UltimateAuth and configuring the security behavior it needs:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.FromDays(7);\n options.CredentialSessionPolicy.OnRecovery = CredentialSessionBehavior.RevokeAll;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA Blazor application registers the client adapter:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.AutoDetectClientProfile = true;\n options.Endpoints.BasePath = \u0026quot;/auth\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese examples illustrate the configuration entry points rather than a complete application setup.\u003C/p\u003E\n\u003Cp\u003EThe server and client have different responsibilities, and a complete deployment also needs the appropriate hosting, persistence, transport, and authentication integration.\u003C/p\u003E\n\u003Cp\u003EThe important idea is that common behavior is configured through structured options rather than scattered application-specific code.\u003C/p\u003E\n\u003Cp\u003E\u003Ca href=\u0022/docs/getting-started/quickstart\u0022\u003EStart with QuickStart\u003C/a\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022who-is-ultimateauth-for\u0022\u003EWho Is UltimateAuth For?\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is particularly relevant if you are building:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EA Blazor application\u003C/strong\u003E that needs authentication and authorization integrated with its hosting model.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EA multi-client .NET system\u003C/strong\u003E where different frontends should share the same authentication backend.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAn application with device-aware sessions\u003C/strong\u003E where users or administrators need control over active authentication.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EA multi-tenant platform\u003C/strong\u003E with explicit tenant resolution and security boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EA security-sensitive application\u003C/strong\u003E that requires controlled refresh, credential recovery, and revocation behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EA self-hosted system\u003C/strong\u003E where the team wants visibility into and control over its authentication infrastructure.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\u003Cstrong\u003EFrom a simple login page to a complex authentication platform, UltimateAuth is designed to make getting started easy without limiting what comes next.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EYou shouldn\u0027t need to understand an entire identity framework just to add authentication to a small .NET application. And you shouldn\u0027t need to replace your authentication system when that application grows.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth is built for both.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022simple-applications.simple-beginnings\u0022\u003ESimple Applications. Simple Beginnings.\u003C/h3\u003E\n\u003Cp\u003EFor a small application, authentication should be straightforward.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth aims to provide a lightweight integration experience with clear configuration, sensible defaults, and ready-to-use authentication flows.\u003C/p\u003E\n\u003Cp\u003EInstead of requiring developers to assemble numerous identity components before they can begin, UltimateAuth provides a cohesive foundation that can be configured through a small number of well-defined entry points.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EStart with what you need. You don\u0027t have to configure everything the framework supports.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022complex-applications.room-to-grow\u0022\u003EComplex Applications. Room to Grow.\u003C/h3\u003E\n\u003Cp\u003EWhen requirements become more demanding, the same framework provides a deeper security architecture.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth is particularly useful for applications requiring multiple client types, device-aware session management, tenant isolation, advanced authorization, credential recovery, controlled refresh, and targeted revocation.\u003C/p\u003E\n\u003Cp\u003EIts modular architecture, explicit authentication flows, configurable policies, and extension points allow developers to introduce additional capabilities without abandoning the original authentication foundation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe complexity of your application should not determine how difficult authentication is to begin with.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022one-framework-for-both\u0022\u003EOne Framework for Both\u003C/h3\u003E\n\u003Cp\u003EWhether you\u0027re building a small personal project, a business application, a SaaS platform, or a larger distributed system, UltimateAuth follows the same principle:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EEasy to start. Powerful when needed. Consistent as you grow.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EYou can begin with a straightforward authentication setup and progressively adopt the capabilities your application actually requires.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth can also serve smaller applications, but its architecture becomes especially valuable when authentication requirements extend beyond a basic login form.\u003C/p\u003E\n\u003Cp\u003EIf your application only needs a minimal authentication mechanism, evaluate whether UltimateAuth\u0027s broader lifecycle model provides enough value for your use case.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022current-scope-and-maturity\u0022\u003ECurrent Scope and Maturity\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is an evolving open-source framework.\u003C/p\u003E\n\u003Cp\u003EIts architecture covers several authentication models and client environments, but not every designed capability is necessarily available in the current release.\u003C/p\u003E\n\u003Cp\u003EIn particular:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication mode availability must be checked against the current server implementation.\u003C/li\u003E\n\u003Cli\u003EClient adapter availability varies by platform.\u003C/li\u003E\n\u003Cli\u003ESome advanced session and device policy options may be reserved for future enforcement.\u003C/li\u003E\n\u003Cli\u003EProduction adoption requires appropriate configuration, persistence, deployment security, and testing.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe documentation distinguishes supported behavior from planned or partially implemented capabilities.\u003C/p\u003E\n\u003Cp\u003EThis matters because a trustworthy authentication framework should be clear not only about what it can do, but also about its current limits.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022open-source-first.self-hosted-by-design\u0022\u003EOpen Source First. Self-Hosted by Design.\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth is developed as an open-source project with self-hosting at its foundation.\u003C/p\u003E\n\u003Cp\u003EThe project\u0027s direction is to keep self-hosted authentication free, while a potential managed hosting service could provide an alternative for teams that prefer not to operate the infrastructure themselves.\u003C/p\u003E\n\u003Cp\u003EThe intention is not to divide authentication capabilities into free and paid feature tiers.\u003C/p\u003E\n\u003Cp\u003EIt is to make the framework accessible, inspectable, extensible, and useful to the .NET community.\u003C/p\u003E\n\u003Cp\u003E\u003Ca href=\u0022https://github.com/CodeBeamOrg/UltimateAuth\u0022\u003EView UltimateAuth on GitHub\u003C/a\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choose-your-starting-point\u0022\u003EChoose Your Starting Point\u003C/h2\u003E\n\u003Cp\u003EYou do not need to understand the entire architecture before building your first application.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EI want to\u2026\u003C/th\u003E\n\u003Cth\u003EStart here\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESet up UltimateAuth for the first time\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022/docs/getting-started/quickstart\u0022\u003EQuickStart\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUnderstand how authentication works internally\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022/docs/auth-flows/overview\u0022\u003EAuth Flows\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EConfigure session, token, and security behavior\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIntegrate authentication into a Blazor application\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUnderstand the source code and contribute\u003C/td\u003E\n\u003Ctd\u003E\u003Ca href=\u0022https://github.com/CodeBeamOrg/UltimateAuth\u0022\u003EGitHub Repository\u003C/a\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Chr\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-is-a-lifecycle-not-a-login-endpoint\u0022\u003EAuthentication is a lifecycle, not a login endpoint.\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth brings together the concepts needed to establish, maintain, evaluate, and terminate authentication within a consistent .NET architecture.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EStart simple. Understand the security model. Extend when you need to.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003E\u003Ca href=\u0022/docs/getting-started/quickstart\u0022\u003EContinue to QuickStart\u003C/a\u003E\u003C/p\u003E\n", "Headings": [ { - "Id": "what-is-ultimateauth", - "Text": "What is UltimateAuth?", + "Id": "why-ultimateauth", + "Text": "Why UltimateAuth?", "Level": 0 }, { - "Id": "unified-does-not-mean-one-size-fits-all", - "Text": "Unified Does Not Mean One-Size-Fits-All", + "Id": "a-different-way-to-think-about-authentication", + "Text": "A Different Way to Think About Authentication", "Level": 0 }, { - "Id": "authentication-is-more-than-issuing-a-token", - "Text": "Authentication Is More Than Issuing a Token", + "Id": "what-makes-ultimateauth-different", + "Text": "What Makes UltimateAuth Different?", "Level": 0 }, { - "Id": "sessions-are-first-class", - "Text": "Sessions Are First-Class", + "Id": "one-security-model-across-different-clients", + "Text": "One Security Model Across Different Clients", "Level": 0 }, { - "Id": "flow-driven-by-design", - "Text": "Flow-Driven by Design", + "Id": "authentication-flows-not-disconnected-endpoints", + "Text": "Authentication Flows, Not Disconnected Endpoints", "Level": 0 }, { - "Id": "tenant-aware-from-the-request-boundary", - "Text": "Tenant-Aware from the Request Boundary", + "Id": "security-continues-after-login", + "Text": "Security Continues After Login", "Level": 0 }, { - "Id": "built-for-different-clients-not-just-different-screens", - "Text": "Built for Different Clients, Not Just Different Screens", + "Id": "authentication-and-authorization-together", + "Text": "Authentication and Authorization Together", "Level": 0 }, { - "Id": "security-is-a-lifecycle-too", - "Text": "Security Is a Lifecycle, Too", + "Id": "tenant-aware-by-design", + "Text": "Tenant-Aware by Design", "Level": 0 }, { - "Id": "modular-and-extensible", - "Text": "Modular and Extensible", + "Id": "modular-without-becoming-fragmented", + "Text": "Modular Without Becoming Fragmented", "Level": 0 }, { - "Id": "when-should-you-choose-ultimateauth", - "Text": "When Should You Choose UltimateAuth?", + "Id": "designed-for-practical.net-development", + "Text": "Designed for Practical .NET Development", "Level": 0 }, { - "Id": "open-source-first", - "Text": "Open Source First", + "Id": "who-is-ultimateauth-for", + "Text": "Who Is UltimateAuth For?", "Level": 0 }, { - "Id": "where-to-go-next", - "Text": "Where to Go Next", + "Id": "simple-applications.simple-beginnings", + "Text": "Simple Applications. Simple Beginnings.", + "Level": 1 + }, + { + "Id": "complex-applications.room-to-grow", + "Text": "Complex Applications. Room to Grow.", + "Level": 1 + }, + { + "Id": "one-framework-for-both", + "Text": "One Framework for Both", + "Level": 1 + }, + { + "Id": "current-scope-and-maturity", + "Text": "Current Scope and Maturity", + "Level": 0 + }, + { + "Id": "open-source-first.self-hosted-by-design", + "Text": "Open Source First. Self-Hosted by Design.", "Level": 0 + }, + { + "Id": "choose-your-starting-point", + "Text": "Choose Your Starting Point", + "Level": 0 + }, + { + "Id": "authentication-is-a-lifecycle-not-a-login-endpoint", + "Text": "Authentication is a lifecycle, not a login endpoint.", + "Level": 1 } ] } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json index 1f0ba736..1feb45f4 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json @@ -1,66 +1,71 @@ { "Slug": "getting-started/quickstart", "Title": "QuickStart", - "Html": "\n\u003Cp\u003EIn this guide, you will set up UltimateAuth in a few minutes and perform your \u003Cstrong\u003Efirst login\u003C/strong\u003E.\u003C/p\u003E\n\u003Chr\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-project\u0022\u003E1. Create a Project\u003C/h2\u003E\n\u003Cp\u003EStart by creating a new Blazor app:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet new blazorserver -n UltimateAuthDemo\ncd UltimateAuthDemo\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-packages\u0022\u003E2. Install Packages\u003C/h2\u003E\n\u003Cp\u003EInstall the required UltimateAuth packages:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.InMemory.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services\u0022\u003E3. Configure Services\u003C/h2\u003E\n\u003Cp\u003EUpdate your Program.cs:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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\n .AddUltimateAuthServer()\n .AddUltimateAuthInMemory();\n\nbuilder.Services\n .AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-middleware\u0022\u003E4. Configure Middleware\u003C/h2\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\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\u003Eapp.UseUltimateAuthWithAspNetCore();\napp.MapUltimateAuthEndpoints();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022enable-blazor-integration\u0022\u003E5. Enable Blazor Integration\u003C/h2\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\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\u003Eapp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveServerRenderMode() // or webassembly (depends on your application type)\n .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient());\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-uauth-script\u0022\u003E6. Add UAuth Script\u003C/h2\u003E\n\u003Cp\u003EAdd this to \u003Ccode\u003EApp.razor\u003C/code\u003E or \u003Ccode\u003Eindex.html\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\u003E\u0026lt;script src=\u0026quot;_content/CodeBeam.UltimateAuth.Client.Blazor/uauth.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-application-lifecycle\u0022\u003E7. Configure Application Lifecycle\u003C/h2\u003E\n\u003Cp\u003EReplace \u003Ccode\u003ERoutes.razor\u003C/code\u003E with this code:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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\u0026lt;UAuthApp UseBuiltInRouter=\u0026quot;true\u0026quot; AppAssembly=\u0026quot;typeof(Program).Assembly\u0026quot; DefaultLayout=\u0026quot;typeof(Layout.MainLayout)\u0026quot; /\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022recommended-setup-optional\u0022\u003E8. Recommended Setup (Optional)\u003C/h2\u003E\n\u003Cp\u003EAdd these for better experience:\u003C/p\u003E\n\u003Cp\u003EFor login page (Use this only once in your application)\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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@attribute [UAuthLoginPage]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor protected pages\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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@attribute [UAuthAuthorize]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor any page that you use UltimateAuth features like AuthState etc.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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@inherits UAuthPageBase\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022seed-data-for-quickstart-optional\u0022\u003E9. Seed Data For QuickStart (Optional)\u003C/h2\u003E\n\u003Cp\u003EThis code creates admin and user users with same password and admin role.\u003C/p\u003E\n\u003Cp\u003EFor in memory\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthSampleSeed();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor entity framework core, use scoped services:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddScopedUltimateAuthSampleSeed();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn pipeline 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\u003Eif (app.Environment.IsDevelopment())\n{\n await app.SeedUltimateAuthAsync();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022perform-your-first-login\u0022\u003E10. Perform Your First Login\u003C/h2\u003E\n\u003Cp\u003EExample using IUAuthClient:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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[Inject] IUAuthClient UAuthClient { get; set; } = null!;\n\nprivate async Task Login()\n{\n await UAuthClient.Flows.LoginAsync(new LoginRequest\n {\n Identifier = \u0026quot;admin\u0026quot;,\n Secret = \u0026quot;admin\u0026quot;\n });\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022thats-it\u0022\u003E\uD83C\uDF89 That\u2019s It\u003C/h2\u003E\n\u003Cp\u003EYou now have a working authentication system with:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession-based authentication\u003C/li\u003E\n\u003Cli\u003EAutomatic client detection\u003C/li\u003E\n\u003Cli\u003EBuilt-in login flow\u003C/li\u003E\n\u003Cli\u003ESecure session handling\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-just-happened\u0022\u003EWhat Just Happened?\u003C/h2\u003E\n\u003Cp\u003EWhen you logged in:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA session (with root and chain) was created on the server,\u003C/li\u003E\n\u003Cli\u003EYour client received an authentication grant (cookie or token),\u003C/li\u003E\n\u003Cli\u003EUltimateAuth established your auth state automatically.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You didn\u2019t manage cookies, tokens, or redirects manually.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cp\u003EDiscover the setup for real world applications with entity framework core.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003E\u003Cstrong\u003EYour first UltimateAuth application, in just a few steps.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis guide walks you through creating a Blazor Server application, adding UltimateAuth with in-memory storage, and signing in with a sample account.\u003C/p\u003E\n\u003Cp\u003EYou\u0027ll start with a minimal setup. No database configuration, migrations, or custom authentication infrastructure required.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022prerequisites\u0022\u003EPrerequisites\u003C/h2\u003E\n\u003Cp\u003EYou\u0027ll need:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E.NET SDK (8 and higher, latest recommended)\u003C/li\u003E\n\u003Cli\u003EA code editor such as Visual Studio, VS Code, or Rider\u003C/li\u003E\n\u003Cli\u003EBasic familiarity with Blazor\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis guide uses \u003Cstrong\u003EBlazor Web App with Interactive Server rendering\u003C/strong\u003E and UltimateAuth\u0027s in-memory persistence bundle.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-blazor-application\u0022\u003E1. Create a Blazor Application\u003C/h2\u003E\n\u003Cp\u003EOpen a terminal and create a new project:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet new blazor -n UltimateAuthDemo -int Server\ncd UltimateAuthDemo\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis creates a Blazor Web App with Interactive Server components.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-ultimateauth\u0022\u003E2. Install UltimateAuth\u003C/h2\u003E\n\u003Cp\u003EInstall the Blazor client adapter and the in-memory bundle:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.InMemory.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor this tutorial, also install the sample seed package:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Sample.Seed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe packages serve different purposes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPackage\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EClient.Blazor\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBlazor components, authentication state, and client integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EInMemory.Bundle\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIn-memory implementations for authentication domains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESample.Seed\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDevelopment accounts and sample authorization data\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe in-memory bundle includes the relevant persistence implementations, so you do not need to register each store individually.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-ultimateauth-services\u0022\u003E3. Configure UltimateAuth Services\u003C/h2\u003E\n\u003Cp\u003EOpen \u003Ccode\u003EProgram.cs\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EAdd the following namespaces:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.Blazor;\nusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.InMemory;\nusing CodeBeam.UltimateAuth.Sample.Seed.Extensions;\nusing CodeBeam.UltimateAuth.Server.Extensions;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter creating the builder, register UltimateAuth:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services\n .AddUltimateAuthServer()\n .AddUltimateAuthInMemory();\n\nbuilder.Services.AddUltimateAuthClientBlazor();\n\nbuilder.Services.AddUltimateAuthSampleSeed();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThat\u0027s the core service configuration.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E registers the authentication server.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthInMemory()\u003C/code\u003E provides in-memory stores.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthClientBlazor()\u003C/code\u003E registers the Blazor client integration.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthSampleSeed()\u003C/code\u003E prepares the development seed services.\u003C/p\u003E\n\u003Cp\u003EYou don\u0027t need to call \u003Ccode\u003EAddUltimateAuth()\u003C/code\u003E or \u003Ccode\u003EAddUltimateAuthClient()\u003C/code\u003E separately.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-http-pipeline\u0022\u003E4. Configure the HTTP Pipeline\u003C/h2\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\u003C/code\u003E, configure the application pipeline:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar app = builder.Build();\n\nif (!app.Environment.IsDevelopment())\n{\n app.UseExceptionHandler(\u0026quot;/Error\u0026quot;, createScopeForErrors: true);\n app.UseHsts();\n}\n\napp.UseHttpsRedirection();\napp.UseStaticFiles();\n\napp.UseUltimateAuthWithAspNetCore();\napp.MapUltimateAuthEndpoints();\n\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveServerRenderMode()\n .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient());\n\nif (app.Environment.IsDevelopment())\n{\n await app.SeedUltimateAuthAsync();\n}\n\napp.Run();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EKeep the \u003Ccode\u003EAddRazorComponents().AddInteractiveServerComponents()\u003C/code\u003E registration generated by the Blazor project template.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EUseUltimateAuthWithAspNetCore()\u003C/code\u003E integrates UltimateAuth\u0027s middleware with ASP.NET Core authentication and authorization.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EMapUltimateAuthEndpoints()\u003C/code\u003E registers the authentication endpoints.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthRoutes()\u003C/code\u003E includes UltimateAuth\u0027s Blazor client route assemblies in endpoint discovery.\u003C/p\u003E\n\u003Cp\u003EThe development-only seed call creates the sample users before the application begins accepting requests.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-the-ultimateauth-script\u0022\u003E5. Add the UltimateAuth Script\u003C/h2\u003E\n\u003Cp\u003EOpen \u003Ccode\u003EComponents/App.razor\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EKeep the existing Blazor script and add UltimateAuth\u0027s JavaScript file:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EHTML\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-html\u0022\u003E\u0026lt;script src=\u0026quot;_content/CodeBeam.UltimateAuth.Client.Blazor/uauth.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EPlace these near the end of the document body.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s Blazor integration uses this script for browser-side functionality.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022initialize-the-ultimateauth-application\u0022\u003E6. Initialize the UltimateAuth Application\u003C/h2\u003E\n\u003Cp\u003EOpen \u003Ccode\u003EComponents/Routes.razor\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EReplace its contents with:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client.Blazor\n\n\u0026lt;UAuthApp UseBuiltInRouter=\u0026quot;true\u0026quot; AppAssembly=\u0026quot;typeof(Program).Assembly\u0026quot; DefaultLayout=\u0026quot;typeof(Layout.MainLayout)\u0026quot; /\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthApp\u003C/code\u003E is the root integration component for UltimateAuth in Blazor.\u003C/p\u003E\n\u003Cp\u003EIt initializes the client runtime, provides authentication state to child components, and coordinates the authenticated session lifecycle.\u003C/p\u003E\n\u003Cp\u003EWith \u003Ccode\u003EUseBuiltInRouter=\u0026quot;true\u0026quot;\u003C/code\u003E, it also provides the application\u0027s router.\u003C/p\u003E\n\u003Cp\u003EYou don\u0027t need to create a separate \u003Ccode\u003ERouter\u003C/code\u003E or \u003Ccode\u003ECascadingAuthenticationState\u003C/code\u003E component for this setup.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-your-login-page\u0022\u003E7. Create Your Login Page\u003C/h2\u003E\n\u003Cp\u003ECreate \u003Ccode\u003EComponents/Pages/Login.razor\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@page \u0026quot;/login\u0026quot;\n@attribute [UAuthLoginPage]\n\n@using CodeBeam.UltimateAuth.Client.Blazor\n\n\u0026lt;h3\u0026gt;Sign in\u0026lt;/h3\u0026gt;\n\u0026lt;UAuthLoginForm Identifier=\u0026quot;@_identifier\u0026quot; Secret=\u0026quot;@_password\u0026quot;\u0026gt;\n \u0026lt;div\u0026gt;\n \u0026lt;label\u0026gt;Username\u0026lt;/label\u0026gt;\n \u0026lt;input @bind=\u0026quot;_identifier\u0026quot; /\u0026gt;\n \u0026lt;/div\u0026gt;\n\n \u0026lt;div\u0026gt;\n \u0026lt;label\u0026gt;Password\u0026lt;/label\u0026gt;\n \u0026lt;input type=\u0026quot;password\u0026quot; @bind=\u0026quot;_password\u0026quot; /\u0026gt;\n \u0026lt;/div\u0026gt;\n\n \u0026lt;button type=\u0026quot;submit\u0026quot;\u0026gt;Sign in\u0026lt;/button\u0026gt;\n\u0026lt;/UAuthLoginForm\u0026gt;\n\n@code {\n private string _identifier = \u0026quot;\u0026quot;;\n private string _password = \u0026quot;\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E handles the login submission through UltimateAuth\u0027s client flow.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003E[UAuthLoginPage]\u003C/code\u003E identifies this component as the application\u0027s login page.\u003C/p\u003E\n\u003Cp\u003EOnly designate one login page for this purpose.\u003C/p\u003E\n\u003Cp\u003EThe form above is intentionally minimal. You can replace the inputs with your preferred UI components later.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-protected-page\u0022\u003E8. Add a Protected Page\u003C/h2\u003E\n\u003Cp\u003ECreate \u003Ccode\u003EComponents/Pages/Dashboard.razor\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@page \u0026quot;/dashboard\u0026quot;\n@attribute [UAuthAuthorize]\n\n@using CodeBeam.UltimateAuth.Client.Blazor\n\n\u0026lt;h3\u0026gt;Dashboard\u0026lt;/h3\u0026gt;\n\n\u0026lt;p\u0026gt;You have reached an authenticated page.\u0026lt;/p\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E declares an UltimateAuth authorization requirement for the page.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E Client-side route protection is not a substitute for server-side authorization. Protected APIs and resources must enforce authorization on the server.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022run-the-application\u0022\u003E9. Run the Application\u003C/h2\u003E\n\u003Cp\u003EStart the application:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet run\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOpen the HTTPS address displayed in your terminal, then navigate to:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E/login\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe sample seed package creates two development accounts:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EUsername\u003C/th\u003E\n\u003Cth\u003EPassword\u003C/th\u003E\n\u003Cth\u003ERole\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eadmin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eadmin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAdministrator\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Euser\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Euser\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EStandard user\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ESign in with:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EUsername: admin\nPassword: admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter successful authentication, navigate to:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E/dashboard\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EYou now have a Blazor application using UltimateAuth authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022your-first-ultimateauth-application-is-running\u0022\u003E\uD83C\uDF89 Your First UltimateAuth Application Is Running\u003C/h2\u003E\n\u003Cp\u003EWith this setup, you have:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn authentication server integrated into ASP.NET Core.\u003C/li\u003E\n\u003Cli\u003EIn-memory user, credential, session, token, and authorization storage.\u003C/li\u003E\n\u003Cli\u003EA Blazor authentication client.\u003C/li\u003E\n\u003Cli\u003EA working login form.\u003C/li\u003E\n\u003Cli\u003EAuthentication state initialization.\u003C/li\u003E\n\u003Cli\u003ESession lifecycle coordination.\u003C/li\u003E\n\u003Cli\u003ESample users and authorization data.\u003C/li\u003E\n\u003Cli\u003EA page declaring an authentication requirement.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EYou did not need to create your own password hashing, token issuance, session validation, or authentication middleware implementations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-happened-when-you-logged-in\u0022\u003EWhat Happened When You Logged In?\u003C/h2\u003E\n\u003Cp\u003EFor this Blazor Server scenario, UltimateAuth uses the applicable session-oriented authentication model.\u003C/p\u003E\n\u003Cp\u003EThe login flow validates the credentials and establishes authentication state on the server.\u003C/p\u003E\n\u003Cp\u003EWhere a new session is created, it participates in UltimateAuth\u0027s root, chain, and session lifecycle.\u003C/p\u003E\n\u003Cp\u003EThe Blazor client then coordinates authentication state for the application.\u003C/p\u003E\n\u003Cp\u003EYou can explore these operations in detail later through the Authentication Flows and Session Management documentation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-in-memory-storage\u0022\u003EImportant: In-Memory Storage\u003C/h2\u003E\n\u003Cp\u003EThis QuickStart intentionally uses in-memory persistence.\u003C/p\u003E\n\u003Cp\u003EIt is ideal for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELearning UltimateAuth.\u003C/li\u003E\n\u003Cli\u003ELocal development.\u003C/li\u003E\n\u003Cli\u003ETesting authentication flows.\u003C/li\u003E\n\u003Cli\u003EBuilding prototypes.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\u003Cstrong\u003EIn-memory authentication data is not durable.\u003C/strong\u003E Users, credentials, sessions, and other stored state may be lost when the application restarts.\u003C/p\u003E\n\u003Cp\u003EThe sample credentials are also for development only. Do not deploy the seeded accounts or their example passwords in a production application.\u003C/p\u003E\n\u003Cp\u003EFor production use, configure durable persistence, appropriate secrets and signing keys, HTTPS, and the security settings required by your deployment.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/getting-started/ecosystem\u0022\u003EEcosystem\u003C/a\u003E\u003C/strong\u003E \u2014 Explore packages, integrations, and supported environments.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E\u003C/strong\u003E \u2014 Configure sessions, tokens, login policies, and security behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E\u003C/strong\u003E \u2014 Customize Blazor integration and authentication flows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/auth-flows/overview\u0022\u003EAuthentication Flows\u003C/a\u003E\u003C/strong\u003E \u2014 Understand login, refresh, logout, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/getting-started/entity-framework-core\u0022\u003EEntity Framework Core Setup\u003C/a\u003E\u003C/strong\u003E \u2014 Move from in-memory storage to persistent authentication data.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { - "Id": "create-a-project", - "Text": "1. Create a Project", + "Id": "prerequisites", + "Text": "Prerequisites", "Level": 0 }, { - "Id": "install-packages", - "Text": "2. Install Packages", + "Id": "create-a-blazor-application", + "Text": "1. Create a Blazor Application", "Level": 0 }, { - "Id": "configure-services", - "Text": "3. Configure Services", + "Id": "install-ultimateauth", + "Text": "2. Install UltimateAuth", "Level": 0 }, { - "Id": "configure-middleware", - "Text": "4. Configure Middleware", + "Id": "configure-ultimateauth-services", + "Text": "3. Configure UltimateAuth Services", "Level": 0 }, { - "Id": "enable-blazor-integration", - "Text": "5. Enable Blazor Integration", + "Id": "configure-the-http-pipeline", + "Text": "4. Configure the HTTP Pipeline", "Level": 0 }, { - "Id": "add-uauth-script", - "Text": "6. Add UAuth Script", + "Id": "add-the-ultimateauth-script", + "Text": "5. Add the UltimateAuth Script", "Level": 0 }, { - "Id": "configure-application-lifecycle", - "Text": "7. Configure Application Lifecycle", + "Id": "initialize-the-ultimateauth-application", + "Text": "6. Initialize the UltimateAuth Application", "Level": 0 }, { - "Id": "recommended-setup-optional", - "Text": "8. Recommended Setup (Optional)", + "Id": "create-your-login-page", + "Text": "7. Create Your Login Page", "Level": 0 }, { - "Id": "seed-data-for-quickstart-optional", - "Text": "9. Seed Data For QuickStart (Optional)", + "Id": "add-a-protected-page", + "Text": "8. Add a Protected Page", "Level": 0 }, { - "Id": "perform-your-first-login", - "Text": "10. Perform Your First Login", + "Id": "run-the-application", + "Text": "9. Run the Application", "Level": 0 }, { - "Id": "thats-it", - "Text": "\uD83C\uDF89 That\u2019s It", + "Id": "your-first-ultimateauth-application-is-running", + "Text": "\uD83C\uDF89 Your First UltimateAuth Application Is Running", "Level": 0 }, { - "Id": "what-just-happened", - "Text": "What Just Happened?", + "Id": "what-happened-when-you-logged-in", + "Text": "What Happened When You Logged In?", + "Level": 0 + }, + { + "Id": "important-in-memory-storage", + "Text": "Important: In-Memory Storage", "Level": 0 }, { diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json index 338049d9..d1ea0238 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json @@ -1,56 +1,311 @@ { "Slug": "getting-started/real-world-setup", "Title": "Real World Setup", - "Html": "\n\u003Cp\u003EThe Quick Start uses an in-memory setup for simplicity.\nIn real-world applications, you should replace it with a persistent configuration as shown below.\u003C/p\u003E\n\u003Cp\u003EIn real applications, you will typically configure:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA persistent database\u003C/li\u003E\n\u003Cli\u003EAn appropriate client profile\u003C/li\u003E\n\u003Cli\u003EA suitable authentication mode\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis guide shows how to set up UltimateAuth for real-world scenarios.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-entity-framework-core\u0022\u003E\uD83D\uDDC4\uFE0F Using Entity Framework Core\u003C/h2\u003E\n\u003Cp\u003EFor production, you should use a persistent store. (In-memory provider is volatile and automatically resets on each restart.)\u003C/p\u003E\n\u003Cp\u003EIn this setup, you no longer need the \u003Ccode\u003ECodeBeam.UltimateAuth.InMemory.Bundle\u003C/code\u003E package.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-packages\u0022\u003EInstall Packages\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services\u0022\u003EConfigure Services\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\n .AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(\u0026quot;Data Source=uauth.db\u0026quot;);\n // or UseSqlServer(...) / UseNpgsql(...) / UseMySql(...) etc.\n });\n\nbuilder.Services\n .AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022database-migrations\u0022\u003EDatabase Migrations\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth integrates with Entity Framework Core, but database migrations belong to your application.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth does not automatically create or apply migrations on your behalf. This keeps your database schema lifecycle under your control and allows migrations to follow the same deployment and review process as the rest of your application.\u003C/p\u003E\n\u003Cp\u003EAfter configuring the Entity Framework Core provider, create the initial migration and update the database using either the .NET CLI or Visual Studio Package Manager Console.\u003C/p\u003E\n\u003Ch4 id=\u0022option-a.net-cli\u0022\u003EOption A \u2014 .NET CLI\u003C/h4\u003E\n\u003Cp\u003EIf you use the .NET CLI:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet ef migrations add InitUAuth --context UAuthDbContext\ndotnet ef database update --context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIf the dotnet ef command is not available, install the EF Core CLI tool:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet tool install --global dotnet-ef\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EYour project also needs the Entity Framework Core design package:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package Microsoft.EntityFrameworkCore.Design\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch4 id=\u0022option-b-visual-studio-package-manager-console\u0022\u003EOption B \u2014 Visual Studio Package Manager Console\u003C/h4\u003E\n\u003Cp\u003EIf you use Visual Studio, you can perform the same operation from Tools \u2192 NuGet Package Manager \u2192 Package Manager Console:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003EAdd-Migration InitUAuth -Context UAuthDbContext\nUpdate-Database -Context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor Package Manager Console tooling, make sure the required EF Core tooling package is available:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003EInstall-Package Microsoft.EntityFrameworkCore.Tools\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022who-owns-the-migrations\u0022\u003EWho Owns the Migrations?\u003C/h3\u003E\n\u003Cp\u003EYour application does.\u003C/p\u003E\n\u003Cp\u003EThis is intentional. UltimateAuth provides the authentication and identity model through its Entity Framework Core integration, while your application remains responsible for managing the resulting database schema.\u003C/p\u003E\n\u003Cp\u003EThis means you can:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003Ereview migrations before applying them,\u003C/li\u003E\n\u003Cli\u003Einclude UltimateAuth schema changes in your normal deployment process,\u003C/li\u003E\n\u003Cli\u003Econtrol when database changes are applied,\u003C/li\u003E\n\u003Cli\u003Emaintain migration history alongside your application,\u003C/li\u003E\n\u003Cli\u003Euse the database provider and deployment strategy appropriate for your environment.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EWhen upgrading UltimateAuth, review the release notes for persistence-related schema changes and create a new migration when required.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003ETip:\u003C/strong\u003E Treat UltimateAuth model changes like any other Entity Framework Core model change: upgrade the package, create a migration, review the generated migration, and apply it through your normal deployment process.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services-with-options\u0022\u003EConfigure Services With Options\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides rich options for server and client service registration.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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(o =\u0026gt; {\n o.Diagnostics.EnableRefreshDetails = true;\n o.Login.MaxFailedAttempts = 4;\n o.Identifiers.AllowMultipleUsernames = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-standalone-wasm-setup\u0022\u003EBlazor Standalone WASM Setup\u003C/h2\u003E\n\u003Cp\u003EBlazor WASM applications run entirely on the client and cannot securely handle credentials.\nFor this reason, UltimateAuth uses a dedicated Auth server called \u003Cstrong\u003EUAuthHub\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EWASM \u003Ccode\u003EProgram.cs\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\u003Ebuilder.Services.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6110/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6130/home\u0026quot;; // Your (WASM) application domain \u002B return path\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUAuthHub \u003Ccode\u003EProgram.cs\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\u003Ebuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthInMemory()\n .AddUAuthHub(o =\u0026gt; o.AllowedClientOrigins.Add(\u0026quot;https://localhost:6130\u0026quot;)); // WASM application\u0027s URL\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUAuthHub Pipeline 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\u003Eapp.MapUltimateAuthEndpoints();\napp.MapUAuthHub();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-web-app-setup\u0022\u003EBlazor Web App Setup\u003C/h2\u003E\n\u003Cp\u003EA blazor web app contains two projects that includes host and client. You need to arrange them both.\u003C/p\u003E\n\u003Cp\u003EIn the host project:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;; // Current application domain \u002B path\n});\n\n// In pipeline configuration\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveWebAssemblyRenderMode()\n .AddAdditionalAssemblies(UAuthAssemblies.BlazorClient().First());\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn the client project:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddUltimateAuthClientBlazor(o =\u0026gt;\n{\n o.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;; // UAuthHub URL\n o.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;; // Current application domain \u002B path\n});\n\nbuilder.Services.AddScoped(sp =\u0026gt; new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });\n\n// Optional if you use external API calls in your client project.\nbuilder.Services.AddHttpClient(\u0026quot;resourceApi\u0026quot;, client =\u0026gt;\n{\n client.BaseAddress = new Uri(\u0026quot;https://localhost:6122\u0026quot;);\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003EIf you want to use embedded UAuthHub in host project, you can register server services as shown in quickstart.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u2139\uFE0F UltimateAuth automatically selects the appropriate authentication mode (PureOpaque, Hybrid, etc.) based on the client type.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resourceapi-setup\u0022\u003EResourceApi Setup\u003C/h2\u003E\n\u003Cp\u003EYou may want to secure your custom API with UltimateAuth. UltimateAuth provides a lightweight option for this case. (ResourceApi doesn\u0027t have to be a blazor application, it can be any server-side project like MVC.)\u003C/p\u003E\n\u003Cp\u003EResourceApi\u0027s \u003Ccode\u003EProgram.cs\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\u003Ebuilder.Services.AddUltimateAuthResourceApi(o =\u0026gt;\n {\n o.UAuthHubBaseUrl = \u0026quot;https://localhost:6110\u0026quot;;\n o.AllowedClientOrigins.Add(\u0026quot;https://localhost:6130\u0026quot;);\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EConfigure pipeline:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.UseUltimateAuthResourceApiWithAspNetCore();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ENotes:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EResourceApi should connect with an UAuthHub, not a pure-server. Make sure \u003Ccode\u003E.AddUAuthHub()\u003C/code\u003E after calling \u003Ccode\u003Ebuilder.Services.AddUltimateAuthServer()\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EUltimateAuth automatically configures CORS based on the provided origins.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUse ResourceApi when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EYou have a separate backend API\u003C/li\u003E\n\u003Cli\u003EYou want to validate sessions or tokens externally\u003C/li\u003E\n\u003Cli\u003EYour API is not hosting UltimateAuth directly\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-to-think-about-setup\u0022\u003E\uD83E\uDDE0 How to Think About Setup\u003C/h2\u003E\n\u003Cp\u003EIn UltimateAuth:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EServer\u003C/strong\u003E manages authentication flows and sessions\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EClient\u003C/strong\u003E interacts through flows (not tokens directly)\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EStorage layer\u003C/strong\u003E (InMemory / EF Core) defines persistence\u003C/li\u003E\n\u003Cli\u003EThe \u003Cstrong\u003EApplication type\u003C/strong\u003E determines runtime behavior\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\uD83D\uDC49 You configure the system once, and UltimateAuth adapts automatically.\u003C/p\u003E\n", + "Html": "\n\u003Cp\u003E\u003Cstrong\u003EOne authentication framework. Different deployment models.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThe \u003Ca href=\u0022/docs/getting-started/quickstart\u0022\u003EQuickStart\u003C/a\u003E demonstrates UltimateAuth with Blazor Server and in-memory storage.\u003C/p\u003E\n\u003Cp\u003EReal applications may require persistent data, a separate authentication server, browser-based clients, or independently deployed APIs.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports these scenarios through different service registrations and hosting integrations. You can choose the architecture that fits your application without configuring every available component.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the setup patterns demonstrated by the UltimateAuth samples.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choose-your-architecture\u0022\u003E1. Choose Your Architecture\u003C/h2\u003E\n\u003Cp\u003EStart by identifying where authentication is hosted and how your application communicates with it.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EApplication architecture\u003C/th\u003E\n\u003Cth\u003EAuthentication host\u003C/th\u003E\n\u003Cth\u003EApplication registration\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor Server, single project\u003C/td\u003E\n\u003Ctd\u003ESame application\u003C/td\u003E\n\u003Ctd\u003EServer \u002B persistence \u002B Blazor client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor Server with EF Core\u003C/td\u003E\n\u003Ctd\u003ESame application\u003C/td\u003E\n\u003Ctd\u003EServer \u002B EF Core \u002B Blazor client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDedicated UAuthHub\u003C/td\u003E\n\u003Ctd\u003ESeparate authentication application\u003C/td\u003E\n\u003Ctd\u003EServer \u002B persistence \u002B Hub\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EStandalone Blazor WASM\u003C/td\u003E\n\u003Ctd\u003ERemote UAuthHub\u003C/td\u003E\n\u003Ctd\u003EBlazor client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor Web App, Interactive WASM\u003C/td\u003E\n\u003Ctd\u003ERemote UAuthHub\u003C/td\u003E\n\u003Ctd\u003EHost integration \u002B WASM client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESeparate Resource API\u003C/td\u003E\n\u003Ctd\u003ERemote UAuthHub\u003C/td\u003E\n\u003Ctd\u003EResource API integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese models can be combined.\u003C/p\u003E\n\u003Cp\u003EFor example, an application might use an EF Core-backed UAuthHub, a standalone Blazor WASM frontend, and a separate Resource API.\u003C/p\u003E\n\u003Cp\u003EThe frontend handles user interaction, UAuthHub manages authentication, and the Resource API enforces access to protected resources.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022persistent-storage-with-entity-framework-core\u0022\u003E2. Persistent Storage with Entity Framework Core\u003C/h2\u003E\n\u003Cp\u003EFor applications that need authentication state to survive restarts, use a persistent storage provider.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides an Entity Framework Core bundle that registers its reference domain implementations and EF Core-backed stores.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-packages\u0022\u003EInstall packages\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\ndotnet add package Microsoft.EntityFrameworkCore.Sqlite\ndotnet add package Microsoft.EntityFrameworkCore.Design\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe SQLite provider is used here for simplicity. You can use another compatible EF Core database provider.\u003C/p\u003E\n\u003Cp\u003EWhen using the EF Core bundle, you do not need the in-memory bundle.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022register-the-services\u0022\u003ERegister the services\u003C/h3\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\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\u003Eusing CodeBeam.UltimateAuth.EntityFrameworkCore;\nusing CodeBeam.UltimateAuth.Server.Extensions;\nusing Microsoft.EntityFrameworkCore;\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(\u0026quot;Data Source=uauth.db\u0026quot;, sqlite =\u0026gt; sqlite.MigrationsAssembly(typeof(Program).Assembly.GetName().Name!));\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthEntityFrameworkCore()\u003C/code\u003E registers the EF Core stores and reference domain implementations.\u003C/p\u003E\n\u003Cp\u003EThe bundle provides a unified \u003Ccode\u003EUAuthDbContext\u003C/code\u003E for the supported authentication domains.\u003C/p\u003E\n\u003Cp\u003EYou do not need to register each user, session, credential, token, or authorization store individually.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-database-connection\u0022\u003EConfigure the database connection\u003C/h3\u003E\n\u003Cp\u003EFor a deployed application, prefer configuration over hardcoded connection strings.\u003C/p\u003E\n\u003Cp\u003E\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;ConnectionStrings\u0026quot;: {\n \u0026quot;UltimateAuth\u0026quot;: \u0026quot;Data Source=uauth.db\u0026quot;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EProgram.cs\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 connectionString = builder.Configuration.GetConnectionString(\u0026quot;UltimateAuth\u0026quot;)\n ?? throw new InvalidOperationException(\u0026quot;UltimateAuth connection string is missing.\u0026quot;);\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(connectionString);\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor production databases, use your deployment environment\u0027s secret management facilities rather than storing credentials in source control.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022database-migrations\u0022\u003EDatabase migrations\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth does not automatically create or apply migrations when the EF Core bundle is registered.\u003C/p\u003E\n\u003Cp\u003EYour application owns the database migration lifecycle.\u003C/p\u003E\n\u003Cp\u003EInstall the EF Core CLI if 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\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet tool install --global dotnet-ef\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECreate the initial migration:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet ef migrations add InitUAuth --context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EApply 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\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet ef database update --context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor Visual Studio Package Manager Console:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EPOWERSHELL\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-powershell\u0022\u003EAdd-Migration InitUAuth -Context UAuthDbContext\nUpdate-Database -Context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EInstall \u003Ccode\u003EMicrosoft.EntityFrameworkCore.Tools\u003C/code\u003E if required.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022applying-migrations-programmatically\u0022\u003EApplying migrations programmatically\u003C/h3\u003E\n\u003Cp\u003EThe EF Core samples demonstrate initialization through a scoped \u003Ccode\u003EUAuthDbContext\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor a controlled development environment:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore;\n\nusing (var scope = app.Services.CreateScope())\n{\n var db = scope.ServiceProvider.GetRequiredService\u0026lt;UAuthDbContext\u0026gt;();\n await db.Database.MigrateAsync();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis applies pending migrations. It does not generate migration files.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EProduction guidance:\u003C/strong\u003E Prefer applying reviewed migrations as a separate deployment step, particularly when multiple application instances share a database.\u003C/p\u003E\n\u003Cp\u003EDo not copy the sample initializer\u0027s \u003Ccode\u003Ereset: true\u003C/code\u003E behavior into production. The samples use database reset functionality for repeatable demonstrations, not as a safe deployment strategy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022optional-sample-accounts-with-ef-core\u0022\u003EOptional: sample accounts with EF Core\u003C/h3\u003E\n\u003Cp\u003EThe sample seed package can also work with EF Core persistence.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Sample.Seed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERegister the scoped seed services:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddScopedUltimateAuthSampleSeed();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter preparing the database:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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 (app.Environment.IsDevelopment())\n{\n await app.SeedUltimateAuthAsync();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe difference matters:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EAddUltimateAuthSampleSeed()\u003C/code\u003E is used by the in-memory samples.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAddScopedUltimateAuthSampleSeed()\u003C/code\u003E is used by the EF Core samples.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EDo not deploy the demonstration accounts and passwords to production.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-integrated-authentication\u0022\u003E3. Blazor Server \u2014 Integrated Authentication\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E A Blazor Server application that wants to host its own authentication system.\u003C/p\u003E\n\u003Cp\u003EThis is the simplest deployment model for a server-side Blazor application.\u003C/p\u003E\n\u003Cp\u003EThe application hosts UltimateAuth Server and uses the Blazor client adapter in the same project.\u003C/p\u003E\n\u003Cp\u003EA separate UAuthHub is not required.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022packages\u0022\u003EPackages\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\ndotnet add package Microsoft.EntityFrameworkCore.Sqlite\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022services\u0022\u003EServices\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\u003Eusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.EntityFrameworkCore;\nusing CodeBeam.UltimateAuth.Server.Extensions;\nusing Microsoft.EntityFrameworkCore;\n\nbuilder.Services\n .AddRazorComponents()\n .AddInteractiveServerComponents();\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(\u0026quot;Data Source=uauth.db\u0026quot;);\n });\n\nbuilder.Services.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pipeline\u0022\u003EPipeline\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\u003Eapp.UseHttpsRedirection();\napp.UseStaticFiles();\n\napp.UseUltimateAuthWithAspNetCore();\napp.UseAntiforgery();\n\napp.MapUltimateAuthEndpoints();\n\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveServerRenderMode()\n .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient());\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAlso configure the \u003Ccode\u003EUAuthApp\u003C/code\u003E root component, login page, and UltimateAuth JavaScript file as demonstrated in QuickStart.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-behavior\u0022\u003EAuthentication behavior\u003C/h3\u003E\n\u003Cp\u003EThe default client profile resolution selects \u003Ccode\u003EPureOpaque\u003C/code\u003E for Blazor Server.\u003C/p\u003E\n\u003Cp\u003EThis uses session-oriented authentication rather than requiring the application to manage access and refresh tokens manually.\u003C/p\u003E\n\u003Cp\u003EYou can configure session lifetimes, login policies, credential policies, and other behavior through \u003Ccode\u003EAddUltimateAuthServer()\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\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Login.MaxFailedAttempts = 5;\n options.Login.LockoutDuration = TimeSpan.FromMinutes(15);\n options.Session.Lifetime = TimeSpan.FromDays(7);\n options.Identifiers.Behavior.AllowMultipleUsernames = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe effective authentication mode is resolved at runtime. The application does not need to select a single mode globally.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022uauthhub-dedicated-authentication-server\u0022\u003E4. UAuthHub \u2014 Dedicated Authentication Server\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E Applications where multiple clients share one authentication authority.\u003C/p\u003E\n\u003Cp\u003EUAuthHub hosts UltimateAuth\u0027s authentication services separately from your frontend applications.\u003C/p\u003E\n\u003Cp\u003EIt is particularly relevant for standalone WASM applications, Interactive WASM applications, and systems with separate Resource APIs.\u003C/p\u003E\n\u003Cp\u003EA typical architecture 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\u003E Standalone WASM Interactive WASM\n \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u25BC\n UAuthHub\n Authentication\n Sessions \u0026amp; Users\n \u2502\n \u25BC\n Persistence\n\n Resource API \u2500\u2500\u2500\u25BA UAuthHub\n (validation)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-the-hub-application\u0022\u003ECreate the Hub application\u003C/h3\u003E\n\u003Cp\u003EA UAuthHub can be hosted in an ASP.NET Core Blazor Server application.\u003C/p\u003E\n\u003Cp\u003EThe current samples use Blazor Server to provide the Hub\u0027s login UI.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022in-memory-uauthhub\u0022\u003EIn-memory UAuthHub\u003C/h3\u003E\n\u003Cp\u003EFor development:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.InMemory.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERegister:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.InMemory;\nusing CodeBeam.UltimateAuth.Server.Extensions;\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthInMemory()\n .AddUAuthHub(options =\u0026gt;\n {\n options.AllowedClientOrigins.Add(\u0026quot;https://localhost:6130\u0026quot;);\n });\n\nbuilder.Services.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe Hub\u0027s Blazor client registration is needed when the Hub itself hosts UltimateAuth-powered Blazor pages and login UI.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022ef-core-uauthhub\u0022\u003EEF Core UAuthHub\u003C/h3\u003E\n\u003Cp\u003EFor persistent storage:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore;\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(\u0026quot;Data Source=uauthhub.db\u0026quot;);\n })\n .AddUAuthHub(options =\u0026gt;\n {\n options.AllowedClientOrigins.Add(\u0026quot;https://localhost:6132\u0026quot;);\n });\n\nbuilder.Services.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003EAllowedClientOrigins\u003C/code\u003E collection identifies browser origins permitted to interact with the Hub.\u003C/p\u003E\n\u003Cp\u003EUse exact origins, including scheme and port. Do not include paths such as \u003Ccode\u003E/home\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hub-pipeline\u0022\u003EHub pipeline\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\u003Eapp.UseHttpsRedirection();\n\napp.UseUltimateAuthWithAspNetCore();\napp.UseAntiforgery();\n\napp.MapUltimateAuthEndpoints();\napp.MapUAuthHub();\n\napp.MapStaticAssets();\n\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveServerRenderMode()\n .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient());\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EMapUltimateAuthEndpoints()\u003C/code\u003E maps the authentication endpoints.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EMapUAuthHub()\u003C/code\u003E maps the Hub-specific entry endpoint.\u003C/p\u003E\n\u003Cp\u003EBoth registrations are used by the current UAuthHub samples.\u003C/p\u003E\n\u003Cp\u003EThe Hub also needs its login page and Blazor application lifecycle components.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-origins\u0022\u003EChoosing origins\u003C/h3\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EApplication\u003C/th\u003E\n\u003Cth\u003EURL\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUAuthHub\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ehttps://localhost:6112\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EStandalone WASM\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ehttps://localhost:6130\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInteractive WASM\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ehttps://localhost:6132\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EConfigure the Hub\u0027s allowed origins for the actual frontend applications it serves.\u003C/p\u003E\n\u003Cp\u003EThese localhost addresses are illustrative and must match your deployment.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-standalone-webassembly\u0022\u003E5. Blazor Standalone WebAssembly\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E A browser-hosted application with a separate UAuthHub.\u003C/p\u003E\n\u003Cp\u003EStandalone Blazor WASM runs in the browser. It does not host UltimateAuth Server or database persistence.\u003C/p\u003E\n\u003Cp\u003EAuthentication is handled by the remote UAuthHub.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-packages\u0022\u003EClient packages\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services\u0022\u003EConfigure services\u003C/h3\u003E\n\u003Cp\u003EIn the standalone WASM application\u0027s \u003Ccode\u003EProgram.cs\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\u003Eusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.Core.Domain;\n\nvar builder = WebAssemblyHostBuilder.CreateDefault(args);\n\nbuilder.RootComponents.Add\u0026lt;App\u0026gt;(\u0026quot;#app\u0026quot;);\nbuilder.RootComponents.Add\u0026lt;HeadOutlet\u0026gt;(\u0026quot;head::after\u0026quot;);\n\nbuilder.Services.AddScoped(sp =\u0026gt; new HttpClient\n{\n BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)\n});\n\nbuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;https://localhost:6110/auth\u0026quot;;\n options.Pkce.ReturnUrl = \u0026quot;https://localhost:6130/home\u0026quot;;\n options.Login.AllowCredentialPost = true;\n options.Reauth.Behavior = ReauthBehavior.RaiseEvent;\n});\n\nawait builder.Build().RunAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe current standalone WASM sample explicitly configures \u003Ccode\u003ELogin.AllowCredentialPost\u003C/code\u003E and the PKCE return URL.\u003C/p\u003E\n\u003Cp\u003EThese are client-side settings. The UAuthHub must be configured independently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-the-urls-mean\u0022\u003EWhat the URLs mean\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EEndpoints.BasePath\u003C/code\u003E points to the remote authentication endpoints.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EPkce.ReturnUrl\u003C/code\u003E identifies the frontend destination used by the relevant PKCE flow.\u003C/p\u003E\n\u003Cp\u003EThe Hub must recognize the frontend\u0027s origin, and any return URL used in an authentication flow must be validated against the application\u0027s security policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-component\u0022\u003ERoot component\u003C/h3\u003E\n\u003Cp\u003EA standalone WASM application should initialize UltimateAuth through \u003Ccode\u003EUAuthApp\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example, its root application or routing component can contain:\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;UAuthApp UseBuiltInRouter=\u0026quot;true\u0026quot; AppAssembly=\u0026quot;typeof(Program).Assembly\u0026quot; DefaultLayout=\u0026quot;typeof(Layout.MainLayout)\u0026quot; /\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EInclude the UltimateAuth JavaScript asset in the application\u0027s HTML host page.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-modes\u0022\u003EAuthentication modes\u003C/h3\u003E\n\u003Cp\u003EThe default \u003Ccode\u003EBlazorWasm\u003C/code\u003E profile resolves to \u003Ccode\u003EHybrid\u003C/code\u003E in the current mode resolver.\u003C/p\u003E\n\u003Cp\u003EDo not manually force \u003Ccode\u003EPureOpaque\u003C/code\u003E for a standalone browser client simply because a Blazor Server application uses it.\u003C/p\u003E\n\u003Cp\u003EThe transport and authentication lifecycle must match the client environment.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-web-app-interactive-webassembly\u0022\u003E6. Blazor Web App \u2014 Interactive WebAssembly\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E A Blazor Web App with an ASP.NET Core host and a separate \u003Ccode\u003E.Client\u003C/code\u003E project.\u003C/p\u003E\n\u003Cp\u003EThis scenario differs from standalone WASM because components may participate in both server-side rendering and browser-side interactivity.\u003C/p\u003E\n\u003Cp\u003EThe current UltimateAuth sample registers the Blazor client integration in \u003Cstrong\u003Eboth projects\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EIt also adds a separate ASP.NET Core compatibility registration in the host.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022project-structure\u0022\u003EProject structure\u003C/h3\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\u003EMyApplication/\n\u251C\u2500\u2500 MyApplication/\n\u2502 \u251C\u2500\u2500 Program.cs\n\u2502 \u2514\u2500\u2500 Components/\n\u2502 \u2514\u2500\u2500 App.razor\n\u2502\n\u2514\u2500\u2500 MyApplication.Client/\n \u251C\u2500\u2500 Program.cs\n \u2514\u2500\u2500 Pages/\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe host serves the application.\u003C/p\u003E\n\u003Cp\u003EThe \u003Ccode\u003E.Client\u003C/code\u003E project contains browser-executed components and services.\u003C/p\u003E\n\u003Cp\u003EA remote UAuthHub manages authentication.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022host-project-packages\u0022\u003E6.1 Host project packages\u003C/h3\u003E\n\u003Cp\u003EInstall the Blazor client integration and ASP.NET Core compatibility package in the host:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.Client.AspNetCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022host-project-services\u0022\u003E6.2 Host project services\u003C/h3\u003E\n\u003Cp\u003EIn the host \u003Ccode\u003EProgram.cs\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\u003Eusing CodeBeam.UltimateAuth.Client.AspNetCore;\nusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.Core.Domain;\n\nbuilder.Services\n .AddRazorComponents()\n .AddInteractiveWebAssemblyComponents();\n\nbuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;;\n options.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;;\n options.Login.AllowCredentialPost = true;\n options.Reauth.Behavior = ReauthBehavior.RaiseEvent;\n});\n\nbuilder.Services.AddUltimateAuthAspNetCoreCompatibility();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthAspNetCoreCompatibility()\u003C/code\u003E registers the compatibility authentication scheme and ASP.NET Core authorization services used by this hosting pattern.\u003C/p\u003E\n\u003Cp\u003EIt does \u003Cstrong\u003Enot\u003C/strong\u003E turn the host into an UltimateAuth authentication server.\u003C/p\u003E\n\u003Cp\u003EIt is also not equivalent to \u003Ccode\u003EAddUltimateAuthResourceApi()\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022host-project-pipeline\u0022\u003E6.3 Host project pipeline\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\u003Eapp.UseHttpsRedirection();\n\napp.UseAntiforgery();\n\napp.MapStaticAssets();\n\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveWebAssemblyRenderMode()\n .AddAdditionalAssemblies(typeof(MyApplication.Client._Imports).Assembly, UAuthAssemblies.BlazorClient().First());\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EReplace \u003Ccode\u003EMyApplication.Client._Imports\u003C/code\u003E with the corresponding type from your client project.\u003C/p\u003E\n\u003Cp\u003EThe additional assemblies allow the host to discover routable components from the WASM client project and UltimateAuth\u0027s Blazor client assembly.\u003C/p\u003E\n\u003Cp\u003EThe sample does not register \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E in this host because authentication is delegated to a separate UAuthHub.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-project-packages\u0022\u003E6.4 Client project packages\u003C/h3\u003E\n\u003Cp\u003EInstall the Blazor client adapter in the \u003Ccode\u003E.Client\u003C/code\u003E project:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-project-services\u0022\u003E6.5 Client project services\u003C/h3\u003E\n\u003Cp\u003EIn \u003Ccode\u003EMyApplication.Client/Program.cs\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\u003Eusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.Core.Domain;\n\nvar builder = WebAssemblyHostBuilder.CreateDefault(args);\n\nbuilder.Services.AddScoped(sp =\u0026gt; new HttpClient\n{\n BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)\n});\n\nbuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;;\n options.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;;\n options.Login.AllowCredentialPost = true;\n options.Reauth.Behavior = ReauthBehavior.RaiseEvent;\n});\n\nawait builder.Build().RunAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe host and client configurations should agree on the remote UAuthHub URL and authentication flow settings.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-both-registrations-are-necessary\u0022\u003EWhy both registrations are necessary\u003C/h3\u003E\n\u003Cp\u003EThe host and browser client have separate dependency injection containers.\u003C/p\u003E\n\u003Cp\u003ERegistering \u003Ccode\u003EAddUltimateAuthClientBlazor()\u003C/code\u003E in the host does not automatically register those services inside the WebAssembly runtime.\u003C/p\u003E\n\u003Cp\u003ELikewise, registering the client adapter only in the \u003Ccode\u003E.Client\u003C/code\u003E project does not supply the services required during server-side integration.\u003C/p\u003E\n\u003Cp\u003EThe additional ASP.NET Core compatibility registration addresses the host\u0027s ASP.NET Core authentication and authorization integration requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E This compatibility layer is not a replacement for server-side authorization of your protected business APIs.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-api-separate-protected-backend\u0022\u003E7. Resource API \u2014 Separate Protected Backend\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E An API that needs UltimateAuth authentication validation but does not host the authentication system itself.\u003C/p\u003E\n\u003Cp\u003EA Resource API can be an ASP.NET Core Web API, MVC application, or another supported server-side application.\u003C/p\u003E\n\u003Cp\u003EIt does not need to be a Blazor application.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022architecture\u0022\u003EArchitecture\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Blazor Client\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2510\n \u25BC \u25BC\n UAuthHub Resource API\n \u25B2 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n Authentication\n validation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe Resource API uses the configured UAuthHub for remote authentication and session validation behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-the-server-integration-package\u0022\u003EInstall the server integration package\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Server\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services-1\u0022\u003EConfigure services\u003C/h3\u003E\n\u003Cp\u003EIn the Resource API\u0027s \u003Ccode\u003EProgram.cs\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\u003Eusing CodeBeam.UltimateAuth.Server.Extensions;\n\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddControllers();\n\nbuilder.Services.AddUltimateAuthResourceApi(options =\u0026gt;\n{\n options.UAuthHubBaseUrl = \u0026quot;https://localhost:6112\u0026quot;;\n options.AllowedClientOrigins.Add(\u0026quot;https://localhost:6132\u0026quot;);\n options.CorsPolicyName = \u0026quot;resourceApi\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthHubBaseUrl\u003C/code\u003E is the base address of the authentication server.\u003C/p\u003E\n\u003Cp\u003ENotice that this example uses the Hub origin without the \u003Ccode\u003E/auth\u003C/code\u003E suffix, matching the current Resource API samples.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAllowedClientOrigins\u003C/code\u003E controls the browser origins permitted by the Resource API\u0027s CORS configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-pipeline\u0022\u003EConfigure the pipeline\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 app = builder.Build();\n\napp.UseHttpsRedirection();\n\napp.UseUltimateAuthResourceApiWithAspNetCore();\n\napp.MapControllers();\n\napp.Run();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUseUltimateAuthResourceApiWithAspNetCore()\u003C/code\u003E installs the Resource API integration middleware together with ASP.NET Core authentication and authorization middleware.\u003C/p\u003E\n\u003Cp\u003EYou do not need to register a local UltimateAuth Server or its persistence bundle in the Resource API.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022protecting-endpoints\u0022\u003EProtecting endpoints\u003C/h3\u003E\n\u003Cp\u003EUse ASP.NET Core authorization attributes for endpoints that require authentication:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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 Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Mvc;\n\n[ApiController]\n[Route(\u0026quot;api/products\u0026quot;)]\npublic class ProductsController : ControllerBase\n{\n [HttpGet]\n [Authorize]\n public IActionResult GetProducts()\n {\n return Ok(new[]\n {\n new { Id = 1, Name = \u0026quot;Example product\u0026quot; }\n });\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe authorization attribute is enforced by ASP.NET Core on the server.\u003C/p\u003E\n\u003Cp\u003EFor permission-specific rules, configure and apply the corresponding authorization policies.\u003C/p\u003E\n\u003Cp\u003EDo not rely on Blazor UI authorization checks to protect Resource API endpoints.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022cors-configuration\u0022\u003ECORS configuration\u003C/h3\u003E\n\u003Cp\u003EThe current Resource API integration registers CORS from the configured allowed origins.\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\u003Eoptions.AllowedClientOrigins.Add(\u0026quot;https://localhost:6132\u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOnly add origins that should be allowed to call your API from a browser.\u003C/p\u003E\n\u003Cp\u003ECORS is a browser access control mechanism, not an authentication or authorization mechanism.\u003C/p\u003E\n\u003Cp\u003EA request must still pass the applicable authentication and authorization checks.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-api-vs-uauthhub\u0022\u003EResource API vs UAuthHub\u003C/h3\u003E\n\u003Cp\u003EThese are different responsibilities.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EUAuthHub\u003C/th\u003E\n\u003Cth\u003EResource API\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EHosts authentication flows\u003C/td\u003E\n\u003Ctd\u003EHosts application resources\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EManages authentication sessions\u003C/td\u003E\n\u003Ctd\u003EValidates remote authentication context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUses authentication persistence\u003C/td\u003E\n\u003Ctd\u003EDoes not require local authentication stores\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EProvides login and related endpoints\u003C/td\u003E\n\u003Ctd\u003EProvides application endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EServes authentication clients\u003C/td\u003E\n\u003Ctd\u003EServes authorized API consumers\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EUse a Resource API when you want independent deployment of business endpoints without duplicating the authentication server.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-api-with-an-ef-core-backed-uauthhub\u0022\u003E8. Resource API with an EF Core-Backed UAuthHub\u003C/h2\u003E\n\u003Cp\u003EThe Resource API does not need to use EF Core just because UAuthHub does.\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\u003EInteractive WASM\n \u2502\n \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25BA Resource API\n \u2502\n \u25BC\n UAuthHub\n \u2502\n \u25BC\n EF Core Database\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe Hub stores users, credentials, sessions, and related security state in its database.\u003C/p\u003E\n\u003Cp\u003EThe Resource API communicates with the Hub through the configured remote validation integration.\u003C/p\u003E\n\u003Cp\u003EIts registration remains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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://localhost:6112\u0026quot;;\n options.AllowedClientOrigins.Add(\u0026quot;https://localhost:6132\u0026quot;);\n options.CorsPolicyName = \u0026quot;resourceApi\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis separation allows the API and authentication infrastructure to be deployed, scaled, and maintained independently.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022calling-a-resource-api-from-blazor\u0022\u003E9. Calling a Resource API from Blazor\u003C/h2\u003E\n\u003Cp\u003EThe current samples demonstrate calling a separate Resource API from Blazor applications.\u003C/p\u003E\n\u003Cp\u003EThe client integration and the business API\u0027s HTTP client are separate concerns.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022interactive-wasm-host\u0022\u003EInteractive WASM host\u003C/h3\u003E\n\u003Cp\u003EIn the ASP.NET Core host:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddHttpClient\u0026lt;SampleProductApiService\u0026gt;(\n client =\u0026gt;\n {\n client.BaseAddress = new Uri(\u0026quot;https://localhost:6122\u0026quot;);\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022interactive-wasm-client\u0022\u003EInteractive WASM client\u003C/h3\u003E\n\u003Cp\u003ERegister the same application API service in the \u003Ccode\u003E.Client\u003C/code\u003E project:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddHttpClient\u0026lt;SampleProductApiService\u0026gt;(\n client =\u0026gt;\n {\n client.BaseAddress = new Uri(\u0026quot;https://localhost:6122\u0026quot;);\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe host and client each require the service when application components or services execute in their respective environments.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003ESampleProductApiService\u003C/code\u003E is an example application service, not a required UltimateAuth framework type.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022standalone-wasm\u0022\u003EStandalone WASM\u003C/h3\u003E\n\u003Cp\u003EA standalone WASM application can register an HTTP client for its Resource 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\u003Ebuilder.Services.AddScoped(sp =\u0026gt; new HttpClient\n{\n BaseAddress = new Uri(\u0026quot;https://localhost:6120\u0026quot;)\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIf your application has multiple HTTP clients, prefer explicitly named or typed clients to avoid ambiguity.\u003C/p\u003E\n\u003Cp\u003EThe current samples also demonstrate browser request credentials handling for cookie-based requests.\u003C/p\u003E\n\u003Cp\u003EFor cross-origin cookie authentication, the browser request, API CORS policy, cookie attributes, and Hub configuration must all be compatible.\u003C/p\u003E\n\u003Cp\u003EDo not assume that registering an \u003Ccode\u003EHttpClient\u003C/code\u003E automatically forwards authentication credentials to every remote API.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-modes-and-client-profiles\u0022\u003E10. Authentication Modes and Client Profiles\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates \u003Cstrong\u003Eclient profile detection\u003C/strong\u003E from \u003Cstrong\u003Eauthentication mode enforcement\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe default resolver maps client profiles to authentication modes.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EClient profile\u003C/th\u003E\n\u003Cth\u003EDefault resolved mode\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\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor WASM\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EWeb Server\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMAUI\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAPI\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureJwt\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis mapping describes the resolver\u0027s behavior, not a guarantee that every mode is currently fully supported by the server.\u003C/p\u003E\n\u003Cp\u003EIn particular, \u003Ccode\u003EPureJwt\u003C/code\u003E and \u003Ccode\u003ESemiHybrid\u003C/code\u003E have implementation limitations in the current release.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022restrict-allowed-modes\u0022\u003ERestrict allowed modes\u003C/h3\u003E\n\u003Cp\u003EOn the authentication 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\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\u003E\u003Ccode\u003EAllowedModes\u003C/code\u003E is a guardrail.\u003C/p\u003E\n\u003Cp\u003EIt does not select a mode for every request.\u003C/p\u003E\n\u003Cp\u003EThe effective mode is resolved from the client profile and then checked against the server\u0027s allowed-mode policy.\u003C/p\u003E\n\u003Cp\u003EFor most applications, use the default mode resolution unless you have a specific integration requirement.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-options-for-real-applications\u0022\u003E11. Server Options for Real Applications\u003C/h2\u003E\n\u003Cp\u003EProduction configuration should reflect your application\u0027s security requirements.\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.Login.MaxFailedAttempts = 5;\n options.Login.LockoutDuration = TimeSpan.FromMinutes(15);\n options.Identifiers.Behavior.AllowMultipleUsernames = true;\n options.UserProfile.EnableMultiProfile = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EServer options also compose the Core-defined login, session, token, PKCE, and multi-tenant options.\u003C/p\u003E\n\u003Cp\u003EConfigure those through \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EDo not independently configure Core options and assume the server will automatically use them.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-files\u0022\u003EConfiguration files\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth also supports configuration through \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 \u0026quot;LockoutDuration\u0026quot;: \u0026quot;00:15:00\u0026quot;\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server options registration binds the \u003Ccode\u003EUltimateAuth:Server\u003C/code\u003E configuration section.\u003C/p\u003E\n\u003Cp\u003ERemember that effective mode defaults and mode-specific overrides are applied after the base options have been configured.\u003C/p\u003E\n\u003Cp\u003EFor detailed configuration behavior, see Server Options and Advanced Configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022development-and-production-differences\u0022\u003E12. Development and Production Differences\u003C/h2\u003E\n\u003Cp\u003EThe samples intentionally prioritize visibility and repeatability.\u003C/p\u003E\n\u003Cp\u003EA production application needs additional safeguards.\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\u003ESample or development\u003C/th\u003E\n\u003Cth\u003EProduction\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPersistence\u003C/td\u003E\n\u003Ctd\u003EInMemory or local SQLite\u003C/td\u003E\n\u003Ctd\u003EDurable database\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESeed accounts\u003C/td\u003E\n\u003Ctd\u003EExample users and passwords\u003C/td\u003E\n\u003Ctd\u003EReal provisioning\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDatabase initialization\u003C/td\u003E\n\u003Ctd\u003EMay reset sample data\u003C/td\u003E\n\u003Ctd\u003EReviewed migrations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EHTTPS\u003C/td\u003E\n\u003Ctd\u003ELocal development certificate\u003C/td\u003E\n\u003Ctd\u003EProper TLS termination\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECORS\u003C/td\u003E\n\u003Ctd\u003ELocalhost origins\u003C/td\u003E\n\u003Ctd\u003EExact trusted origins\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESigning keys\u003C/td\u003E\n\u003Ctd\u003EDevelopment defaults\u003C/td\u003E\n\u003Ctd\u003EManaged, persistent keys\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDiagnostics\u003C/td\u003E\n\u003Ctd\u003EDetailed debugging\u003C/td\u003E\n\u003Ctd\u003ERestrict sensitive diagnostics\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential recovery\u003C/td\u003E\n\u003Ctd\u003EDevelopment notifier\u003C/td\u003E\n\u003Ctd\u003EReal secure notification channel\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIdentifier verification\u003C/td\u003E\n\u003Ctd\u003EDevelopment bypass\u003C/td\u003E\n\u003Ctd\u003EProof-based verification\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESecrets\u003C/td\u003E\n\u003Ctd\u003ELocal settings\u003C/td\u003E\n\u003Ctd\u003ESecure configuration provider\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe sample projects include development-only implementations for credential reset notifications and identifier verification.\u003C/p\u003E\n\u003Cp\u003EThose implementations demonstrate extension points and should not be treated as production verification systems.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-recovery\u0022\u003ECredential recovery\u003C/h3\u003E\n\u003Cp\u003EIf your application exposes credential recovery, configure a real implementation of the credential reset notifier.\u003C/p\u003E\n\u003Cp\u003ERecovery secrets must be delivered through an appropriate channel and must not be exposed directly through anonymous API responses.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-verification\u0022\u003EIdentifier verification\u003C/h3\u003E\n\u003Cp\u003EIf your application supports verified email addresses, phone numbers, or other identifiers, use an implementation that verifies ownership through suitable proof.\u003C/p\u003E\n\u003Cp\u003EDo not use a development bypass verifier in production.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022signing-keys\u0022\u003ESigning keys\u003C/h3\u003E\n\u003Cp\u003EConfigure persistent signing keys for authentication modes that issue JWTs.\u003C/p\u003E\n\u003Cp\u003EDevelopment signing key providers are not a substitute for production key management.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022deployment-checklist\u0022\u003E13. Deployment Checklist\u003C/h2\u003E\n\u003Cp\u003EBefore deploying, verify the following:\u003C/p\u003E\n\u003Cul class=\u0022contains-task-list\u0022\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E The selected architecture matches the actual client hosting model.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E The authentication server uses appropriate persistent storage.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Database migrations have been reviewed and applied.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Sample users and development-only verification services are disabled.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Production signing keys and secrets are configured.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E HTTPS is enforced at the appropriate boundary.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E UAuthHub and Resource API origins are explicitly configured.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Authentication endpoints and application endpoints are protected appropriately.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Client and server endpoint URLs match the deployment.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Session expiration, refresh, logout, and revocation have been tested.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Protected resources enforce server-side authorization.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Authentication state and persistence survive the expected application lifecycle.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-your-architecture\u0022\u003E14. Choosing Your Architecture\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EStart with one server and one client. Connect more clients whenever you need them.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EUltimateAuth is built around two fundamental responsibilities:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EUltimateAuth Server\u003C/strong\u003E \u2014 Manages authentication, users, credentials, sessions, authorization, and the security lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUltimateAuth Client\u003C/strong\u003E \u2014 Integrates an application with the authentication system using the appropriate client profile and authentication flows.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EEvery application needs access to an authentication server, but that server does not have to be a separate application. It can be hosted within your existing ASP.NET Core application or deployed independently as UAuthHub.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022single-application-the-simplest-setup\u0022\u003ESingle Application \u2014 The Simplest Setup\u003C/h3\u003E\n\u003Cp\u003EFor a traditional ASP.NET Core or Blazor Server application, UltimateAuth Server and Client can run together.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Your Application \u2502\n\u2502 \u2502\n\u2502 UltimateAuth Client \u2502\n\u2502 \u2502 \u2502\n\u2502 UltimateAuth Server \u2502\n\u2502 \u2502 \u2502\n\u2502 Database \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis approach is ideal when you want authentication without introducing another service or deployment.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022multiple-applications-one-authentication-server\u0022\u003EMultiple Applications \u2014 One Authentication Server\u003C/h3\u003E\n\u003Cp\u003EAs your system grows, you can connect multiple client applications to the same UltimateAuth 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\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Blazor Server Blazor WASM\n \u2502 \u2502\n \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n \u2502 \u2502 \u2502\n Razor Pages MAUI Other .NET Clients\n \u2502 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u25BC\n UltimateAuth Server\n UAuthHub\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u25BC \u25BC \u25BC\n Users Sessions Authorization\n \u2502\n \u25BC\n Persistence\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach client can have its own application structure, hosting model, and authentication requirements while sharing the same authentication authority.\u003C/p\u003E\n\u003Cp\u003EFor example, a business platform might use Blazor Server for administration, Blazor WASM for its customer portal, MAUI for mobile access, and Razor Pages for an internal application.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAdding another client does not require replacing the existing clients or creating another UltimateAuth Server.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThe same users, credentials, security policies, and session infrastructure can serve multiple applications, subject to the authentication modes and client integrations supported by the installed version.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022distributed-applications-separate-resource-apis\u0022\u003EDistributed Applications \u2014 Separate Resource APIs\u003C/h3\u003E\n\u003Cp\u003EWhen your architecture includes independently deployed backend services, Resource APIs can integrate with the same authentication authority.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Multiple Client Applications\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u25BC \u25BC\n UAuthHub Resource APIs\n \u25B2 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n Authentication\n Validation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EResource APIs protect application resources without becoming additional authentication servers.\u003C/p\u003E\n\u003Cp\u003EThis lets you separate business services from authentication infrastructure while maintaining a shared security model.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022one-foundation-different-scales\u0022\u003EOne Foundation, Different Scales\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EYour scenario\u003C/th\u003E\n\u003Cth\u003ERecommended approach\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA simple Blazor Server application\u003C/td\u003E\n\u003Ctd\u003EIntegrated Server \u002B Client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA traditional ASP.NET Core application\u003C/td\u003E\n\u003Ctd\u003EServer hosted within the application\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA standalone Blazor WASM application\u003C/td\u003E\n\u003Ctd\u003EClient \u002B remote UAuthHub\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAn Interactive WASM application\u003C/td\u003E\n\u003Ctd\u003EHost \u002B WASM Client \u002B remote UAuthHub\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMultiple web, desktop, or mobile applications\u003C/td\u003E\n\u003Ctd\u003EMultiple Clients \u002B shared UAuthHub\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMultiple backend services\u003C/td\u003E\n\u003Ctd\u003EShared UAuthHub \u002B Resource APIs\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EUltimateAuth does not require a complex architecture to get started, and it does not restrict you to a single client as your application grows.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EStart with the simplest deployment that meets your needs. Add new client applications, persistent storage, or separate Resource APIs as your requirements evolve.\u003C/p\u003E\n\u003Cp\u003EThe authentication foundation remains consistent.\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/getting-started/quickstart\u0022\u003EQuickStart\u003C/a\u003E \u2014 Build the minimal in-memory application.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/getting-started/ecosystem\u0022\u003EEcosystem\u003C/a\u003E \u2014 Explore packages and supported integrations.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E \u2014 Configure server 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, endpoints, refresh, and reauthentication.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/advanced-configuration\u0022\u003EAdvanced Configuration\u003C/a\u003E \u2014 Explore effective options, dependency injection, and extension points.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { - "Id": "using-entity-framework-core", - "Text": "\uD83D\uDDC4\uFE0F Using Entity Framework Core", + "Id": "choose-your-architecture", + "Text": "1. Choose Your Architecture", + "Level": 0 + }, + { + "Id": "persistent-storage-with-entity-framework-core", + "Text": "2. Persistent Storage with Entity Framework Core", "Level": 0 }, { "Id": "install-packages", - "Text": "Install Packages", + "Text": "Install packages", "Level": 1 }, { - "Id": "configure-services", - "Text": "Configure Services", + "Id": "register-the-services", + "Text": "Register the services", + "Level": 1 + }, + { + "Id": "configure-the-database-connection", + "Text": "Configure the database connection", "Level": 1 }, { "Id": "database-migrations", - "Text": "Database Migrations", + "Text": "Database migrations", + "Level": 1 + }, + { + "Id": "applying-migrations-programmatically", + "Text": "Applying migrations programmatically", "Level": 1 }, { - "Id": "who-owns-the-migrations", - "Text": "Who Owns the Migrations?", + "Id": "optional-sample-accounts-with-ef-core", + "Text": "Optional: sample accounts with EF Core", "Level": 1 }, { - "Id": "configure-services-with-options", - "Text": "Configure Services With Options", + "Id": "blazor-server-integrated-authentication", + "Text": "3. Blazor Server \u2014 Integrated Authentication", "Level": 0 }, { - "Id": "blazor-standalone-wasm-setup", - "Text": "Blazor Standalone WASM Setup", + "Id": "packages", + "Text": "Packages", + "Level": 1 + }, + { + "Id": "services", + "Text": "Services", + "Level": 1 + }, + { + "Id": "pipeline", + "Text": "Pipeline", + "Level": 1 + }, + { + "Id": "authentication-behavior", + "Text": "Authentication behavior", + "Level": 1 + }, + { + "Id": "uauthhub-dedicated-authentication-server", + "Text": "4. UAuthHub \u2014 Dedicated Authentication Server", + "Level": 0 + }, + { + "Id": "create-the-hub-application", + "Text": "Create the Hub application", + "Level": 1 + }, + { + "Id": "in-memory-uauthhub", + "Text": "In-memory UAuthHub", + "Level": 1 + }, + { + "Id": "ef-core-uauthhub", + "Text": "EF Core UAuthHub", + "Level": 1 + }, + { + "Id": "hub-pipeline", + "Text": "Hub pipeline", + "Level": 1 + }, + { + "Id": "choosing-origins", + "Text": "Choosing origins", + "Level": 1 + }, + { + "Id": "blazor-standalone-webassembly", + "Text": "5. Blazor Standalone WebAssembly", "Level": 0 }, { - "Id": "blazor-web-app-setup", - "Text": "Blazor Web App Setup", + "Id": "client-packages", + "Text": "Client packages", + "Level": 1 + }, + { + "Id": "configure-services", + "Text": "Configure services", + "Level": 1 + }, + { + "Id": "what-the-urls-mean", + "Text": "What the URLs mean", + "Level": 1 + }, + { + "Id": "root-component", + "Text": "Root component", + "Level": 1 + }, + { + "Id": "authentication-modes", + "Text": "Authentication modes", + "Level": 1 + }, + { + "Id": "blazor-web-app-interactive-webassembly", + "Text": "6. Blazor Web App \u2014 Interactive WebAssembly", "Level": 0 }, { - "Id": "resourceapi-setup", - "Text": "ResourceApi Setup", + "Id": "project-structure", + "Text": "Project structure", + "Level": 1 + }, + { + "Id": "host-project-packages", + "Text": "6.1 Host project packages", + "Level": 1 + }, + { + "Id": "host-project-services", + "Text": "6.2 Host project services", + "Level": 1 + }, + { + "Id": "host-project-pipeline", + "Text": "6.3 Host project pipeline", + "Level": 1 + }, + { + "Id": "client-project-packages", + "Text": "6.4 Client project packages", + "Level": 1 + }, + { + "Id": "client-project-services", + "Text": "6.5 Client project services", + "Level": 1 + }, + { + "Id": "why-both-registrations-are-necessary", + "Text": "Why both registrations are necessary", + "Level": 1 + }, + { + "Id": "resource-api-separate-protected-backend", + "Text": "7. Resource API \u2014 Separate Protected Backend", "Level": 0 }, { - "Id": "how-to-think-about-setup", - "Text": "\uD83E\uDDE0 How to Think About Setup", + "Id": "architecture", + "Text": "Architecture", + "Level": 1 + }, + { + "Id": "install-the-server-integration-package", + "Text": "Install the server integration package", + "Level": 1 + }, + { + "Id": "configure-services-1", + "Text": "Configure services", + "Level": 1 + }, + { + "Id": "configure-the-pipeline", + "Text": "Configure the pipeline", + "Level": 1 + }, + { + "Id": "protecting-endpoints", + "Text": "Protecting endpoints", + "Level": 1 + }, + { + "Id": "cors-configuration", + "Text": "CORS configuration", + "Level": 1 + }, + { + "Id": "resource-api-vs-uauthhub", + "Text": "Resource API vs UAuthHub", + "Level": 1 + }, + { + "Id": "resource-api-with-an-ef-core-backed-uauthhub", + "Text": "8. Resource API with an EF Core-Backed UAuthHub", + "Level": 0 + }, + { + "Id": "calling-a-resource-api-from-blazor", + "Text": "9. Calling a Resource API from Blazor", + "Level": 0 + }, + { + "Id": "interactive-wasm-host", + "Text": "Interactive WASM host", + "Level": 1 + }, + { + "Id": "interactive-wasm-client", + "Text": "Interactive WASM client", + "Level": 1 + }, + { + "Id": "standalone-wasm", + "Text": "Standalone WASM", + "Level": 1 + }, + { + "Id": "authentication-modes-and-client-profiles", + "Text": "10. Authentication Modes and Client Profiles", + "Level": 0 + }, + { + "Id": "restrict-allowed-modes", + "Text": "Restrict allowed modes", + "Level": 1 + }, + { + "Id": "server-options-for-real-applications", + "Text": "11. Server Options for Real Applications", + "Level": 0 + }, + { + "Id": "configuration-files", + "Text": "Configuration files", + "Level": 1 + }, + { + "Id": "development-and-production-differences", + "Text": "12. Development and Production Differences", + "Level": 0 + }, + { + "Id": "credential-recovery", + "Text": "Credential recovery", + "Level": 1 + }, + { + "Id": "identifier-verification", + "Text": "Identifier verification", + "Level": 1 + }, + { + "Id": "signing-keys", + "Text": "Signing keys", + "Level": 1 + }, + { + "Id": "deployment-checklist", + "Text": "13. Deployment Checklist", + "Level": 0 + }, + { + "Id": "choosing-your-architecture", + "Text": "14. Choosing Your Architecture", + "Level": 0 + }, + { + "Id": "single-application-the-simplest-setup", + "Text": "Single Application \u2014 The Simplest Setup", + "Level": 1 + }, + { + "Id": "multiple-applications-one-authentication-server", + "Text": "Multiple Applications \u2014 One Authentication Server", + "Level": 1 + }, + { + "Id": "distributed-applications-separate-resource-apis", + "Text": "Distributed Applications \u2014 Separate Resource APIs", + "Level": 1 + }, + { + "Id": "one-foundation-different-scales", + "Text": "One Foundation, Different Scales", + "Level": 1 + }, + { + "Id": "next-steps", + "Text": "Next Steps", "Level": 0 } ] diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization.json new file mode 100644 index 00000000..7a58645b --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization.json @@ -0,0 +1,157 @@ +{ + "Slug": "plugin-domains/authorization", + "Title": "Authorization", + "Html": "\n\u003Cp\u003EAuthentication establishes an identity. Authorization determines what that identity is allowed to do.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth treats authorization as a separate responsibility, built around \u003Cstrong\u003Estructured actions, permissions, roles, and policies\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003ERather than relying exclusively on role names or scattered permission checks, UltimateAuth provides a consistent model for describing protected operations and evaluating access within their security context.\u003C/p\u003E\n\u003Cp\u003EThe Authorization Plugin Domain extends this model with permission management, role assignments, and supporting infrastructure.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-authorization\u0022\u003EUnderstanding Authorization\u003C/h2\u003E\n\u003Cp\u003EA successful authentication does not automatically grant access to protected operations.\u003C/p\u003E\n\u003Cp\u003EAn authenticated identity may be permitted to read its own profile while being prohibited from modifying another user\u0027s account. An administrator may have elevated permissions within one tenant without having authority over another tenant.\u003C/p\u003E\n\u003Cp\u003EThese decisions require more than knowing whether the caller is authenticated.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth separates authorization into several responsibilities:\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\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAction\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifies the protected operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPermission\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERepresents authority to perform an action\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERole\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EGroups permissions for assignment\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPolicy\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEvaluates the conditions required to allow access\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAccessContext\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECarries the identity, target, tenant, and operation context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAccessOrchestrator\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECoordinates policy evaluation and the access decision\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese concepts work together but are not interchangeable.\u003C/p\u003E\n\u003Cp\u003EA permission can indicate that an identity is authorized to perform a class of operations. A policy may still deny a particular request because its target, tenant, or security context is invalid.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthorization is therefore a decision process, not simply a permission lookup.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022structured-actions\u0022\u003EStructured Actions\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth identifies protected operations through a structured action model.\u003C/p\u003E\n\u003Cp\u003EActions use a hierarchical naming convention:\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\u003Eresource.operation.scope\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor operations involving a more specific resource:\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\u003Eresource.subresource.operation.scope\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EExamples 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\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.create.admin\nusers.delete.self\nusers.profile.update.self\ncredentials.change.self\nsessions.revokechain.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe structure makes the intended operation and its access boundary explicit.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ESegment\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\u003EResource\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eusers\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EThe primary protected domain\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESubresource\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eprofile\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAn optional, more specific resource\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOperation\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eupdate\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EThe requested behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EScope\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eself\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EThe intended access boundary\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis convention supports consistent naming, grouping, permission matching, and policy evaluation.\u003C/p\u003E\n\u003Cp\u003EIt also helps developers understand an operation without needing to inspect its endpoint implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022uauthactions\u0022\u003EUAuthActions\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthActions\u003C/code\u003E is UltimateAuth\u0027s built-in action catalog.\u003C/p\u003E\n\u003Cp\u003EIt provides predefined identifiers for framework operations so that services, endpoints, and authorization logic can refer to the same action consistently.\u003C/p\u003E\n\u003Cp\u003EExamples of built-in actions 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\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\u003Eflows.logout.self\nsessions.listchains.admin\nusers.delete.self\ncredentials.revoke.admin\nauthorization.roles.assign.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUsing a shared action catalog avoids duplicating string literals throughout the framework and reduces the risk of inconsistent permission names.\u003C/p\u003E\n\u003Cp\u003EActions are identifiers, not authorization decisions.\u003C/p\u003E\n\u003Cp\u003EReferencing an action does not automatically authorize a request. The action must be evaluated against the applicable authorization rules.\u003C/p\u003E\n\u003Cp\u003EApplications can also define actions for their own protected resources using the same naming convention.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-scope\u0022\u003EUnderstanding Scope\u003C/h2\u003E\n\u003Cp\u003EThe final segment of an action describes its intended security scope.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth distinguishes three principal scopes:\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\u003EMeaning\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eself\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAn operation targeting the caller\u0027s own identity or resources\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eadmin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAn operation performed with elevated authority over another target\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eanonymous\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAn operation that can be initiated without an authenticated identity\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe scope is part of the action\u0027s identity.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self-and-admin-are-different-permissions\u0022\u003ESelf and Admin Are Different Permissions\u003C/h3\u003E\n\u003Cp\u003EConsider:\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.delete.self\nusers.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe first represents the authority to delete one\u0027s own account.\u003C/p\u003E\n\u003Cp\u003EThe second represents the authority to perform an administrative deletion.\u003C/p\u003E\n\u003Cp\u003EGranting \u003Ccode\u003Eusers.delete.self\u003C/code\u003E must not implicitly grant \u003Ccode\u003Eusers.delete.admin\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ELikewise, an authenticated user must not gain administrative access simply by supplying another user\u0027s identifier to a self-service operation.\u003C/p\u003E\n\u003Cp\u003EThe application must establish the actual relationship between the actor and the target.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe requested scope is not proof that the caller satisfies that scope.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA self operation requires a valid self relationship. An administrative operation requires the corresponding authority and must satisfy the applicable target and tenant policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022anonymous-does-not-mean-unrestricted\u0022\u003EAnonymous Does Not Mean Unrestricted\u003C/h3\u003E\n\u003Cp\u003EAn anonymous action is designed to be initiated without an authenticated session.\u003C/p\u003E\n\u003Cp\u003EExamples include certain login and credential recovery operations.\u003C/p\u003E\n\u003Cp\u003EHowever, anonymous availability does not remove the need for validation, proof requirements, abuse protection, or operation-specific security policies.\u003C/p\u003E\n\u003Cp\u003EAn anonymous credential reset request, for example, must not be allowed to replace a password without completing the required recovery proof.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permissions\u0022\u003EPermissions\u003C/h2\u003E\n\u003Cp\u003EA permission represents authority associated with a protected action.\u003C/p\u003E\n\u003Cp\u003EIn UltimateAuth, permission identifiers follow the same structured action convention.\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.profile.update.self\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA user granted this permission may be eligible to update their own profile, provided the other required policies succeed.\u003C/p\u003E\n\u003Cp\u003EPermissions do not replace those policies.\u003C/p\u003E\n\u003Cp\u003EA permission match answers whether the relevant authority is present. It does not independently establish that the target belongs to the caller, that the tenant boundary is valid, or that the account satisfies the required security conditions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hierarchical-permissions\u0022\u003EHierarchical Permissions\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth supports hierarchical permission matching.\u003C/p\u003E\n\u003Cp\u003EExamples:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPermission\u003C/th\u003E\n\u003Cth\u003EIntended coverage\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eusers.profile.update.self\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOne specific action\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eusers.profile.*\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EActions under the profile group\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eusers.*\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EActions under the users resource\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003E*\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAll actions covered by the permission matcher\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows permissions to be granted at different levels of granularity.\u003C/p\u003E\n\u003Cp\u003EAn application can assign a narrowly scoped permission to ordinary users while granting broader permission groups to trusted administrative roles.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-normalization\u0022\u003EPermission Normalization\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth provides permission normalization to reduce redundant representations.\u003C/p\u003E\n\u003Cp\u003EWhen a permission set fully covers a known group of actions, the corresponding permissions can be represented by a wildcard for that group.\u003C/p\u003E\n\u003Cp\u003EFor example, complete coverage of a defined group may be normalized 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\u003Eusers.profile.*\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ENormalization is intended to preserve the effective permission coverage, not expand it.\u003C/p\u003E\n\u003Cp\u003EA wildcard must not be introduced merely because several permissions share a prefix. The relevant group must actually be fully covered according to the known permission catalog.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022compiled-permission-sets\u0022\u003ECompiled Permission Sets\u003C/h3\u003E\n\u003Cp\u003EPermission resolution may involve exact matches, hierarchical groups, and wildcards.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003ECompiledPermissionSet\u003C/code\u003E provides an optimized representation for evaluating these permissions at runtime.\u003C/p\u003E\n\u003Cp\u003EThis allows the authorization system to avoid repeatedly performing the same permission-processing work during access checks.\u003C/p\u003E\n\u003Cp\u003EThe optimization does not change the authorization model: permission matching remains one part of the overall access decision.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022roles-and-role-assignments\u0022\u003ERoles and Role Assignments\u003C/h2\u003E\n\u003Cp\u003ERoles provide a convenient way to manage collections of permissions.\u003C/p\u003E\n\u003Cp\u003EInstead of assigning every permission directly to every user, an application can define roles corresponding to its operational responsibilities.\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\u003ESupportAgent\nAccountManager\nTenantAdministrator\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach role can contain the permissions required for that responsibility.\u003C/p\u003E\n\u003Cp\u003EA role is not inherently a security bypass or a substitute for policy evaluation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022role-management\u0022\u003ERole Management\u003C/h3\u003E\n\u003Cp\u003EThe reference authorization model supports role management and user-role assignments.\u003C/p\u003E\n\u003Cp\u003EIts responsibilities include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDefining roles and their permissions.\u003C/li\u003E\n\u003Cli\u003EAssigning roles to users.\u003C/li\u003E\n\u003Cli\u003EManaging role membership.\u003C/li\u003E\n\u003Cli\u003EResolving effective permissions.\u003C/li\u003E\n\u003Cli\u003EMaintaining the relevant tenant boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ERoles and assignments are designed to support tenant-aware authorization.\u003C/p\u003E\n\u003Cp\u003EAn administrative role in one tenant must not automatically imply authority in another tenant.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022roles-versus-permissions\u0022\u003ERoles Versus Permissions\u003C/h3\u003E\n\u003Cp\u003ERoles describe how authority is organized and assigned.\u003C/p\u003E\n\u003Cp\u003EPermissions describe which operations that authority covers.\u003C/p\u003E\n\u003Cp\u003EPolicies determine whether the current operation is permitted under the relevant conditions.\u003C/p\u003E\n\u003Cp\u003EKeeping these concepts separate allows applications to change role structures without redesigning their protected operations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-policies-1\u0022\u003EAuthorization Policies\u003C/h2\u003E\n\u003Cp\u003EPolicies are the enforcement layer of UltimateAuth\u0027s authorization model.\u003C/p\u003E\n\u003Cp\u003EA policy evaluates a security requirement against the current access context.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth includes policy concepts such as:\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\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequireAuthenticated\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequires an authenticated actor\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequireActiveUser\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequires the relevant active-user condition\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequireSelf\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEnforces the self-access relationship\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequireSystem\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERestricts operations to an appropriate system context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EDenyCrossTenant\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEnforces tenant isolation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EMustHavePermission\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERequires the applicable permission\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese policies address different questions.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EMustHavePermission\u003C/code\u003E may confirm that the actor has \u003Ccode\u003Eusers.delete.admin\u003C/code\u003E, while \u003Ccode\u003EDenyCrossTenant\u003C/code\u003E may independently prevent the operation from targeting a user in an unauthorized tenant.\u003C/p\u003E\n\u003Cp\u003EA successful permission match must not bypass another mandatory security policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies-are-composable\u0022\u003EPolicies Are Composable\u003C/h3\u003E\n\u003Cp\u003EA protected operation may require multiple conditions.\u003C/p\u003E\n\u003Cp\u003EAn administrative account operation might require:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EAn authenticated actor.\u003C/li\u003E\n\u003Cli\u003EA valid and active identity.\u003C/li\u003E\n\u003Cli\u003EPermission for the requested administrative action.\u003C/li\u003E\n\u003Cli\u003EA valid target identity.\u003C/li\u003E\n\u003Cli\u003ECompliance with tenant isolation rules.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EAuthorization succeeds only when the required conditions are satisfied.\u003C/p\u003E\n\u003Cp\u003EThis approach allows UltimateAuth to separate reusable security requirements from the business operation being protected.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accesscontext-and-accessorchestrator\u0022\u003EAccessContext and AccessOrchestrator\u003C/h2\u003E\n\u003Cp\u003EAuthorization needs a trustworthy representation of the operation being evaluated.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E carries the relevant information for the decision, including the actor, target, tenant context, and requested operation.\u003C/p\u003E\n\u003Cp\u003EThe \u003Ccode\u003EAccessOrchestrator\u003C/code\u003E coordinates the applicable policy evaluations and produces the authorization decision.\u003C/p\u003E\n\u003Cp\u003EConceptually, the process 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\u003EAuthenticated identity\n |\n v\nRequested action\n |\n v\nBuild AccessContext\n |\n v\nEvaluate required policies\n |\n \u002B---- Denied ---\u0026gt; Reject operation\n |\n v\n Allowed\n |\n v\nExecute protected operation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe context must be constructed from trusted authentication state and validated operation data.\u003C/p\u003E\n\u003Cp\u003EA caller-provided user ID, tenant ID, or scope string must not automatically be accepted as authoritative.\u003C/p\u003E\n\u003Cp\u003EThis is particularly important for administrative operations and multi-tenant applications.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-and-authentication-are-separate\u0022\u003EAuthorization and Authentication Are Separate\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlowContext\u003C/code\u003E and \u003Ccode\u003EAccessContext\u003C/code\u003E serve different purposes.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAuthFlowContext\u003C/code\u003E belongs to authentication flow execution.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E belongs to authorization evaluation.\u003C/p\u003E\n\u003Cp\u003EA successfully completed authentication flow may establish the identity required for an access context, but it does not replace authorization evaluation.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/flow-based-authentication\u0022\u003EFlow-Based Authentication\u003C/a\u003E and \u003Ca href=\u0022/docs/fundamentals/request-lifecycle\u0022\u003ERequest Lifecycle\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022claims-and-permission-resolution\u0022\u003EClaims and Permission Resolution\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth can integrate authorization information with authentication claims.\u003C/p\u003E\n\u003Cp\u003ERelevant claims may 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\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\u003EClaimTypes.Role\npermission\ntenant\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EClaims allow authorization information to participate in token-based and ASP.NET Core authorization scenarios.\u003C/p\u003E\n\u003Cp\u003EHowever, claims are representations of authorization information, not necessarily its authoritative source.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-based-authorization\u0022\u003ESession-Based Authorization\u003C/h3\u003E\n\u003Cp\u003EIn session-authoritative authentication modes, authorization can rely on server-side state and the relevant runtime resolution mechanisms.\u003C/p\u003E\n\u003Cp\u003EChanges to roles, permissions, or account security state can be reflected according to the session validation and authorization configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022jwt-based-authorization\u0022\u003EJWT-Based Authorization\u003C/h3\u003E\n\u003Cp\u003EIn JWT-based modes, roles or permissions may be included in issued tokens.\u003C/p\u003E\n\u003Cp\u003EA protected resource can evaluate those claims without contacting the authentication server on every request.\u003C/p\u003E\n\u003Cp\u003EThis introduces an important tradeoff.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA permission removed after token issuance may remain represented in an already-issued JWT until that token expires.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EApplications requiring immediate permission revocation must account for this behavior through their token lifetimes, refresh strategy, or additional server-side validation.\u003C/p\u003E\n\u003Cp\u003EThe authorization model remains consistent, but the freshness of authorization information depends on how it is resolved.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022protecting-application-endpoints\u0022\u003EProtecting Application Endpoints\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s authorization architecture is not limited to its built-in identity management endpoints.\u003C/p\u003E\n\u003Cp\u003EApplications can protect their own business resources using ASP.NET Core authorization and, where integrated, UltimateAuth\u0027s action and policy model.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022requiring-authentication\u0022\u003ERequiring Authentication\u003C/h3\u003E\n\u003Cp\u003EFor a controller-based resource API, standard ASP.NET Core authorization attributes can require an authenticated caller.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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[ApiController]\n[Route(\u0026quot;api/products\u0026quot;)]\n[Authorize]\npublic sealed class ProductsController : ControllerBase\n{\n [HttpGet]\n public IActionResult GetProducts()\n {\n return Ok();\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen the application is configured with the appropriate UltimateAuth authentication integration, \u003Ccode\u003E[Authorize]\u003C/code\u003E requires a valid authenticated identity.\u003C/p\u003E\n\u003Cp\u003EThis is the simplest protection boundary.\u003C/p\u003E\n\u003Cp\u003EIt does not, by itself, require a particular UltimateAuth action permission.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022requiring-a-named-policy\u0022\u003ERequiring a Named Policy\u003C/h3\u003E\n\u003Cp\u003EASP.NET Core also supports policy-based endpoint authorization.\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\u003E[Authorize(Policy = \u0026quot;Products.Read\u0026quot;)]\n[HttpGet]\npublic IActionResult GetProducts()\n{\n return Ok();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe named policy must be registered in the application\u0027s authorization configuration.\u003C/p\u003E\n\u003Cp\u003EA standard ASP.NET Core policy can require a particular permission claim:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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.AddAuthorization(options =\u0026gt;\n{\n options.AddPolicy(\u0026quot;Products.Read\u0026quot;, policy =\u0026gt;\n {\n policy.RequireAuthenticatedUser();\n policy.RequireClaim(\n \u0026quot;permission\u0026quot;,\n \u0026quot;products.read.self\u0026quot;);\n });\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example uses ASP.NET Core\u0027s built-in claim authorization.\u003C/p\u003E\n\u003Cp\u003EIt checks for the specified claim in the authenticated principal. It does not automatically execute UltimateAuth\u0027s \u003Ccode\u003EAccessOrchestrator\u003C/code\u003E or its domain policies.\u003C/p\u003E\n\u003Cp\u003EIt is suitable when the application\u0027s security model permits authorization based on the available claims.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022using-ultimateauth-actions-for-application-resources\u0022\u003EUsing UltimateAuth Actions for Application Resources\u003C/h3\u003E\n\u003Cp\u003EApplications can define their own action identifiers using the same convention as UltimateAuth\u0027s built-in catalog.\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\u003Epublic static class ProductActions\n{\n public const string ReadSelf = \u0026quot;products.read.self\u0026quot;;\n public const string CreateAdmin = \u0026quot;products.create.admin\u0026quot;;\n public const string DeleteAdmin = \u0026quot;products.delete.admin\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese constants establish consistent names for application permissions.\u003C/p\u003E\n\u003Cp\u003EThey can be used by permission management, policy registration, and endpoint authorization integrations.\u003C/p\u003E\n\u003Cp\u003EDefining the constants alone does not register them with the authorization system or enforce them at an endpoint.\u003C/p\u003E\n\u003Cp\u003EFor operations requiring actor-target validation, tenant isolation, or other UltimateAuth domain policies, the application must use the appropriate UltimateAuth authorization integration rather than relying solely on a permission claim.\u003C/p\u003E\n\u003Cp\u003EThe exact registration and invocation APIs depend on the configured authorization packages and hosting model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-in-different-hosting-models\u0022\u003EAuthorization in Different Hosting Models\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports different hosting architectures, and the enforcement boundary matters.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-apis\u0022\u003EResource APIs\u003C/h3\u003E\n\u003Cp\u003EFor HTTP resource APIs, authentication and authorization are normally enforced through the ASP.NET Core request pipeline.\u003C/p\u003E\n\u003Cp\u003EControllers and endpoints can use standard authorization attributes and policies.\u003C/p\u003E\n\u003Cp\u003EThe UltimateAuth resource API integration establishes the authentication context required for protected requests.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022integrated-server-applications\u0022\u003EIntegrated Server Applications\u003C/h3\u003E\n\u003Cp\u003EIn an integrated server application, business operations may execute directly through application services without making an HTTP request.\u003C/p\u003E\n\u003Cp\u003ESuch calls do not automatically pass through ASP.NET Core endpoint authorization attributes.\u003C/p\u003E\n\u003Cp\u003EIf an operation bypasses the HTTP endpoint, authorization must be enforced at an appropriate service or application boundary.\u003C/p\u003E\n\u003Cp\u003EThis is particularly relevant for Blazor Server applications that invoke services directly.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EProtecting an HTTP controller does not automatically protect every direct call to the underlying service.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAuthorization must remain effective at the actual execution boundary.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extending-authorization\u0022\u003EExtending Authorization\u003C/h2\u003E\n\u003Cp\u003EApplications may need policies beyond the framework\u0027s built-in requirements.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EResource ownership rules.\u003C/li\u003E\n\u003Cli\u003EOrganization-specific access restrictions.\u003C/li\u003E\n\u003Cli\u003EContext-dependent administrative authority.\u003C/li\u003E\n\u003Cli\u003EBusiness workflow conditions.\u003C/li\u003E\n\u003Cli\u003EAdditional tenant or resource boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth\u0027s policy-driven architecture is intended to support such requirements without embedding them directly into the authentication engine.\u003C/p\u003E\n\u003Cp\u003EThe reference Authorization domain provides a foundation for roles, permissions, and their management.\u003C/p\u003E\n\u003Cp\u003EApplications can build on that foundation while keeping application-specific authorization rules close to the resources and operations they protect.\u003C/p\u003E\n\u003Cp\u003EDetailed customization, custom policy implementation, and advanced permission registration are covered separately.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s authorization model is built around a distinction:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EActions describe operations. Permissions represent authority. Policies enforce security requirements.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ERoles make permissions manageable, while \u003Ccode\u003EAccessContext\u003C/code\u003E and \u003Ccode\u003EAccessOrchestrator\u003C/code\u003E provide the context and coordination needed for authorization decisions.\u003C/p\u003E\n\u003Cp\u003EThis allows applications to use consistent authorization semantics across authentication modes, hosting models, and business domains without reducing security to a collection of role checks.\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 Identity ownership, account lifecycle, and user management.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Authentication evidence and credential security.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains\u0022\u003EPlugin Domains\u003C/a\u003E \u2014 The broader Plugin Domain architecture.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/request-lifecycle\u0022\u003ERequest Lifecycle\u003C/a\u003E \u2014 How authentication and authorization participate in protected requests.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Headings": [ + { + "Id": "understanding-authorization", + "Text": "Understanding Authorization", + "Level": 0 + }, + { + "Id": "structured-actions", + "Text": "Structured Actions", + "Level": 0 + }, + { + "Id": "uauthactions", + "Text": "UAuthActions", + "Level": 1 + }, + { + "Id": "understanding-scope", + "Text": "Understanding Scope", + "Level": 0 + }, + { + "Id": "self-and-admin-are-different-permissions", + "Text": "Self and Admin Are Different Permissions", + "Level": 1 + }, + { + "Id": "anonymous-does-not-mean-unrestricted", + "Text": "Anonymous Does Not Mean Unrestricted", + "Level": 1 + }, + { + "Id": "permissions", + "Text": "Permissions", + "Level": 0 + }, + { + "Id": "hierarchical-permissions", + "Text": "Hierarchical Permissions", + "Level": 1 + }, + { + "Id": "permission-normalization", + "Text": "Permission Normalization", + "Level": 1 + }, + { + "Id": "compiled-permission-sets", + "Text": "Compiled Permission Sets", + "Level": 1 + }, + { + "Id": "roles-and-role-assignments", + "Text": "Roles and Role Assignments", + "Level": 0 + }, + { + "Id": "role-management", + "Text": "Role Management", + "Level": 1 + }, + { + "Id": "roles-versus-permissions", + "Text": "Roles Versus Permissions", + "Level": 1 + }, + { + "Id": "authorization-policies-1", + "Text": "Authorization Policies", + "Level": 0 + }, + { + "Id": "policies-are-composable", + "Text": "Policies Are Composable", + "Level": 1 + }, + { + "Id": "accesscontext-and-accessorchestrator", + "Text": "AccessContext and AccessOrchestrator", + "Level": 0 + }, + { + "Id": "authorization-and-authentication-are-separate", + "Text": "Authorization and Authentication Are Separate", + "Level": 1 + }, + { + "Id": "claims-and-permission-resolution", + "Text": "Claims and Permission Resolution", + "Level": 0 + }, + { + "Id": "session-based-authorization", + "Text": "Session-Based Authorization", + "Level": 1 + }, + { + "Id": "jwt-based-authorization", + "Text": "JWT-Based Authorization", + "Level": 1 + }, + { + "Id": "protecting-application-endpoints", + "Text": "Protecting Application Endpoints", + "Level": 0 + }, + { + "Id": "requiring-authentication", + "Text": "Requiring Authentication", + "Level": 1 + }, + { + "Id": "requiring-a-named-policy", + "Text": "Requiring a Named Policy", + "Level": 1 + }, + { + "Id": "using-ultimateauth-actions-for-application-resources", + "Text": "Using UltimateAuth Actions for Application Resources", + "Level": 1 + }, + { + "Id": "authorization-in-different-hosting-models", + "Text": "Authorization in Different Hosting Models", + "Level": 0 + }, + { + "Id": "resource-apis", + "Text": "Resource APIs", + "Level": 1 + }, + { + "Id": "integrated-server-applications", + "Text": "Integrated Server Applications", + "Level": 1 + }, + { + "Id": "extending-authorization", + "Text": "Extending Authorization", + "Level": 0 + }, + { + "Id": "architectural-perspective", + "Text": "Architectural Perspective", + "Level": 0 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ 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 new file mode 100644 index 00000000..8605b5d0 --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credentials.json @@ -0,0 +1,112 @@ +{ + "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\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", + "Headings": [ + { + "Id": "understanding-credentials", + "Text": "Understanding Credentials", + "Level": 0 + }, + { + "Id": "credentials-and-user-identity", + "Text": "Credentials and User Identity", + "Level": 0 + }, + { + "Id": "responsibilities-across-the-framework", + "Text": "Responsibilities Across the Framework", + "Level": 0 + }, + { + "Id": "password-credentials", + "Text": "Password Credentials", + "Level": 0 + }, + { + "Id": "secret-protection", + "Text": "Secret Protection", + "Level": 1 + }, + { + "Id": "credential-security-state", + "Text": "Credential Security State", + "Level": 1 + }, + { + "Id": "credential-validation", + "Text": "Credential Validation", + "Level": 0 + }, + { + "Id": "credential-lifecycle", + "Text": "Credential Lifecycle", + "Level": 0 + }, + { + "Id": "credential-ownership-and-operation-scope", + "Text": "Credential Ownership and Operation Scope", + "Level": 0 + }, + { + "Id": "self-service-operations", + "Text": "Self-Service Operations", + "Level": 1 + }, + { + "Id": "administrative-operations", + "Text": "Administrative Operations", + "Level": 1 + }, + { + "Id": "anonymous-recovery", + "Text": "Anonymous Recovery", + "Level": 1 + }, + { + "Id": "credential-recovery", + "Text": "Credential Recovery", + "Level": 0 + }, + { + "Id": "beginning-recovery", + "Text": "Beginning Recovery", + "Level": 1 + }, + { + "Id": "completing-recovery", + "Text": "Completing Recovery", + "Level": 1 + }, + { + "Id": "enumeration-resistance", + "Text": "Enumeration Resistance", + "Level": 1 + }, + { + "Id": "credentials-and-authentication-sessions", + "Text": "Credentials and Authentication Sessions", + "Level": 0 + }, + { + "Id": "integration-with-the-users-domain", + "Text": "Integration with the Users Domain", + "Level": 0 + }, + { + "Id": "extending-credential-capabilities", + "Text": "Extending Credential Capabilities", + "Level": 0 + }, + { + "Id": "architectural-perspective", + "Text": "Architectural Perspective", + "Level": 0 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/overview.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/overview.json new file mode 100644 index 00000000..dd659e0b --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/overview.json @@ -0,0 +1,42 @@ +{ + "Slug": "plugin-domains/overview", + "Title": "Plugin-Domains Overview", + "Html": "\n\u003Cp\u003EUltimateAuth separates \u003Cstrong\u003Eauthentication infrastructure\u003C/strong\u003E from the domain capabilities commonly required by identity and access management systems.\u003C/p\u003E\n\u003Cp\u003EThe authentication engine is responsible for establishing and validating authentication state. Managing users, credentials, and authorization policies introduces additional responsibilities that do not inherently belong to that engine.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth organizes these capabilities into \u003Cstrong\u003EPlugin Domains\u003C/strong\u003E: modular, independently structured domains that extend the framework beyond authentication without making their business logic part of the authentication core.\u003C/p\u003E\n\u003Cp\u003EPlugin Domains are an architectural concept specific to UltimateAuth. They are not simply runtime plugins or optional features within a monolithic identity system.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-core-and-plugin-domains\u0022\u003EAuthentication Core and Plugin Domains\u003C/h2\u003E\n\u003Cp\u003EThe distinction is based on responsibility rather than importance.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication Core\u003C/strong\u003E provides the infrastructure for authentication flows, authentication modes, sessions, tokens, and runtime security boundaries.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EPlugin Domains\u003C/strong\u003E provide the identity and access management capabilities built around that infrastructure.\u003C/p\u003E\n\u003Cp\u003EThe framework currently organizes these responsibilities into three primary domains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EDomain\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\u003EUsers\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUser lifecycle, identifiers, profiles, and account management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential registration, management, verification, and recovery\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAuthorization\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EPermissions, roles, policies, and access management\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese domains are not universally required for every authentication scenario. An application may already have its own identity infrastructure or may need only a subset of UltimateAuth\u0027s domain capabilities.\u003C/p\u003E\n\u003Cp\u003EHowever, together they provide the foundation for using UltimateAuth as a broader identity and access management platform rather than only an authentication engine.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-boundaries\u0022\u003EArchitectural Boundaries\u003C/h2\u003E\n\u003Cp\u003EEach Plugin Domain owns a distinct area of business logic.\u003C/p\u003E\n\u003Cp\u003EA domain should not need to understand the internal implementation of another domain to perform its responsibilities.\u003C/p\u003E\n\u003Cp\u003EThis separation establishes several architectural properties:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EIndependent domain boundaries:\u003C/strong\u003E Each domain defines and maintains its own contracts and behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EExplicit integration:\u003C/strong\u003E Cross-domain interactions occur through defined abstractions and integration points.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EReplaceable infrastructure:\u003C/strong\u003E Storage implementations and supporting infrastructure can be substituted without changing the authentication engine.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EControlled extensibility:\u003C/strong\u003E Applications can customize domain behavior while preserving established framework contracts.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIsolation does not mean that domains never interact. Identity management operations frequently involve multiple domains, and those interactions must remain coordinated.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth addresses this through explicit integration contracts rather than direct dependencies between domain implementations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022domain-package-structure\u0022\u003EDomain Package Structure\u003C/h2\u003E\n\u003Cp\u003EPlugin Domains follow a layered package structure that separates integration contracts, public models, domain behavior, and persistence.\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\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EBridge\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EMinimal abstractions required for integration with UltimateAuth Server\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EContracts\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EShared requests, responses, and data transfer models\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EReference Implementation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EDefault application services, domain behavior, and storage abstractions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPersistence\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EConcrete storage implementations, including InMemory and Entity Framework Core\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis structure keeps the authentication server independent of the full internal implementation of each domain.\u003C/p\u003E\n\u003Cp\u003EIt also allows applications to use the provided implementations while retaining control over infrastructure and customization.\u003C/p\u003E\n\u003Cp\u003EThe reference implementations are intended to be practical foundations for real applications, not merely illustrative examples.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022cross-domain-integration\u0022\u003ECross-Domain Integration\u003C/h2\u003E\n\u003Cp\u003ESome identity operations naturally cross domain boundaries.\u003C/p\u003E\n\u003Cp\u003EFor example, creating a user may need to initiate credential-related behavior. That coordination should not require the Users domain to reference the Credentials domain directly.\u003C/p\u003E\n\u003Cp\u003EInstead, UltimateAuth uses integration abstractions and lifecycle hooks to connect independently implemented responsibilities.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EIUserLifecycleIntegration\u003C/code\u003E is an example of such an integration point.\u003C/p\u003E\n\u003Cp\u003EThis approach allows domain-specific behavior to remain within its owning domain while supporting coordinated application workflows.\u003C/p\u003E\n\u003Cp\u003EIntegration hooks are explicit extension boundaries, not a mechanism for bypassing domain ownership or security rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extending-plugin-domains\u0022\u003EExtending Plugin Domains\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides reference implementations so applications do not need to rebuild common identity management capabilities from scratch.\u003C/p\u003E\n\u003Cp\u003EFor most applications, the recommended approach is to \u003Cstrong\u003Estart with the provided domain implementations and customize only what is necessary\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EDepending on the domain and its supported extension contracts, customization may include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EReplacing persistence implementations.\u003C/li\u003E\n\u003Cli\u003EProviding application-specific services.\u003C/li\u003E\n\u003Cli\u003EExtending supported lifecycle integrations.\u003C/li\u003E\n\u003Cli\u003EIntegrating existing identity or access management infrastructure.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EReplacing an entire domain is possible as an architectural objective, but doing so requires preserving the contracts expected by the rest of the system.\u003C/p\u003E\n\u003Cp\u003EModularity provides flexibility; it does not eliminate the need for compatible behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-the-right-capabilities\u0022\u003EChoosing the Right Capabilities\u003C/h2\u003E\n\u003Cp\u003EPlugin Domains are designed to be composed according to application requirements.\u003C/p\u003E\n\u003Cp\u003EAn application focused on authentication infrastructure may rely primarily on the Core and Server capabilities.\u003C/p\u003E\n\u003Cp\u003EAn application requiring complete identity management can incorporate Users, Credentials, and Authorization together.\u003C/p\u003E\n\u003Cp\u003EThe important distinction is that \u003Cstrong\u003Ethe authentication engine does not need to become a monolithic identity management system to support either scenario\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThis separation allows UltimateAuth to serve both focused authentication use cases and more comprehensive identity platforms while maintaining clear architectural boundaries.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022explore-the-domains\u0022\u003EExplore the Domains\u003C/h2\u003E\n\u003Cp\u003EContinue with the individual domains to understand their responsibilities, public contracts, and extension points.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/users\u0022\u003EUsers\u003C/a\u003E \u2014 User identities, identifiers, and lifecycle management.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Credential management, verification, and recovery.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Roles, permissions, policies, and access control.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor the underlying authentication architecture, see \u003Ca href=\u0022/docs/fundamentals/runtime-architecture\u0022\u003ERuntime Architecture\u003C/a\u003E and \u003Ca href=\u0022/docs/fundamentals/auth-model\u0022\u003EAuthentication Model\u003C/a\u003E.\u003C/p\u003E\n", + "Headings": [ + { + "Id": "authentication-core-and-plugin-domains", + "Text": "Authentication Core and Plugin Domains", + "Level": 0 + }, + { + "Id": "architectural-boundaries", + "Text": "Architectural Boundaries", + "Level": 0 + }, + { + "Id": "domain-package-structure", + "Text": "Domain Package Structure", + "Level": 0 + }, + { + "Id": "cross-domain-integration", + "Text": "Cross-Domain Integration", + "Level": 0 + }, + { + "Id": "extending-plugin-domains", + "Text": "Extending Plugin Domains", + "Level": 0 + }, + { + "Id": "choosing-the-right-capabilities", + "Text": "Choosing the Right Capabilities", + "Level": 0 + }, + { + "Id": "explore-the-domains", + "Text": "Explore the Domains", + "Level": 0 + } + ] +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/policies.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/policies.json index 157dd0cb..b69c2e67 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/policies.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/policies.json @@ -1,7 +1,7 @@ { "Slug": "plugin-domains/policies", "Title": "Policies \u0026 Access Control", - "Html": "\n\u003Cp\u003EAuthorization is not simply a question of whether an identity possesses a permission.\u003C/p\u003E\n\u003Cp\u003EA protected operation may involve an authenticated actor, a target identity, tenant boundaries, resource ownership, and security conditions that cannot be represented by a permission alone.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth addresses these requirements through a \u003Cstrong\u003Epolicy-driven access control pipeline\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EPolicies are independent decision units evaluated within a shared access context. The authorization orchestrator coordinates those decisions and determines whether the requested operation can proceed.\u003C/p\u003E\n\u003Cp\u003EThis page explains how that process works.\u003C/p\u003E\n\u003Cp\u003EFor actions, permissions, roles, and scope conventions, see \u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-access-decision-model\u0022\u003EThe Access Decision Model\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth evaluates authorization using three primary elements:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EElement\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\u003EAccessContext\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EDescribes the actor, target, operation, and relevant security context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPolicies\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEvaluate individual authorization requirements\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAccessOrchestrator\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECoordinates policy resolution, evaluation, and execution\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese elements separate the description of an operation from the rules used to authorize it.\u003C/p\u003E\n\u003Cp\u003EA permission may grant authority to perform an action, but policies determine whether that authority is valid in the current context.\u003C/p\u003E\n\u003Cp\u003EFor example, a caller may possess an administrative permission while attempting to access a resource belonging to another tenant.\u003C/p\u003E\n\u003Cp\u003EThe permission may match, but the operation must still satisfy tenant isolation requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accesscontext\u0022\u003EAccessContext\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E represents the security context of an authorization operation.\u003C/p\u003E\n\u003Cp\u003EIt provides the information needed to evaluate access without requiring each policy to reconstruct the request independently.\u003C/p\u003E\n\u003Cp\u003EConceptually, the context includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EComponent\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\u003EActor\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe identity or system principal performing the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ETarget\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe identity or resource affected by the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAction\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe protected operation being requested\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ETenant context\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe tenant boundaries relevant to the actor and target\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAuthorization information\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERelevant claims, permissions, and security state\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe context is more than a collection of request parameters.\u003C/p\u003E\n\u003Cp\u003EIt establishes the relationship between the authenticated actor and the operation being performed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022actor-and-target-are-different-concepts\u0022\u003EActor and Target Are Different Concepts\u003C/h3\u003E\n\u003Cp\u003EConsider an administrative operation:\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.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actor is the administrator requesting the deletion.\u003C/p\u003E\n\u003Cp\u003EThe target is the user account being deleted.\u003C/p\u003E\n\u003Cp\u003EThese identities must remain distinguishable throughout authorization and execution.\u003C/p\u003E\n\u003Cp\u003EFor a self-service operation:\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.delete.self\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actor and target are expected to represent the same identity.\u003C/p\u003E\n\u003Cp\u003EThat relationship must be established from trusted context, not assumed from the action suffix.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022trusted-context-construction\u0022\u003ETrusted Context Construction\u003C/h3\u003E\n\u003Cp\u003EAuthorization decisions are only as reliable as the information supplied to them.\u003C/p\u003E\n\u003Cp\u003EAn endpoint may receive a target user ID or tenant ID from the caller, but these values must not automatically become trusted authorization facts.\u003C/p\u003E\n\u003Cp\u003EThe authentication infrastructure establishes the actor\u0027s identity. The application or domain operation resolves the intended target. The authorization context brings those elements together.\u003C/p\u003E\n\u003Cp\u003EPolicies then evaluate the resulting relationships.\u003C/p\u003E\n\u003Cp\u003EThis distinction is essential for preventing cross-user and cross-tenant access.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies-as-decision-units\u0022\u003EPolicies as Decision Units\u003C/h2\u003E\n\u003Cp\u003EA policy evaluates a specific security requirement.\u003C/p\u003E\n\u003Cp\u003ERather than combining every authorization rule into one large conditional expression, UltimateAuth separates requirements into reusable policies.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequiring an authenticated actor.\u003C/li\u003E\n\u003Cli\u003ERequiring an active user.\u003C/li\u003E\n\u003Cli\u003EEnforcing tenant isolation.\u003C/li\u003E\n\u003Cli\u003EVerifying self-access relationships.\u003C/li\u003E\n\u003Cli\u003ERequiring an appropriate permission.\u003C/li\u003E\n\u003Cli\u003ERestricting an operation to a system context.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EEach policy evaluates its own responsibility within the available access context.\u003C/p\u003E\n\u003Cp\u003EA policy should not need to understand every other policy to make its decision.\u003C/p\u003E\n\u003Cp\u003EThis allows authorization rules to be composed without losing their individual meaning.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-resolution\u0022\u003EPolicy Resolution\u003C/h2\u003E\n\u003Cp\u003ENot every policy is relevant to every operation.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports policy resolution based on action matching and runtime applicability.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022action-based-matching\u0022\u003EAction-Based Matching\u003C/h3\u003E\n\u003Cp\u003EPolicies can be associated with action patterns.\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.create.admin\nusers.*\nauthorization.roles.*\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn action-specific policy can target one operation, while a broader pattern can apply to a related group of operations.\u003C/p\u003E\n\u003Cp\u003EThis allows common rules to be shared across a domain without duplicating policy registration for every action.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-applicability\u0022\u003ERuntime Applicability\u003C/h3\u003E\n\u003Cp\u003EAction matching alone may not provide enough information to determine whether a policy should execute.\u003C/p\u003E\n\u003Cp\u003EA policy can also evaluate whether it applies to the current context through \u003Ccode\u003EAppliesTo\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis enables context-dependent policy selection.\u003C/p\u003E\n\u003Cp\u003EFor example, a policy may apply only to administrative operations that modify account state, rather than every administrative read operation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EPolicy resolution determines which rules participate in the decision. Policy evaluation determines whether those rules permit the operation.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EKeeping these stages separate makes authorization behavior easier to reason about.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-categories\u0022\u003EPolicy Categories\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between security requirements that must be enforced broadly and policies selected for particular operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022invariants\u0022\u003EInvariants\u003C/h3\u003E\n\u003Cp\u003EInvariants represent fundamental security rules that must not be bypassed by ordinary permission grants or application-level authorization decisions.\u003C/p\u003E\n\u003Cp\u003EThey are evaluated before the remaining policy stages.\u003C/p\u003E\n\u003Cp\u003EAn invariant is not equivalent to a role permission or an optional business rule.\u003C/p\u003E\n\u003Cp\u003EIts purpose is to preserve a security condition that must hold regardless of the requested operation\u0027s ordinary authorization configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022global-policies\u0022\u003EGlobal Policies\u003C/h3\u003E\n\u003Cp\u003EGlobal policies apply broadly across protected operations.\u003C/p\u003E\n\u003Cp\u003EThey provide common security requirements that should not need to be registered independently for every action.\u003C/p\u003E\n\u003Cp\u003EExamples of such requirements include authentication and tenant isolation.\u003C/p\u003E\n\u003Cp\u003ETheir applicability may still depend on the operation\u0027s context. An intentionally anonymous action, for example, cannot be treated identically to an authenticated administrative operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-policies\u0022\u003ERuntime Policies\u003C/h3\u003E\n\u003Cp\u003ERuntime policies are selected and evaluated according to the requested action and current access context.\u003C/p\u003E\n\u003Cp\u003EThey support requirements such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPermission checks.\u003C/li\u003E\n\u003Cli\u003ESelf-access validation.\u003C/li\u003E\n\u003Cli\u003EActive-user requirements.\u003C/li\u003E\n\u003Cli\u003EOperation-specific restrictions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis separation allows the authorization system to maintain mandatory security boundaries while remaining extensible for individual domains and applications.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-evaluation-order\u0022\u003EPolicy Evaluation Order\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s authorization pipeline follows a defined evaluation order:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003E\u003Cstrong\u003EInvariants\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EGlobal policies\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERuntime policies\u003C/strong\u003E\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThis order establishes a clear security hierarchy.\u003C/p\u003E\n\u003Cp\u003EA runtime permission grant must not override an invariant that has already rejected the operation.\u003C/p\u003E\n\u003Cp\u003ELikewise, satisfying an action-specific policy does not eliminate the need to satisfy applicable global requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deny-takes-precedence\u0022\u003EDeny Takes Precedence\u003C/h3\u003E\n\u003Cp\u003EThe decision model is deny-oriented.\u003C/p\u003E\n\u003Cp\u003EWhen a required policy denies an operation, a later policy cannot convert that denial into an authorization grant.\u003C/p\u003E\n\u003Cp\u003EThis prevents authorization from depending on the accidental presence of a permissive policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022allow-is-not-a-universal-grant\u0022\u003EAllow Is Not a Universal Grant\u003C/h3\u003E\n\u003Cp\u003EA policy returning an allow result means that its own requirement has been satisfied or that it has no objection to the operation.\u003C/p\u003E\n\u003Cp\u003EIt does not mean that all other requirements have been satisfied.\u003C/p\u003E\n\u003Cp\u003EThe overall decision is produced by the authorization pipeline, not by one successful policy in isolation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reauthentication\u0022\u003EReauthentication\u003C/h3\u003E\n\u003Cp\u003ESome authorization decisions may require additional authentication rather than producing an ordinary allow or deny outcome.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s decision model includes the ability to represent a reauthentication requirement.\u003C/p\u003E\n\u003Cp\u003EThis allows authorization to distinguish between insufficient authority and situations where the actor may need to establish a stronger or more recent authentication state.\u003C/p\u003E\n\u003Cp\u003EThe exact handling of such a result depends on the surrounding authentication and application flow.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessorchestrator\u0022\u003EAccessOrchestrator\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessOrchestrator\u003C/code\u003E coordinates the authorization process.\u003C/p\u003E\n\u003Cp\u003EIts responsibilities include preparing the effective access context, resolving applicable policies, evaluating the required security conditions, and coordinating execution when access is permitted.\u003C/p\u003E\n\u003Cp\u003EThe conceptual pipeline 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\u003ERequested operation\n |\n v\nBuild AccessContext\n |\n v\nResolve authorization information\n |\n v\nEvaluate invariants\n |\n v\nEvaluate global policies\n |\n v\nResolve applicable runtime policies\n |\n v\nEvaluate runtime policies\n |\n \u002B---- Deny ------\u0026gt; Reject operation\n |\n \u002B---- Reauth ----\u0026gt; Require reauthentication\n |\n v\n Allow\n |\n v\nExecute authorized operation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is a conceptual representation of the access decision pipeline, not a substitute for the concrete orchestration API.\u003C/p\u003E\n\u003Cp\u003EThe orchestrator centralizes authorization coordination so that individual application services do not need to reproduce the complete evaluation process.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-before-execution\u0022\u003EAuthorization Before Execution\u003C/h2\u003E\n\u003Cp\u003EAuthorization should be enforced before a protected operation performs security-sensitive changes.\u003C/p\u003E\n\u003Cp\u003EConsider an administrative credential operation.\u003C/p\u003E\n\u003Cp\u003EThe system may need to establish:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EWho is performing the operation.\u003C/li\u003E\n\u003Cli\u003EWhich account is being targeted.\u003C/li\u003E\n\u003Cli\u003EWhether the actor possesses the required administrative permission.\u003C/li\u003E\n\u003Cli\u003EWhether the target belongs to an allowed tenant.\u003C/li\u003E\n\u003Cli\u003EWhether additional operation-specific restrictions apply.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EOnly after the applicable requirements succeed should the protected mutation proceed.\u003C/p\u003E\n\u003Cp\u003EThis separation is particularly important for operations that affect credentials, account status, roles, or sessions.\u003C/p\u003E\n\u003Cp\u003EA rejected authorization decision must not leave behind a partially completed protected operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-preventing-administrative-self-modification\u0022\u003EExample: Preventing Administrative Self-Modification\u003C/h2\u003E\n\u003Cp\u003ESome administrative operations may intentionally prohibit an administrator from modifying their own account through an administrative route.\u003C/p\u003E\n\u003Cp\u003EThis is different from ordinary self-service account management.\u003C/p\u003E\n\u003Cp\u003EA policy enforcing this restriction could consider:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhether the requested action has administrative scope.\u003C/li\u003E\n\u003Cli\u003EWhether the operation modifies security-sensitive account state.\u003C/li\u003E\n\u003Cli\u003EWhether the actor and target represent the same identity.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIf the policy applies and the actor is also the target, it denies the operation.\u003C/p\u003E\n\u003Cp\u003EThe purpose is to prevent an administrative capability from bypassing the separate rules governing self-service operations.\u003C/p\u003E\n\u003Cp\u003ESuch a policy illustrates why permission checks alone are insufficient.\u003C/p\u003E\n\u003Cp\u003EAn administrator may possess the required action permission while still being prohibited from performing the operation against a particular target.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-requiring-an-active-user\u0022\u003EExample: Requiring an Active User\u003C/h2\u003E\n\u003Cp\u003EAn active-user policy evaluates whether the relevant identity satisfies the account state requirements for an operation.\u003C/p\u003E\n\u003Cp\u003EAn authenticated principal may exist even after the corresponding user account has been suspended or otherwise made ineligible.\u003C/p\u003E\n\u003Cp\u003EThe authorization system therefore needs a way to enforce account eligibility independently of the original authentication event.\u003C/p\u003E\n\u003Cp\u003EThe policy can evaluate the relevant user state and deny operations that require an active account.\u003C/p\u003E\n\u003Cp\u003EAnonymous operations may follow different applicability rules because they do not necessarily have an authenticated user identity.\u003C/p\u003E\n\u003Cp\u003EThe exact enforcement behavior also depends on how current user security state is obtained.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-decisions-and-authentication-modes\u0022\u003EPolicy Decisions and Authentication Modes\u003C/h2\u003E\n\u003Cp\u003EThe policy model is conceptually independent of the authentication mode, but the freshness of its input data matters.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, server-side state may be consulted as part of protected operations.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, some authorization information may originate from claims issued earlier.\u003C/p\u003E\n\u003Cp\u003EThis creates a distinction between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEvaluating a policy against current authoritative data.\u003C/li\u003E\n\u003Cli\u003EEvaluating a policy against previously issued authorization claims.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, a permission removed after token issuance may still be present in a locally validated JWT until that token expires.\u003C/p\u003E\n\u003Cp\u003EA policy can evaluate its inputs correctly while those inputs reflect an earlier authorization state.\u003C/p\u003E\n\u003Cp\u003EApplications must account for this when choosing token lifetimes, permission resolution strategies, and security requirements.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022application-level-access-control\u0022\u003EApplication-Level Access Control\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s built-in authorization rules protect framework operations, but applications also have their own resources and business requirements.\u003C/p\u003E\n\u003Cp\u003EA business application may need policies for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEditing resources owned by a particular user.\u003C/li\u003E\n\u003Cli\u003ERestricting operations to a tenant.\u003C/li\u003E\n\u003Cli\u003ERequiring elevated authority for administrative changes.\u003C/li\u003E\n\u003Cli\u003EPreventing sensitive changes under particular account conditions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese requirements can be modeled as application-specific authorization policies.\u003C/p\u003E\n\u003Cp\u003EThe recommended architectural approach is to keep security decisions at a boundary that cannot be bypassed by alternative execution paths.\u003C/p\u003E\n\u003Cp\u003EFor HTTP endpoints, ASP.NET Core authorization can provide the request-level enforcement boundary.\u003C/p\u003E\n\u003Cp\u003EFor direct application service calls, including in-process operations, authorization must also be enforced where the protected operation actually executes.\u003C/p\u003E\n\u003Cp\u003EThe presence of an \u003Ccode\u003E[Authorize]\u003C/code\u003E attribute on a controller does not automatically protect a service invoked directly from another part of the application.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extending-the-policy-pipeline\u0022\u003EExtending the Policy Pipeline\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s policy model is designed to accommodate application-specific requirements without modifying the authentication engine.\u003C/p\u003E\n\u003Cp\u003ECustom policies can introduce additional authorization conditions while participating in the established access decision process.\u003C/p\u003E\n\u003Cp\u003ESuch policies should preserve the framework\u0027s security boundaries:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDo not treat untrusted request values as established identity facts.\u003C/li\u003E\n\u003Cli\u003EDo not bypass mandatory invariants.\u003C/li\u003E\n\u003Cli\u003EDo not use broad permission grants to override target or tenant restrictions.\u003C/li\u003E\n\u003Cli\u003EKeep policy applicability and evaluation behavior explicit.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EDetailed custom policy implementation and registration examples belong in \u003Ca href=\u0022/docs/how-to\u0022\u003EHow-to Guides\u003C/a\u003E, where they can be presented alongside complete application code.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth authorization is a coordinated evaluation of security requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E describes the operation. Policy resolution identifies the relevant requirements. Policy evaluation determines whether those requirements are satisfied. \u003Ccode\u003EAccessOrchestrator\u003C/code\u003E coordinates the process and its execution boundary.\u003C/p\u003E\n\u003Cp\u003EThe result is an authorization model that is \u003Cstrong\u003Econtext-aware, policy-driven, and extensible without making individual permissions responsible for every security decision\u003C/strong\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/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Actions, permissions, roles, and scope semantics.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/users\u0022\u003EUsers\u003C/a\u003E \u2014 Account lifecycle and identity state.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Credential ownership and security operations.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/request-lifecycle\u0022\u003ERequest Lifecycle\u003C/a\u003E \u2014 Authorization within protected requests.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EAuthorization is not simply a question of whether an identity possesses a permission.\u003C/p\u003E\n\u003Cp\u003EA protected operation may involve an authenticated actor, a target identity, tenant boundaries, resource ownership, and security conditions that cannot be represented by a permission alone.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth addresses these requirements through a \u003Cstrong\u003Epolicy-driven access control pipeline\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EPolicies are independent decision units evaluated within a shared access context. The authorization orchestrator coordinates those decisions and determines whether the requested operation can proceed.\u003C/p\u003E\n\u003Cp\u003EThis page explains how that process works.\u003C/p\u003E\n\u003Cp\u003EFor actions, permissions, roles, and scope conventions, see \u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-access-decision-model\u0022\u003EThe Access Decision Model\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth evaluates authorization using three primary elements:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EElement\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\u003EAccessContext\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EDescribes the actor, target, operation, and relevant security context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPolicies\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEvaluate individual authorization requirements\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAccessOrchestrator\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECoordinates policy resolution, evaluation, and execution\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese elements separate the description of an operation from the rules used to authorize it.\u003C/p\u003E\n\u003Cp\u003EA permission may grant authority to perform an action, but policies determine whether that authority is valid in the current context.\u003C/p\u003E\n\u003Cp\u003EFor example, a caller may possess an administrative permission while attempting to access a resource belonging to another tenant.\u003C/p\u003E\n\u003Cp\u003EThe permission may match, but the operation must still satisfy tenant isolation requirements.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accesscontext\u0022\u003EAccessContext\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E represents the security context of an authorization operation.\u003C/p\u003E\n\u003Cp\u003EIt provides the information needed to evaluate access without requiring each policy to reconstruct the request independently.\u003C/p\u003E\n\u003Cp\u003EConceptually, the context includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EComponent\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\u003EActor\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe identity or system principal performing the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ETarget\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe identity or resource affected by the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAction\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe protected operation being requested\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ETenant context\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe tenant boundaries relevant to the actor and target\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAuthorization information\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERelevant claims, permissions, and security state\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe context is more than a collection of request parameters.\u003C/p\u003E\n\u003Cp\u003EIt establishes the relationship between the authenticated actor and the operation being performed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022actor-and-target-are-different-concepts\u0022\u003EActor and Target Are Different Concepts\u003C/h3\u003E\n\u003Cp\u003EConsider an administrative operation:\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.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actor is the administrator requesting the deletion.\u003C/p\u003E\n\u003Cp\u003EThe target is the user account being deleted.\u003C/p\u003E\n\u003Cp\u003EThese identities must remain distinguishable throughout authorization and execution.\u003C/p\u003E\n\u003Cp\u003EFor a self-service operation:\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.delete.self\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actor and target are expected to represent the same identity.\u003C/p\u003E\n\u003Cp\u003EThat relationship must be established from trusted context, not assumed from the action suffix.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022trusted-context-construction\u0022\u003ETrusted Context Construction\u003C/h3\u003E\n\u003Cp\u003EAuthorization decisions are only as reliable as the information supplied to them.\u003C/p\u003E\n\u003Cp\u003EAn endpoint may receive a target user ID or tenant ID from the caller, but these values must not automatically become trusted authorization facts.\u003C/p\u003E\n\u003Cp\u003EThe authentication infrastructure establishes the actor\u0027s identity. The application or domain operation resolves the intended target. The authorization context brings those elements together.\u003C/p\u003E\n\u003Cp\u003EPolicies then evaluate the resulting relationships.\u003C/p\u003E\n\u003Cp\u003EThis distinction is essential for preventing cross-user and cross-tenant access.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policies-as-decision-units\u0022\u003EPolicies as Decision Units\u003C/h2\u003E\n\u003Cp\u003EA policy evaluates a specific security requirement.\u003C/p\u003E\n\u003Cp\u003ERather than combining every authorization rule into one large conditional expression, UltimateAuth separates requirements into reusable policies.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequiring an authenticated actor.\u003C/li\u003E\n\u003Cli\u003ERequiring an active user.\u003C/li\u003E\n\u003Cli\u003EEnforcing tenant isolation.\u003C/li\u003E\n\u003Cli\u003EVerifying self-access relationships.\u003C/li\u003E\n\u003Cli\u003ERequiring an appropriate permission.\u003C/li\u003E\n\u003Cli\u003ERestricting an operation to a system context.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EEach policy evaluates its own responsibility within the available access context.\u003C/p\u003E\n\u003Cp\u003EA policy should not need to understand every other policy to make its decision.\u003C/p\u003E\n\u003Cp\u003EThis allows authorization rules to be composed without losing their individual meaning.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-resolution\u0022\u003EPolicy Resolution\u003C/h2\u003E\n\u003Cp\u003ENot every policy is relevant to every operation.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports policy resolution based on action matching and runtime applicability.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022action-based-matching\u0022\u003EAction-Based Matching\u003C/h3\u003E\n\u003Cp\u003EPolicies can be associated with action patterns.\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.create.admin\nusers.*\nauthorization.roles.*\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn action-specific policy can target one operation, while a broader pattern can apply to a related group of operations.\u003C/p\u003E\n\u003Cp\u003EThis allows common rules to be shared across a domain without duplicating policy registration for every action.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-applicability\u0022\u003ERuntime Applicability\u003C/h3\u003E\n\u003Cp\u003EAction matching alone may not provide enough information to determine whether a policy should execute.\u003C/p\u003E\n\u003Cp\u003EA policy can also evaluate whether it applies to the current context through \u003Ccode\u003EAppliesTo\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis enables context-dependent policy selection.\u003C/p\u003E\n\u003Cp\u003EFor example, a policy may apply only to administrative operations that modify account state, rather than every administrative read operation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EPolicy resolution determines which rules participate in the decision. Policy evaluation determines whether those rules permit the operation.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EKeeping these stages separate makes authorization behavior easier to reason about.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-categories\u0022\u003EPolicy Categories\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between security requirements that must be enforced broadly and policies selected for particular operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022invariants\u0022\u003EInvariants\u003C/h3\u003E\n\u003Cp\u003EInvariants represent fundamental security rules that must not be bypassed by ordinary permission grants or application-level authorization decisions.\u003C/p\u003E\n\u003Cp\u003EThey are evaluated before the remaining policy stages.\u003C/p\u003E\n\u003Cp\u003EAn invariant is not equivalent to a role permission or an optional business rule.\u003C/p\u003E\n\u003Cp\u003EIts purpose is to preserve a security condition that must hold regardless of the requested operation\u0027s ordinary authorization configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022global-policies\u0022\u003EGlobal Policies\u003C/h3\u003E\n\u003Cp\u003EGlobal policies apply broadly across protected operations.\u003C/p\u003E\n\u003Cp\u003EThey provide common security requirements that should not need to be registered independently for every action.\u003C/p\u003E\n\u003Cp\u003EExamples of such requirements include authentication and tenant isolation.\u003C/p\u003E\n\u003Cp\u003ETheir applicability may still depend on the operation\u0027s context. An intentionally anonymous action, for example, cannot be treated identically to an authenticated administrative operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-policies\u0022\u003ERuntime Policies\u003C/h3\u003E\n\u003Cp\u003ERuntime policies are selected and evaluated according to the requested action and current access context.\u003C/p\u003E\n\u003Cp\u003EThey support requirements such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPermission checks.\u003C/li\u003E\n\u003Cli\u003ESelf-access validation.\u003C/li\u003E\n\u003Cli\u003EActive-user requirements.\u003C/li\u003E\n\u003Cli\u003EOperation-specific restrictions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis separation allows the authorization system to maintain mandatory security boundaries while remaining extensible for individual domains and applications.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-evaluation-order\u0022\u003EPolicy Evaluation Order\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s authorization pipeline follows a defined evaluation order:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003E\u003Cstrong\u003EInvariants\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EGlobal policies\u003C/strong\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ERuntime policies\u003C/strong\u003E\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThis order establishes a clear security hierarchy.\u003C/p\u003E\n\u003Cp\u003EA runtime permission grant must not override an invariant that has already rejected the operation.\u003C/p\u003E\n\u003Cp\u003ELikewise, satisfying an action-specific policy does not eliminate the need to satisfy applicable global requirements.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deny-takes-precedence\u0022\u003EDeny Takes Precedence\u003C/h3\u003E\n\u003Cp\u003EThe decision model is deny-oriented.\u003C/p\u003E\n\u003Cp\u003EWhen a required policy denies an operation, a later policy cannot convert that denial into an authorization grant.\u003C/p\u003E\n\u003Cp\u003EThis prevents authorization from depending on the accidental presence of a permissive policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022allow-is-not-a-universal-grant\u0022\u003EAllow Is Not a Universal Grant\u003C/h3\u003E\n\u003Cp\u003EA policy returning an allow result means that its own requirement has been satisfied or that it has no objection to the operation.\u003C/p\u003E\n\u003Cp\u003EIt does not mean that all other requirements have been satisfied.\u003C/p\u003E\n\u003Cp\u003EThe overall decision is produced by the authorization pipeline, not by one successful policy in isolation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reauthentication\u0022\u003EReauthentication\u003C/h3\u003E\n\u003Cp\u003ESome authorization decisions may require additional authentication rather than producing an ordinary allow or deny outcome.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s decision model includes the ability to represent a reauthentication requirement.\u003C/p\u003E\n\u003Cp\u003EThis allows authorization to distinguish between insufficient authority and situations where the actor may need to establish a stronger or more recent authentication state.\u003C/p\u003E\n\u003Cp\u003EThe exact handling of such a result depends on the surrounding authentication and application flow.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessorchestrator\u0022\u003EAccessOrchestrator\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessOrchestrator\u003C/code\u003E coordinates the authorization process.\u003C/p\u003E\n\u003Cp\u003EIts responsibilities include preparing the effective access context, resolving applicable policies, evaluating the required security conditions, and coordinating execution when access is permitted.\u003C/p\u003E\n\u003Cp\u003EThe conceptual pipeline 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\u003ERequested operation\n |\n v\nBuild AccessContext\n |\n v\nResolve authorization information\n |\n v\nEvaluate invariants\n |\n v\nEvaluate global policies\n |\n v\nResolve applicable runtime policies\n |\n v\nEvaluate runtime policies\n |\n \u002B---- Deny ------\u0026gt; Reject operation\n |\n \u002B---- Reauth ----\u0026gt; Require reauthentication\n |\n v\n Allow\n |\n v\nExecute authorized operation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is a conceptual representation of the access decision pipeline, not a substitute for the concrete orchestration API.\u003C/p\u003E\n\u003Cp\u003EThe orchestrator centralizes authorization coordination so that individual application services do not need to reproduce the complete evaluation process.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-before-execution\u0022\u003EAuthorization Before Execution\u003C/h2\u003E\n\u003Cp\u003EAuthorization should be enforced before a protected operation performs security-sensitive changes.\u003C/p\u003E\n\u003Cp\u003EConsider an administrative credential operation.\u003C/p\u003E\n\u003Cp\u003EThe system may need to establish:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EWho is performing the operation.\u003C/li\u003E\n\u003Cli\u003EWhich account is being targeted.\u003C/li\u003E\n\u003Cli\u003EWhether the actor possesses the required administrative permission.\u003C/li\u003E\n\u003Cli\u003EWhether the target belongs to an allowed tenant.\u003C/li\u003E\n\u003Cli\u003EWhether additional operation-specific restrictions apply.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EOnly after the applicable requirements succeed should the protected mutation proceed.\u003C/p\u003E\n\u003Cp\u003EThis separation is particularly important for operations that affect credentials, account status, roles, or sessions.\u003C/p\u003E\n\u003Cp\u003EA rejected authorization decision must not leave behind a partially completed protected operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-preventing-administrative-self-modification\u0022\u003EExample: Preventing Administrative Self-Modification\u003C/h2\u003E\n\u003Cp\u003ESome administrative operations may intentionally prohibit an administrator from modifying their own account through an administrative route.\u003C/p\u003E\n\u003Cp\u003EThis is different from ordinary self-service account management.\u003C/p\u003E\n\u003Cp\u003EA policy enforcing this restriction could consider:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhether the requested action has administrative scope.\u003C/li\u003E\n\u003Cli\u003EWhether the operation modifies security-sensitive account state.\u003C/li\u003E\n\u003Cli\u003EWhether the actor and target represent the same identity.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIf the policy applies and the actor is also the target, it denies the operation.\u003C/p\u003E\n\u003Cp\u003EThe purpose is to prevent an administrative capability from bypassing the separate rules governing self-service operations.\u003C/p\u003E\n\u003Cp\u003ESuch a policy illustrates why permission checks alone are insufficient.\u003C/p\u003E\n\u003Cp\u003EAn administrator may possess the required action permission while still being prohibited from performing the operation against a particular target.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-requiring-an-active-user\u0022\u003EExample: Requiring an Active User\u003C/h2\u003E\n\u003Cp\u003EAn active-user policy evaluates whether the relevant identity satisfies the account state requirements for an operation.\u003C/p\u003E\n\u003Cp\u003EAn authenticated principal may exist even after the corresponding user account has been suspended or otherwise made ineligible.\u003C/p\u003E\n\u003Cp\u003EThe authorization system therefore needs a way to enforce account eligibility independently of the original authentication event.\u003C/p\u003E\n\u003Cp\u003EThe policy can evaluate the relevant user state and deny operations that require an active account.\u003C/p\u003E\n\u003Cp\u003EAnonymous operations may follow different applicability rules because they do not necessarily have an authenticated user identity.\u003C/p\u003E\n\u003Cp\u003EThe exact enforcement behavior also depends on how current user security state is obtained.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-decisions-and-authentication-modes\u0022\u003EPolicy Decisions and Authentication Modes\u003C/h2\u003E\n\u003Cp\u003EThe policy model is conceptually independent of the authentication mode, but the freshness of its input data matters.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, server-side state may be consulted as part of protected operations.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, some authorization information may originate from claims issued earlier.\u003C/p\u003E\n\u003Cp\u003EThis creates a distinction between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEvaluating a policy against current authoritative data.\u003C/li\u003E\n\u003Cli\u003EEvaluating a policy against previously issued authorization claims.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor example, a permission removed after token issuance may still be present in a locally validated JWT until that token expires.\u003C/p\u003E\n\u003Cp\u003EA policy can evaluate its inputs correctly while those inputs reflect an earlier authorization state.\u003C/p\u003E\n\u003Cp\u003EApplications must account for this when choosing token lifetimes, permission resolution strategies, and security requirements.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022application-level-access-control\u0022\u003EApplication-Level Access Control\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s built-in authorization rules protect framework operations, but applications also have their own resources and business requirements.\u003C/p\u003E\n\u003Cp\u003EA business application may need policies for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEditing resources owned by a particular user.\u003C/li\u003E\n\u003Cli\u003ERestricting operations to a tenant.\u003C/li\u003E\n\u003Cli\u003ERequiring elevated authority for administrative changes.\u003C/li\u003E\n\u003Cli\u003EPreventing sensitive changes under particular account conditions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese requirements can be modeled as application-specific authorization policies.\u003C/p\u003E\n\u003Cp\u003EThe recommended architectural approach is to keep security decisions at a boundary that cannot be bypassed by alternative execution paths.\u003C/p\u003E\n\u003Cp\u003EFor HTTP endpoints, ASP.NET Core authorization can provide the request-level enforcement boundary.\u003C/p\u003E\n\u003Cp\u003EFor direct application service calls, including in-process operations, authorization must also be enforced where the protected operation actually executes.\u003C/p\u003E\n\u003Cp\u003EThe presence of an \u003Ccode\u003E[Authorize]\u003C/code\u003E attribute on a controller does not automatically protect a service invoked directly from another part of the application.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extending-the-policy-pipeline\u0022\u003EExtending the Policy Pipeline\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s policy model is designed to accommodate application-specific requirements without modifying the authentication engine.\u003C/p\u003E\n\u003Cp\u003ECustom policies can introduce additional authorization conditions while participating in the established access decision process.\u003C/p\u003E\n\u003Cp\u003ESuch policies should preserve the framework\u0027s security boundaries:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDo not treat untrusted request values as established identity facts.\u003C/li\u003E\n\u003Cli\u003EDo not bypass mandatory invariants.\u003C/li\u003E\n\u003Cli\u003EDo not use broad permission grants to override target or tenant restrictions.\u003C/li\u003E\n\u003Cli\u003EKeep policy applicability and evaluation behavior explicit.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EDetailed custom policy implementation and registration examples belong in \u003Ca href=\u0022/docs/how-to\u0022\u003EHow-to Guides\u003C/a\u003E, where they can be presented alongside complete application code.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth authorization is a coordinated evaluation of security requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E describes the operation. Policy resolution identifies the relevant requirements. Policy evaluation determines whether those requirements are satisfied. \u003Ccode\u003EAccessOrchestrator\u003C/code\u003E coordinates the process and its execution boundary.\u003C/p\u003E\n\u003Cp\u003EThe result is an authorization model that is \u003Cstrong\u003Econtext-aware, policy-driven, and extensible without making individual permissions responsible for every security decision\u003C/strong\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/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Actions, permissions, roles, and scope semantics.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/users\u0022\u003EUsers\u003C/a\u003E \u2014 Account lifecycle and identity state.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Credential ownership and security operations.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/request-lifecycle\u0022\u003ERequest Lifecycle\u003C/a\u003E \u2014 Authorization within protected requests.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "the-access-decision-model", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/users.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/users.json new file mode 100644 index 00000000..d0323db1 --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/users.json @@ -0,0 +1,82 @@ +{ + "Slug": "plugin-domains/users", + "Title": "Users", + "Html": "\n\u003Cp\u003EA user is more than a database record, an email address, or a profile.\u003C/p\u003E\n\u003Cp\u003EIn UltimateAuth, user identity is modeled through a separation of \u003Cstrong\u003Eauthentication identity, account lifecycle, identifiers, and application-specific user information\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThis distinction allows the authentication engine to operate independently of a particular user entity or persistence model, while the Users Plugin Domain provides the capabilities needed to manage identities throughout their lifecycle.\u003C/p\u003E\n\u003Cp\u003EThe Users domain builds on the authentication infrastructure provided by UltimateAuth Core and Server. It does not redefine how authentication works; it defines how user identities are represented, maintained, and integrated with that infrastructure.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-user-identity\u0022\u003EUnderstanding User Identity\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between three aspects of a user:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EComponent\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\u003ELifecycle\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAccount existence, status, and security-relevant state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EIdentifiers\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EValues through which a user can be identified or discovered\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EProfile\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EApplication-specific information associated with the user\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese components represent different responsibilities and should not be treated as interchangeable.\u003C/p\u003E\n\u003Cp\u003EFor example, changing a display name should not affect authentication state. Verifying an email address should not implicitly change account status. Suspending an account should not require deleting its identifiers or profile.\u003C/p\u003E\n\u003Cp\u003ESeparating these concerns makes identity operations more predictable and allows security-sensitive changes to be handled explicitly.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022user-identity-in-core-and-server\u0022\u003EUser Identity in Core and Server\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth does not require applications to adopt the Users Plugin Domain simply to authenticate users.\u003C/p\u003E\n\u003Cp\u003ECore and Server provide the foundational authentication infrastructure, including the contracts and runtime mechanisms needed to associate authentication operations with user identities.\u003C/p\u003E\n\u003Cp\u003EAt this level, a user is primarily an \u003Cstrong\u003Eauthentication subject\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe authentication system needs to establish which identity is involved in an operation, evaluate the relevant security conditions, and maintain the appropriate authentication state.\u003C/p\u003E\n\u003Cp\u003EIt does not inherently need to know the user\u0027s display name, profile fields, or how the application\u0027s user records are stored.\u003C/p\u003E\n\u003Cp\u003EThis separation is intentional:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ECore\u003C/strong\u003E defines foundational authentication concepts, contracts, and security semantics.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EServer\u003C/strong\u003E executes authentication flows and enforces the relevant runtime security boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUsers\u003C/strong\u003E provides higher-level identity management capabilities through a dedicated Plugin Domain.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAn application with an existing identity system can integrate that system with UltimateAuth\u0027s authentication infrastructure without necessarily adopting the reference user model.\u003C/p\u003E\n\u003Cp\u003EThe authentication identity is therefore a framework-level concept, while the complete user management model belongs to the application or the Users domain.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-the-users-domain-adds\u0022\u003EWhat the Users Domain Adds\u003C/h2\u003E\n\u003Cp\u003EThe Users Plugin Domain provides a structured approach to managing user identities beyond the minimum requirements of authentication.\u003C/p\u003E\n\u003Cp\u003EIts responsibilities include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECreating and managing user accounts.\u003C/li\u003E\n\u003Cli\u003EMaintaining account lifecycle and status.\u003C/li\u003E\n\u003Cli\u003ERegistering and managing user identifiers.\u003C/li\u003E\n\u003Cli\u003EEnforcing identifier ownership and verification rules.\u003C/li\u003E\n\u003Cli\u003EApplying user management validation and access boundaries.\u003C/li\u003E\n\u003Cli\u003ECoordinating security-relevant changes with authentication infrastructure.\u003C/li\u003E\n\u003Cli\u003EExposing user management operations through application services and shared contracts.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese capabilities are distinct from credential management.\u003C/p\u003E\n\u003Cp\u003EThe Users domain determines \u003Cstrong\u003Ewhich identity an identifier belongs to\u003C/strong\u003E and maintains the relevant user state. The Credentials domain manages the authentication credentials associated with identities.\u003C/p\u003E\n\u003Cp\u003EFor example, an email address is a user identifier; a password is a credential. They may participate in the same login flow, but they have different ownership and lifecycle rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022account-lifecycle\u0022\u003EAccount Lifecycle\u003C/h2\u003E\n\u003Cp\u003EThe lifecycle represents the existence and security-relevant state of a user account.\u003C/p\u003E\n\u003Cp\u003EIn the reference model, lifecycle information includes account creation, account status, and security state used to coordinate authentication-related changes.\u003C/p\u003E\n\u003Cp\u003EAccount status may represent conditions such as active, suspended, or deleted, according to the supported lifecycle model.\u003C/p\u003E\n\u003Cp\u003EThese states are not merely descriptive profile properties. They can influence whether an account is eligible to participate in authentication and other protected operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-state-and-session-invalidation\u0022\u003ESecurity State and Session Invalidation\u003C/h3\u003E\n\u003Cp\u003EUser lifecycle changes may require existing authentication state to be invalidated.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth separates the decision to change a user\u0027s security state from the mechanisms used to enforce that change.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured authentication mode, enforcement may involve server-side session invalidation, token lifecycle rules, or other supported security mechanisms.\u003C/p\u003E\n\u003Cp\u003EThis distinction matters because not every authentication mode can invalidate an already-issued credential immediately.\u003C/p\u003E\n\u003Cp\u003EFor example, a locally validated JWT may remain valid until expiration unless the protected resource performs additional server-side validation.\u003C/p\u003E\n\u003Cp\u003EThe Users domain participates in security-sensitive account operations, while the authentication infrastructure determines how the resulting security state is enforced.\u003C/p\u003E\n\u003Cp\u003EFor more information, 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=\u0022user-identifiers\u0022\u003EUser Identifiers\u003C/h2\u003E\n\u003Cp\u003EAn identifier is a value associated with a user that allows the identity to be referenced or resolved.\u003C/p\u003E\n\u003Cp\u003ECommon examples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EEmail addresses.\u003C/li\u003E\n\u003Cli\u003EUsernames.\u003C/li\u003E\n\u003Cli\u003EPhone numbers.\u003C/li\u003E\n\u003Cli\u003EApplication-defined identifier types.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth treats identifiers as independent identity records rather than assuming that every user has exactly one email address or username.\u003C/p\u003E\n\u003Cp\u003EThis supports applications with different identity requirements without changing the underlying authentication model.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-properties\u0022\u003EIdentifier Properties\u003C/h3\u003E\n\u003Cp\u003EThe reference identifier model distinguishes several important 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\u003Cstrong\u003EType\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifies the kind of identifier, such as email or username\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EValue\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe identifier value associated with the user\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ENormalized value\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EA representation used for consistent lookup and comparison\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPrimary state\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EIndicates the identifier\u0027s designated primary role\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EVerification state\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EIndicates whether ownership has been verified\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese properties address separate questions.\u003C/p\u003E\n\u003Cp\u003EAn identifier may exist without being verified. A verified identifier does not automatically become primary. A primary identifier does not necessarily satisfy every authentication policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022login-identifier-resolution\u0022\u003ELogin Identifier Resolution\u003C/h3\u003E\n\u003Cp\u003EIdentifier resolution determines how an incoming login identifier is associated with a user identity.\u003C/p\u003E\n\u003Cp\u003EIn the reference approach, primary identifiers define the intended login surface, subject to the configured identifier resolution and verification policies.\u003C/p\u003E\n\u003Cp\u003EApplications can control which identifier types are accepted and how identifiers are resolved.\u003C/p\u003E\n\u003Cp\u003EThis allows authentication to support different identity strategies without embedding email-specific or username-specific assumptions into Core.\u003C/p\u003E\n\u003Cp\u003EThe distinction is important: \u003Cstrong\u003Ean identifier is identity data; whether it is acceptable for login is an authentication policy decision.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-ownership-and-verification\u0022\u003EIdentifier Ownership and Verification\u003C/h3\u003E\n\u003Cp\u003EIdentifier verification is a security operation, not a simple update to a Boolean property.\u003C/p\u003E\n\u003Cp\u003EMarking an identifier as verified must be based on an appropriate ownership proof or an explicitly authorized administrative operation.\u003C/p\u003E\n\u003Cp\u003EThe reference domain therefore distinguishes between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERegistering an identifier.\u003C/li\u003E\n\u003Cli\u003EEstablishing ownership of an identifier.\u003C/li\u003E\n\u003Cli\u003EVerifying that ownership.\u003C/li\u003E\n\u003Cli\u003EChanging an identifier\u0027s role or status.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EAn anonymous or self-service request must not be able to claim verified ownership merely by supplying a verification flag.\u003C/p\u003E\n\u003Cp\u003ELikewise, operations targeting another user\u0027s identifiers must respect the applicable authorization and ownership boundaries.\u003C/p\u003E\n\u003Cp\u003EThis prevents identifier management from becoming an alternative route around authentication security.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022user-profiles\u0022\u003EUser Profiles\u003C/h2\u003E\n\u003Cp\u003EA profile contains information about the user that is not itself an authentication credential or an identity verification proof.\u003C/p\u003E\n\u003Cp\u003ETypical profile information may include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EDisplay name and personal information.\u003C/li\u003E\n\u003Cli\u003ELocalization preferences.\u003C/li\u003E\n\u003Cli\u003EApplication metadata.\u003C/li\u003E\n\u003Cli\u003EOther domain-specific user attributes.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth deliberately separates profile data from the security-critical identity model.\u003C/p\u003E\n\u003Cp\u003EApplications should be able to evolve their profile structure without redesigning authentication flows or changing how identifiers are verified.\u003C/p\u003E\n\u003Cp\u003EA profile attribute may still be relevant to application authorization or business rules, but it should not automatically be treated as proof of identity.\u003C/p\u003E\n\u003Cp\u003EThe reference model provides a starting point; applications remain responsible for deciding which additional user attributes belong in their own domain.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022reference-implementation\u0022\u003EReference Implementation\u003C/h2\u003E\n\u003Cp\u003EThe Users Plugin Domain includes a reference implementation that demonstrates and provides a practical user management model.\u003C/p\u003E\n\u003Cp\u003EIt brings together lifecycle, identifiers, and profile management through application services and supporting abstractions.\u003C/p\u003E\n\u003Cp\u003ERather than exposing persistence operations as the primary application interface, the reference implementation places user management rules within the application service layer.\u003C/p\u003E\n\u003Cp\u003EThis layer coordinates operations such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EUser creation and account management.\u003C/li\u003E\n\u003Cli\u003EIdentifier registration and updates.\u003C/li\u003E\n\u003Cli\u003EIdentifier verification and ownership validation.\u003C/li\u003E\n\u003Cli\u003EAccount status changes.\u003C/li\u003E\n\u003Cli\u003EIntegration and lifecycle notifications.\u003C/li\u003E\n\u003Cli\u003ESecurity-related coordination with the authentication infrastructure.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis is an important architectural boundary.\u003C/p\u003E\n\u003Cp\u003EA request to verify an identifier, for example, is not equivalent to directly updating a database field. The application service is responsible for enforcing the rules that make the operation valid.\u003C/p\u003E\n\u003Cp\u003EThe reference implementation provides a recommended starting point for these behaviors without requiring every application to use the same persistence technology or user profile structure.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022domain-integration\u0022\u003EDomain Integration\u003C/h2\u003E\n\u003Cp\u003EUser management frequently affects other parts of an identity platform.\u003C/p\u003E\n\u003Cp\u003ECreating a user may be followed by credential enrollment. Changing account security state may require session invalidation. Deleting an account may require coordinated cleanup.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports these scenarios through explicit integration contracts rather than direct dependencies between Plugin Domain implementations.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EIUserLifecycleIntegration\u003C/code\u003E provides an integration boundary for user lifecycle operations.\u003C/p\u003E\n\u003Cp\u003EThis allows other capabilities to participate in user-related workflows while keeping the Users domain responsible for its own state and rules.\u003C/p\u003E\n\u003Cp\u003EThe distinction between domain ownership and cross-domain coordination is central to UltimateAuth\u0027s modular architecture.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022customizing-user-management\u0022\u003ECustomizing User Management\u003C/h2\u003E\n\u003Cp\u003EApplications are not required to accept every detail of the reference implementation.\u003C/p\u003E\n\u003Cp\u003EDepending on their requirements and the available extension contracts, they may:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EProvide custom identifier resolution.\u003C/li\u003E\n\u003Cli\u003EConfigure supported identifier and verification policies.\u003C/li\u003E\n\u003Cli\u003EReplace storage implementations.\u003C/li\u003E\n\u003Cli\u003EIntegrate existing user repositories.\u003C/li\u003E\n\u003Cli\u003EExtend supported lifecycle and application service integrations.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor most applications, the recommended approach is to begin with the reference implementation and customize specific behavior rather than replacing the entire domain.\u003C/p\u003E\n\u003Cp\u003EMore advanced scenarios, including custom domain implementations and integration with existing identity systems, will be covered separately in the customization documentation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-your-user-model\u0022\u003EChoosing Your User Model\u003C/h2\u003E\n\u003Cp\u003EThere are two broad integration approaches.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EUse the reference Users domain\u003C/strong\u003E when the application needs a ready-made foundation for account lifecycle, identifiers, verification, and user management.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EIntegrate an existing identity model\u003C/strong\u003E when the application already owns these responsibilities and needs UltimateAuth primarily for its authentication infrastructure.\u003C/p\u003E\n\u003Cp\u003ENeither approach changes the fundamental security requirements.\u003C/p\u003E\n\u003Cp\u003EIdentity resolution must be trustworthy, identifier ownership must be protected, and authentication state must be managed according to the configured security model.\u003C/p\u003E\n\u003Cp\u003EThe difference is which part of the application owns the user management implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cp\u003EThe Users domain establishes identity ownership and account lifecycle, but it does not independently provide every capability of an identity platform.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Learn how authentication credentials are managed independently of user identifiers.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Explore how identities are associated with permissions and access policies.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains\u0022\u003EPlugin Domains\u003C/a\u003E \u2014 Return to the overall Plugin Domain architecture.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor the distinction between identity and authentication infrastructure, see \u003Ca href=\u0022/docs/fundamentals/auth-model\u0022\u003EAuthentication Model\u003C/a\u003E.\u003C/p\u003E\n", + "Headings": [ + { + "Id": "understanding-user-identity", + "Text": "Understanding User Identity", + "Level": 0 + }, + { + "Id": "user-identity-in-core-and-server", + "Text": "User Identity in Core and Server", + "Level": 0 + }, + { + "Id": "what-the-users-domain-adds", + "Text": "What the Users Domain Adds", + "Level": 0 + }, + { + "Id": "account-lifecycle", + "Text": "Account Lifecycle", + "Level": 0 + }, + { + "Id": "security-state-and-session-invalidation", + "Text": "Security State and Session Invalidation", + "Level": 1 + }, + { + "Id": "user-identifiers", + "Text": "User Identifiers", + "Level": 0 + }, + { + "Id": "identifier-properties", + "Text": "Identifier Properties", + "Level": 1 + }, + { + "Id": "login-identifier-resolution", + "Text": "Login Identifier Resolution", + "Level": 1 + }, + { + "Id": "identifier-ownership-and-verification", + "Text": "Identifier Ownership and Verification", + "Level": 1 + }, + { + "Id": "user-profiles", + "Text": "User Profiles", + "Level": 0 + }, + { + "Id": "reference-implementation", + "Text": "Reference Implementation", + "Level": 0 + }, + { + "Id": "domain-integration", + "Text": "Domain Integration", + "Level": 0 + }, + { + "Id": "customizing-user-management", + "Text": "Customizing User Management", + "Level": 0 + }, + { + "Id": "choosing-your-user-model", + "Text": "Choosing Your User Model", + "Level": 0 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/authorization-pipeline.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/authorization-pipeline.json index bd52bb12..0a08921f 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/authorization-pipeline.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/authorization-pipeline.json @@ -1,7 +1,7 @@ { "Slug": "security/authorization-pipeline", "Title": "Authorization Pipeline Internals", - "Html": "\n\u003Cp\u003EUltimateAuth authorization is implemented as a coordinated evaluation process rather than a collection of unrelated permission checks.\u003C/p\u003E\n\u003Cp\u003EThe authorization pipeline brings together the operation being requested, the identities involved, the applicable permissions, and the policies responsible for enforcing security requirements.\u003C/p\u003E\n\u003Cp\u003EAt its center are several distinct responsibilities:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EContext preparation:\u003C/strong\u003E Establish the security information required to evaluate an operation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EPermission resolution:\u003C/strong\u003E Make effective authorization grants available to the decision process.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EPolicy resolution:\u003C/strong\u003E Determine which security rules apply to the requested action.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EPolicy evaluation:\u003C/strong\u003E Evaluate the applicable requirements in a defined order.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDecision enforcement:\u003C/strong\u003E Permit the protected operation to proceed only when the authorization result allows it.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis page examines how those responsibilities fit together.\u003C/p\u003E\n\u003Cp\u003EFor an introduction to actions, permissions, and roles, see \u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E. For policy concepts and their intended use, see \u003Ca href=\u0022/docs/plugin-domains/policies\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pipeline-architecture\u0022\u003EPipeline Architecture\u003C/h2\u003E\n\u003Cp\u003EThe authorization process can be represented as a sequence of stages:\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\u003EProtected Operation\n |\n v\n AccessContext\n |\n v\nContext Enrichment\n |\n v\nPermission Resolution\n |\n v\nInvariant Evaluation\n |\n v\nGlobal Policy Evaluation\n |\n v\nRuntime Policy Resolution\n |\n v\nRuntime Policy Evaluation\n |\n v\n Final Decision\n |\n \u002B---- Deny ----------------\u0026gt; Reject\n |\n \u002B---- Reauthentication ---\u0026gt; Require Reauthentication\n |\n \u002B---- Allow ---------------\u0026gt; Execute\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis diagram describes the logical responsibilities of the pipeline.\u003C/p\u003E\n\u003Cp\u003EIt should not be interpreted as a guarantee that every stage performs a separate database query or that every authentication mode uses the same mechanism to obtain authorization information.\u003C/p\u003E\n\u003Cp\u003EThe important property is that \u003Cstrong\u003Eauthorization is evaluated against a coherent access context before the protected operation is allowed to proceed\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accesscontext-as-the-evaluation-boundary\u0022\u003EAccessContext as the Evaluation Boundary\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E carries the information required to evaluate an authorization request.\u003C/p\u003E\n\u003Cp\u003EIt connects the authenticated actor, the intended target, the requested action, and the relevant security environment.\u003C/p\u003E\n\u003Cp\u003EConceptually, its responsibilities include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EInformation\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\u003EActor\u003C/td\u003E\n\u003Ctd\u003EIdentifies the principal performing the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ETarget\u003C/td\u003E\n\u003Ctd\u003EIdentifies the account or resource affected\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAction\u003C/td\u003E\n\u003Ctd\u003EDescribes the protected operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ETenant context\u003C/td\u003E\n\u003Ctd\u003EEstablishes the relevant isolation boundaries\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClaims\u003C/td\u003E\n\u003Ctd\u003EProvides authentication and authorization information\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPermissions\u003C/td\u003E\n\u003Ctd\u003ERepresents the effective permission information available for evaluation\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe context is shared across the authorization process so that individual policies do not need to independently reconstruct the operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022actor-and-target-separation\u0022\u003EActor and Target Separation\u003C/h3\u003E\n\u003Cp\u003EConsider 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\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.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actor is the principal requesting the deletion.\u003C/p\u003E\n\u003Cp\u003EThe target is the user account being deleted.\u003C/p\u003E\n\u003Cp\u003EThese identities may be different, and the authorization pipeline must preserve that distinction.\u003C/p\u003E\n\u003Cp\u003EFor a self-service 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\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.delete.self\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe action indicates a self-service operation, but the action name alone does not prove that actor and target are the same identity.\u003C/p\u003E\n\u003Cp\u003EThat relationship must be established from trusted context and evaluated by the applicable security rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022context-integrity\u0022\u003EContext Integrity\u003C/h3\u003E\n\u003Cp\u003EThe reliability of the authorization decision depends on the integrity of the context.\u003C/p\u003E\n\u003Cp\u003EA target identifier or tenant identifier supplied by a client must not automatically be accepted as a trusted authorization fact.\u003C/p\u003E\n\u003Cp\u003EThe pipeline must operate on the authenticated actor and appropriately resolved target information.\u003C/p\u003E\n\u003Cp\u003EA correctly implemented policy cannot compensate for a context that incorrectly identifies the actor or misrepresents the target.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022context-enrichment\u0022\u003EContext Enrichment\u003C/h2\u003E\n\u003Cp\u003EThe initial authorization context may not contain all the information required by the applicable policies.\u003C/p\u003E\n\u003Cp\u003EContext enrichment makes additional authorization information available before evaluation.\u003C/p\u003E\n\u003Cp\u003EThis can include resolving effective permissions and preparing them for efficient policy checks.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EAuthenticated Actor\n |\n v\nResolve Authorization Information\n |\n v\nEffective Permissions\n |\n v\nCompiledPermissionSet\n |\n v\nEnriched AccessContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe purpose of enrichment is to separate authorization data resolution from the individual rules that consume that data.\u003C/p\u003E\n\u003Cp\u003EA permission policy should not need to independently load role assignments and rebuild the user\u0027s effective permission set every time it evaluates an action.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-data-freshness\u0022\u003EAuthorization Data Freshness\u003C/h3\u003E\n\u003Cp\u003EEnrichment does not automatically guarantee that all authorization information is current.\u003C/p\u003E\n\u003Cp\u003EThe source of permission data matters.\u003C/p\u003E\n\u003Cp\u003EFor example, permissions obtained from current server-side state may reflect a recent role change, while permissions derived exclusively from an already-issued JWT may reflect an earlier state.\u003C/p\u003E\n\u003Cp\u003EThe pipeline can evaluate the information it receives consistently, but it cannot make stale claims current without consulting an authoritative source.\u003C/p\u003E\n\u003Cp\u003EThis distinction is particularly relevant to SemiHybrid and PureJwt authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-compilation\u0022\u003EPermission Compilation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses \u003Ccode\u003ECompiledPermissionSet\u003C/code\u003E to represent permissions in a form suitable for repeated authorization checks.\u003C/p\u003E\n\u003Cp\u003EPermission compilation separates the representation of stored grants from the process of deciding whether an action is permitted.\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\u003ERequested action:\n users.delete.admin\n\nEffective grants:\n users.read.self\n users.delete.admin\n credentials.list.self\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA permission policy can evaluate whether the effective grants cover the requested action.\u003C/p\u003E\n\u003Cp\u003EConceptually:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EpermissionSet.IsAllowed(action);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis illustrates the permission-checking responsibility, rather than a complete public API integration example.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022wildcard-permissions\u0022\u003EWildcard Permissions\u003C/h3\u003E\n\u003Cp\u003ECompiled permissions may also represent wildcard grants.\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.*\nusers.profile.*\n*\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWildcard matching must respect the permission model\u0027s defined action hierarchy.\u003C/p\u003E\n\u003Cp\u003EA broad grant should not be inferred merely because several currently visible permissions share a prefix.\u003C/p\u003E\n\u003Cp\u003EWhere permission normalization replaces a complete action group with a wildcard, the normalization must preserve the intended permission semantics.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022compilation-is-not-authorization\u0022\u003ECompilation Is Not Authorization\u003C/h3\u003E\n\u003Cp\u003EA compiled permission set is an efficient representation of grants.\u003C/p\u003E\n\u003Cp\u003EIt does not independently establish that the actor may perform the operation.\u003C/p\u003E\n\u003Cp\u003EA matching permission can still be insufficient when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe actor is not permitted to access the target.\u003C/li\u003E\n\u003Cli\u003EThe operation crosses a prohibited tenant boundary.\u003C/li\u003E\n\u003Cli\u003EThe account does not satisfy required security conditions.\u003C/li\u003E\n\u003Cli\u003EAn operation-specific policy denies the action.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EPermission matching is one requirement within the broader authorization decision.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-registration-and-resolution\u0022\u003EPolicy Registration and Resolution\u003C/h2\u003E\n\u003Cp\u003ERuntime policies must be discoverable for the operations they protect.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s authorization architecture includes policy registration and compilation concepts represented by:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EAccessPolicyRegistry\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ECompiledAccessPolicySet\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese components separate policy configuration from runtime selection.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003ERegistered Policies\n |\n v\nAccessPolicyRegistry\n |\n v\nCompiledAccessPolicySet\n |\n v\nPolicies Matching Requested Action\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022action-based-selection\u0022\u003EAction-Based Selection\u003C/h3\u003E\n\u003Cp\u003EPolicies may be associated with action prefixes or patterns.\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.*\nusers.delete.*\nusers.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA request for:\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.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Emay select policies associated with the relevant action hierarchy.\u003C/p\u003E\n\u003Cp\u003EThis allows broad policies to cover a domain while more specific policies enforce requirements for individual operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022prefix-matching-and-boundaries\u0022\u003EPrefix Matching and Boundaries\u003C/h3\u003E\n\u003Cp\u003EAction matching must respect the intended structure of action 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\u003Eusers.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Ebelongs to the \u003Ccode\u003Eusers\u003C/code\u003E action domain.\u003C/p\u003E\n\u003Cp\u003EBut an unrelated action whose text merely begins with similar characters should not automatically be treated as belonging to that domain.\u003C/p\u003E\n\u003Cp\u003EPolicy matching must therefore follow the actual action-pattern semantics implemented by the registry.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resolution-versus-evaluation\u0022\u003EResolution Versus Evaluation\u003C/h3\u003E\n\u003Cp\u003EPolicy resolution answers:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EWhich policies are candidates for this operation?\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EPolicy evaluation answers:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo those policies permit this operation in the current context?\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA policy may match an action pattern but still be inapplicable to a particular runtime context.\u003C/p\u003E\n\u003Cp\u003EThat distinction is handled through policy applicability rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-applicability\u0022\u003ERuntime Applicability\u003C/h2\u003E\n\u003Cp\u003EA policy can determine whether it applies to the current authorization context.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth represents this responsibility through \u003Ccode\u003EAppliesTo\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example, a policy restricting administrative self-modification may only be relevant when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe requested action has administrative scope.\u003C/li\u003E\n\u003Cli\u003EThe operation changes protected account state.\u003C/li\u003E\n\u003Cli\u003EThe actor and target are the same identity.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIt may not apply to administrative read operations or unrelated domains.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EPolicy selected by action\n |\n v\nAppliesTo(context)\n |\n \u002B---- No ----\u0026gt; Skip policy\n |\n \u002B---- Yes ---\u0026gt; Evaluate policy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis enables policies to remain focused on specific security requirements without requiring every action to have an entirely separate policy implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022applicability-is-not-a-security-bypass\u0022\u003EApplicability Is Not a Security Bypass\u003C/h3\u003E\n\u003Cp\u003EAn applicability rule determines when a policy participates in evaluation.\u003C/p\u003E\n\u003Cp\u003EIf a mandatory security requirement can be skipped because its applicability condition is too broad or incorrectly implemented, the resulting authorization boundary may be weaker than intended.\u003C/p\u003E\n\u003Cp\u003EPolicies enforcing fundamental security invariants should not depend on application-controlled applicability rules that can bypass those invariants.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-evaluation-stages\u0022\u003EPolicy Evaluation Stages\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s policy architecture distinguishes between invariants, global policies, and runtime policies.\u003C/p\u003E\n\u003Cp\u003EThese categories establish the order and scope of security evaluation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022stage-1-invariants\u0022\u003EStage 1: Invariants\u003C/h3\u003E\n\u003Cp\u003EInvariants represent mandatory security requirements.\u003C/p\u003E\n\u003Cp\u003EThey are evaluated before ordinary policy stages.\u003C/p\u003E\n\u003Cp\u003EAn invariant denial prevents the requested operation from proceeding, regardless of whether another policy would otherwise permit it.\u003C/p\u003E\n\u003Cp\u003EInvariants provide the strongest enforcement boundary within the policy pipeline.\u003C/p\u003E\n\u003Cp\u003EThey should not be treated as ordinary permissions that can be overridden by a broader grant.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022stage-2-global-policies\u0022\u003EStage 2: Global Policies\u003C/h3\u003E\n\u003Cp\u003EGlobal policies enforce broadly applicable security requirements.\u003C/p\u003E\n\u003Cp\u003EThey may examine the operation\u0027s context to determine whether their rule is relevant.\u003C/p\u003E\n\u003Cp\u003EExamples include requirements related to authentication state, account eligibility, and tenant isolation.\u003C/p\u003E\n\u003Cp\u003EGlobal policies allow these requirements to be enforced consistently without attaching identical rules to every individual action.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022stage-3-runtime-policies\u0022\u003EStage 3: Runtime Policies\u003C/h3\u003E\n\u003Cp\u003ERuntime policies are resolved according to the requested action and evaluated against the current context.\u003C/p\u003E\n\u003Cp\u003EThey support requirements such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequired permissions.\u003C/li\u003E\n\u003Cli\u003ESelf-access restrictions.\u003C/li\u003E\n\u003Cli\u003EAdministrative operation constraints.\u003C/li\u003E\n\u003Cli\u003EDomain-specific authorization rules.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ERuntime policies extend the decision process without replacing the mandatory security requirements established by earlier stages.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022iaccessauthority-and-decision-evaluation\u0022\u003EIAccessAuthority and Decision Evaluation\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EIAccessAuthority\u003C/code\u003E represents the authorization decision responsibility within the pipeline.\u003C/p\u003E\n\u003Cp\u003EThe authority evaluates the applicable security requirements and produces an authorization result.\u003C/p\u003E\n\u003Cp\u003EConceptually, the result can represent:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EResult\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\u003Cstrong\u003EAllow\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe applicable requirements permit the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EDeny\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EA required security condition rejects the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EReauthentication required\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe operation requires an additional authentication condition before proceeding\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese outcomes must not be confused with the result of an individual policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-allow-versus-final-allow\u0022\u003EPolicy Allow Versus Final Allow\u003C/h3\u003E\n\u003Cp\u003EAn individual policy may return an allow result because its own requirement has been satisfied.\u003C/p\u003E\n\u003Cp\u003EThat does not necessarily mean the entire operation is authorized.\u003C/p\u003E\n\u003Cp\u003EOther applicable policies may still deny the request or require reauthentication.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAn individual policy\u0027s approval is not a universal authorization grant.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deny-precedence\u0022\u003EDeny Precedence\u003C/h3\u003E\n\u003Cp\u003EA denial from an applicable mandatory policy prevents the operation from being authorized.\u003C/p\u003E\n\u003Cp\u003EThe pipeline must not allow a later permissive policy to override a previously established denial.\u003C/p\u003E\n\u003Cp\u003EThis supports predictable enforcement across multiple independent security rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reauthentication-requirements\u0022\u003EReauthentication Requirements\u003C/h3\u003E\n\u003Cp\u003ESome operations may require stronger or more recent authentication.\u003C/p\u003E\n\u003Cp\u003EA policy can represent that requirement separately from an ordinary denial.\u003C/p\u003E\n\u003Cp\u003EThe authorization system must distinguish between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn operation that is prohibited.\u003C/li\u003E\n\u003Cli\u003EAn operation that may proceed after additional authentication.\u003C/li\u003E\n\u003Cli\u003EAn operation that is currently authorized.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact precedence and propagation of reauthentication results must follow the implemented decision engine.\u003C/p\u003E\n\u003Cp\u003EA reauthentication result should not be interpreted as permission to execute the protected operation immediately.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-policies\u0022\u003EPermission Policies\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EMustHavePermissionPolicy\u003C/code\u003E represents the connection between the permission model and the policy pipeline.\u003C/p\u003E\n\u003Cp\u003EIts responsibility is to evaluate whether the actor has the permission required for the requested action.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EAccessContext.Action\n |\n v\nCompiledPermissionSet\n |\n v\nPermission Evaluation\n |\n v\nPolicy Decision\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis design separates permission storage and resolution from permission enforcement.\u003C/p\u003E\n\u003Cp\u003ERoles and assignments determine effective grants.\u003C/p\u003E\n\u003Cp\u003EThe compiled permission set represents those grants.\u003C/p\u003E\n\u003Cp\u003EThe permission policy evaluates the requested action against them.\u003C/p\u003E\n\u003Cp\u003EThe final authorization decision also considers the other applicable security policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permissions-are-not-a-replacement-for-policies\u0022\u003EPermissions Are Not a Replacement for Policies\u003C/h3\u003E\n\u003Cp\u003EFor example, an actor may possess:\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.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThat permission does not automatically authorize deleting any user in any tenant.\u003C/p\u003E\n\u003Cp\u003EThe operation must still satisfy the relevant target, tenant, and account security requirements.\u003C/p\u003E\n\u003Cp\u003EThis is why the pipeline evaluates multiple requirements rather than relying on a single permission match.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022orchestration-and-protected-execution\u0022\u003EOrchestration and Protected Execution\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessOrchestrator\u003C/code\u003E coordinates authorization and the protected operation.\u003C/p\u003E\n\u003Cp\u003EIts purpose is to establish a consistent enforcement boundary.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EOperation Requested\n |\n v\nPrepare AccessContext\n |\n v\nResolve and Evaluate Policies\n |\n \u002B---- Not Allowed ---\u0026gt; Stop\n |\n v\nExecute Protected Operation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe security requirement is straightforward:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA protected operation must not execute merely because its caller reached an application service or endpoint.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAuthorization must succeed at an enforcement boundary that covers the operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-and-in-process-execution\u0022\u003EHTTP and In-Process Execution\u003C/h3\u003E\n\u003Cp\u003EHTTP endpoints may enforce authorization through ASP.NET Core authentication and authorization integration.\u003C/p\u003E\n\u003Cp\u003EHowever, applications may also execute operations directly through in-process services.\u003C/p\u003E\n\u003Cp\u003EFor example, a Blazor Server application can invoke a server-side service without making a separate HTTP request.\u003C/p\u003E\n\u003Cp\u003EA controller-level \u003Ccode\u003E[Authorize]\u003C/code\u003E attribute does not automatically protect such a direct service invocation.\u003C/p\u003E\n\u003Cp\u003EWhere operations can be reached through multiple execution paths, authorization must be enforced at an appropriate shared boundary.\u003C/p\u003E\n\u003Cp\u003EThis prevents one entry point from bypassing rules enforced by another.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022determinism-and-security\u0022\u003EDeterminism and Security\u003C/h2\u003E\n\u003Cp\u003EA structured policy pipeline makes authorization behavior easier to reason about.\u003C/p\u003E\n\u003Cp\u003EFor the same effective context, policy configuration, and authoritative security state, the decision should follow the same evaluation rules.\u003C/p\u003E\n\u003Cp\u003EHowever, authorization can depend on mutable information:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAccount status.\u003C/li\u003E\n\u003Cli\u003ETenant membership.\u003C/li\u003E\n\u003Cli\u003ERole assignments.\u003C/li\u003E\n\u003Cli\u003EEffective permissions.\u003C/li\u003E\n\u003Cli\u003ESession security state.\u003C/li\u003E\n\u003Cli\u003ETarget ownership.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA policy evaluation that uses stale or inconsistent data may produce a result that differs from one using current authoritative state.\u003C/p\u003E\n\u003Cp\u003EDeterministic policy evaluation therefore depends on both the evaluation rules and the consistency of the information supplied to them.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022time-of-check-and-time-of-use\u0022\u003ETime-of-Check and Time-of-Use\u003C/h3\u003E\n\u003Cp\u003EAuthorization may be evaluated before a protected operation modifies data.\u003C/p\u003E\n\u003Cp\u003EIf relevant security or ownership information changes between the authorization decision and the mutation, a time-of-check/time-of-use problem may arise.\u003C/p\u003E\n\u003Cp\u003ESensitive operations may require transactional consistency, concurrency controls, or revalidation within the protected operation.\u003C/p\u003E\n\u003Cp\u003EA policy pipeline provides a structured decision process, but it does not automatically make every application-level data mutation atomic.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extensibility-boundaries\u0022\u003EExtensibility Boundaries\u003C/h2\u003E\n\u003Cp\u003EThe authorization pipeline is designed to support application-specific policies.\u003C/p\u003E\n\u003Cp\u003ECustom policies can introduce new requirements without replacing the underlying permission model or authentication engine.\u003C/p\u003E\n\u003Cp\u003EHowever, extensibility must preserve the security hierarchy.\u003C/p\u003E\n\u003Cp\u003EA custom policy should not:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EOverride mandatory invariants.\u003C/li\u003E\n\u003Cli\u003ETrust caller-supplied identity information without validation.\u003C/li\u003E\n\u003Cli\u003ETreat an action name as proof of actor-target ownership.\u003C/li\u003E\n\u003Cli\u003EBypass tenant isolation through a broad permission.\u003C/li\u003E\n\u003Cli\u003EAssume that JWT claims always reflect current authorization state.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ECustom policies should evaluate well-defined requirements using trusted context and participate in the established decision model.\u003C/p\u003E\n\u003Cp\u003EImplementation and registration examples belong in \u003Ca href=\u0022/docs/how-to\u0022\u003EHow-to Guides\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s authorization pipeline separates the responsibilities involved in an access decision.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E describes the operation.\u003C/p\u003E\n\u003Cp\u003EContext enrichment and permission compilation prepare the required authorization information.\u003C/p\u003E\n\u003Cp\u003EPolicy resolution identifies the relevant rules.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EIAccessAuthority\u003C/code\u003E evaluates those rules according to the pipeline\u0027s decision semantics.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessOrchestrator\u003C/code\u003E coordinates authorization with protected execution.\u003C/p\u003E\n\u003Cp\u003ETogether, these components provide a structured foundation for context-aware, policy-driven authorization.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe pipeline\u0027s security comes not from the number of policies it executes, but from consistently enforcing the applicable requirements before a protected operation is allowed to proceed.\u003C/strong\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/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Actions, permissions, roles, and scopes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/policies\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E \u2014 Policy concepts and access decision behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security-model\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Authentication security state and validation boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E \u2014 Credential validity and authorization freshness.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EUltimateAuth authorization is implemented as a coordinated evaluation process rather than a collection of unrelated permission checks.\u003C/p\u003E\n\u003Cp\u003EThe authorization pipeline brings together the operation being requested, the identities involved, the applicable permissions, and the policies responsible for enforcing security requirements.\u003C/p\u003E\n\u003Cp\u003EAt its center are several distinct responsibilities:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EContext preparation:\u003C/strong\u003E Establish the security information required to evaluate an operation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EPermission resolution:\u003C/strong\u003E Make effective authorization grants available to the decision process.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EPolicy resolution:\u003C/strong\u003E Determine which security rules apply to the requested action.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EPolicy evaluation:\u003C/strong\u003E Evaluate the applicable requirements in a defined order.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDecision enforcement:\u003C/strong\u003E Permit the protected operation to proceed only when the authorization result allows it.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis page examines how those responsibilities fit together.\u003C/p\u003E\n\u003Cp\u003EFor an introduction to actions, permissions, and roles, see \u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E. For policy concepts and their intended use, see \u003Ca href=\u0022/docs/plugin-domains/policies\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022pipeline-architecture\u0022\u003EPipeline Architecture\u003C/h2\u003E\n\u003Cp\u003EThe authorization process can be represented as a sequence of stages:\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\u003EProtected Operation\n |\n v\n AccessContext\n |\n v\nContext Enrichment\n |\n v\nPermission Resolution\n |\n v\nInvariant Evaluation\n |\n v\nGlobal Policy Evaluation\n |\n v\nRuntime Policy Resolution\n |\n v\nRuntime Policy Evaluation\n |\n v\n Final Decision\n |\n \u002B---- Deny ----------------\u0026gt; Reject\n |\n \u002B---- Reauthentication ---\u0026gt; Require Reauthentication\n |\n \u002B---- Allow ---------------\u0026gt; Execute\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis diagram describes the logical responsibilities of the pipeline.\u003C/p\u003E\n\u003Cp\u003EIt should not be interpreted as a guarantee that every stage performs a separate database query or that every authentication mode uses the same mechanism to obtain authorization information.\u003C/p\u003E\n\u003Cp\u003EThe important property is that \u003Cstrong\u003Eauthorization is evaluated against a coherent access context before the protected operation is allowed to proceed\u003C/strong\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accesscontext-as-the-evaluation-boundary\u0022\u003EAccessContext as the Evaluation Boundary\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E carries the information required to evaluate an authorization request.\u003C/p\u003E\n\u003Cp\u003EIt connects the authenticated actor, the intended target, the requested action, and the relevant security environment.\u003C/p\u003E\n\u003Cp\u003EConceptually, its responsibilities include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EInformation\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\u003EActor\u003C/td\u003E\n\u003Ctd\u003EIdentifies the principal performing the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ETarget\u003C/td\u003E\n\u003Ctd\u003EIdentifies the account or resource affected\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAction\u003C/td\u003E\n\u003Ctd\u003EDescribes the protected operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ETenant context\u003C/td\u003E\n\u003Ctd\u003EEstablishes the relevant isolation boundaries\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EClaims\u003C/td\u003E\n\u003Ctd\u003EProvides authentication and authorization information\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPermissions\u003C/td\u003E\n\u003Ctd\u003ERepresents the effective permission information available for evaluation\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe context is shared across the authorization process so that individual policies do not need to independently reconstruct the operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022actor-and-target-separation\u0022\u003EActor and Target Separation\u003C/h3\u003E\n\u003Cp\u003EConsider 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\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.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actor is the principal requesting the deletion.\u003C/p\u003E\n\u003Cp\u003EThe target is the user account being deleted.\u003C/p\u003E\n\u003Cp\u003EThese identities may be different, and the authorization pipeline must preserve that distinction.\u003C/p\u003E\n\u003Cp\u003EFor a self-service 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\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.delete.self\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe action indicates a self-service operation, but the action name alone does not prove that actor and target are the same identity.\u003C/p\u003E\n\u003Cp\u003EThat relationship must be established from trusted context and evaluated by the applicable security rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022context-integrity\u0022\u003EContext Integrity\u003C/h3\u003E\n\u003Cp\u003EThe reliability of the authorization decision depends on the integrity of the context.\u003C/p\u003E\n\u003Cp\u003EA target identifier or tenant identifier supplied by a client must not automatically be accepted as a trusted authorization fact.\u003C/p\u003E\n\u003Cp\u003EThe pipeline must operate on the authenticated actor and appropriately resolved target information.\u003C/p\u003E\n\u003Cp\u003EA correctly implemented policy cannot compensate for a context that incorrectly identifies the actor or misrepresents the target.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022context-enrichment\u0022\u003EContext Enrichment\u003C/h2\u003E\n\u003Cp\u003EThe initial authorization context may not contain all the information required by the applicable policies.\u003C/p\u003E\n\u003Cp\u003EContext enrichment makes additional authorization information available before evaluation.\u003C/p\u003E\n\u003Cp\u003EThis can include resolving effective permissions and preparing them for efficient policy checks.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EAuthenticated Actor\n |\n v\nResolve Authorization Information\n |\n v\nEffective Permissions\n |\n v\nCompiledPermissionSet\n |\n v\nEnriched AccessContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe purpose of enrichment is to separate authorization data resolution from the individual rules that consume that data.\u003C/p\u003E\n\u003Cp\u003EA permission policy should not need to independently load role assignments and rebuild the user\u0027s effective permission set every time it evaluates an action.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-data-freshness\u0022\u003EAuthorization Data Freshness\u003C/h3\u003E\n\u003Cp\u003EEnrichment does not automatically guarantee that all authorization information is current.\u003C/p\u003E\n\u003Cp\u003EThe source of permission data matters.\u003C/p\u003E\n\u003Cp\u003EFor example, permissions obtained from current server-side state may reflect a recent role change, while permissions derived exclusively from an already-issued JWT may reflect an earlier state.\u003C/p\u003E\n\u003Cp\u003EThe pipeline can evaluate the information it receives consistently, but it cannot make stale claims current without consulting an authoritative source.\u003C/p\u003E\n\u003Cp\u003EThis distinction is particularly relevant to SemiHybrid and PureJwt authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-compilation\u0022\u003EPermission Compilation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses \u003Ccode\u003ECompiledPermissionSet\u003C/code\u003E to represent permissions in a form suitable for repeated authorization checks.\u003C/p\u003E\n\u003Cp\u003EPermission compilation separates the representation of stored grants from the process of deciding whether an action is permitted.\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\u003ERequested action:\n users.delete.admin\n\nEffective grants:\n users.read.self\n users.delete.admin\n credentials.list.self\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA permission policy can evaluate whether the effective grants cover the requested action.\u003C/p\u003E\n\u003Cp\u003EConceptually:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EpermissionSet.IsAllowed(action);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis illustrates the permission-checking responsibility, rather than a complete public API integration example.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022wildcard-permissions\u0022\u003EWildcard Permissions\u003C/h3\u003E\n\u003Cp\u003ECompiled permissions may also represent wildcard grants.\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.*\nusers.profile.*\n*\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWildcard matching must respect the permission model\u0027s defined action hierarchy.\u003C/p\u003E\n\u003Cp\u003EA broad grant should not be inferred merely because several currently visible permissions share a prefix.\u003C/p\u003E\n\u003Cp\u003EWhere permission normalization replaces a complete action group with a wildcard, the normalization must preserve the intended permission semantics.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022compilation-is-not-authorization\u0022\u003ECompilation Is Not Authorization\u003C/h3\u003E\n\u003Cp\u003EA compiled permission set is an efficient representation of grants.\u003C/p\u003E\n\u003Cp\u003EIt does not independently establish that the actor may perform the operation.\u003C/p\u003E\n\u003Cp\u003EA matching permission can still be insufficient when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe actor is not permitted to access the target.\u003C/li\u003E\n\u003Cli\u003EThe operation crosses a prohibited tenant boundary.\u003C/li\u003E\n\u003Cli\u003EThe account does not satisfy required security conditions.\u003C/li\u003E\n\u003Cli\u003EAn operation-specific policy denies the action.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EPermission matching is one requirement within the broader authorization decision.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-registration-and-resolution\u0022\u003EPolicy Registration and Resolution\u003C/h2\u003E\n\u003Cp\u003ERuntime policies must be discoverable for the operations they protect.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s authorization architecture includes policy registration and compilation concepts represented by:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EAccessPolicyRegistry\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ECompiledAccessPolicySet\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese components separate policy configuration from runtime selection.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003ERegistered Policies\n |\n v\nAccessPolicyRegistry\n |\n v\nCompiledAccessPolicySet\n |\n v\nPolicies Matching Requested Action\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022action-based-selection\u0022\u003EAction-Based Selection\u003C/h3\u003E\n\u003Cp\u003EPolicies may be associated with action prefixes or patterns.\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.*\nusers.delete.*\nusers.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA request for:\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.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Emay select policies associated with the relevant action hierarchy.\u003C/p\u003E\n\u003Cp\u003EThis allows broad policies to cover a domain while more specific policies enforce requirements for individual operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022prefix-matching-and-boundaries\u0022\u003EPrefix Matching and Boundaries\u003C/h3\u003E\n\u003Cp\u003EAction matching must respect the intended structure of action 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\u003Eusers.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003Ebelongs to the \u003Ccode\u003Eusers\u003C/code\u003E action domain.\u003C/p\u003E\n\u003Cp\u003EBut an unrelated action whose text merely begins with similar characters should not automatically be treated as belonging to that domain.\u003C/p\u003E\n\u003Cp\u003EPolicy matching must therefore follow the actual action-pattern semantics implemented by the registry.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resolution-versus-evaluation\u0022\u003EResolution Versus Evaluation\u003C/h3\u003E\n\u003Cp\u003EPolicy resolution answers:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EWhich policies are candidates for this operation?\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EPolicy evaluation answers:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo those policies permit this operation in the current context?\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA policy may match an action pattern but still be inapplicable to a particular runtime context.\u003C/p\u003E\n\u003Cp\u003EThat distinction is handled through policy applicability rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-applicability\u0022\u003ERuntime Applicability\u003C/h2\u003E\n\u003Cp\u003EA policy can determine whether it applies to the current authorization context.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth represents this responsibility through \u003Ccode\u003EAppliesTo\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example, a policy restricting administrative self-modification may only be relevant when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe requested action has administrative scope.\u003C/li\u003E\n\u003Cli\u003EThe operation changes protected account state.\u003C/li\u003E\n\u003Cli\u003EThe actor and target are the same identity.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EIt may not apply to administrative read operations or unrelated domains.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EPolicy selected by action\n |\n v\nAppliesTo(context)\n |\n \u002B---- No ----\u0026gt; Skip policy\n |\n \u002B---- Yes ---\u0026gt; Evaluate policy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis enables policies to remain focused on specific security requirements without requiring every action to have an entirely separate policy implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022applicability-is-not-a-security-bypass\u0022\u003EApplicability Is Not a Security Bypass\u003C/h3\u003E\n\u003Cp\u003EAn applicability rule determines when a policy participates in evaluation.\u003C/p\u003E\n\u003Cp\u003EIf a mandatory security requirement can be skipped because its applicability condition is too broad or incorrectly implemented, the resulting authorization boundary may be weaker than intended.\u003C/p\u003E\n\u003Cp\u003EPolicies enforcing fundamental security invariants should not depend on application-controlled applicability rules that can bypass those invariants.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-evaluation-stages\u0022\u003EPolicy Evaluation Stages\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s policy architecture distinguishes between invariants, global policies, and runtime policies.\u003C/p\u003E\n\u003Cp\u003EThese categories establish the order and scope of security evaluation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022stage-1-invariants\u0022\u003EStage 1: Invariants\u003C/h3\u003E\n\u003Cp\u003EInvariants represent mandatory security requirements.\u003C/p\u003E\n\u003Cp\u003EThey are evaluated before ordinary policy stages.\u003C/p\u003E\n\u003Cp\u003EAn invariant denial prevents the requested operation from proceeding, regardless of whether another policy would otherwise permit it.\u003C/p\u003E\n\u003Cp\u003EInvariants provide the strongest enforcement boundary within the policy pipeline.\u003C/p\u003E\n\u003Cp\u003EThey should not be treated as ordinary permissions that can be overridden by a broader grant.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022stage-2-global-policies\u0022\u003EStage 2: Global Policies\u003C/h3\u003E\n\u003Cp\u003EGlobal policies enforce broadly applicable security requirements.\u003C/p\u003E\n\u003Cp\u003EThey may examine the operation\u0027s context to determine whether their rule is relevant.\u003C/p\u003E\n\u003Cp\u003EExamples include requirements related to authentication state, account eligibility, and tenant isolation.\u003C/p\u003E\n\u003Cp\u003EGlobal policies allow these requirements to be enforced consistently without attaching identical rules to every individual action.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022stage-3-runtime-policies\u0022\u003EStage 3: Runtime Policies\u003C/h3\u003E\n\u003Cp\u003ERuntime policies are resolved according to the requested action and evaluated against the current context.\u003C/p\u003E\n\u003Cp\u003EThey support requirements such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequired permissions.\u003C/li\u003E\n\u003Cli\u003ESelf-access restrictions.\u003C/li\u003E\n\u003Cli\u003EAdministrative operation constraints.\u003C/li\u003E\n\u003Cli\u003EDomain-specific authorization rules.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ERuntime policies extend the decision process without replacing the mandatory security requirements established by earlier stages.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022iaccessauthority-and-decision-evaluation\u0022\u003EIAccessAuthority and Decision Evaluation\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EIAccessAuthority\u003C/code\u003E represents the authorization decision responsibility within the pipeline.\u003C/p\u003E\n\u003Cp\u003EThe authority evaluates the applicable security requirements and produces an authorization result.\u003C/p\u003E\n\u003Cp\u003EConceptually, the result can represent:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EResult\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\u003Cstrong\u003EAllow\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe applicable requirements permit the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EDeny\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EA required security condition rejects the operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EReauthentication required\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe operation requires an additional authentication condition before proceeding\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese outcomes must not be confused with the result of an individual policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022policy-allow-versus-final-allow\u0022\u003EPolicy Allow Versus Final Allow\u003C/h3\u003E\n\u003Cp\u003EAn individual policy may return an allow result because its own requirement has been satisfied.\u003C/p\u003E\n\u003Cp\u003EThat does not necessarily mean the entire operation is authorized.\u003C/p\u003E\n\u003Cp\u003EOther applicable policies may still deny the request or require reauthentication.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAn individual policy\u0027s approval is not a universal authorization grant.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deny-precedence\u0022\u003EDeny Precedence\u003C/h3\u003E\n\u003Cp\u003EA denial from an applicable mandatory policy prevents the operation from being authorized.\u003C/p\u003E\n\u003Cp\u003EThe pipeline must not allow a later permissive policy to override a previously established denial.\u003C/p\u003E\n\u003Cp\u003EThis supports predictable enforcement across multiple independent security rules.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reauthentication-requirements\u0022\u003EReauthentication Requirements\u003C/h3\u003E\n\u003Cp\u003ESome operations may require stronger or more recent authentication.\u003C/p\u003E\n\u003Cp\u003EA policy can represent that requirement separately from an ordinary denial.\u003C/p\u003E\n\u003Cp\u003EThe authorization system must distinguish between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn operation that is prohibited.\u003C/li\u003E\n\u003Cli\u003EAn operation that may proceed after additional authentication.\u003C/li\u003E\n\u003Cli\u003EAn operation that is currently authorized.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact precedence and propagation of reauthentication results must follow the implemented decision engine.\u003C/p\u003E\n\u003Cp\u003EA reauthentication result should not be interpreted as permission to execute the protected operation immediately.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022permission-policies\u0022\u003EPermission Policies\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EMustHavePermissionPolicy\u003C/code\u003E represents the connection between the permission model and the policy pipeline.\u003C/p\u003E\n\u003Cp\u003EIts responsibility is to evaluate whether the actor has the permission required for the requested action.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EAccessContext.Action\n |\n v\nCompiledPermissionSet\n |\n v\nPermission Evaluation\n |\n v\nPolicy Decision\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis design separates permission storage and resolution from permission enforcement.\u003C/p\u003E\n\u003Cp\u003ERoles and assignments determine effective grants.\u003C/p\u003E\n\u003Cp\u003EThe compiled permission set represents those grants.\u003C/p\u003E\n\u003Cp\u003EThe permission policy evaluates the requested action against them.\u003C/p\u003E\n\u003Cp\u003EThe final authorization decision also considers the other applicable security policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022permissions-are-not-a-replacement-for-policies\u0022\u003EPermissions Are Not a Replacement for Policies\u003C/h3\u003E\n\u003Cp\u003EFor example, an actor may possess:\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.delete.admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThat permission does not automatically authorize deleting any user in any tenant.\u003C/p\u003E\n\u003Cp\u003EThe operation must still satisfy the relevant target, tenant, and account security requirements.\u003C/p\u003E\n\u003Cp\u003EThis is why the pipeline evaluates multiple requirements rather than relying on a single permission match.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022orchestration-and-protected-execution\u0022\u003EOrchestration and Protected Execution\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessOrchestrator\u003C/code\u003E coordinates authorization and the protected operation.\u003C/p\u003E\n\u003Cp\u003EIts purpose is to establish a consistent enforcement boundary.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EOperation Requested\n |\n v\nPrepare AccessContext\n |\n v\nResolve and Evaluate Policies\n |\n \u002B---- Not Allowed ---\u0026gt; Stop\n |\n v\nExecute Protected Operation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe security requirement is straightforward:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA protected operation must not execute merely because its caller reached an application service or endpoint.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EAuthorization must succeed at an enforcement boundary that covers the operation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-and-in-process-execution\u0022\u003EHTTP and In-Process Execution\u003C/h3\u003E\n\u003Cp\u003EHTTP endpoints may enforce authorization through ASP.NET Core authentication and authorization integration.\u003C/p\u003E\n\u003Cp\u003EHowever, applications may also execute operations directly through in-process services.\u003C/p\u003E\n\u003Cp\u003EFor example, a Blazor Server application can invoke a server-side service without making a separate HTTP request.\u003C/p\u003E\n\u003Cp\u003EA controller-level \u003Ccode\u003E[Authorize]\u003C/code\u003E attribute does not automatically protect such a direct service invocation.\u003C/p\u003E\n\u003Cp\u003EWhere operations can be reached through multiple execution paths, authorization must be enforced at an appropriate shared boundary.\u003C/p\u003E\n\u003Cp\u003EThis prevents one entry point from bypassing rules enforced by another.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022determinism-and-security\u0022\u003EDeterminism and Security\u003C/h2\u003E\n\u003Cp\u003EA structured policy pipeline makes authorization behavior easier to reason about.\u003C/p\u003E\n\u003Cp\u003EFor the same effective context, policy configuration, and authoritative security state, the decision should follow the same evaluation rules.\u003C/p\u003E\n\u003Cp\u003EHowever, authorization can depend on mutable information:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAccount status.\u003C/li\u003E\n\u003Cli\u003ETenant membership.\u003C/li\u003E\n\u003Cli\u003ERole assignments.\u003C/li\u003E\n\u003Cli\u003EEffective permissions.\u003C/li\u003E\n\u003Cli\u003ESession security state.\u003C/li\u003E\n\u003Cli\u003ETarget ownership.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA policy evaluation that uses stale or inconsistent data may produce a result that differs from one using current authoritative state.\u003C/p\u003E\n\u003Cp\u003EDeterministic policy evaluation therefore depends on both the evaluation rules and the consistency of the information supplied to them.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022time-of-check-and-time-of-use\u0022\u003ETime-of-Check and Time-of-Use\u003C/h3\u003E\n\u003Cp\u003EAuthorization may be evaluated before a protected operation modifies data.\u003C/p\u003E\n\u003Cp\u003EIf relevant security or ownership information changes between the authorization decision and the mutation, a time-of-check/time-of-use problem may arise.\u003C/p\u003E\n\u003Cp\u003ESensitive operations may require transactional consistency, concurrency controls, or revalidation within the protected operation.\u003C/p\u003E\n\u003Cp\u003EA policy pipeline provides a structured decision process, but it does not automatically make every application-level data mutation atomic.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extensibility-boundaries\u0022\u003EExtensibility Boundaries\u003C/h2\u003E\n\u003Cp\u003EThe authorization pipeline is designed to support application-specific policies.\u003C/p\u003E\n\u003Cp\u003ECustom policies can introduce new requirements without replacing the underlying permission model or authentication engine.\u003C/p\u003E\n\u003Cp\u003EHowever, extensibility must preserve the security hierarchy.\u003C/p\u003E\n\u003Cp\u003EA custom policy should not:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EOverride mandatory invariants.\u003C/li\u003E\n\u003Cli\u003ETrust caller-supplied identity information without validation.\u003C/li\u003E\n\u003Cli\u003ETreat an action name as proof of actor-target ownership.\u003C/li\u003E\n\u003Cli\u003EBypass tenant isolation through a broad permission.\u003C/li\u003E\n\u003Cli\u003EAssume that JWT claims always reflect current authorization state.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ECustom policies should evaluate well-defined requirements using trusted context and participate in the established decision model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s authorization pipeline separates the responsibilities involved in an access decision.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessContext\u003C/code\u003E describes the operation.\u003C/p\u003E\n\u003Cp\u003EContext enrichment and permission compilation prepare the required authorization information.\u003C/p\u003E\n\u003Cp\u003EPolicy resolution identifies the relevant rules.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EIAccessAuthority\u003C/code\u003E evaluates those rules according to the pipeline\u0027s decision semantics.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAccessOrchestrator\u003C/code\u003E coordinates authorization with protected execution.\u003C/p\u003E\n\u003Cp\u003ETogether, these components provide a structured foundation for context-aware, policy-driven authorization.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe pipeline\u0027s security comes not from the number of policies it executes, but from consistently enforcing the applicable requirements before a protected operation is allowed to proceed.\u003C/strong\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/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Actions, permissions, roles, and scopes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/policies\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E \u2014 Policy concepts and access decision behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Authentication security state and validation boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E \u2014 Credential validity and authorization freshness.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "pipeline-architecture", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/refresh-rotation.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/refresh-rotation.json index f0c60fbf..6b16d9af 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/refresh-rotation.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/refresh-rotation.json @@ -1,7 +1,7 @@ { "Slug": "security/refresh-rotation", "Title": "Refresh Rotation \u0026 Replay Protection", - "Html": "\n\u003Cp\u003ERefresh credentials allow authentication to continue without requiring the user to repeat the original login process.\u003C/p\u003E\n\u003Cp\u003EBecause a refresh credential may remain useful longer than an access credential, its compromise can have significant security consequences.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERefresh rotation\u003C/strong\u003E is one mechanism for reducing this risk. It replaces previously issued refresh authentication material as part of a successful continuation operation.\u003C/p\u003E\n\u003Cp\u003EHowever, secure rotation involves more than generating a new token. It requires consistent state transitions, concurrency control, invalid credential handling, and a clear policy for suspected credential reuse.\u003C/p\u003E\n\u003Cp\u003EThis page examines those security properties and their limitations.\u003C/p\u003E\n\u003Cp\u003EFor token formats, access token validation, and general refresh behavior, see \u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-as-a-security-protocol\u0022\u003ERotation as a Security Protocol\u003C/h2\u003E\n\u003Cp\u003EIn a rotating refresh strategy, a successfully consumed refresh credential is replaced with new authentication material.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003ERefresh A\n |\n | Successful continuation\n v\nRefresh A consumed\nRefresh B issued\n |\n | Successful continuation\n v\nRefresh B consumed\nRefresh C issued\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe security objective is to prevent an older refresh credential from remaining indefinitely reusable after a successful continuation.\u003C/p\u003E\n\u003Cp\u003EA rotating refresh mechanism must maintain enough authoritative state to distinguish between credentials that are currently eligible for use and credentials that have already been consumed or invalidated.\u003C/p\u003E\n\u003Cp\u003EThis is fundamentally different from validating a self-contained access token using only its signature and expiration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-rotation-does-and-does-not-guarantee\u0022\u003EWhat Rotation Does and Does Not Guarantee\u003C/h3\u003E\n\u003Cp\u003ERotation can:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELimit the continued usefulness of previously consumed credentials.\u003C/li\u003E\n\u003Cli\u003EProvide a signal when an invalidated credential is presented again.\u003C/li\u003E\n\u003Cli\u003ESupport targeted security responses to suspicious continuation attempts.\u003C/li\u003E\n\u003Cli\u003EReduce the opportunities for prolonged reuse of the same refresh credential.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ERotation does not inherently:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPrevent the initial theft of a refresh credential.\u003C/li\u003E\n\u003Cli\u003EProve that a reuse attempt was made by an attacker.\u003C/li\u003E\n\u003Cli\u003EGuarantee that the legitimate client wins a race against an attacker.\u003C/li\u003E\n\u003Cli\u003EInvalidate every previously issued access token.\u003C/li\u003E\n\u003Cli\u003EGuarantee immediate containment without an enforced revocation mechanism.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe strength of the protection depends on how the refresh strategy implements and validates its state transitions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-considerations\u0022\u003EAuthentication Mode Considerations\u003C/h2\u003E\n\u003Cp\u003ERefresh behavior is not identical across UltimateAuth\u0027s authentication modes.\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\u003ERefresh and rotation considerations\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureOpaque\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses opaque session continuation rather than a separate access/refresh credential pair\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses opaque access and refresh credentials with server-authoritative session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESemiHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses JWT access tokens and server-managed refresh lifecycle state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureJwt\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses locally validated JWT access tokens and may use an optional refresh store\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe presence of a refresh operation does not necessarily mean that a separate refresh credential is replaced on every invocation.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured mode and strategy, a continuation operation may rotate credentials, update existing server-managed state, or follow another supported lifecycle behavior.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERotation guarantees must be evaluated against the selected refresh strategy, not assumed from the existence of a refresh endpoint.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-credential-state\u0022\u003ERefresh Credential State\u003C/h2\u003E\n\u003Cp\u003EA rotating refresh mechanism needs to distinguish credentials that remain eligible for continuation from credentials that are no longer acceptable.\u003C/p\u003E\n\u003Cp\u003EConceptually, relevant conditions include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECondition\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\u003Cstrong\u003EActive\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe credential is eligible for a refresh attempt, subject to all applicable checks\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EConsumed or replaced\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe credential has already participated in a successful rotation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoked\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe credential or associated refresh authority has been explicitly invalidated\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EExpired\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe credential has exceeded its permitted lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EInvalid or unknown\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe presented value cannot be resolved to an acceptable refresh credential\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese are conceptual security conditions, not necessarily distinct persisted enum values in every implementation.\u003C/p\u003E\n\u003Cp\u003EA consumed credential and an administratively revoked credential may both be unusable, but the reasons for rejection are different.\u003C/p\u003E\n\u003Cp\u003EThat distinction matters when evaluating whether a failed refresh represents ordinary expiration, an invalid request, or potential credential reuse.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-rotation-transaction\u0022\u003EThe Rotation Transaction\u003C/h2\u003E\n\u003Cp\u003EA successful rotating refresh operation involves a sequence of security-sensitive steps.\u003C/p\u003E\n\u003Cp\u003EConceptually:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EResolve the presented refresh credential.\u003C/li\u003E\n\u003Cli\u003EValidate its eligibility for continuation.\u003C/li\u003E\n\u003Cli\u003EValidate the applicable session, account, and client context.\u003C/li\u003E\n\u003Cli\u003EEstablish that the credential has not already been consumed.\u003C/li\u003E\n\u003Cli\u003EApply the configured rotation transition.\u003C/li\u003E\n\u003Cli\u003EIssue the replacement authentication material.\u003C/li\u003E\n\u003Cli\u003EPersist the resulting state consistently.\u003C/li\u003E\n\u003Cli\u003EReturn the renewed credentials to the client.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe exact implementation and ordering depend on the selected refresh strategy.\u003C/p\u003E\n\u003Cp\u003EThe important security requirement is that the transition must not leave multiple conflicting refresh credentials valid because two requests observed the same earlier state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022atomicity\u0022\u003EAtomicity\u003C/h3\u003E\n\u003Cp\u003EConsider a refresh credential \u003Ccode\u003EA\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ETwo requests arrive almost simultaneously:\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\u003ERequest 1: Refresh(A)\nRequest 2: Refresh(A)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn unsafe implementation might allow both requests to read \u003Ccode\u003EA\u003C/code\u003E as active before either marks it consumed.\u003C/p\u003E\n\u003Cp\u003EBoth requests could then issue different replacement credentials:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Refresh A\n |\n \u002B------\u002B------\u002B\n | |\n Request 1 Request 2\n | |\n v v\n Refresh B Refresh C\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIf both replacements remain valid, the system has unintentionally created competing continuation paths.\u003C/p\u003E\n\u003Cp\u003EA single-use rotation strategy must prevent this outcome through an appropriate atomic state transition or concurrency mechanism.\u003C/p\u003E\n\u003Cp\u003EPossible implementation techniques include transactional updates, compare-and-swap operations, or other storage-specific concurrency controls.\u003C/p\u003E\n\u003Cp\u003EThe precise mechanism is an implementation concern, but the security property is essential:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOnly the transition permitted by the configured rotation semantics may succeed.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022concurrent-refresh-requests\u0022\u003EConcurrent Refresh Requests\u003C/h2\u003E\n\u003Cp\u003EConcurrent refresh requests are not necessarily malicious.\u003C/p\u003E\n\u003Cp\u003EThey can occur when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESeveral API requests discover an expired access token at the same time.\u003C/li\u003E\n\u003Cli\u003EMultiple browser tabs attempt to refresh authentication.\u003C/li\u003E\n\u003Cli\u003EA client retries a request after a network interruption.\u003C/li\u003E\n\u003Cli\u003EBackground and foreground operations initiate refresh simultaneously.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EWithout coordination, a legitimate client can submit the same refresh credential more than once.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-side-coordination\u0022\u003EClient-Side Coordination\u003C/h3\u003E\n\u003Cp\u003EApplications should avoid issuing unnecessary concurrent refresh requests.\u003C/p\u003E\n\u003Cp\u003EA common approach is to coordinate refresh through a single in-progress operation and allow other requests to await its result.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EAPI Request A \u2500\u2500\u2510\nAPI Request B \u2500\u2500\u253C\u2500\u2500\u0026gt; One refresh operation\nAPI Request C \u2500\u2500\u2518 |\n v\n Renewed credentials\n |\n \u002B-----------\u002B-----------\u002B\n | | |\n v v v\n Retry A Retry B Retry C\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EClient coordination improves reliability and reduces accidental reuse attempts.\u003C/p\u003E\n\u003Cp\u003EHowever, it is not a replacement for server-side concurrency enforcement.\u003C/p\u003E\n\u003Cp\u003EDifferent devices, processes, or compromised clients cannot be assumed to share the same coordination mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-side-enforcement\u0022\u003EServer-Side Enforcement\u003C/h3\u003E\n\u003Cp\u003EThe server remains responsible for enforcing the configured refresh credential lifecycle.\u003C/p\u003E\n\u003Cp\u003EA client-side lock cannot guarantee that an attacker or another client instance will not present the same credential.\u003C/p\u003E\n\u003Cp\u003EFor single-use rotation, the server must ensure that a consumed credential cannot successfully establish another independent continuation path.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-credential-reuse\u0022\u003ERefresh Credential Reuse\u003C/h2\u003E\n\u003Cp\u003ERefresh credential reuse occurs when a credential is presented after it is no longer eligible for the operation.\u003C/p\u003E\n\u003Cp\u003EFor a single-use rotating strategy, presenting a previously consumed credential is a particularly important case.\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\u003EInitial state:\n Refresh A is active\n\nFirst request:\n Refresh(A) succeeds\n A is consumed\n B is issued\n\nLater request:\n Refresh(A) is attempted again\n A is no longer eligible\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe later request must not be accepted as a new successful use of \u003Ccode\u003EA\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reuse-is-a-security-signal\u0022\u003EReuse Is a Security Signal\u003C/h3\u003E\n\u003Cp\u003EA consumed refresh credential may be presented again because:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn attacker copied the credential before rotation.\u003C/li\u003E\n\u003Cli\u003EA legitimate client retried after losing the refresh response.\u003C/li\u003E\n\u003Cli\u003ETwo client operations raced to refresh simultaneously.\u003C/li\u003E\n\u003Cli\u003EClient credential storage was not updated consistently.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe server may not have enough information to distinguish these situations conclusively.\u003C/p\u003E\n\u003Cp\u003ETherefore, \u003Cstrong\u003Ereuse detection identifies a suspicious credential lifecycle event; it does not automatically prove credential theft.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022detection-versus-response\u0022\u003EDetection Versus Response\u003C/h3\u003E\n\u003Cp\u003EDetecting invalid reuse and responding to it are separate responsibilities.\u003C/p\u003E\n\u003Cp\u003EA refresh strategy may reject the request without taking broader action.\u003C/p\u003E\n\u003Cp\u003EA stricter security policy may invalidate additional refresh state or revoke an associated session or chain.\u003C/p\u003E\n\u003Cp\u003EThe appropriate response depends on the implementation, authentication mode, and supported security policy.\u003C/p\u003E\n\u003Cp\u003EA documentation claim that reuse always causes chain-wide revocation would be correct only if that behavior is actually enforced by the selected strategy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022replay-and-token-theft-scenarios\u0022\u003EReplay and Token Theft Scenarios\u003C/h2\u003E\n\u003Cp\u003ERotation is particularly useful when reasoning about stolen refresh credentials.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022scenario-1-the-legitimate-client-refreshes-first\u0022\u003EScenario 1: The Legitimate Client Refreshes First\u003C/h3\u003E\n\u003Cp\u003EAn attacker copies refresh credential \u003Ccode\u003EA\u003C/code\u003E, but the legitimate client uses it before the attacker.\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\u003ELegitimate client:\n A -\u0026gt; B\n\nAttacker:\n Attempts A\n A is already consumed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIf the rotation state is enforced correctly, the attacker\u0027s attempt to use \u003Ccode\u003EA\u003C/code\u003E cannot produce another successful continuation.\u003C/p\u003E\n\u003Cp\u003EThe server may also be able to identify the attempt as reuse.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022scenario-2-the-attacker-refreshes-first\u0022\u003EScenario 2: The Attacker Refreshes First\u003C/h3\u003E\n\u003Cp\u003EAn attacker copies active refresh credential \u003Ccode\u003EA\u003C/code\u003E and successfully uses it before the legitimate client.\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\u003EAttacker:\n A -\u0026gt; B\n\nLegitimate client:\n Attempts A\n A is already consumed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn this case, the attacker may already possess the replacement credential \u003Ccode\u003EB\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ERejecting the legitimate client\u0027s later use of \u003Ccode\u003EA\u003C/code\u003E is necessary, but it does not by itself remove the attacker\u0027s newly obtained credential.\u003C/p\u003E\n\u003Cp\u003EA broader revocation response may be required to contain the compromise.\u003C/p\u003E\n\u003Cp\u003EThis illustrates why rotation and reuse detection are not identical to automatic compromise recovery.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022scenario-3-a-refresh-response-is-lost\u0022\u003EScenario 3: A Refresh Response Is Lost\u003C/h3\u003E\n\u003Cp\u003EThe legitimate client sends \u003Ccode\u003EA\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe server successfully consumes \u003Ccode\u003EA\u003C/code\u003E and issues \u003Ccode\u003EB\u003C/code\u003E, but the response never reaches the client.\u003C/p\u003E\n\u003Cp\u003EThe client still possesses \u003Ccode\u003EA\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA retry may now appear to be reuse.\u003C/p\u003E\n\u003Cp\u003EThis creates an important reliability and security tradeoff.\u003C/p\u003E\n\u003Cp\u003EStrict single-use semantics can reject the retry, while a more tolerant continuation strategy requires carefully defined behavior that does not accidentally create an exploitable replay window.\u003C/p\u003E\n\u003Cp\u003EThe chosen strategy must handle this situation consistently.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-and-chain-association\u0022\u003ESession and Chain Association\u003C/h2\u003E\n\u003Cp\u003EIn session-managed authentication modes, refresh operations can be associated with session and chain state.\u003C/p\u003E\n\u003Cp\u003EThis allows the authentication infrastructure to evaluate continuation within the relevant security context.\u003C/p\u003E\n\u003Cp\u003EFor example, a refresh operation may need to establish that:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe refresh credential belongs to the expected authentication state.\u003C/li\u003E\n\u003Cli\u003EThe associated session remains eligible for continuation.\u003C/li\u003E\n\u003Cli\u003EThe relevant chain has not been revoked.\u003C/li\u003E\n\u003Cli\u003EThe account-level security state permits continuation.\u003C/li\u003E\n\u003Cli\u003EAny configured device context requirements are satisfied.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact checks depend on the mode and implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-level-containment\u0022\u003EChain-Level Containment\u003C/h3\u003E\n\u003Cp\u003EWhen a refresh compromise is associated with a session chain, chain-level revocation can provide a broader response than invalidating a single refresh credential.\u003C/p\u003E\n\u003Cp\u003EThis may be appropriate when multiple related sessions or continuation credentials could be affected.\u003C/p\u003E\n\u003Cp\u003EHowever, a chain is a logical session continuity boundary, not a cryptographically verified physical device.\u003C/p\u003E\n\u003Cp\u003EChain-level revocation controls the associated authentication state; it does not permanently prevent the physical device from authenticating again.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/security/session-security-model\u0022\u003ESession Security Model\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022invalid-refresh-conditions\u0022\u003EInvalid Refresh Conditions\u003C/h2\u003E\n\u003Cp\u003ENot every failed refresh should be classified as token reuse.\u003C/p\u003E\n\u003Cp\u003EDifferent failure conditions have different security meanings.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECondition\u003C/th\u003E\n\u003Cth\u003EExpected security interpretation\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUnknown credential\u003C/td\u003E\n\u003Ctd\u003ENo acceptable refresh authority was established\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EExpired credential\u003C/td\u003E\n\u003Ctd\u003EThe permitted continuation lifetime has ended\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EExplicitly revoked credential\u003C/td\u003E\n\u003Ctd\u003EThe refresh authority has been invalidated\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPreviously consumed credential\u003C/td\u003E\n\u003Ctd\u003EPotential reuse under a single-use rotation strategy\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession or chain mismatch\u003C/td\u003E\n\u003Ctd\u003EPresented credential is inconsistent with expected authentication context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAccount security invalidation\u003C/td\u003E\n\u003Ctd\u003EContinuation is no longer permitted under current account security state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EConcurrent use\u003C/td\u003E\n\u003Ctd\u003EMultiple attempts may compete for the same continuation authority\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA secure implementation must reject invalid continuation attempts according to the selected strategy.\u003C/p\u003E\n\u003Cp\u003EThe application should not assume that every rejection requires the same logging, revocation, or user-facing response.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-and-access-token-validity\u0022\u003ERotation and Access Token Validity\u003C/h2\u003E\n\u003Cp\u003ERefresh rotation affects authentication continuation.\u003C/p\u003E\n\u003Cp\u003EIt does not necessarily affect access tokens that were issued earlier.\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\u003EJWT Access A issued\nRefresh A issued\n |\n v\nRefresh A rotated to Refresh B\nJWT Access B issued\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIf \u003Ccode\u003EJWT Access A\u003C/code\u003E has not expired, it may remain valid under local JWT validation even after refresh rotation.\u003C/p\u003E\n\u003Cp\u003ESimilarly, revoking \u003Ccode\u003ERefresh B\u003C/code\u003E may prevent future refresh operations without immediately invalidating \u003Ccode\u003EJWT Access B\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis distinction is especially important in SemiHybrid and PureJwt.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-scope\u0022\u003ERevocation Scope\u003C/h3\u003E\n\u003Cp\u003EThere are separate questions to answer:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECan the old refresh credential be used again?\u003C/li\u003E\n\u003Cli\u003ECan the replacement refresh credential still be used?\u003C/li\u003E\n\u003Cli\u003EIs the associated server-managed session valid?\u003C/li\u003E\n\u003Cli\u003EAre previously issued access tokens still accepted?\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA refresh security operation may affect some of these conditions without affecting all of them.\u003C/p\u003E\n\u003Cp\u003EThe authentication mode determines where current server-side state is consulted.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-and-credential-storage\u0022\u003ERotation and Credential Storage\u003C/h2\u003E\n\u003Cp\u003ERefresh rotation depends on maintaining authoritative information about credential eligibility.\u003C/p\u003E\n\u003Cp\u003EFor opaque refresh credentials, an implementation may store a one-way verifier or hash rather than the original credential value.\u003C/p\u003E\n\u003Cp\u003EThis reduces the exposure of usable credentials if the persistence store is disclosed.\u003C/p\u003E\n\u003Cp\u003EHowever, secure storage alone does not provide rotation.\u003C/p\u003E\n\u003Cp\u003EThe implementation must also maintain the necessary lifecycle state and enforce transitions correctly.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022operational-requirements\u0022\u003EOperational Requirements\u003C/h3\u003E\n\u003Cp\u003EA production refresh mechanism should account for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESecure credential generation.\u003C/li\u003E\n\u003Cli\u003EAppropriate credential expiration.\u003C/li\u003E\n\u003Cli\u003EProtected transport and client storage.\u003C/li\u003E\n\u003Cli\u003EConsistent persistence updates.\u003C/li\u003E\n\u003Cli\u003EConcurrency control.\u003C/li\u003E\n\u003Cli\u003ESafe handling of invalid and consumed credentials.\u003C/li\u003E\n\u003Cli\u003ERevocation behavior appropriate to the authentication mode.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact persistence and concurrency mechanisms are implementation-specific.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-tradeoffs\u0022\u003ESecurity Tradeoffs\u003C/h2\u003E\n\u003Cp\u003EStrict rotation improves control over credential reuse, but it introduces operational complexity.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EBenefit\u003C/th\u003E\n\u003Cth\u003ECorresponding consideration\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EConsumed credentials cannot remain reusable\u003C/td\u003E\n\u003Ctd\u003ERequires authoritative lifecycle state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReuse attempts can become observable\u003C/td\u003E\n\u003Ctd\u003ELegitimate retries may resemble attacks\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EContinuation can be invalidated independently\u003C/td\u003E\n\u003Ctd\u003ERevocation must be consistently enforced\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReplacement credentials limit prolonged reuse\u003C/td\u003E\n\u003Ctd\u003EConcurrent refresh requests require coordination\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECompromise responses can target session state\u003C/td\u003E\n\u003Ctd\u003EBroader revocation may interrupt legitimate sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese tradeoffs are not reasons to avoid rotation.\u003C/p\u003E\n\u003Cp\u003EThey are reasons to treat it as a security protocol rather than a simple token replacement operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-design-principles\u0022\u003ESecurity Design Principles\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003ERotation must have defined semantics.\u003C/strong\u003E A refresh operation should not be described as single-use unless the configured strategy actually enforces single-use behavior.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EState transitions must be consistent.\u003C/strong\u003E Concurrent requests must not create conflicting valid continuation paths under a single-use strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EReuse detection is not theft attribution.\u003C/strong\u003E A reused credential is suspicious, but the cause may be malicious or accidental.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDetection and containment are separate.\u003C/strong\u003E Rejecting reuse, revoking refresh state, and revoking a session chain are different security actions.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient coordination is helpful but insufficient.\u003C/strong\u003E The server must enforce credential eligibility independently.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAccess and refresh validity remain distinct.\u003C/strong\u003E Refresh rotation does not automatically invalidate previously issued JWT access tokens.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EMode-specific behavior matters.\u003C/strong\u003E The same security guarantee must not be attributed to every authentication mode without considering its actual refresh implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003ERefresh rotation is a mechanism for controlling how authentication authority continues over time.\u003C/p\u003E\n\u003Cp\u003EIts security value comes from enforcing credential lifecycle transitions, preventing invalid continuation, and supporting an appropriate response when a previously consumed credential is presented again.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s authentication modes provide different continuation models. Rotation behavior must therefore be understood in the context of the selected mode, its server-managed state, and its configured refresh strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA secure rotating refresh mechanism does not merely issue a new credential. It ensures that the previous credential can no longer establish an unauthorized continuation path.\u003C/strong\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/security/token-security\u0022\u003EToken Security\u003C/a\u003E \u2014 Access and refresh credential security fundamentals.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security-model\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Hierarchical validation and revocation boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/session-model\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E \u2014 Session continuity, expiration, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Mode-specific authentication and refresh behavior.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003ERefresh credentials allow authentication to continue without requiring the user to repeat the original login process.\u003C/p\u003E\n\u003Cp\u003EBecause a refresh credential may remain useful longer than an access credential, its compromise can have significant security consequences.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERefresh rotation\u003C/strong\u003E is one mechanism for reducing this risk. It replaces previously issued refresh authentication material as part of a successful continuation operation.\u003C/p\u003E\n\u003Cp\u003EHowever, secure rotation involves more than generating a new token. It requires consistent state transitions, concurrency control, invalid credential handling, and a clear policy for suspected credential reuse.\u003C/p\u003E\n\u003Cp\u003EThis page examines those security properties and their limitations.\u003C/p\u003E\n\u003Cp\u003EFor token formats, access token validation, and general refresh behavior, see \u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-as-a-security-protocol\u0022\u003ERotation as a Security Protocol\u003C/h2\u003E\n\u003Cp\u003EIn a rotating refresh strategy, a successfully consumed refresh credential is replaced with new authentication material.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003ERefresh A\n |\n | Successful continuation\n v\nRefresh A consumed\nRefresh B issued\n |\n | Successful continuation\n v\nRefresh B consumed\nRefresh C issued\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe security objective is to prevent an older refresh credential from remaining indefinitely reusable after a successful continuation.\u003C/p\u003E\n\u003Cp\u003EA rotating refresh mechanism must maintain enough authoritative state to distinguish between credentials that are currently eligible for use and credentials that have already been consumed or invalidated.\u003C/p\u003E\n\u003Cp\u003EThis is fundamentally different from validating a self-contained access token using only its signature and expiration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-rotation-does-and-does-not-guarantee\u0022\u003EWhat Rotation Does and Does Not Guarantee\u003C/h3\u003E\n\u003Cp\u003ERotation can:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELimit the continued usefulness of previously consumed credentials.\u003C/li\u003E\n\u003Cli\u003EProvide a signal when an invalidated credential is presented again.\u003C/li\u003E\n\u003Cli\u003ESupport targeted security responses to suspicious continuation attempts.\u003C/li\u003E\n\u003Cli\u003EReduce the opportunities for prolonged reuse of the same refresh credential.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ERotation does not inherently:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EPrevent the initial theft of a refresh credential.\u003C/li\u003E\n\u003Cli\u003EProve that a reuse attempt was made by an attacker.\u003C/li\u003E\n\u003Cli\u003EGuarantee that the legitimate client wins a race against an attacker.\u003C/li\u003E\n\u003Cli\u003EInvalidate every previously issued access token.\u003C/li\u003E\n\u003Cli\u003EGuarantee immediate containment without an enforced revocation mechanism.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe strength of the protection depends on how the refresh strategy implements and validates its state transitions.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-considerations\u0022\u003EAuthentication Mode Considerations\u003C/h2\u003E\n\u003Cp\u003ERefresh behavior is not identical across UltimateAuth\u0027s authentication modes.\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\u003ERefresh and rotation considerations\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureOpaque\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses opaque session continuation rather than a separate access/refresh credential pair\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses opaque access and refresh credentials with server-authoritative session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESemiHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses JWT access tokens and server-managed refresh lifecycle state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureJwt\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses locally validated JWT access tokens and may use an optional refresh store\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe presence of a refresh operation does not necessarily mean that a separate refresh credential is replaced on every invocation.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured mode and strategy, a continuation operation may rotate credentials, update existing server-managed state, or follow another supported lifecycle behavior.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERotation guarantees must be evaluated against the selected refresh strategy, not assumed from the existence of a refresh endpoint.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-credential-state\u0022\u003ERefresh Credential State\u003C/h2\u003E\n\u003Cp\u003EA rotating refresh mechanism needs to distinguish credentials that remain eligible for continuation from credentials that are no longer acceptable.\u003C/p\u003E\n\u003Cp\u003EConceptually, relevant conditions include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECondition\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\u003Cstrong\u003EActive\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe credential is eligible for a refresh attempt, subject to all applicable checks\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EConsumed or replaced\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe credential has already participated in a successful rotation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoked\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe credential or associated refresh authority has been explicitly invalidated\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EExpired\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe credential has exceeded its permitted lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EInvalid or unknown\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EThe presented value cannot be resolved to an acceptable refresh credential\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese are conceptual security conditions, not necessarily distinct persisted enum values in every implementation.\u003C/p\u003E\n\u003Cp\u003EA consumed credential and an administratively revoked credential may both be unusable, but the reasons for rejection are different.\u003C/p\u003E\n\u003Cp\u003EThat distinction matters when evaluating whether a failed refresh represents ordinary expiration, an invalid request, or potential credential reuse.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-rotation-transaction\u0022\u003EThe Rotation Transaction\u003C/h2\u003E\n\u003Cp\u003EA successful rotating refresh operation involves a sequence of security-sensitive steps.\u003C/p\u003E\n\u003Cp\u003EConceptually:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EResolve the presented refresh credential.\u003C/li\u003E\n\u003Cli\u003EValidate its eligibility for continuation.\u003C/li\u003E\n\u003Cli\u003EValidate the applicable session, account, and client context.\u003C/li\u003E\n\u003Cli\u003EEstablish that the credential has not already been consumed.\u003C/li\u003E\n\u003Cli\u003EApply the configured rotation transition.\u003C/li\u003E\n\u003Cli\u003EIssue the replacement authentication material.\u003C/li\u003E\n\u003Cli\u003EPersist the resulting state consistently.\u003C/li\u003E\n\u003Cli\u003EReturn the renewed credentials to the client.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe exact implementation and ordering depend on the selected refresh strategy.\u003C/p\u003E\n\u003Cp\u003EThe important security requirement is that the transition must not leave multiple conflicting refresh credentials valid because two requests observed the same earlier state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022atomicity\u0022\u003EAtomicity\u003C/h3\u003E\n\u003Cp\u003EConsider a refresh credential \u003Ccode\u003EA\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ETwo requests arrive almost simultaneously:\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\u003ERequest 1: Refresh(A)\nRequest 2: Refresh(A)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAn unsafe implementation might allow both requests to read \u003Ccode\u003EA\u003C/code\u003E as active before either marks it consumed.\u003C/p\u003E\n\u003Cp\u003EBoth requests could then issue different replacement credentials:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Refresh A\n |\n \u002B------\u002B------\u002B\n | |\n Request 1 Request 2\n | |\n v v\n Refresh B Refresh C\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIf both replacements remain valid, the system has unintentionally created competing continuation paths.\u003C/p\u003E\n\u003Cp\u003EA single-use rotation strategy must prevent this outcome through an appropriate atomic state transition or concurrency mechanism.\u003C/p\u003E\n\u003Cp\u003EPossible implementation techniques include transactional updates, compare-and-swap operations, or other storage-specific concurrency controls.\u003C/p\u003E\n\u003Cp\u003EThe precise mechanism is an implementation concern, but the security property is essential:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EOnly the transition permitted by the configured rotation semantics may succeed.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022concurrent-refresh-requests\u0022\u003EConcurrent Refresh Requests\u003C/h2\u003E\n\u003Cp\u003EConcurrent refresh requests are not necessarily malicious.\u003C/p\u003E\n\u003Cp\u003EThey can occur when:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESeveral API requests discover an expired access token at the same time.\u003C/li\u003E\n\u003Cli\u003EMultiple browser tabs attempt to refresh authentication.\u003C/li\u003E\n\u003Cli\u003EA client retries a request after a network interruption.\u003C/li\u003E\n\u003Cli\u003EBackground and foreground operations initiate refresh simultaneously.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EWithout coordination, a legitimate client can submit the same refresh credential more than once.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-side-coordination\u0022\u003EClient-Side Coordination\u003C/h3\u003E\n\u003Cp\u003EApplications should avoid issuing unnecessary concurrent refresh requests.\u003C/p\u003E\n\u003Cp\u003EA common approach is to coordinate refresh through a single in-progress operation and allow other requests to await its result.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EAPI Request A \u2500\u2500\u2510\nAPI Request B \u2500\u2500\u253C\u2500\u2500\u0026gt; One refresh operation\nAPI Request C \u2500\u2500\u2518 |\n v\n Renewed credentials\n |\n \u002B-----------\u002B-----------\u002B\n | | |\n v v v\n Retry A Retry B Retry C\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EClient coordination improves reliability and reduces accidental reuse attempts.\u003C/p\u003E\n\u003Cp\u003EHowever, it is not a replacement for server-side concurrency enforcement.\u003C/p\u003E\n\u003Cp\u003EDifferent devices, processes, or compromised clients cannot be assumed to share the same coordination mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-side-enforcement\u0022\u003EServer-Side Enforcement\u003C/h3\u003E\n\u003Cp\u003EThe server remains responsible for enforcing the configured refresh credential lifecycle.\u003C/p\u003E\n\u003Cp\u003EA client-side lock cannot guarantee that an attacker or another client instance will not present the same credential.\u003C/p\u003E\n\u003Cp\u003EFor single-use rotation, the server must ensure that a consumed credential cannot successfully establish another independent continuation path.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-credential-reuse\u0022\u003ERefresh Credential Reuse\u003C/h2\u003E\n\u003Cp\u003ERefresh credential reuse occurs when a credential is presented after it is no longer eligible for the operation.\u003C/p\u003E\n\u003Cp\u003EFor a single-use rotating strategy, presenting a previously consumed credential is a particularly important case.\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\u003EInitial state:\n Refresh A is active\n\nFirst request:\n Refresh(A) succeeds\n A is consumed\n B is issued\n\nLater request:\n Refresh(A) is attempted again\n A is no longer eligible\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe later request must not be accepted as a new successful use of \u003Ccode\u003EA\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reuse-is-a-security-signal\u0022\u003EReuse Is a Security Signal\u003C/h3\u003E\n\u003Cp\u003EA consumed refresh credential may be presented again because:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn attacker copied the credential before rotation.\u003C/li\u003E\n\u003Cli\u003EA legitimate client retried after losing the refresh response.\u003C/li\u003E\n\u003Cli\u003ETwo client operations raced to refresh simultaneously.\u003C/li\u003E\n\u003Cli\u003EClient credential storage was not updated consistently.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe server may not have enough information to distinguish these situations conclusively.\u003C/p\u003E\n\u003Cp\u003ETherefore, \u003Cstrong\u003Ereuse detection identifies a suspicious credential lifecycle event; it does not automatically prove credential theft.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022detection-versus-response\u0022\u003EDetection Versus Response\u003C/h3\u003E\n\u003Cp\u003EDetecting invalid reuse and responding to it are separate responsibilities.\u003C/p\u003E\n\u003Cp\u003EA refresh strategy may reject the request without taking broader action.\u003C/p\u003E\n\u003Cp\u003EA stricter security policy may invalidate additional refresh state or revoke an associated session or chain.\u003C/p\u003E\n\u003Cp\u003EThe appropriate response depends on the implementation, authentication mode, and supported security policy.\u003C/p\u003E\n\u003Cp\u003EA documentation claim that reuse always causes chain-wide revocation would be correct only if that behavior is actually enforced by the selected strategy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022replay-and-token-theft-scenarios\u0022\u003EReplay and Token Theft Scenarios\u003C/h2\u003E\n\u003Cp\u003ERotation is particularly useful when reasoning about stolen refresh credentials.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022scenario-1-the-legitimate-client-refreshes-first\u0022\u003EScenario 1: The Legitimate Client Refreshes First\u003C/h3\u003E\n\u003Cp\u003EAn attacker copies refresh credential \u003Ccode\u003EA\u003C/code\u003E, but the legitimate client uses it before the attacker.\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\u003ELegitimate client:\n A -\u0026gt; B\n\nAttacker:\n Attempts A\n A is already consumed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIf the rotation state is enforced correctly, the attacker\u0027s attempt to use \u003Ccode\u003EA\u003C/code\u003E cannot produce another successful continuation.\u003C/p\u003E\n\u003Cp\u003EThe server may also be able to identify the attempt as reuse.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022scenario-2-the-attacker-refreshes-first\u0022\u003EScenario 2: The Attacker Refreshes First\u003C/h3\u003E\n\u003Cp\u003EAn attacker copies active refresh credential \u003Ccode\u003EA\u003C/code\u003E and successfully uses it before the legitimate client.\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\u003EAttacker:\n A -\u0026gt; B\n\nLegitimate client:\n Attempts A\n A is already consumed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIn this case, the attacker may already possess the replacement credential \u003Ccode\u003EB\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ERejecting the legitimate client\u0027s later use of \u003Ccode\u003EA\u003C/code\u003E is necessary, but it does not by itself remove the attacker\u0027s newly obtained credential.\u003C/p\u003E\n\u003Cp\u003EA broader revocation response may be required to contain the compromise.\u003C/p\u003E\n\u003Cp\u003EThis illustrates why rotation and reuse detection are not identical to automatic compromise recovery.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022scenario-3-a-refresh-response-is-lost\u0022\u003EScenario 3: A Refresh Response Is Lost\u003C/h3\u003E\n\u003Cp\u003EThe legitimate client sends \u003Ccode\u003EA\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe server successfully consumes \u003Ccode\u003EA\u003C/code\u003E and issues \u003Ccode\u003EB\u003C/code\u003E, but the response never reaches the client.\u003C/p\u003E\n\u003Cp\u003EThe client still possesses \u003Ccode\u003EA\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA retry may now appear to be reuse.\u003C/p\u003E\n\u003Cp\u003EThis creates an important reliability and security tradeoff.\u003C/p\u003E\n\u003Cp\u003EStrict single-use semantics can reject the retry, while a more tolerant continuation strategy requires carefully defined behavior that does not accidentally create an exploitable replay window.\u003C/p\u003E\n\u003Cp\u003EThe chosen strategy must handle this situation consistently.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-and-chain-association\u0022\u003ESession and Chain Association\u003C/h2\u003E\n\u003Cp\u003EIn session-managed authentication modes, refresh operations can be associated with session and chain state.\u003C/p\u003E\n\u003Cp\u003EThis allows the authentication infrastructure to evaluate continuation within the relevant security context.\u003C/p\u003E\n\u003Cp\u003EFor example, a refresh operation may need to establish that:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe refresh credential belongs to the expected authentication state.\u003C/li\u003E\n\u003Cli\u003EThe associated session remains eligible for continuation.\u003C/li\u003E\n\u003Cli\u003EThe relevant chain has not been revoked.\u003C/li\u003E\n\u003Cli\u003EThe account-level security state permits continuation.\u003C/li\u003E\n\u003Cli\u003EAny configured device context requirements are satisfied.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact checks depend on the mode and implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-level-containment\u0022\u003EChain-Level Containment\u003C/h3\u003E\n\u003Cp\u003EWhen a refresh compromise is associated with a session chain, chain-level revocation can provide a broader response than invalidating a single refresh credential.\u003C/p\u003E\n\u003Cp\u003EThis may be appropriate when multiple related sessions or continuation credentials could be affected.\u003C/p\u003E\n\u003Cp\u003EHowever, a chain is a logical session continuity boundary, not a cryptographically verified physical device.\u003C/p\u003E\n\u003Cp\u003EChain-level revocation controls the associated authentication state; it does not permanently prevent the physical device from authenticating again.\u003C/p\u003E\n\u003Cp\u003ESee \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=\u0022invalid-refresh-conditions\u0022\u003EInvalid Refresh Conditions\u003C/h2\u003E\n\u003Cp\u003ENot every failed refresh should be classified as token reuse.\u003C/p\u003E\n\u003Cp\u003EDifferent failure conditions have different security meanings.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECondition\u003C/th\u003E\n\u003Cth\u003EExpected security interpretation\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUnknown credential\u003C/td\u003E\n\u003Ctd\u003ENo acceptable refresh authority was established\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EExpired credential\u003C/td\u003E\n\u003Ctd\u003EThe permitted continuation lifetime has ended\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EExplicitly revoked credential\u003C/td\u003E\n\u003Ctd\u003EThe refresh authority has been invalidated\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPreviously consumed credential\u003C/td\u003E\n\u003Ctd\u003EPotential reuse under a single-use rotation strategy\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESession or chain mismatch\u003C/td\u003E\n\u003Ctd\u003EPresented credential is inconsistent with expected authentication context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAccount security invalidation\u003C/td\u003E\n\u003Ctd\u003EContinuation is no longer permitted under current account security state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EConcurrent use\u003C/td\u003E\n\u003Ctd\u003EMultiple attempts may compete for the same continuation authority\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA secure implementation must reject invalid continuation attempts according to the selected strategy.\u003C/p\u003E\n\u003Cp\u003EThe application should not assume that every rejection requires the same logging, revocation, or user-facing response.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-and-access-token-validity\u0022\u003ERotation and Access Token Validity\u003C/h2\u003E\n\u003Cp\u003ERefresh rotation affects authentication continuation.\u003C/p\u003E\n\u003Cp\u003EIt does not necessarily affect access tokens that were issued earlier.\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\u003EJWT Access A issued\nRefresh A issued\n |\n v\nRefresh A rotated to Refresh B\nJWT Access B issued\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIf \u003Ccode\u003EJWT Access A\u003C/code\u003E has not expired, it may remain valid under local JWT validation even after refresh rotation.\u003C/p\u003E\n\u003Cp\u003ESimilarly, revoking \u003Ccode\u003ERefresh B\u003C/code\u003E may prevent future refresh operations without immediately invalidating \u003Ccode\u003EJWT Access B\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis distinction is especially important in SemiHybrid and PureJwt.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-scope\u0022\u003ERevocation Scope\u003C/h3\u003E\n\u003Cp\u003EThere are separate questions to answer:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECan the old refresh credential be used again?\u003C/li\u003E\n\u003Cli\u003ECan the replacement refresh credential still be used?\u003C/li\u003E\n\u003Cli\u003EIs the associated server-managed session valid?\u003C/li\u003E\n\u003Cli\u003EAre previously issued access tokens still accepted?\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA refresh security operation may affect some of these conditions without affecting all of them.\u003C/p\u003E\n\u003Cp\u003EThe authentication mode determines where current server-side state is consulted.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-and-credential-storage\u0022\u003ERotation and Credential Storage\u003C/h2\u003E\n\u003Cp\u003ERefresh rotation depends on maintaining authoritative information about credential eligibility.\u003C/p\u003E\n\u003Cp\u003EFor opaque refresh credentials, an implementation may store a one-way verifier or hash rather than the original credential value.\u003C/p\u003E\n\u003Cp\u003EThis reduces the exposure of usable credentials if the persistence store is disclosed.\u003C/p\u003E\n\u003Cp\u003EHowever, secure storage alone does not provide rotation.\u003C/p\u003E\n\u003Cp\u003EThe implementation must also maintain the necessary lifecycle state and enforce transitions correctly.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022operational-requirements\u0022\u003EOperational Requirements\u003C/h3\u003E\n\u003Cp\u003EA production refresh mechanism should account for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESecure credential generation.\u003C/li\u003E\n\u003Cli\u003EAppropriate credential expiration.\u003C/li\u003E\n\u003Cli\u003EProtected transport and client storage.\u003C/li\u003E\n\u003Cli\u003EConsistent persistence updates.\u003C/li\u003E\n\u003Cli\u003EConcurrency control.\u003C/li\u003E\n\u003Cli\u003ESafe handling of invalid and consumed credentials.\u003C/li\u003E\n\u003Cli\u003ERevocation behavior appropriate to the authentication mode.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact persistence and concurrency mechanisms are implementation-specific.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-tradeoffs\u0022\u003ESecurity Tradeoffs\u003C/h2\u003E\n\u003Cp\u003EStrict rotation improves control over credential reuse, but it introduces operational complexity.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EBenefit\u003C/th\u003E\n\u003Cth\u003ECorresponding consideration\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EConsumed credentials cannot remain reusable\u003C/td\u003E\n\u003Ctd\u003ERequires authoritative lifecycle state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReuse attempts can become observable\u003C/td\u003E\n\u003Ctd\u003ELegitimate retries may resemble attacks\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EContinuation can be invalidated independently\u003C/td\u003E\n\u003Ctd\u003ERevocation must be consistently enforced\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReplacement credentials limit prolonged reuse\u003C/td\u003E\n\u003Ctd\u003EConcurrent refresh requests require coordination\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECompromise responses can target session state\u003C/td\u003E\n\u003Ctd\u003EBroader revocation may interrupt legitimate sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese tradeoffs are not reasons to avoid rotation.\u003C/p\u003E\n\u003Cp\u003EThey are reasons to treat it as a security protocol rather than a simple token replacement operation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-design-principles\u0022\u003ESecurity Design Principles\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003ERotation must have defined semantics.\u003C/strong\u003E A refresh operation should not be described as single-use unless the configured strategy actually enforces single-use behavior.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EState transitions must be consistent.\u003C/strong\u003E Concurrent requests must not create conflicting valid continuation paths under a single-use strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EReuse detection is not theft attribution.\u003C/strong\u003E A reused credential is suspicious, but the cause may be malicious or accidental.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDetection and containment are separate.\u003C/strong\u003E Rejecting reuse, revoking refresh state, and revoking a session chain are different security actions.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient coordination is helpful but insufficient.\u003C/strong\u003E The server must enforce credential eligibility independently.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAccess and refresh validity remain distinct.\u003C/strong\u003E Refresh rotation does not automatically invalidate previously issued JWT access tokens.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EMode-specific behavior matters.\u003C/strong\u003E The same security guarantee must not be attributed to every authentication mode without considering its actual refresh implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003ERefresh rotation is a mechanism for controlling how authentication authority continues over time.\u003C/p\u003E\n\u003Cp\u003EIts security value comes from enforcing credential lifecycle transitions, preventing invalid continuation, and supporting an appropriate response when a previously consumed credential is presented again.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s authentication modes provide different continuation models. Rotation behavior must therefore be understood in the context of the selected mode, its server-managed state, and its configured refresh strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA secure rotating refresh mechanism does not merely issue a new credential. It ensures that the previous credential can no longer establish an unauthorized continuation path.\u003C/strong\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/security/token-security\u0022\u003EToken Security\u003C/a\u003E \u2014 Access and refresh credential security fundamentals.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Hierarchical validation and revocation boundaries.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/session-lifecycle\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E \u2014 Session continuity, expiration, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Mode-specific authentication and refresh behavior.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "rotation-as-a-security-protocol", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security-model.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security-model.json index 733ca89b..a264287e 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security-model.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security-model.json @@ -1,7 +1,7 @@ { "Slug": "security/session-security-model", "Title": "Session Security Model", - "Html": "\n\u003Cp\u003EA valid authentication credential is not necessarily sufficient to establish that an authentication session should still be trusted.\u003C/p\u003E\n\u003Cp\u003EA session may have been revoked, its associated account security state may have changed, or the client may no longer satisfy the conditions under which authentication was established.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth addresses these concerns through a \u003Cstrong\u003Ehierarchical session security model\u003C/strong\u003E, with explicit boundaries for session validity, device-associated continuity, and account-level invalidation.\u003C/p\u003E\n\u003Cp\u003EIn session-managed authentication modes, the security hierarchy is:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERoot \u2192 Chain \u2192 Session\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis page focuses on the security guarantees, validation requirements, and enforcement boundaries of that hierarchy.\u003C/p\u003E\n\u003Cp\u003EFor the underlying entities and their lifecycle, see \u003Ca href=\u0022/docs/sessions-devices/session-model\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-objectives\u0022\u003ESecurity Objectives\u003C/h2\u003E\n\u003Cp\u003EThe session security model is designed to support several distinct requirements.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EObjective\u003C/th\u003E\n\u003Cth\u003ESecurity purpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESession isolation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EInvalidate a particular authentication session without necessarily affecting others\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EChain isolation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EControl sessions associated with a particular device or client context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAccount-level invalidation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EInvalidate authentication state across the account\u0027s session hierarchy\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESecurity state propagation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EDetect changes to account-level authentication security\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EContext consistency\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEnsure that related session, chain, root, and tenant information remains consistent\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELifecycle enforcement\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EReject authentication state that has expired or been revoked\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese requirements are related, but they should not be collapsed into a single token expiration check.\u003C/p\u003E\n\u003Cp\u003EA token can remain structurally valid even when the server-managed authentication state associated with it is no longer acceptable.\u003C/p\u003E\n\u003Cp\u003EWhether that state is consulted during a protected request depends on the authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-boundaries\u0022\u003ESecurity Boundaries\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s session hierarchy separates security responsibilities 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\u003EAccount-level security\n |\n v\n SessionRoot\n |\n | Account-wide security state\n | Security version\n |\n v\n SessionChain\n |\n | Session continuity\n | Device context\n | Chain-level revocation\n |\n v\n Session\n |\n | Individual validity\n | Expiration\n | Revocation\n |\n v\n Authentication state\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach level provides a distinct boundary for evaluating or invalidating authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-security-boundary\u0022\u003ERoot Security Boundary\u003C/h3\u003E\n\u003Cp\u003EThe root represents account-level authentication security.\u003C/p\u003E\n\u003Cp\u003EIt provides a common authority for security state that affects sessions associated with the same account.\u003C/p\u003E\n\u003Cp\u003EThis enables account-wide invalidation without requiring the user identity itself to be deleted or recreated.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-security-boundary\u0022\u003EChain Security Boundary\u003C/h3\u003E\n\u003Cp\u003EA chain groups related authentication sessions and may be associated with a device or client environment.\u003C/p\u003E\n\u003Cp\u003EIt provides a boundary for revoking related sessions together.\u003C/p\u003E\n\u003Cp\u003EA chain is not proof of physical device identity, and its existence does not automatically mean that the associated device is trusted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-security-boundary\u0022\u003ESession Security Boundary\u003C/h3\u003E\n\u003Cp\u003EA session represents individual server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003EIt can be independently subject to expiration, revocation, and consistency checks.\u003C/p\u003E\n\u003Cp\u003EA session is not equivalent to an access token: the token is presented by the client, while the session represents server-managed security state where the selected mode uses it.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022layered-session-validation\u0022\u003ELayered Session Validation\u003C/h2\u003E\n\u003Cp\u003EIn a session-authoritative authentication mode, accepting a presented credential requires more than recognizing its identifier.\u003C/p\u003E\n\u003Cp\u003EThe associated session hierarchy must satisfy the applicable validity and consistency requirements.\u003C/p\u003E\n\u003Cp\u003EThese requirements can be understood in several categories.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-validity\u0022\u003ESession Validity\u003C/h3\u003E\n\u003Cp\u003EThe session must satisfy its own security conditions.\u003C/p\u003E\n\u003Cp\u003ERelevant checks include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe session exists in the authoritative session store.\u003C/li\u003E\n\u003Cli\u003EThe session has not been revoked.\u003C/li\u003E\n\u003Cli\u003EThe session has not exceeded its applicable expiration limits.\u003C/li\u003E\n\u003Cli\u003EThe session is associated with the expected chain.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA credential referencing a missing, expired, or revoked session must not establish a valid session-authoritative authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-validity\u0022\u003EChain Validity\u003C/h3\u003E\n\u003Cp\u003EThe associated chain must satisfy the applicable chain security conditions.\u003C/p\u003E\n\u003Cp\u003EThese may include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe chain exists.\u003C/li\u003E\n\u003Cli\u003EThe chain has not been revoked.\u003C/li\u003E\n\u003Cli\u003EThe chain belongs to the expected account and tenant context.\u003C/li\u003E\n\u003Cli\u003EThe session-to-chain relationship is consistent.\u003C/li\u003E\n\u003Cli\u003EAny applicable chain-level expiration or inactivity conditions are satisfied.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EChain validation prevents an otherwise recognizable session from bypassing a security restriction applied to its broader session continuity boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-validity\u0022\u003ERoot Validity\u003C/h3\u003E\n\u003Cp\u003EThe root must satisfy the account-level security requirements.\u003C/p\u003E\n\u003Cp\u003ERelevant conditions include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe expected root exists.\u003C/li\u003E\n\u003Cli\u003EThe root is associated with the expected identity and tenant context.\u003C/li\u003E\n\u003Cli\u003EThe chain belongs to the expected root.\u003C/li\u003E\n\u003Cli\u003EThe root has not been invalidated.\u003C/li\u003E\n\u003Cli\u003EApplicable security version requirements are satisfied.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis prevents a valid-looking session from being accepted when its account-level security authority has changed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022relationship-consistency\u0022\u003ERelationship Consistency\u003C/h3\u003E\n\u003Cp\u003EThe hierarchy must also be internally consistent.\u003C/p\u003E\n\u003Cp\u003EA session cannot be considered valid merely because a session record, a chain record, and a root record each exist independently.\u003C/p\u003E\n\u003Cp\u003ETheir ownership and association must match the expected authentication context.\u003C/p\u003E\n\u003Cp\u003EThis is especially important in multi-tenant applications, where a mismatched tenant or account association must not be accepted as a valid authentication relationship.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe security decision concerns the entire applicable hierarchy, not an isolated record.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-versioning\u0022\u003ESecurity Versioning\u003C/h2\u003E\n\u003Cp\u003ESecurity versioning allows UltimateAuth to represent changes in account-level authentication security.\u003C/p\u003E\n\u003Cp\u003EA root maintains a security version, and a session can retain the version associated with its creation.\u003C/p\u003E\n\u003Cp\u003EDuring validation, the system can compare these values.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022matching-security-state\u0022\u003EMatching Security State\u003C/h3\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\u003ERoot.SecurityVersion = 7\nSession.SecurityVersionAtCreation = 7\n\nSecurity version check: PASS\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe session was created under the current security version.\u003C/p\u003E\n\u003Cp\u003EThis does not mean that the session is automatically valid; expiration, revocation, and other applicable checks must still succeed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022changed-security-state\u0022\u003EChanged Security State\u003C/h3\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\u003ERoot.SecurityVersion = 8\nSession.SecurityVersionAtCreation = 7\n\nSecurity version check: FAIL\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe session was created under an earlier account-level security state.\u003C/p\u003E\n\u003Cp\u003EWhen security version validation is enforced, the mismatch invalidates that session.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-versioning-matters\u0022\u003EWhy Versioning Matters\u003C/h3\u003E\n\u003Cp\u003EWithout a shared security version, an account-wide security change may require every individual session to be located and updated before the change can be enforced.\u003C/p\u003E\n\u003Cp\u003EA security version provides a common condition against which existing session state can be evaluated.\u003C/p\u003E\n\u003Cp\u003EThis can support responses to events such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredential changes or recovery.\u003C/li\u003E\n\u003Cli\u003EAdministrative security actions.\u003C/li\u003E\n\u003Cli\u003EAccount compromise.\u003C/li\u003E\n\u003Cli\u003EAccount-level session invalidation.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EHowever, the triggering operation and configured session revocation policy determine whether a security version change should occur.\u003C/p\u003E\n\u003Cp\u003ENot every credential change must automatically invalidate every session.\u003C/p\u003E\n\u003Cp\u003EFor example, UltimateAuth supports credential-related session handling policies such as \u003Ccode\u003EKeepAll\u003C/code\u003E, \u003Ccode\u003EKeepCurrent\u003C/code\u003E, and \u003Ccode\u003ERevokeAll\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA security operation must preserve the intended policy semantics rather than indiscriminately invalidating the entire account hierarchy.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-context-validation\u0022\u003EDevice Context Validation\u003C/h2\u003E\n\u003Cp\u003EDevice-associated session management can add security conditions to session validation.\u003C/p\u003E\n\u003Cp\u003EA chain may be associated with a device identifier or other client context.\u003C/p\u003E\n\u003Cp\u003EWhen device binding checks are configured and enforced, the system can compare the expected context with the context presented by a request.\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\u003EChain.DeviceId = device-A\nRequest.DeviceId = device-B\n\nDevice context mismatch\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA configured mismatch policy may reject the operation.\u003C/p\u003E\n\u003Cp\u003EHowever, this comparison must be interpreted correctly.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identification-is-not-authentication\u0022\u003EIdentification Is Not Authentication\u003C/h3\u003E\n\u003Cp\u003EA device identifier is an identifying value, not necessarily a secret.\u003C/p\u003E\n\u003Cp\u003EIf an attacker can copy or forge that value, matching the identifier does not prove that the request originated from the legitimate physical device.\u003C/p\u003E\n\u003Cp\u003EDevice context validation can enforce consistency with recorded metadata, but stronger device authentication requires an appropriate proof mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-metadata-is-not-a-security-guarantee\u0022\u003EDevice Metadata Is Not a Security Guarantee\u003C/h3\u003E\n\u003Cp\u003EOperating system, browser, platform, and IP information may provide useful context.\u003C/p\u003E\n\u003Cp\u003EThey are not universally stable or unique.\u003C/p\u003E\n\u003Cp\u003EChanges to this information can occur during legitimate usage, and matching values can occur across unrelated clients.\u003C/p\u003E\n\u003Cp\u003EApplications should distinguish between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInformational device metadata.\u003C/li\u003E\n\u003Cli\u003EConfigured device consistency requirements.\u003C/li\u003E\n\u003Cli\u003ECryptographically established device binding.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe security guarantee must reflect the mechanism actually enforced.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022expiration-and-inactivity\u0022\u003EExpiration and Inactivity\u003C/h2\u003E\n\u003Cp\u003EExpiration is a security control, not merely a cleanup mechanism.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth distinguishes between time-based validity and explicit security invalidation.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured mode and options, relevant conditions may include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECondition\u003C/th\u003E\n\u003Cth\u003ESecurity meaning\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\u003EThe authentication state has exceeded its permitted duration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIdle timeout\u003C/td\u003E\n\u003Ctd\u003EThe required activity has not occurred within the allowed interval\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESliding expiration\u003C/td\u003E\n\u003Ctd\u003EQualifying activity can extend validity within configured limits\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAbsolute lifetime\u003C/td\u003E\n\u003Ctd\u003EAuthentication state cannot remain valid beyond a fixed boundary\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERevocation\u003C/td\u003E\n\u003Ctd\u003EAuthentication state has been explicitly invalidated\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA session may be expired without being revoked.\u003C/p\u003E\n\u003Cp\u003ESimilarly, a session may be revoked before reaching its natural expiration.\u003C/p\u003E\n\u003Cp\u003EThese conditions should remain distinguishable for correct enforcement and security diagnostics.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022activity-tracking-is-not-universal\u0022\u003EActivity Tracking Is Not Universal\u003C/h3\u003E\n\u003Cp\u003ESession activity may be recorded through touch operations.\u003C/p\u003E\n\u003Cp\u003EHowever, a locally validated JWT does not necessarily cause a server-side session touch on every protected request.\u003C/p\u003E\n\u003Cp\u003ETherefore, inactivity enforcement depends on where authoritative activity state is maintained and when it is checked.\u003C/p\u003E\n\u003Cp\u003EAn application should not assume that all authentication modes provide identical idle-timeout guarantees.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022hierarchical-revocation\u0022\u003EHierarchical Revocation\u003C/h2\u003E\n\u003Cp\u003ERevocation allows authentication state to be invalidated before its normal expiration.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s hierarchy supports different revocation scopes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-level-revocation\u0022\u003ESession-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ETargets an individual session.\u003C/p\u003E\n\u003Cp\u003EOther sessions may remain valid if their own security conditions continue to be satisfied.\u003C/p\u003E\n\u003Cp\u003EThis is appropriate for terminating a particular authentication instance.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-level-revocation\u0022\u003EChain-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ETargets the session continuity associated with a chain.\u003C/p\u003E\n\u003Cp\u003EIt can invalidate related sessions without necessarily affecting other chains belonging to the same account.\u003C/p\u003E\n\u003Cp\u003EThis supports targeted containment when a particular client environment is suspected of compromise.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-level-revocation\u0022\u003ERoot-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ETargets the account-level session security boundary.\u003C/p\u003E\n\u003Cp\u003EIt can invalidate the applicable session hierarchy, including chains and sessions associated with that root.\u003C/p\u003E\n\u003Cp\u003ESecurity versioning can contribute to this mechanism by invalidating sessions created under earlier account-level security state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-scope-and-enforcement\u0022\u003ERevocation Scope and Enforcement\u003C/h3\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 revoke\n -\u0026gt; Targeted session\n\nChain revoke\n -\u0026gt; Sessions associated with the chain\n\nRoot revoke\n -\u0026gt; Account-level session hierarchy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese are logical revocation scopes.\u003C/p\u003E\n\u003Cp\u003EThe effect on protected requests depends on whether the authentication mode consults the revoked state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERevoking server-side state is not equivalent to cryptographically invalidating an already-issued JWT.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-enforcement\u0022\u003EAuthentication Mode Enforcement\u003C/h2\u003E\n\u003Cp\u003EThe most important limitation of hierarchical session security is that its enforcement depends on the selected authentication mode.\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\u003EProtected request enforcement\u003C/th\u003E\n\u003Cth\u003ERevocation considerations\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureOpaque\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses authoritative server-side session validation\u003C/td\u003E\n\u003Ctd\u003ERevoked session state can be rejected on subsequent validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses server-authoritative opaque credential and session validation\u003C/td\u003E\n\u003Ctd\u003ESession and chain revocation can be enforced through authoritative validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESemiHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT access tokens are validated locally for protected requests\u003C/td\u003E\n\u003Ctd\u003EServer-side revocation may not invalidate an already-issued JWT until expiration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureJwt\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT access tokens are primarily validated without a server-side session lookup\u003C/td\u003E\n\u003Ctd\u003EImmediate session-hierarchy revocation must not be assumed\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=\u0022server-authoritative-modes\u0022\u003EServer-Authoritative Modes\u003C/h3\u003E\n\u003Cp\u003EPureOpaque and Hybrid can evaluate protected requests against current server-managed session state.\u003C/p\u003E\n\u003Cp\u003EThis allows applicable expiration, revocation, and security version conditions to affect subsequent authentication decisions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022locally-validated-jwt-modes\u0022\u003ELocally Validated JWT Modes\u003C/h3\u003E\n\u003Cp\u003ESemiHybrid and PureJwt can accept a valid JWT without consulting current session hierarchy state on every protected request.\u003C/p\u003E\n\u003Cp\u003EConsequently, changes to server-side session security may not immediately affect an already-issued access token.\u003C/p\u003E\n\u003Cp\u003EThe duration of this exposure depends on token lifetime and any additional validation or revocation mechanism configured by the application.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-tradeoff\u0022\u003ESecurity Tradeoff\u003C/h3\u003E\n\u003Cp\u003EThis is a deliberate architectural distinction.\u003C/p\u003E\n\u003Cp\u003EServer-authoritative validation provides stronger immediate control over active authentication state at the cost of server-side validation work.\u003C/p\u003E\n\u003Cp\u003ELocally validated JWTs reduce that dependency but cannot inherently provide the same immediate revocation behavior.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022threat-scenarios\u0022\u003EThreat Scenarios\u003C/h2\u003E\n\u003Cp\u003EThe session security model helps address several common security scenarios.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-session-credential-is-stolen\u0022\u003EA Session Credential Is Stolen\u003C/h3\u003E\n\u003Cp\u003EAn attacker obtains authentication material associated with one session.\u003C/p\u003E\n\u003Cp\u003EIf the compromise is detected, the affected session can be revoked.\u003C/p\u003E\n\u003Cp\u003EIn server-authoritative modes, subsequent attempts to use that session can be rejected through session validation.\u003C/p\u003E\n\u003Cp\u003EIf the attacker possesses an independently valid JWT, the result depends on JWT validation and revocation behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-client-environment-is-compromised\u0022\u003EA Client Environment Is Compromised\u003C/h3\u003E\n\u003Cp\u003EAn attacker may have access to authentication material associated with a particular browser or application installation.\u003C/p\u003E\n\u003Cp\u003EChain-level revocation provides a broader response than revoking only one session.\u003C/p\u003E\n\u003Cp\u003EIt can invalidate related session state while preserving unrelated chains where appropriate.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022an-account-is-compromised\u0022\u003EAn Account Is Compromised\u003C/h3\u003E\n\u003Cp\u003EAn account-level security event may require invalidating authentication state across multiple devices.\u003C/p\u003E\n\u003Cp\u003ERoot-level invalidation provides a common security boundary for the applicable session hierarchy.\u003C/p\u003E\n\u003Cp\u003EThe operation must still account for the configured authentication mode and credential-related revocation policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-tenant-relationship-is-inconsistent\u0022\u003EA Tenant Relationship Is Inconsistent\u003C/h3\u003E\n\u003Cp\u003EA session, chain, or root references an unexpected tenant or account relationship.\u003C/p\u003E\n\u003Cp\u003EA session-authoritative validation process must not accept the credential solely because the individual records exist.\u003C/p\u003E\n\u003Cp\u003EThe relationships must satisfy the expected security context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-device-identifier-is-spoofed\u0022\u003EA Device Identifier Is Spoofed\u003C/h3\u003E\n\u003Cp\u003EAn attacker presents a device identifier matching a known client.\u003C/p\u003E\n\u003Cp\u003EThe identifier match alone does not establish device ownership.\u003C/p\u003E\n\u003Cp\u003EThe request must still satisfy the authentication and applicable session validation requirements.\u003C/p\u003E\n\u003Cp\u003EThese scenarios illustrate how different controls address different classes of risk.\u003C/p\u003E\n\u003Cp\u003ENo single session metadata field or policy provides complete protection against every form of credential compromise.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-design-principles\u0022\u003ESecurity Design Principles\u003C/h2\u003E\n\u003Cp\u003EThe session security model follows several important principles.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EValidate relationships, not just identifiers.\u003C/strong\u003E Session, chain, root, account, and tenant associations must remain consistent.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESeparate authentication credentials from authoritative state.\u003C/strong\u003E A credential can reference security state without being equivalent to that state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EApply revocation at the appropriate scope.\u003C/strong\u003E Session, chain, and root invalidation serve different security purposes.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not equate device recognition with device proof.\u003C/strong\u003E Client metadata provides context but not necessarily strong device authentication.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERespect authentication mode boundaries.\u003C/strong\u003E A security control is only effective where its underlying state is actually checked.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EKeep lifecycle policy explicit.\u003C/strong\u003E Credential changes, recovery, expiration, and administrative actions may require different invalidation behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s hierarchical session model provides multiple security boundaries instead of relying on a single token validity check.\u003C/p\u003E\n\u003Cp\u003EThe root represents account-level security authority, the chain organizes related session continuity, and the session represents individual server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003ETogether, they support targeted revocation, security versioning, relationship validation, and device-aware session control.\u003C/p\u003E\n\u003Cp\u003ETheir practical security guarantees depend on the authentication mode and the validation mechanisms enforced during protected operations.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA secure session is not merely one that has not expired. It is one whose applicable security state, ownership relationships, and validation requirements still hold.\u003C/strong\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/sessions-devices/session-model\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E \u2014 Understand the underlying hierarchy and lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E \u2014 Explore device context and chain association.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Compare enforcement across authentication modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Understand credential changes and session revocation policies.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EA valid authentication credential is not necessarily sufficient to establish that an authentication session should still be trusted.\u003C/p\u003E\n\u003Cp\u003EA session may have been revoked, its associated account security state may have changed, or the client may no longer satisfy the conditions under which authentication was established.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth addresses these concerns through a \u003Cstrong\u003Ehierarchical session security model\u003C/strong\u003E, with explicit boundaries for session validity, device-associated continuity, and account-level invalidation.\u003C/p\u003E\n\u003Cp\u003EIn session-managed authentication modes, the security hierarchy is:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERoot \u2192 Chain \u2192 Session\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis page focuses on the security guarantees, validation requirements, and enforcement boundaries of that hierarchy.\u003C/p\u003E\n\u003Cp\u003EFor the underlying entities and their lifecycle, 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=\u0022security-objectives\u0022\u003ESecurity Objectives\u003C/h2\u003E\n\u003Cp\u003EThe session security model is designed to support several distinct requirements.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EObjective\u003C/th\u003E\n\u003Cth\u003ESecurity purpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESession isolation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EInvalidate a particular authentication session without necessarily affecting others\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EChain isolation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EControl sessions associated with a particular device or client context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAccount-level invalidation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EInvalidate authentication state across the account\u0027s session hierarchy\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESecurity state propagation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EDetect changes to account-level authentication security\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EContext consistency\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEnsure that related session, chain, root, and tenant information remains consistent\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELifecycle enforcement\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EReject authentication state that has expired or been revoked\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese requirements are related, but they should not be collapsed into a single token expiration check.\u003C/p\u003E\n\u003Cp\u003EA token can remain structurally valid even when the server-managed authentication state associated with it is no longer acceptable.\u003C/p\u003E\n\u003Cp\u003EWhether that state is consulted during a protected request depends on the authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-boundaries\u0022\u003ESecurity Boundaries\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s session hierarchy separates security responsibilities 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\u003EAccount-level security\n |\n v\n SessionRoot\n |\n | Account-wide security state\n | Security version\n |\n v\n SessionChain\n |\n | Session continuity\n | Device context\n | Chain-level revocation\n |\n v\n Session\n |\n | Individual validity\n | Expiration\n | Revocation\n |\n v\n Authentication state\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach level provides a distinct boundary for evaluating or invalidating authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-security-boundary\u0022\u003ERoot Security Boundary\u003C/h3\u003E\n\u003Cp\u003EThe root represents account-level authentication security.\u003C/p\u003E\n\u003Cp\u003EIt provides a common authority for security state that affects sessions associated with the same account.\u003C/p\u003E\n\u003Cp\u003EThis enables account-wide invalidation without requiring the user identity itself to be deleted or recreated.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-security-boundary\u0022\u003EChain Security Boundary\u003C/h3\u003E\n\u003Cp\u003EA chain groups related authentication sessions and may be associated with a device or client environment.\u003C/p\u003E\n\u003Cp\u003EIt provides a boundary for revoking related sessions together.\u003C/p\u003E\n\u003Cp\u003EA chain is not proof of physical device identity, and its existence does not automatically mean that the associated device is trusted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-security-boundary\u0022\u003ESession Security Boundary\u003C/h3\u003E\n\u003Cp\u003EA session represents individual server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003EIt can be independently subject to expiration, revocation, and consistency checks.\u003C/p\u003E\n\u003Cp\u003EA session is not equivalent to an access token: the token is presented by the client, while the session represents server-managed security state where the selected mode uses it.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022layered-session-validation\u0022\u003ELayered Session Validation\u003C/h2\u003E\n\u003Cp\u003EIn a session-authoritative authentication mode, accepting a presented credential requires more than recognizing its identifier.\u003C/p\u003E\n\u003Cp\u003EThe associated session hierarchy must satisfy the applicable validity and consistency requirements.\u003C/p\u003E\n\u003Cp\u003EThese requirements can be understood in several categories.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-validity\u0022\u003ESession Validity\u003C/h3\u003E\n\u003Cp\u003EThe session must satisfy its own security conditions.\u003C/p\u003E\n\u003Cp\u003ERelevant checks include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe session exists in the authoritative session store.\u003C/li\u003E\n\u003Cli\u003EThe session has not been revoked.\u003C/li\u003E\n\u003Cli\u003EThe session has not exceeded its applicable expiration limits.\u003C/li\u003E\n\u003Cli\u003EThe session is associated with the expected chain.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA credential referencing a missing, expired, or revoked session must not establish a valid session-authoritative authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-validity\u0022\u003EChain Validity\u003C/h3\u003E\n\u003Cp\u003EThe associated chain must satisfy the applicable chain security conditions.\u003C/p\u003E\n\u003Cp\u003EThese may include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe chain exists.\u003C/li\u003E\n\u003Cli\u003EThe chain has not been revoked.\u003C/li\u003E\n\u003Cli\u003EThe chain belongs to the expected account and tenant context.\u003C/li\u003E\n\u003Cli\u003EThe session-to-chain relationship is consistent.\u003C/li\u003E\n\u003Cli\u003EAny applicable chain-level expiration or inactivity conditions are satisfied.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EChain validation prevents an otherwise recognizable session from bypassing a security restriction applied to its broader session continuity boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-validity\u0022\u003ERoot Validity\u003C/h3\u003E\n\u003Cp\u003EThe root must satisfy the account-level security requirements.\u003C/p\u003E\n\u003Cp\u003ERelevant conditions include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe expected root exists.\u003C/li\u003E\n\u003Cli\u003EThe root is associated with the expected identity and tenant context.\u003C/li\u003E\n\u003Cli\u003EThe chain belongs to the expected root.\u003C/li\u003E\n\u003Cli\u003EThe root has not been invalidated.\u003C/li\u003E\n\u003Cli\u003EApplicable security version requirements are satisfied.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis prevents a valid-looking session from being accepted when its account-level security authority has changed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022relationship-consistency\u0022\u003ERelationship Consistency\u003C/h3\u003E\n\u003Cp\u003EThe hierarchy must also be internally consistent.\u003C/p\u003E\n\u003Cp\u003EA session cannot be considered valid merely because a session record, a chain record, and a root record each exist independently.\u003C/p\u003E\n\u003Cp\u003ETheir ownership and association must match the expected authentication context.\u003C/p\u003E\n\u003Cp\u003EThis is especially important in multi-tenant applications, where a mismatched tenant or account association must not be accepted as a valid authentication relationship.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe security decision concerns the entire applicable hierarchy, not an isolated record.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-versioning\u0022\u003ESecurity Versioning\u003C/h2\u003E\n\u003Cp\u003ESecurity versioning allows UltimateAuth to represent changes in account-level authentication security.\u003C/p\u003E\n\u003Cp\u003EA root maintains a security version, and a session can retain the version associated with its creation.\u003C/p\u003E\n\u003Cp\u003EDuring validation, the system can compare these values.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022matching-security-state\u0022\u003EMatching Security State\u003C/h3\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\u003ERoot.SecurityVersion = 7\nSession.SecurityVersionAtCreation = 7\n\nSecurity version check: PASS\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe session was created under the current security version.\u003C/p\u003E\n\u003Cp\u003EThis does not mean that the session is automatically valid; expiration, revocation, and other applicable checks must still succeed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022changed-security-state\u0022\u003EChanged Security State\u003C/h3\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\u003ERoot.SecurityVersion = 8\nSession.SecurityVersionAtCreation = 7\n\nSecurity version check: FAIL\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe session was created under an earlier account-level security state.\u003C/p\u003E\n\u003Cp\u003EWhen security version validation is enforced, the mismatch invalidates that session.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-versioning-matters\u0022\u003EWhy Versioning Matters\u003C/h3\u003E\n\u003Cp\u003EWithout a shared security version, an account-wide security change may require every individual session to be located and updated before the change can be enforced.\u003C/p\u003E\n\u003Cp\u003EA security version provides a common condition against which existing session state can be evaluated.\u003C/p\u003E\n\u003Cp\u003EThis can support responses to events such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredential changes or recovery.\u003C/li\u003E\n\u003Cli\u003EAdministrative security actions.\u003C/li\u003E\n\u003Cli\u003EAccount compromise.\u003C/li\u003E\n\u003Cli\u003EAccount-level session invalidation.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EHowever, the triggering operation and configured session revocation policy determine whether a security version change should occur.\u003C/p\u003E\n\u003Cp\u003ENot every credential change must automatically invalidate every session.\u003C/p\u003E\n\u003Cp\u003EFor example, UltimateAuth supports credential-related session handling policies such as \u003Ccode\u003EKeepAll\u003C/code\u003E, \u003Ccode\u003EKeepCurrent\u003C/code\u003E, and \u003Ccode\u003ERevokeAll\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA security operation must preserve the intended policy semantics rather than indiscriminately invalidating the entire account hierarchy.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-context-validation\u0022\u003EDevice Context Validation\u003C/h2\u003E\n\u003Cp\u003EDevice-associated session management can add security conditions to session validation.\u003C/p\u003E\n\u003Cp\u003EA chain may be associated with a device identifier or other client context.\u003C/p\u003E\n\u003Cp\u003EWhen device binding checks are configured and enforced, the system can compare the expected context with the context presented by a request.\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\u003EChain.DeviceId = device-A\nRequest.DeviceId = device-B\n\nDevice context mismatch\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA configured mismatch policy may reject the operation.\u003C/p\u003E\n\u003Cp\u003EHowever, this comparison must be interpreted correctly.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identification-is-not-authentication\u0022\u003EIdentification Is Not Authentication\u003C/h3\u003E\n\u003Cp\u003EA device identifier is an identifying value, not necessarily a secret.\u003C/p\u003E\n\u003Cp\u003EIf an attacker can copy or forge that value, matching the identifier does not prove that the request originated from the legitimate physical device.\u003C/p\u003E\n\u003Cp\u003EDevice context validation can enforce consistency with recorded metadata, but stronger device authentication requires an appropriate proof mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-metadata-is-not-a-security-guarantee\u0022\u003EDevice Metadata Is Not a Security Guarantee\u003C/h3\u003E\n\u003Cp\u003EOperating system, browser, platform, and IP information may provide useful context.\u003C/p\u003E\n\u003Cp\u003EThey are not universally stable or unique.\u003C/p\u003E\n\u003Cp\u003EChanges to this information can occur during legitimate usage, and matching values can occur across unrelated clients.\u003C/p\u003E\n\u003Cp\u003EApplications should distinguish between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInformational device metadata.\u003C/li\u003E\n\u003Cli\u003EConfigured device consistency requirements.\u003C/li\u003E\n\u003Cli\u003ECryptographically established device binding.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe security guarantee must reflect the mechanism actually enforced.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022expiration-and-inactivity\u0022\u003EExpiration and Inactivity\u003C/h2\u003E\n\u003Cp\u003EExpiration is a security control, not merely a cleanup mechanism.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth distinguishes between time-based validity and explicit security invalidation.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured mode and options, relevant conditions may include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECondition\u003C/th\u003E\n\u003Cth\u003ESecurity meaning\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\u003EThe authentication state has exceeded its permitted duration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIdle timeout\u003C/td\u003E\n\u003Ctd\u003EThe required activity has not occurred within the allowed interval\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESliding expiration\u003C/td\u003E\n\u003Ctd\u003EQualifying activity can extend validity within configured limits\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAbsolute lifetime\u003C/td\u003E\n\u003Ctd\u003EAuthentication state cannot remain valid beyond a fixed boundary\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERevocation\u003C/td\u003E\n\u003Ctd\u003EAuthentication state has been explicitly invalidated\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA session may be expired without being revoked.\u003C/p\u003E\n\u003Cp\u003ESimilarly, a session may be revoked before reaching its natural expiration.\u003C/p\u003E\n\u003Cp\u003EThese conditions should remain distinguishable for correct enforcement and security diagnostics.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022activity-tracking-is-not-universal\u0022\u003EActivity Tracking Is Not Universal\u003C/h3\u003E\n\u003Cp\u003ESession activity may be recorded through touch operations.\u003C/p\u003E\n\u003Cp\u003EHowever, a locally validated JWT does not necessarily cause a server-side session touch on every protected request.\u003C/p\u003E\n\u003Cp\u003ETherefore, inactivity enforcement depends on where authoritative activity state is maintained and when it is checked.\u003C/p\u003E\n\u003Cp\u003EAn application should not assume that all authentication modes provide identical idle-timeout guarantees.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022hierarchical-revocation\u0022\u003EHierarchical Revocation\u003C/h2\u003E\n\u003Cp\u003ERevocation allows authentication state to be invalidated before its normal expiration.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s hierarchy supports different revocation scopes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-level-revocation\u0022\u003ESession-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ETargets an individual session.\u003C/p\u003E\n\u003Cp\u003EOther sessions may remain valid if their own security conditions continue to be satisfied.\u003C/p\u003E\n\u003Cp\u003EThis is appropriate for terminating a particular authentication instance.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-level-revocation\u0022\u003EChain-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ETargets the session continuity associated with a chain.\u003C/p\u003E\n\u003Cp\u003EIt can invalidate related sessions without necessarily affecting other chains belonging to the same account.\u003C/p\u003E\n\u003Cp\u003EThis supports targeted containment when a particular client environment is suspected of compromise.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-level-revocation\u0022\u003ERoot-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ETargets the account-level session security boundary.\u003C/p\u003E\n\u003Cp\u003EIt can invalidate the applicable session hierarchy, including chains and sessions associated with that root.\u003C/p\u003E\n\u003Cp\u003ESecurity versioning can contribute to this mechanism by invalidating sessions created under earlier account-level security state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-scope-and-enforcement\u0022\u003ERevocation Scope and Enforcement\u003C/h3\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 revoke\n -\u0026gt; Targeted session\n\nChain revoke\n -\u0026gt; Sessions associated with the chain\n\nRoot revoke\n -\u0026gt; Account-level session hierarchy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese are logical revocation scopes.\u003C/p\u003E\n\u003Cp\u003EThe effect on protected requests depends on whether the authentication mode consults the revoked state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERevoking server-side state is not equivalent to cryptographically invalidating an already-issued JWT.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-enforcement\u0022\u003EAuthentication Mode Enforcement\u003C/h2\u003E\n\u003Cp\u003EThe most important limitation of hierarchical session security is that its enforcement depends on the selected authentication mode.\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\u003EProtected request enforcement\u003C/th\u003E\n\u003Cth\u003ERevocation considerations\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureOpaque\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses authoritative server-side session validation\u003C/td\u003E\n\u003Ctd\u003ERevoked session state can be rejected on subsequent validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses server-authoritative opaque credential and session validation\u003C/td\u003E\n\u003Ctd\u003ESession and chain revocation can be enforced through authoritative validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESemiHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT access tokens are validated locally for protected requests\u003C/td\u003E\n\u003Ctd\u003EServer-side revocation may not invalidate an already-issued JWT until expiration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureJwt\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT access tokens are primarily validated without a server-side session lookup\u003C/td\u003E\n\u003Ctd\u003EImmediate session-hierarchy revocation must not be assumed\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=\u0022server-authoritative-modes\u0022\u003EServer-Authoritative Modes\u003C/h3\u003E\n\u003Cp\u003EPureOpaque and Hybrid can evaluate protected requests against current server-managed session state.\u003C/p\u003E\n\u003Cp\u003EThis allows applicable expiration, revocation, and security version conditions to affect subsequent authentication decisions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022locally-validated-jwt-modes\u0022\u003ELocally Validated JWT Modes\u003C/h3\u003E\n\u003Cp\u003ESemiHybrid and PureJwt can accept a valid JWT without consulting current session hierarchy state on every protected request.\u003C/p\u003E\n\u003Cp\u003EConsequently, changes to server-side session security may not immediately affect an already-issued access token.\u003C/p\u003E\n\u003Cp\u003EThe duration of this exposure depends on token lifetime and any additional validation or revocation mechanism configured by the application.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-tradeoff\u0022\u003ESecurity Tradeoff\u003C/h3\u003E\n\u003Cp\u003EThis is a deliberate architectural distinction.\u003C/p\u003E\n\u003Cp\u003EServer-authoritative validation provides stronger immediate control over active authentication state at the cost of server-side validation work.\u003C/p\u003E\n\u003Cp\u003ELocally validated JWTs reduce that dependency but cannot inherently provide the same immediate revocation behavior.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022threat-scenarios\u0022\u003EThreat Scenarios\u003C/h2\u003E\n\u003Cp\u003EThe session security model helps address several common security scenarios.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-session-credential-is-stolen\u0022\u003EA Session Credential Is Stolen\u003C/h3\u003E\n\u003Cp\u003EAn attacker obtains authentication material associated with one session.\u003C/p\u003E\n\u003Cp\u003EIf the compromise is detected, the affected session can be revoked.\u003C/p\u003E\n\u003Cp\u003EIn server-authoritative modes, subsequent attempts to use that session can be rejected through session validation.\u003C/p\u003E\n\u003Cp\u003EIf the attacker possesses an independently valid JWT, the result depends on JWT validation and revocation behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-client-environment-is-compromised\u0022\u003EA Client Environment Is Compromised\u003C/h3\u003E\n\u003Cp\u003EAn attacker may have access to authentication material associated with a particular browser or application installation.\u003C/p\u003E\n\u003Cp\u003EChain-level revocation provides a broader response than revoking only one session.\u003C/p\u003E\n\u003Cp\u003EIt can invalidate related session state while preserving unrelated chains where appropriate.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022an-account-is-compromised\u0022\u003EAn Account Is Compromised\u003C/h3\u003E\n\u003Cp\u003EAn account-level security event may require invalidating authentication state across multiple devices.\u003C/p\u003E\n\u003Cp\u003ERoot-level invalidation provides a common security boundary for the applicable session hierarchy.\u003C/p\u003E\n\u003Cp\u003EThe operation must still account for the configured authentication mode and credential-related revocation policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-tenant-relationship-is-inconsistent\u0022\u003EA Tenant Relationship Is Inconsistent\u003C/h3\u003E\n\u003Cp\u003EA session, chain, or root references an unexpected tenant or account relationship.\u003C/p\u003E\n\u003Cp\u003EA session-authoritative validation process must not accept the credential solely because the individual records exist.\u003C/p\u003E\n\u003Cp\u003EThe relationships must satisfy the expected security context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-device-identifier-is-spoofed\u0022\u003EA Device Identifier Is Spoofed\u003C/h3\u003E\n\u003Cp\u003EAn attacker presents a device identifier matching a known client.\u003C/p\u003E\n\u003Cp\u003EThe identifier match alone does not establish device ownership.\u003C/p\u003E\n\u003Cp\u003EThe request must still satisfy the authentication and applicable session validation requirements.\u003C/p\u003E\n\u003Cp\u003EThese scenarios illustrate how different controls address different classes of risk.\u003C/p\u003E\n\u003Cp\u003ENo single session metadata field or policy provides complete protection against every form of credential compromise.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-design-principles\u0022\u003ESecurity Design Principles\u003C/h2\u003E\n\u003Cp\u003EThe session security model follows several important principles.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EValidate relationships, not just identifiers.\u003C/strong\u003E Session, chain, root, account, and tenant associations must remain consistent.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESeparate authentication credentials from authoritative state.\u003C/strong\u003E A credential can reference security state without being equivalent to that state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EApply revocation at the appropriate scope.\u003C/strong\u003E Session, chain, and root invalidation serve different security purposes.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not equate device recognition with device proof.\u003C/strong\u003E Client metadata provides context but not necessarily strong device authentication.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERespect authentication mode boundaries.\u003C/strong\u003E A security control is only effective where its underlying state is actually checked.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EKeep lifecycle policy explicit.\u003C/strong\u003E Credential changes, recovery, expiration, and administrative actions may require different invalidation behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s hierarchical session model provides multiple security boundaries instead of relying on a single token validity check.\u003C/p\u003E\n\u003Cp\u003EThe root represents account-level security authority, the chain organizes related session continuity, and the session represents individual server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003ETogether, they support targeted revocation, security versioning, relationship validation, and device-aware session control.\u003C/p\u003E\n\u003Cp\u003ETheir practical security guarantees depend on the authentication mode and the validation mechanisms enforced during protected operations.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA secure session is not merely one that has not expired. It is one whose applicable security state, ownership relationships, and validation requirements still hold.\u003C/strong\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/sessions-devices/session-model\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E \u2014 Understand the underlying hierarchy and lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E \u2014 Explore device context and chain association.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Compare enforcement across authentication modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Understand credential changes and session revocation policies.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "security-objectives", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security.json new file mode 100644 index 00000000..1d65cf71 --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security.json @@ -0,0 +1,192 @@ +{ + "Slug": "security/session-security", + "Title": "Session Security Model", + "Html": "\n\u003Cp\u003EA valid authentication credential is not necessarily sufficient to establish that an authentication session should still be trusted.\u003C/p\u003E\n\u003Cp\u003EA session may have been revoked, its associated account security state may have changed, or the client may no longer satisfy the conditions under which authentication was established.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth addresses these concerns through a \u003Cstrong\u003Ehierarchical session security model\u003C/strong\u003E, with explicit boundaries for session validity, device-associated continuity, and account-level invalidation.\u003C/p\u003E\n\u003Cp\u003EIn session-managed authentication modes, the security hierarchy is:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERoot \u2192 Chain \u2192 Session\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis page focuses on the security guarantees, validation requirements, and enforcement boundaries of that hierarchy.\u003C/p\u003E\n\u003Cp\u003EFor the underlying entities and their lifecycle, 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=\u0022security-objectives\u0022\u003ESecurity Objectives\u003C/h2\u003E\n\u003Cp\u003EThe session security model is designed to support several distinct requirements.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EObjective\u003C/th\u003E\n\u003Cth\u003ESecurity purpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESession isolation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EInvalidate a particular authentication session without necessarily affecting others\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EChain isolation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EControl sessions associated with a particular device or client context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAccount-level invalidation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EInvalidate authentication state across the account\u0027s session hierarchy\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESecurity state propagation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EDetect changes to account-level authentication security\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EContext consistency\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEnsure that related session, chain, root, and tenant information remains consistent\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ELifecycle enforcement\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EReject authentication state that has expired or been revoked\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese requirements are related, but they should not be collapsed into a single token expiration check.\u003C/p\u003E\n\u003Cp\u003EA token can remain structurally valid even when the server-managed authentication state associated with it is no longer acceptable.\u003C/p\u003E\n\u003Cp\u003EWhether that state is consulted during a protected request depends on the authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-boundaries\u0022\u003ESecurity Boundaries\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s session hierarchy separates security responsibilities 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\u003EAccount-level security\n |\n v\n SessionRoot\n |\n | Account-wide security state\n | Security version\n |\n v\n SessionChain\n |\n | Session continuity\n | Device context\n | Chain-level revocation\n |\n v\n Session\n |\n | Individual validity\n | Expiration\n | Revocation\n |\n v\n Authentication state\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach level provides a distinct boundary for evaluating or invalidating authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-security-boundary\u0022\u003ERoot Security Boundary\u003C/h3\u003E\n\u003Cp\u003EThe root represents account-level authentication security.\u003C/p\u003E\n\u003Cp\u003EIt provides a common authority for security state that affects sessions associated with the same account.\u003C/p\u003E\n\u003Cp\u003EThis enables account-wide invalidation without requiring the user identity itself to be deleted or recreated.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-security-boundary\u0022\u003EChain Security Boundary\u003C/h3\u003E\n\u003Cp\u003EA chain groups related authentication sessions and may be associated with a device or client environment.\u003C/p\u003E\n\u003Cp\u003EIt provides a boundary for revoking related sessions together.\u003C/p\u003E\n\u003Cp\u003EA chain is not proof of physical device identity, and its existence does not automatically mean that the associated device is trusted.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-security-boundary\u0022\u003ESession Security Boundary\u003C/h3\u003E\n\u003Cp\u003EA session represents individual server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003EIt can be independently subject to expiration, revocation, and consistency checks.\u003C/p\u003E\n\u003Cp\u003EA session is not equivalent to an access token: the token is presented by the client, while the session represents server-managed security state where the selected mode uses it.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022layered-session-validation\u0022\u003ELayered Session Validation\u003C/h2\u003E\n\u003Cp\u003EIn a session-authoritative authentication mode, accepting a presented credential requires more than recognizing its identifier.\u003C/p\u003E\n\u003Cp\u003EThe associated session hierarchy must satisfy the applicable validity and consistency requirements.\u003C/p\u003E\n\u003Cp\u003EThese requirements can be understood in several categories.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-validity\u0022\u003ESession Validity\u003C/h3\u003E\n\u003Cp\u003EThe session must satisfy its own security conditions.\u003C/p\u003E\n\u003Cp\u003ERelevant checks include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe session exists in the authoritative session store.\u003C/li\u003E\n\u003Cli\u003EThe session has not been revoked.\u003C/li\u003E\n\u003Cli\u003EThe session has not exceeded its applicable expiration limits.\u003C/li\u003E\n\u003Cli\u003EThe session is associated with the expected chain.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA credential referencing a missing, expired, or revoked session must not establish a valid session-authoritative authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-validity\u0022\u003EChain Validity\u003C/h3\u003E\n\u003Cp\u003EThe associated chain must satisfy the applicable chain security conditions.\u003C/p\u003E\n\u003Cp\u003EThese may include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe chain exists.\u003C/li\u003E\n\u003Cli\u003EThe chain has not been revoked.\u003C/li\u003E\n\u003Cli\u003EThe chain belongs to the expected account and tenant context.\u003C/li\u003E\n\u003Cli\u003EThe session-to-chain relationship is consistent.\u003C/li\u003E\n\u003Cli\u003EAny applicable chain-level expiration or inactivity conditions are satisfied.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EChain validation prevents an otherwise recognizable session from bypassing a security restriction applied to its broader session continuity boundary.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-validity\u0022\u003ERoot Validity\u003C/h3\u003E\n\u003Cp\u003EThe root must satisfy the account-level security requirements.\u003C/p\u003E\n\u003Cp\u003ERelevant conditions include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe expected root exists.\u003C/li\u003E\n\u003Cli\u003EThe root is associated with the expected identity and tenant context.\u003C/li\u003E\n\u003Cli\u003EThe chain belongs to the expected root.\u003C/li\u003E\n\u003Cli\u003EThe root has not been invalidated.\u003C/li\u003E\n\u003Cli\u003EApplicable security version requirements are satisfied.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis prevents a valid-looking session from being accepted when its account-level security authority has changed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022relationship-consistency\u0022\u003ERelationship Consistency\u003C/h3\u003E\n\u003Cp\u003EThe hierarchy must also be internally consistent.\u003C/p\u003E\n\u003Cp\u003EA session cannot be considered valid merely because a session record, a chain record, and a root record each exist independently.\u003C/p\u003E\n\u003Cp\u003ETheir ownership and association must match the expected authentication context.\u003C/p\u003E\n\u003Cp\u003EThis is especially important in multi-tenant applications, where a mismatched tenant or account association must not be accepted as a valid authentication relationship.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe security decision concerns the entire applicable hierarchy, not an isolated record.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-versioning\u0022\u003ESecurity Versioning\u003C/h2\u003E\n\u003Cp\u003ESecurity versioning allows UltimateAuth to represent changes in account-level authentication security.\u003C/p\u003E\n\u003Cp\u003EA root maintains a security version, and a session can retain the version associated with its creation.\u003C/p\u003E\n\u003Cp\u003EDuring validation, the system can compare these values.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022matching-security-state\u0022\u003EMatching Security State\u003C/h3\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\u003ERoot.SecurityVersion = 7\nSession.SecurityVersionAtCreation = 7\n\nSecurity version check: PASS\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe session was created under the current security version.\u003C/p\u003E\n\u003Cp\u003EThis does not mean that the session is automatically valid; expiration, revocation, and other applicable checks must still succeed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022changed-security-state\u0022\u003EChanged Security State\u003C/h3\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\u003ERoot.SecurityVersion = 8\nSession.SecurityVersionAtCreation = 7\n\nSecurity version check: FAIL\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe session was created under an earlier account-level security state.\u003C/p\u003E\n\u003Cp\u003EWhen security version validation is enforced, the mismatch invalidates that session.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-versioning-matters\u0022\u003EWhy Versioning Matters\u003C/h3\u003E\n\u003Cp\u003EWithout a shared security version, an account-wide security change may require every individual session to be located and updated before the change can be enforced.\u003C/p\u003E\n\u003Cp\u003EA security version provides a common condition against which existing session state can be evaluated.\u003C/p\u003E\n\u003Cp\u003EThis can support responses to events such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredential changes or recovery.\u003C/li\u003E\n\u003Cli\u003EAdministrative security actions.\u003C/li\u003E\n\u003Cli\u003EAccount compromise.\u003C/li\u003E\n\u003Cli\u003EAccount-level session invalidation.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EHowever, the triggering operation and configured session revocation policy determine whether a security version change should occur.\u003C/p\u003E\n\u003Cp\u003ENot every credential change must automatically invalidate every session.\u003C/p\u003E\n\u003Cp\u003EFor example, UltimateAuth supports credential-related session handling policies such as \u003Ccode\u003EKeepAll\u003C/code\u003E, \u003Ccode\u003EKeepCurrent\u003C/code\u003E, and \u003Ccode\u003ERevokeAll\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA security operation must preserve the intended policy semantics rather than indiscriminately invalidating the entire account hierarchy.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-context-validation\u0022\u003EDevice Context Validation\u003C/h2\u003E\n\u003Cp\u003EDevice-associated session management can add security conditions to session validation.\u003C/p\u003E\n\u003Cp\u003EA chain may be associated with a device identifier or other client context.\u003C/p\u003E\n\u003Cp\u003EWhen device binding checks are configured and enforced, the system can compare the expected context with the context presented by a request.\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\u003EChain.DeviceId = device-A\nRequest.DeviceId = device-B\n\nDevice context mismatch\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA configured mismatch policy may reject the operation.\u003C/p\u003E\n\u003Cp\u003EHowever, this comparison must be interpreted correctly.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identification-is-not-authentication\u0022\u003EIdentification Is Not Authentication\u003C/h3\u003E\n\u003Cp\u003EA device identifier is an identifying value, not necessarily a secret.\u003C/p\u003E\n\u003Cp\u003EIf an attacker can copy or forge that value, matching the identifier does not prove that the request originated from the legitimate physical device.\u003C/p\u003E\n\u003Cp\u003EDevice context validation can enforce consistency with recorded metadata, but stronger device authentication requires an appropriate proof mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-metadata-is-not-a-security-guarantee\u0022\u003EDevice Metadata Is Not a Security Guarantee\u003C/h3\u003E\n\u003Cp\u003EOperating system, browser, platform, and IP information may provide useful context.\u003C/p\u003E\n\u003Cp\u003EThey are not universally stable or unique.\u003C/p\u003E\n\u003Cp\u003EChanges to this information can occur during legitimate usage, and matching values can occur across unrelated clients.\u003C/p\u003E\n\u003Cp\u003EApplications should distinguish between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EInformational device metadata.\u003C/li\u003E\n\u003Cli\u003EConfigured device consistency requirements.\u003C/li\u003E\n\u003Cli\u003ECryptographically established device binding.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe security guarantee must reflect the mechanism actually enforced.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022expiration-and-inactivity\u0022\u003EExpiration and Inactivity\u003C/h2\u003E\n\u003Cp\u003EExpiration is a security control, not merely a cleanup mechanism.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth distinguishes between time-based validity and explicit security invalidation.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured mode and options, relevant conditions may include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECondition\u003C/th\u003E\n\u003Cth\u003ESecurity meaning\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\u003EThe authentication state has exceeded its permitted duration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIdle timeout\u003C/td\u003E\n\u003Ctd\u003EThe required activity has not occurred within the allowed interval\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESliding expiration\u003C/td\u003E\n\u003Ctd\u003EQualifying activity can extend validity within configured limits\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAbsolute lifetime\u003C/td\u003E\n\u003Ctd\u003EAuthentication state cannot remain valid beyond a fixed boundary\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERevocation\u003C/td\u003E\n\u003Ctd\u003EAuthentication state has been explicitly invalidated\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA session may be expired without being revoked.\u003C/p\u003E\n\u003Cp\u003ESimilarly, a session may be revoked before reaching its natural expiration.\u003C/p\u003E\n\u003Cp\u003EThese conditions should remain distinguishable for correct enforcement and security diagnostics.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022activity-tracking-is-not-universal\u0022\u003EActivity Tracking Is Not Universal\u003C/h3\u003E\n\u003Cp\u003ESession activity may be recorded through touch operations.\u003C/p\u003E\n\u003Cp\u003EHowever, a locally validated JWT does not necessarily cause a server-side session touch on every protected request.\u003C/p\u003E\n\u003Cp\u003ETherefore, inactivity enforcement depends on where authoritative activity state is maintained and when it is checked.\u003C/p\u003E\n\u003Cp\u003EAn application should not assume that all authentication modes provide identical idle-timeout guarantees.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022hierarchical-revocation\u0022\u003EHierarchical Revocation\u003C/h2\u003E\n\u003Cp\u003ERevocation allows authentication state to be invalidated before its normal expiration.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s hierarchy supports different revocation scopes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-level-revocation\u0022\u003ESession-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ETargets an individual session.\u003C/p\u003E\n\u003Cp\u003EOther sessions may remain valid if their own security conditions continue to be satisfied.\u003C/p\u003E\n\u003Cp\u003EThis is appropriate for terminating a particular authentication instance.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-level-revocation\u0022\u003EChain-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ETargets the session continuity associated with a chain.\u003C/p\u003E\n\u003Cp\u003EIt can invalidate related sessions without necessarily affecting other chains belonging to the same account.\u003C/p\u003E\n\u003Cp\u003EThis supports targeted containment when a particular client environment is suspected of compromise.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-level-revocation\u0022\u003ERoot-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ETargets the account-level session security boundary.\u003C/p\u003E\n\u003Cp\u003EIt can invalidate the applicable session hierarchy, including chains and sessions associated with that root.\u003C/p\u003E\n\u003Cp\u003ESecurity versioning can contribute to this mechanism by invalidating sessions created under earlier account-level security state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-scope-and-enforcement\u0022\u003ERevocation Scope and Enforcement\u003C/h3\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 revoke\n -\u0026gt; Targeted session\n\nChain revoke\n -\u0026gt; Sessions associated with the chain\n\nRoot revoke\n -\u0026gt; Account-level session hierarchy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese are logical revocation scopes.\u003C/p\u003E\n\u003Cp\u003EThe effect on protected requests depends on whether the authentication mode consults the revoked state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERevoking server-side state is not equivalent to cryptographically invalidating an already-issued JWT.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-enforcement\u0022\u003EAuthentication Mode Enforcement\u003C/h2\u003E\n\u003Cp\u003EThe most important limitation of hierarchical session security is that its enforcement depends on the selected authentication mode.\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\u003EProtected request enforcement\u003C/th\u003E\n\u003Cth\u003ERevocation considerations\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureOpaque\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses authoritative server-side session validation\u003C/td\u003E\n\u003Ctd\u003ERevoked session state can be rejected on subsequent validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EUses server-authoritative opaque credential and session validation\u003C/td\u003E\n\u003Ctd\u003ESession and chain revocation can be enforced through authoritative validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESemiHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT access tokens are validated locally for protected requests\u003C/td\u003E\n\u003Ctd\u003EServer-side revocation may not invalidate an already-issued JWT until expiration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureJwt\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT access tokens are primarily validated without a server-side session lookup\u003C/td\u003E\n\u003Ctd\u003EImmediate session-hierarchy revocation must not be assumed\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=\u0022server-authoritative-modes\u0022\u003EServer-Authoritative Modes\u003C/h3\u003E\n\u003Cp\u003EPureOpaque and Hybrid can evaluate protected requests against current server-managed session state.\u003C/p\u003E\n\u003Cp\u003EThis allows applicable expiration, revocation, and security version conditions to affect subsequent authentication decisions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022locally-validated-jwt-modes\u0022\u003ELocally Validated JWT Modes\u003C/h3\u003E\n\u003Cp\u003ESemiHybrid and PureJwt can accept a valid JWT without consulting current session hierarchy state on every protected request.\u003C/p\u003E\n\u003Cp\u003EConsequently, changes to server-side session security may not immediately affect an already-issued access token.\u003C/p\u003E\n\u003Cp\u003EThe duration of this exposure depends on token lifetime and any additional validation or revocation mechanism configured by the application.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-tradeoff\u0022\u003ESecurity Tradeoff\u003C/h3\u003E\n\u003Cp\u003EThis is a deliberate architectural distinction.\u003C/p\u003E\n\u003Cp\u003EServer-authoritative validation provides stronger immediate control over active authentication state at the cost of server-side validation work.\u003C/p\u003E\n\u003Cp\u003ELocally validated JWTs reduce that dependency but cannot inherently provide the same immediate revocation behavior.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022threat-scenarios\u0022\u003EThreat Scenarios\u003C/h2\u003E\n\u003Cp\u003EThe session security model helps address several common security scenarios.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-session-credential-is-stolen\u0022\u003EA Session Credential Is Stolen\u003C/h3\u003E\n\u003Cp\u003EAn attacker obtains authentication material associated with one session.\u003C/p\u003E\n\u003Cp\u003EIf the compromise is detected, the affected session can be revoked.\u003C/p\u003E\n\u003Cp\u003EIn server-authoritative modes, subsequent attempts to use that session can be rejected through session validation.\u003C/p\u003E\n\u003Cp\u003EIf the attacker possesses an independently valid JWT, the result depends on JWT validation and revocation behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-client-environment-is-compromised\u0022\u003EA Client Environment Is Compromised\u003C/h3\u003E\n\u003Cp\u003EAn attacker may have access to authentication material associated with a particular browser or application installation.\u003C/p\u003E\n\u003Cp\u003EChain-level revocation provides a broader response than revoking only one session.\u003C/p\u003E\n\u003Cp\u003EIt can invalidate related session state while preserving unrelated chains where appropriate.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022an-account-is-compromised\u0022\u003EAn Account Is Compromised\u003C/h3\u003E\n\u003Cp\u003EAn account-level security event may require invalidating authentication state across multiple devices.\u003C/p\u003E\n\u003Cp\u003ERoot-level invalidation provides a common security boundary for the applicable session hierarchy.\u003C/p\u003E\n\u003Cp\u003EThe operation must still account for the configured authentication mode and credential-related revocation policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-tenant-relationship-is-inconsistent\u0022\u003EA Tenant Relationship Is Inconsistent\u003C/h3\u003E\n\u003Cp\u003EA session, chain, or root references an unexpected tenant or account relationship.\u003C/p\u003E\n\u003Cp\u003EA session-authoritative validation process must not accept the credential solely because the individual records exist.\u003C/p\u003E\n\u003Cp\u003EThe relationships must satisfy the expected security context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-device-identifier-is-spoofed\u0022\u003EA Device Identifier Is Spoofed\u003C/h3\u003E\n\u003Cp\u003EAn attacker presents a device identifier matching a known client.\u003C/p\u003E\n\u003Cp\u003EThe identifier match alone does not establish device ownership.\u003C/p\u003E\n\u003Cp\u003EThe request must still satisfy the authentication and applicable session validation requirements.\u003C/p\u003E\n\u003Cp\u003EThese scenarios illustrate how different controls address different classes of risk.\u003C/p\u003E\n\u003Cp\u003ENo single session metadata field or policy provides complete protection against every form of credential compromise.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-design-principles\u0022\u003ESecurity Design Principles\u003C/h2\u003E\n\u003Cp\u003EThe session security model follows several important principles.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EValidate relationships, not just identifiers.\u003C/strong\u003E Session, chain, root, account, and tenant associations must remain consistent.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ESeparate authentication credentials from authoritative state.\u003C/strong\u003E A credential can reference security state without being equivalent to that state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EApply revocation at the appropriate scope.\u003C/strong\u003E Session, chain, and root invalidation serve different security purposes.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not equate device recognition with device proof.\u003C/strong\u003E Client metadata provides context but not necessarily strong device authentication.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERespect authentication mode boundaries.\u003C/strong\u003E A security control is only effective where its underlying state is actually checked.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EKeep lifecycle policy explicit.\u003C/strong\u003E Credential changes, recovery, expiration, and administrative actions may require different invalidation behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s hierarchical session model provides multiple security boundaries instead of relying on a single token validity check.\u003C/p\u003E\n\u003Cp\u003EThe root represents account-level security authority, the chain organizes related session continuity, and the session represents individual server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003ETogether, they support targeted revocation, security versioning, relationship validation, and device-aware session control.\u003C/p\u003E\n\u003Cp\u003ETheir practical security guarantees depend on the authentication mode and the validation mechanisms enforced during protected operations.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA secure session is not merely one that has not expired. It is one whose applicable security state, ownership relationships, and validation requirements still hold.\u003C/strong\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/sessions-devices/session-lifecycle\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E \u2014 Understand the underlying hierarchy and lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E \u2014 Explore device context and chain association.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Compare enforcement across authentication modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Understand credential changes and session revocation policies.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Headings": [ + { + "Id": "security-objectives", + "Text": "Security Objectives", + "Level": 0 + }, + { + "Id": "security-boundaries", + "Text": "Security Boundaries", + "Level": 0 + }, + { + "Id": "root-security-boundary", + "Text": "Root Security Boundary", + "Level": 1 + }, + { + "Id": "chain-security-boundary", + "Text": "Chain Security Boundary", + "Level": 1 + }, + { + "Id": "session-security-boundary", + "Text": "Session Security Boundary", + "Level": 1 + }, + { + "Id": "layered-session-validation", + "Text": "Layered Session Validation", + "Level": 0 + }, + { + "Id": "session-validity", + "Text": "Session Validity", + "Level": 1 + }, + { + "Id": "chain-validity", + "Text": "Chain Validity", + "Level": 1 + }, + { + "Id": "root-validity", + "Text": "Root Validity", + "Level": 1 + }, + { + "Id": "relationship-consistency", + "Text": "Relationship Consistency", + "Level": 1 + }, + { + "Id": "security-versioning", + "Text": "Security Versioning", + "Level": 0 + }, + { + "Id": "matching-security-state", + "Text": "Matching Security State", + "Level": 1 + }, + { + "Id": "changed-security-state", + "Text": "Changed Security State", + "Level": 1 + }, + { + "Id": "why-versioning-matters", + "Text": "Why Versioning Matters", + "Level": 1 + }, + { + "Id": "device-context-validation", + "Text": "Device Context Validation", + "Level": 0 + }, + { + "Id": "identification-is-not-authentication", + "Text": "Identification Is Not Authentication", + "Level": 1 + }, + { + "Id": "device-metadata-is-not-a-security-guarantee", + "Text": "Device Metadata Is Not a Security Guarantee", + "Level": 1 + }, + { + "Id": "expiration-and-inactivity", + "Text": "Expiration and Inactivity", + "Level": 0 + }, + { + "Id": "activity-tracking-is-not-universal", + "Text": "Activity Tracking Is Not Universal", + "Level": 1 + }, + { + "Id": "hierarchical-revocation", + "Text": "Hierarchical Revocation", + "Level": 0 + }, + { + "Id": "session-level-revocation", + "Text": "Session-Level Revocation", + "Level": 1 + }, + { + "Id": "chain-level-revocation", + "Text": "Chain-Level Revocation", + "Level": 1 + }, + { + "Id": "root-level-revocation", + "Text": "Root-Level Revocation", + "Level": 1 + }, + { + "Id": "revocation-scope-and-enforcement", + "Text": "Revocation Scope and Enforcement", + "Level": 1 + }, + { + "Id": "authentication-mode-enforcement", + "Text": "Authentication Mode Enforcement", + "Level": 0 + }, + { + "Id": "server-authoritative-modes", + "Text": "Server-Authoritative Modes", + "Level": 1 + }, + { + "Id": "locally-validated-jwt-modes", + "Text": "Locally Validated JWT Modes", + "Level": 1 + }, + { + "Id": "security-tradeoff", + "Text": "Security Tradeoff", + "Level": 1 + }, + { + "Id": "threat-scenarios", + "Text": "Threat Scenarios", + "Level": 0 + }, + { + "Id": "a-session-credential-is-stolen", + "Text": "A Session Credential Is Stolen", + "Level": 1 + }, + { + "Id": "a-client-environment-is-compromised", + "Text": "A Client Environment Is Compromised", + "Level": 1 + }, + { + "Id": "an-account-is-compromised", + "Text": "An Account Is Compromised", + "Level": 1 + }, + { + "Id": "a-tenant-relationship-is-inconsistent", + "Text": "A Tenant Relationship Is Inconsistent", + "Level": 1 + }, + { + "Id": "a-device-identifier-is-spoofed", + "Text": "A Device Identifier Is Spoofed", + "Level": 1 + }, + { + "Id": "security-design-principles", + "Text": "Security Design Principles", + "Level": 0 + }, + { + "Id": "architectural-perspective", + "Text": "Architectural Perspective", + "Level": 0 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ] +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/token-security.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/token-security.json index 7284aa47..393c76fc 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/token-security.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/token-security.json @@ -1,7 +1,7 @@ { "Slug": "security/token-security", "Title": "Token Security", - "Html": "\n\u003Cp\u003EAuthentication tokens allow clients to present authentication state to protected applications and services.\u003C/p\u003E\n\u003Cp\u003EHowever, not all tokens provide the same security guarantees.\u003C/p\u003E\n\u003Cp\u003EAn opaque credential requires authoritative server-side validation. A JSON Web Token (JWT) can be verified locally using its signature and claims. A refresh credential enables authentication to continue without repeating the original login process.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports these different mechanisms through its authentication modes.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EToken security depends on the credential\u0027s purpose, how it is validated, how long it remains valid, and which security state is consulted during validation.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis page explains the security characteristics of access and refresh credentials, including issuance, expiration, rotation, replay risks, and revocation boundaries.\u003C/p\u003E\n\u003Cp\u003EFor the overall authentication architecture, 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=\u0022authentication-state-and-credentials\u0022\u003EAuthentication State and Credentials\u003C/h2\u003E\n\u003Cp\u003EA token is authentication material presented by a client.\u003C/p\u003E\n\u003Cp\u003EIt is not the user identity itself.\u003C/p\u003E\n\u003Cp\u003EDepending on the authentication mode, the token may reference server-managed authentication state or contain signed claims that can be validated without retrieving a session.\u003C/p\u003E\n\u003Cp\u003EThese approaches establish different trust boundaries.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-authoritative-authentication\u0022\u003EServer-Authoritative Authentication\u003C/h3\u003E\n\u003Cp\u003EIn server-authoritative modes, a presented opaque credential is validated against server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003EThe server can evaluate the current validity of the corresponding session and applicable security conditions.\u003C/p\u003E\n\u003Cp\u003EThis allows revocation and other authoritative state changes to affect subsequent protected requests.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022locally-validated-authentication\u0022\u003ELocally Validated Authentication\u003C/h3\u003E\n\u003Cp\u003EIn JWT-based modes, a protected resource can validate a token using its signature, issuer, audience, expiration, and other applicable token requirements.\u003C/p\u003E\n\u003Cp\u003EThe resource may not need to consult the issuing server or a session store for every request.\u003C/p\u003E\n\u003Cp\u003EThis reduces the dependency on server-side session lookups but changes how quickly revocation and authorization changes can be enforced.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ENeither model makes the token an identity record. The token is a credential whose validity is determined by the selected authentication mechanism.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-types\u0022\u003EToken Types\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses two principal access credential formats: opaque credentials and JWTs.\u003C/p\u003E\n\u003Cp\u003ERefresh credentials serve a separate purpose.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECredential\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003Cth\u003EValidation model\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EOpaque access credential\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthenticate protected requests\u003C/td\u003E\n\u003Ctd\u003EAuthoritative server-side validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EJWT access token\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthenticate protected requests\u003C/td\u003E\n\u003Ctd\u003ECryptographic and claims validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EOpaque refresh credential\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EContinue authentication and obtain renewed credentials\u003C/td\u003E\n\u003Ctd\u003ERefresh-specific validation and state checks\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe distinction between access and refresh credentials is important.\u003C/p\u003E\n\u003Cp\u003EAccess credentials are presented to protected resources.\u003C/p\u003E\n\u003Cp\u003ERefresh credentials are presented to the authentication system for a continuation operation. They should not be accepted as ordinary access credentials.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022opaque-access-credentials\u0022\u003EOpaque Access Credentials\u003C/h2\u003E\n\u003Cp\u003EAn opaque credential is a value whose contents do not expose the authenticated identity or authorization claims to the client.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EAccess credential:\nq8Vn2...random-opaque-value...M7x\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client presents the credential, but cannot determine its authorization meaning by decoding it.\u003C/p\u003E\n\u003Cp\u003EThe server resolves and validates the associated authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-properties\u0022\u003ESecurity Properties\u003C/h3\u003E\n\u003Cp\u003EOpaque access credentials provide several useful characteristics:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ETheir meaning is determined by authoritative server-side state.\u003C/li\u003E\n\u003Cli\u003EThey do not need to expose identity claims in a client-readable payload.\u003C/li\u003E\n\u003Cli\u003ETheir validity can be affected by session expiration or revocation.\u003C/li\u003E\n\u003Cli\u003EThey can participate in account-level and chain-level security enforcement.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EHowever, opaque credentials are still bearer credentials unless an additional proof mechanism is enforced.\u003C/p\u003E\n\u003Cp\u003EAn attacker who obtains a usable opaque credential may be able to present it.\u003C/p\u003E\n\u003Cp\u003EOpacity alone does not prevent credential theft or replay.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-side-validation\u0022\u003EServer-Side Validation\u003C/h3\u003E\n\u003Cp\u003EIn a session-authoritative mode, validation can enforce the applicable session hierarchy requirements, including expiration, revocation, ownership, and security state consistency.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/security/session-security-model\u0022\u003ESession Security Model\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022jwt-access-tokens\u0022\u003EJWT Access Tokens\u003C/h2\u003E\n\u003Cp\u003EA JWT is a structured token containing signed claims.\u003C/p\u003E\n\u003Cp\u003EA typical JWT contains three encoded sections:\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\u003Eheader.payload.signature\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIts payload may describe the authenticated subject, tenant, authorization information, token lifetime, and other relevant context.\u003C/p\u003E\n\u003Cp\u003EA JWT is not encrypted merely because it is encoded and signed.\u003C/p\u003E\n\u003Cp\u003EApplications should not place secrets or unnecessarily sensitive information in a JWT payload.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022cryptographic-validation\u0022\u003ECryptographic Validation\u003C/h3\u003E\n\u003Cp\u003EA protected resource must validate the JWT according to the configured token requirements.\u003C/p\u003E\n\u003Cp\u003ERelevant checks include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESignature validity.\u003C/li\u003E\n\u003Cli\u003EAccepted signing algorithm and key.\u003C/li\u003E\n\u003Cli\u003EExpected issuer.\u003C/li\u003E\n\u003Cli\u003EIntended audience.\u003C/li\u003E\n\u003Cli\u003EExpiration and other applicable time constraints.\u003C/li\u003E\n\u003Cli\u003ERequired token claims and application-specific restrictions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA successfully decoded JWT is not necessarily a valid JWT.\u003C/p\u003E\n\u003Cp\u003ELikewise, a correctly signed JWT is not automatically acceptable to every resource.\u003C/p\u003E\n\u003Cp\u003EThe resource must verify that the token was issued for its intended security context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022local-validation\u0022\u003ELocal Validation\u003C/h3\u003E\n\u003Cp\u003EJWTs can be validated without loading current server-side session state.\u003C/p\u003E\n\u003Cp\u003EThis is useful for distributed APIs and services that need to authenticate requests efficiently.\u003C/p\u003E\n\u003Cp\u003EHowever, local validation introduces an important limitation:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA token can remain cryptographically valid after the underlying account or authorization state has changed.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EWhether such changes immediately affect access depends on additional validation and revocation mechanisms.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-behavior\u0022\u003EAuthentication Mode Behavior\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth selects credential behavior according to the configured authentication mode.\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\u003EAccess mechanism\u003C/th\u003E\n\u003Cth\u003EContinuation mechanism\u003C/th\u003E\n\u003Cth\u003EProtected request authority\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureOpaque\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOpaque session credential\u003C/td\u003E\n\u003Ctd\u003ESession lifecycle\u003C/td\u003E\n\u003Ctd\u003EServer-managed session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOpaque access credential\u003C/td\u003E\n\u003Ctd\u003EOpaque refresh credential\u003C/td\u003E\n\u003Ctd\u003EServer-managed session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESemiHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT access token\u003C/td\u003E\n\u003Ctd\u003EServer-managed refresh flow\u003C/td\u003E\n\u003Ctd\u003ELocally validated JWT\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureJwt\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT access token\u003C/td\u003E\n\u003Ctd\u003EOptional refresh mechanism\u003C/td\u003E\n\u003Ctd\u003ELocally validated JWT\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=\u0022pureopaque\u0022\u003EPureOpaque\u003C/h3\u003E\n\u003Cp\u003EPureOpaque uses an opaque session credential.\u003C/p\u003E\n\u003Cp\u003EIt does not require a separate access-token and refresh-token pair.\u003C/p\u003E\n\u003Cp\u003EThe credential is validated through the authoritative session mechanism.\u003C/p\u003E\n\u003Cp\u003ESession lifecycle operations govern the continuation and validity of authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003EHybrid\u003C/h3\u003E\n\u003Cp\u003EHybrid uses distinct opaque access and refresh credentials.\u003C/p\u003E\n\u003Cp\u003EAccess requests are validated against authoritative server-managed state.\u003C/p\u003E\n\u003Cp\u003ERefresh operations use the refresh credential to continue authentication according to the configured lifecycle and rotation behavior.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EHybrid does not require JWT access tokens.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid\u0022\u003ESemiHybrid\u003C/h3\u003E\n\u003Cp\u003ESemiHybrid issues JWT access tokens while maintaining server-side session state for relevant lifecycle operations.\u003C/p\u003E\n\u003Cp\u003EProtected requests can validate JWT access tokens locally.\u003C/p\u003E\n\u003Cp\u003ERefresh and logout interact with the server-managed authentication lifecycle.\u003C/p\u003E\n\u003Cp\u003EThis means that server-side revocation does not necessarily invalidate an already-issued JWT immediately.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022purejwt\u0022\u003EPureJwt\u003C/h3\u003E\n\u003Cp\u003EPureJwt uses JWT access tokens without requiring the Root \u2192 Chain \u2192 Session hierarchy for ordinary protected request validation.\u003C/p\u003E\n\u003Cp\u003EAn optional refresh mechanism may maintain refresh-specific state.\u003C/p\u003E\n\u003Cp\u003EThe existence of a refresh store does not make JWT access validation session-authoritative.\u003C/p\u003E\n\u003Cp\u003EFor more detail, 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=\u0022access-token-lifetime\u0022\u003EAccess Token Lifetime\u003C/h2\u003E\n\u003Cp\u003EAccess credentials should have lifetimes appropriate to their authentication mode and security requirements.\u003C/p\u003E\n\u003Cp\u003EShort-lived JWT access tokens are particularly important because a token may remain usable until expiration even after server-side authorization state changes.\u003C/p\u003E\n\u003Cp\u003EFor example, an application might choose a JWT access lifetime of several minutes.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Cstrong\u003Ea lifetime such as 5\u201315 minutes is an example of an application security choice, not a universal UltimateAuth default or requirement\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe appropriate value depends on factors such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESensitivity of protected resources.\u003C/li\u003E\n\u003Cli\u003EExpected frequency of authorization changes.\u003C/li\u003E\n\u003Cli\u003EAcceptable revocation delay.\u003C/li\u003E\n\u003Cli\u003EAvailability and security of the refresh mechanism.\u003C/li\u003E\n\u003Cli\u003EClient behavior and network conditions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifetime-versus-revocation\u0022\u003ELifetime Versus Revocation\u003C/h3\u003E\n\u003Cp\u003EExpiration limits how long a credential can be accepted.\u003C/p\u003E\n\u003Cp\u003ERevocation invalidates authentication state before its ordinary expiration.\u003C/p\u003E\n\u003Cp\u003EThese mechanisms are related but not interchangeable.\u003C/p\u003E\n\u003Cp\u003EIn server-authoritative modes, current revocation state can be checked during protected requests.\u003C/p\u003E\n\u003Cp\u003EIn locally validated JWT modes, expiration may remain the primary limit on an already-issued access token unless an additional revocation check is implemented.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-credentials\u0022\u003ERefresh Credentials\u003C/h2\u003E\n\u003Cp\u003ERefresh credentials allow clients to continue authentication without repeating the original authentication ceremony.\u003C/p\u003E\n\u003Cp\u003EThey are intended for the authentication system\u0027s refresh operation, not for general API access.\u003C/p\u003E\n\u003Cp\u003EIn UltimateAuth, refresh behavior is mode-dependent.\u003C/p\u003E\n\u003Cp\u003EPureOpaque does not require a separate refresh credential pair, while Hybrid and SemiHybrid use refresh flows. PureJwt can support an optional refresh mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-security-requirements\u0022\u003ERefresh Security Requirements\u003C/h3\u003E\n\u003Cp\u003EA secure refresh operation must establish that the presented credential is eligible for continued authentication.\u003C/p\u003E\n\u003Cp\u003EDepending on the mode and configured strategy, this may involve checking:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredential existence and validity.\u003C/li\u003E\n\u003Cli\u003EExpiration.\u003C/li\u003E\n\u003Cli\u003ERevocation or consumption state.\u003C/li\u003E\n\u003Cli\u003EAssociated user or session context.\u003C/li\u003E\n\u003Cli\u003EApplicable client and device context.\u003C/li\u003E\n\u003Cli\u003ERotation and reuse conditions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA valid refresh credential should not automatically bypass account-level or session-level security requirements that apply to the selected mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022access-and-refresh-separation\u0022\u003EAccess and Refresh Separation\u003C/h3\u003E\n\u003Cp\u003EAccess and refresh credentials have different responsibilities.\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\n |\n v\nAccess credential\n |\n | Used for protected requests\n v\nAccess expires\n |\n v\nRefresh operation\n |\n | Validate refresh credential\n | Apply continuation rules\n v\nRenewed access credential\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe refresh operation does not extend the expiration timestamp of an already-issued JWT.\u003C/p\u003E\n\u003Cp\u003EIt issues new authentication material when continuation is permitted.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-rotation\u0022\u003ERefresh Rotation\u003C/h2\u003E\n\u003Cp\u003ERefresh rotation replaces refresh authentication material according to the configured refresh strategy.\u003C/p\u003E\n\u003Cp\u003EA rotating refresh mechanism can invalidate the previously used refresh credential and issue a replacement.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003ERefresh credential A\n |\n | Successful refresh\n v\nRefresh credential A consumed\nRefresh credential B issued\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis reduces the period during which an older refresh credential remains reusable.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Cstrong\u003Erotation behavior is not identical across every UltimateAuth mode or refresh strategy\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003ESome session continuation operations may update existing server-side state without replacing a separate refresh credential.\u003C/p\u003E\n\u003Cp\u003EThe effective behavior must follow the selected mode and its configured refresh strategy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-is-not-replay-prevention-by-itself\u0022\u003ERotation Is Not Replay Prevention by Itself\u003C/h3\u003E\n\u003Cp\u003ERotation limits credential reuse only when the server enforces the relevant consumption or replacement state.\u003C/p\u003E\n\u003Cp\u003EA secure rotating refresh mechanism must account for situations such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe same credential being submitted again.\u003C/li\u003E\n\u003Cli\u003EConcurrent refresh requests.\u003C/li\u003E\n\u003Cli\u003EA credential being reused after replacement.\u003C/li\u003E\n\u003Cli\u003EAn interrupted response after a successful rotation.\u003C/li\u003E\n\u003Cli\u003EAn attacker and legitimate client attempting to use the same credential.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact outcome depends on the implemented refresh strategy and concurrency guarantees.\u003C/p\u003E\n\u003Cp\u003ERotation should not be described as automatically eliminating every replay risk.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-token-reuse\u0022\u003ERefresh Token Reuse\u003C/h2\u003E\n\u003Cp\u003ERefresh token reuse occurs when a credential is presented after it has already been consumed, replaced, or otherwise invalidated.\u003C/p\u003E\n\u003Cp\u003EFor a single-use rotating refresh credential, this is a security-relevant event.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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 refreshes with A\n |\n v\nA replaced by B\n |\n v\nAnother request presents A\n |\n v\nReuse / invalid credential condition\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA reuse attempt may indicate credential theft, but it can also arise from concurrent requests or client synchronization failures.\u003C/p\u003E\n\u003Cp\u003EThe refresh implementation must distinguish these situations according to its supported guarantees and policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-response\u0022\u003ESecurity Response\u003C/h3\u003E\n\u003Cp\u003EPossible security responses include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERejecting the invalid refresh attempt.\u003C/li\u003E\n\u003Cli\u003EInvalidating the associated refresh state.\u003C/li\u003E\n\u003Cli\u003ERevoking an affected session.\u003C/li\u003E\n\u003Cli\u003ERevoking a broader session chain where supported and appropriate.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese are distinct actions.\u003C/p\u003E\n\u003Cp\u003EThe presence of a reuse-detection mechanism does not imply that every reuse attempt automatically revokes the entire device-associated chain.\u003C/p\u003E\n\u003Cp\u003ESuch behavior must be explicitly supported by the configured refresh strategy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-credential-storage\u0022\u003ERefresh Credential Storage\u003C/h2\u003E\n\u003Cp\u003ERefresh credentials are generally more sensitive to long-term compromise because they may allow new access credentials to be obtained.\u003C/p\u003E\n\u003Cp\u003ETheir storage therefore deserves stronger protection than ordinary client-visible access metadata.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-side-storage\u0022\u003EServer-Side Storage\u003C/h3\u003E\n\u003Cp\u003EFor persisted opaque refresh credentials, storing a one-way verifier or hash instead of the raw credential can reduce the impact of a storage disclosure.\u003C/p\u003E\n\u003Cp\u003EHowever, the exact persistence model depends on the selected authentication mode and refresh implementation.\u003C/p\u003E\n\u003Cp\u003EIt should not be assumed that every authentication mode uses the same refresh storage structure.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-side-storage\u0022\u003EClient-Side Storage\u003C/h3\u003E\n\u003Cp\u003EThe appropriate client storage mechanism depends on the application environment.\u003C/p\u003E\n\u003Cp\u003EFor browser applications, HttpOnly cookies can prevent JavaScript from directly reading refresh credentials, although cookie-based refresh flows still require appropriate CSRF and origin protections.\u003C/p\u003E\n\u003Cp\u003ENative applications may use platform-provided secure storage where suitable.\u003C/p\u003E\n\u003Cp\u003ERefresh credentials should not be exposed through application logs, diagnostic output, URLs, or unrelated API responses.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022transport-security\u0022\u003ETransport Security\u003C/h3\u003E\n\u003Cp\u003EAuthentication credentials must be protected in transit.\u003C/p\u003E\n\u003Cp\u003EProduction authentication and refresh endpoints should use HTTPS.\u003C/p\u003E\n\u003Cp\u003EApplications should also avoid sending refresh credentials to services that do not need to receive them.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022claims-and-authorization-freshness\u0022\u003EClaims and Authorization Freshness\u003C/h2\u003E\n\u003Cp\u003EJWT claims represent information established when the token was issued.\u003C/p\u003E\n\u003Cp\u003EThey may include standard or application-specific information such 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\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\u003Esub Subject identifier\niss Issuer\naud Audience\nexp Expiration\niat Issued-at time\njti Token identifier\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDepending on the application, a JWT may also include tenant information, roles, permissions, or session references.\u003C/p\u003E\n\u003Cp\u003EThese claims are not automatically updated when the underlying account changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-permission-removal\u0022\u003EExample: Permission Removal\u003C/h3\u003E\n\u003Cp\u003EConsider the following sequence:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EA JWT is issued containing an administrative permission.\u003C/li\u003E\n\u003Cli\u003EAn administrator removes that permission from the user.\u003C/li\u003E\n\u003Cli\u003EThe client continues presenting the previously issued JWT.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EIf the protected resource relies only on the signed claims in that JWT, the removed permission may remain effective until the token expires.\u003C/p\u003E\n\u003Cp\u003EThe signature remains valid because the token has not been modified.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-strategies\u0022\u003EAuthorization Strategies\u003C/h3\u003E\n\u003Cp\u003EApplications can address this limitation through their security architecture, including:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EShorter JWT lifetimes.\u003C/li\u003E\n\u003Cli\u003EReissuing claims during refresh.\u003C/li\u003E\n\u003Cli\u003EConsulting authoritative authorization state for sensitive operations.\u003C/li\u003E\n\u003Cli\u003EUsing server-authoritative authentication modes where immediate state enforcement is required.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe appropriate approach depends on the security requirements of the application.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E and \u003Ca href=\u0022/docs/plugin-domains/policies\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-revocation\u0022\u003EToken Revocation\u003C/h2\u003E\n\u003Cp\u003ERevocation behavior differs fundamentally between opaque credentials and locally validated JWTs.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022opaque-credential-revocation\u0022\u003EOpaque Credential Revocation\u003C/h3\u003E\n\u003Cp\u003EAn opaque credential depends on authoritative validation.\u003C/p\u003E\n\u003Cp\u003EIf the corresponding server-managed authentication state is revoked, subsequent validation can reject the credential.\u003C/p\u003E\n\u003Cp\u003EThe precise scope depends on whether the security operation targets a session, chain, or account-level root.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022jwt-revocation\u0022\u003EJWT Revocation\u003C/h3\u003E\n\u003Cp\u003EA signed JWT cannot be made cryptographically invalid merely by deleting a server-side session record.\u003C/p\u003E\n\u003Cp\u003EIts signature and expiration remain unchanged.\u003C/p\u003E\n\u003Cp\u003ETo reject an otherwise valid JWT before expiration, the resource must enforce an additional condition, such as an authoritative revocation check or another supported invalidation mechanism.\u003C/p\u003E\n\u003Cp\u003EWithout that additional enforcement, a locally validated JWT may remain acceptable until expiration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-revocation\u0022\u003ERefresh Revocation\u003C/h3\u003E\n\u003Cp\u003ERevoking a refresh credential can prevent further successful refresh operations.\u003C/p\u003E\n\u003Cp\u003EHowever, that does not necessarily invalidate access tokens already issued before the revocation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAccess revocation and refresh revocation are separate security concerns.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-security-and-token-security\u0022\u003ESession Security and Token Security\u003C/h2\u003E\n\u003Cp\u003EToken security and session security are closely related, but they operate at different levels.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcern\u003C/th\u003E\n\u003Cth\u003EToken Security\u003C/th\u003E\n\u003Cth\u003ESession Security\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential format\u003C/td\u003E\n\u003Ctd\u003EOpaque or JWT\u003C/td\u003E\n\u003Ctd\u003ERoot, chain, and session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECryptographic validation\u003C/td\u003E\n\u003Ctd\u003EJWT signature and claims\u003C/td\u003E\n\u003Ctd\u003ENot a substitute for token signature validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EExpiration\u003C/td\u003E\n\u003Ctd\u003EToken validity period\u003C/td\u003E\n\u003Ctd\u003ESession and chain lifecycle limits\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERotation\u003C/td\u003E\n\u003Ctd\u003EReplacement of authentication material\u003C/td\u003E\n\u003Ctd\u003EContinuity and associated state transitions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERevocation\u003C/td\u003E\n\u003Ctd\u003EWhether a presented credential remains acceptable\u003C/td\u003E\n\u003Ctd\u003EWhich authentication state has been invalidated\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDevice context\u003C/td\u003E\n\u003Ctd\u003EMay be associated with token use\u003C/td\u003E\n\u003Ctd\u003EManaged through applicable chain and session context\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA token may be valid according to its own format but unacceptable under authoritative session security rules.\u003C/p\u003E\n\u003Cp\u003EConversely, server-side session state may remain active while a particular access token has expired.\u003C/p\u003E\n\u003Cp\u003EBoth levels must be understood to reason correctly about the selected authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-security-misconceptions\u0022\u003ECommon Security Misconceptions\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-valid-signature-means-current-authorization\u0022\u003EA Valid Signature Means Current Authorization\u003C/h3\u003E\n\u003Cp\u003EA valid JWT signature proves that the token has not been modified and was signed by an accepted key.\u003C/p\u003E\n\u003Cp\u003EIt does not prove that the user\u0027s permissions or account status have remained unchanged since issuance.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-device-identifier-prevents-token-theft\u0022\u003EA Device Identifier Prevents Token Theft\u003C/h3\u003E\n\u003Cp\u003EA device identifier may support context consistency checks.\u003C/p\u003E\n\u003Cp\u003EIt does not independently prevent a stolen bearer credential from being replayed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-rotation-makes-token-theft-impossible\u0022\u003ERefresh Rotation Makes Token Theft Impossible\u003C/h3\u003E\n\u003Cp\u003ERotation can reduce reuse opportunities and support detection of invalid reuse.\u003C/p\u003E\n\u003Cp\u003EIt does not prevent a stolen credential from being used before the legitimate client or server detects the compromise.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-invalidates-every-jwt-immediately\u0022\u003ELogout Invalidates Every JWT Immediately\u003C/h3\u003E\n\u003Cp\u003ELogout may revoke server-managed session or refresh state.\u003C/p\u003E\n\u003Cp\u003EPreviously issued JWT access tokens can remain valid until expiration unless protected resources enforce an additional invalidation mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022short-lived-tokens-eliminate-the-need-for-session-security\u0022\u003EShort-Lived Tokens Eliminate the Need for Session Security\u003C/h3\u003E\n\u003Cp\u003EShort lifetimes reduce the exposure window of a compromised token.\u003C/p\u003E\n\u003Cp\u003EThey do not replace account-level security policies, credential recovery protections, or session revocation controls where those capabilities are required.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-design-principles\u0022\u003ESecurity Design Principles\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s token model is best understood through a few principles.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EUse credentials only for their intended purpose.\u003C/strong\u003E Access and refresh credentials must not be interchangeable.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EValidate according to the selected mode.\u003C/strong\u003E Opaque credentials require authoritative validation; JWTs require correct cryptographic and claims validation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ETreat JWT claims as issuance-time information.\u003C/strong\u003E Current authorization state may differ from what an older token contains.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EKeep access credentials appropriately short-lived.\u003C/strong\u003E Token lifetime should reflect the application\u0027s acceptable exposure window.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EEnforce refresh continuation rules.\u003C/strong\u003E Rotation, expiration, consumption, and reuse behavior must be consistent with the configured strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDistinguish token expiration from session revocation.\u003C/strong\u003E They invalidate authentication through different mechanisms.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not promise immediate JWT revocation without enforcement.\u003C/strong\u003E Server-side state changes affect locally validated tokens only when the resource checks the relevant state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth does not impose a single token model on every application.\u003C/p\u003E\n\u003Cp\u003EPureOpaque and Hybrid emphasize server-authoritative authentication state.\u003C/p\u003E\n\u003Cp\u003ESemiHybrid and PureJwt support locally validated JWT access, with different approaches to authentication continuation.\u003C/p\u003E\n\u003Cp\u003EThese choices affect performance, storage requirements, revocation behavior, and authorization freshness.\u003C/p\u003E\n\u003Cp\u003EThe purpose of the token security model is to make those tradeoffs explicit.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA token establishes access only while its applicable validation requirements remain satisfied. The strength and freshness of those requirements depend on the authentication mode.\u003C/strong\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/security/session-security-model\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand authoritative session validation and hierarchical revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Compare credential behavior across modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/session-model\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E \u2014 Explore session creation, continuation, and invalidation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Understand credential management and recovery security.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EAuthentication tokens allow clients to present authentication state to protected applications and services.\u003C/p\u003E\n\u003Cp\u003EHowever, not all tokens provide the same security guarantees.\u003C/p\u003E\n\u003Cp\u003EAn opaque credential requires authoritative server-side validation. A JSON Web Token (JWT) can be verified locally using its signature and claims. A refresh credential enables authentication to continue without repeating the original login process.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports these different mechanisms through its authentication modes.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EToken security depends on the credential\u0027s purpose, how it is validated, how long it remains valid, and which security state is consulted during validation.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis page explains the security characteristics of access and refresh credentials, including issuance, expiration, rotation, replay risks, and revocation boundaries.\u003C/p\u003E\n\u003Cp\u003EFor the overall authentication architecture, 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=\u0022authentication-state-and-credentials\u0022\u003EAuthentication State and Credentials\u003C/h2\u003E\n\u003Cp\u003EA token is authentication material presented by a client.\u003C/p\u003E\n\u003Cp\u003EIt is not the user identity itself.\u003C/p\u003E\n\u003Cp\u003EDepending on the authentication mode, the token may reference server-managed authentication state or contain signed claims that can be validated without retrieving a session.\u003C/p\u003E\n\u003Cp\u003EThese approaches establish different trust boundaries.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-authoritative-authentication\u0022\u003EServer-Authoritative Authentication\u003C/h3\u003E\n\u003Cp\u003EIn server-authoritative modes, a presented opaque credential is validated against server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003EThe server can evaluate the current validity of the corresponding session and applicable security conditions.\u003C/p\u003E\n\u003Cp\u003EThis allows revocation and other authoritative state changes to affect subsequent protected requests.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022locally-validated-authentication\u0022\u003ELocally Validated Authentication\u003C/h3\u003E\n\u003Cp\u003EIn JWT-based modes, a protected resource can validate a token using its signature, issuer, audience, expiration, and other applicable token requirements.\u003C/p\u003E\n\u003Cp\u003EThe resource may not need to consult the issuing server or a session store for every request.\u003C/p\u003E\n\u003Cp\u003EThis reduces the dependency on server-side session lookups but changes how quickly revocation and authorization changes can be enforced.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ENeither model makes the token an identity record. The token is a credential whose validity is determined by the selected authentication mechanism.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-types\u0022\u003EToken Types\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses two principal access credential formats: opaque credentials and JWTs.\u003C/p\u003E\n\u003Cp\u003ERefresh credentials serve a separate purpose.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECredential\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003Cth\u003EValidation model\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EOpaque access credential\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthenticate protected requests\u003C/td\u003E\n\u003Ctd\u003EAuthoritative server-side validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EJWT access token\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthenticate protected requests\u003C/td\u003E\n\u003Ctd\u003ECryptographic and claims validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EOpaque refresh credential\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EContinue authentication and obtain renewed credentials\u003C/td\u003E\n\u003Ctd\u003ERefresh-specific validation and state checks\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe distinction between access and refresh credentials is important.\u003C/p\u003E\n\u003Cp\u003EAccess credentials are presented to protected resources.\u003C/p\u003E\n\u003Cp\u003ERefresh credentials are presented to the authentication system for a continuation operation. They should not be accepted as ordinary access credentials.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022opaque-access-credentials\u0022\u003EOpaque Access Credentials\u003C/h2\u003E\n\u003Cp\u003EAn opaque credential is a value whose contents do not expose the authenticated identity or authorization claims to the client.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003EAccess credential:\nq8Vn2...random-opaque-value...M7x\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe client presents the credential, but cannot determine its authorization meaning by decoding it.\u003C/p\u003E\n\u003Cp\u003EThe server resolves and validates the associated authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-properties\u0022\u003ESecurity Properties\u003C/h3\u003E\n\u003Cp\u003EOpaque access credentials provide several useful characteristics:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ETheir meaning is determined by authoritative server-side state.\u003C/li\u003E\n\u003Cli\u003EThey do not need to expose identity claims in a client-readable payload.\u003C/li\u003E\n\u003Cli\u003ETheir validity can be affected by session expiration or revocation.\u003C/li\u003E\n\u003Cli\u003EThey can participate in account-level and chain-level security enforcement.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EHowever, opaque credentials are still bearer credentials unless an additional proof mechanism is enforced.\u003C/p\u003E\n\u003Cp\u003EAn attacker who obtains a usable opaque credential may be able to present it.\u003C/p\u003E\n\u003Cp\u003EOpacity alone does not prevent credential theft or replay.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-side-validation\u0022\u003EServer-Side Validation\u003C/h3\u003E\n\u003Cp\u003EIn a session-authoritative mode, validation can enforce the applicable session hierarchy requirements, including expiration, revocation, ownership, and security state consistency.\u003C/p\u003E\n\u003Cp\u003ESee \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=\u0022jwt-access-tokens\u0022\u003EJWT Access Tokens\u003C/h2\u003E\n\u003Cp\u003EA JWT is a structured token containing signed claims.\u003C/p\u003E\n\u003Cp\u003EA typical JWT contains three encoded sections:\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\u003Eheader.payload.signature\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIts payload may describe the authenticated subject, tenant, authorization information, token lifetime, and other relevant context.\u003C/p\u003E\n\u003Cp\u003EA JWT is not encrypted merely because it is encoded and signed.\u003C/p\u003E\n\u003Cp\u003EApplications should not place secrets or unnecessarily sensitive information in a JWT payload.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022cryptographic-validation\u0022\u003ECryptographic Validation\u003C/h3\u003E\n\u003Cp\u003EA protected resource must validate the JWT according to the configured token requirements.\u003C/p\u003E\n\u003Cp\u003ERelevant checks include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESignature validity.\u003C/li\u003E\n\u003Cli\u003EAccepted signing algorithm and key.\u003C/li\u003E\n\u003Cli\u003EExpected issuer.\u003C/li\u003E\n\u003Cli\u003EIntended audience.\u003C/li\u003E\n\u003Cli\u003EExpiration and other applicable time constraints.\u003C/li\u003E\n\u003Cli\u003ERequired token claims and application-specific restrictions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA successfully decoded JWT is not necessarily a valid JWT.\u003C/p\u003E\n\u003Cp\u003ELikewise, a correctly signed JWT is not automatically acceptable to every resource.\u003C/p\u003E\n\u003Cp\u003EThe resource must verify that the token was issued for its intended security context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022local-validation\u0022\u003ELocal Validation\u003C/h3\u003E\n\u003Cp\u003EJWTs can be validated without loading current server-side session state.\u003C/p\u003E\n\u003Cp\u003EThis is useful for distributed APIs and services that need to authenticate requests efficiently.\u003C/p\u003E\n\u003Cp\u003EHowever, local validation introduces an important limitation:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA token can remain cryptographically valid after the underlying account or authorization state has changed.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EWhether such changes immediately affect access depends on additional validation and revocation mechanisms.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-behavior\u0022\u003EAuthentication Mode Behavior\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth selects credential behavior according to the configured authentication mode.\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\u003EAccess mechanism\u003C/th\u003E\n\u003Cth\u003EContinuation mechanism\u003C/th\u003E\n\u003Cth\u003EProtected request authority\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureOpaque\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOpaque session credential\u003C/td\u003E\n\u003Ctd\u003ESession lifecycle\u003C/td\u003E\n\u003Ctd\u003EServer-managed session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOpaque access credential\u003C/td\u003E\n\u003Ctd\u003EOpaque refresh credential\u003C/td\u003E\n\u003Ctd\u003EServer-managed session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESemiHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT access token\u003C/td\u003E\n\u003Ctd\u003EServer-managed refresh flow\u003C/td\u003E\n\u003Ctd\u003ELocally validated JWT\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureJwt\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EJWT access token\u003C/td\u003E\n\u003Ctd\u003EOptional refresh mechanism\u003C/td\u003E\n\u003Ctd\u003ELocally validated JWT\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=\u0022pureopaque\u0022\u003EPureOpaque\u003C/h3\u003E\n\u003Cp\u003EPureOpaque uses an opaque session credential.\u003C/p\u003E\n\u003Cp\u003EIt does not require a separate access-token and refresh-token pair.\u003C/p\u003E\n\u003Cp\u003EThe credential is validated through the authoritative session mechanism.\u003C/p\u003E\n\u003Cp\u003ESession lifecycle operations govern the continuation and validity of authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003EHybrid\u003C/h3\u003E\n\u003Cp\u003EHybrid uses distinct opaque access and refresh credentials.\u003C/p\u003E\n\u003Cp\u003EAccess requests are validated against authoritative server-managed state.\u003C/p\u003E\n\u003Cp\u003ERefresh operations use the refresh credential to continue authentication according to the configured lifecycle and rotation behavior.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EHybrid does not require JWT access tokens.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid\u0022\u003ESemiHybrid\u003C/h3\u003E\n\u003Cp\u003ESemiHybrid issues JWT access tokens while maintaining server-side session state for relevant lifecycle operations.\u003C/p\u003E\n\u003Cp\u003EProtected requests can validate JWT access tokens locally.\u003C/p\u003E\n\u003Cp\u003ERefresh and logout interact with the server-managed authentication lifecycle.\u003C/p\u003E\n\u003Cp\u003EThis means that server-side revocation does not necessarily invalidate an already-issued JWT immediately.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022purejwt\u0022\u003EPureJwt\u003C/h3\u003E\n\u003Cp\u003EPureJwt uses JWT access tokens without requiring the Root \u2192 Chain \u2192 Session hierarchy for ordinary protected request validation.\u003C/p\u003E\n\u003Cp\u003EAn optional refresh mechanism may maintain refresh-specific state.\u003C/p\u003E\n\u003Cp\u003EThe existence of a refresh store does not make JWT access validation session-authoritative.\u003C/p\u003E\n\u003Cp\u003EFor more detail, 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=\u0022access-token-lifetime\u0022\u003EAccess Token Lifetime\u003C/h2\u003E\n\u003Cp\u003EAccess credentials should have lifetimes appropriate to their authentication mode and security requirements.\u003C/p\u003E\n\u003Cp\u003EShort-lived JWT access tokens are particularly important because a token may remain usable until expiration even after server-side authorization state changes.\u003C/p\u003E\n\u003Cp\u003EFor example, an application might choose a JWT access lifetime of several minutes.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Cstrong\u003Ea lifetime such as 5\u201315 minutes is an example of an application security choice, not a universal UltimateAuth default or requirement\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe appropriate value depends on factors such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESensitivity of protected resources.\u003C/li\u003E\n\u003Cli\u003EExpected frequency of authorization changes.\u003C/li\u003E\n\u003Cli\u003EAcceptable revocation delay.\u003C/li\u003E\n\u003Cli\u003EAvailability and security of the refresh mechanism.\u003C/li\u003E\n\u003Cli\u003EClient behavior and network conditions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022lifetime-versus-revocation\u0022\u003ELifetime Versus Revocation\u003C/h3\u003E\n\u003Cp\u003EExpiration limits how long a credential can be accepted.\u003C/p\u003E\n\u003Cp\u003ERevocation invalidates authentication state before its ordinary expiration.\u003C/p\u003E\n\u003Cp\u003EThese mechanisms are related but not interchangeable.\u003C/p\u003E\n\u003Cp\u003EIn server-authoritative modes, current revocation state can be checked during protected requests.\u003C/p\u003E\n\u003Cp\u003EIn locally validated JWT modes, expiration may remain the primary limit on an already-issued access token unless an additional revocation check is implemented.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-credentials\u0022\u003ERefresh Credentials\u003C/h2\u003E\n\u003Cp\u003ERefresh credentials allow clients to continue authentication without repeating the original authentication ceremony.\u003C/p\u003E\n\u003Cp\u003EThey are intended for the authentication system\u0027s refresh operation, not for general API access.\u003C/p\u003E\n\u003Cp\u003EIn UltimateAuth, refresh behavior is mode-dependent.\u003C/p\u003E\n\u003Cp\u003EPureOpaque does not require a separate refresh credential pair, while Hybrid and SemiHybrid use refresh flows. PureJwt can support an optional refresh mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-security-requirements\u0022\u003ERefresh Security Requirements\u003C/h3\u003E\n\u003Cp\u003EA secure refresh operation must establish that the presented credential is eligible for continued authentication.\u003C/p\u003E\n\u003Cp\u003EDepending on the mode and configured strategy, this may involve checking:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredential existence and validity.\u003C/li\u003E\n\u003Cli\u003EExpiration.\u003C/li\u003E\n\u003Cli\u003ERevocation or consumption state.\u003C/li\u003E\n\u003Cli\u003EAssociated user or session context.\u003C/li\u003E\n\u003Cli\u003EApplicable client and device context.\u003C/li\u003E\n\u003Cli\u003ERotation and reuse conditions.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA valid refresh credential should not automatically bypass account-level or session-level security requirements that apply to the selected mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022access-and-refresh-separation\u0022\u003EAccess and Refresh Separation\u003C/h3\u003E\n\u003Cp\u003EAccess and refresh credentials have different responsibilities.\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\n |\n v\nAccess credential\n |\n | Used for protected requests\n v\nAccess expires\n |\n v\nRefresh operation\n |\n | Validate refresh credential\n | Apply continuation rules\n v\nRenewed access credential\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe refresh operation does not extend the expiration timestamp of an already-issued JWT.\u003C/p\u003E\n\u003Cp\u003EIt issues new authentication material when continuation is permitted.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-rotation\u0022\u003ERefresh Rotation\u003C/h2\u003E\n\u003Cp\u003ERefresh rotation replaces refresh authentication material according to the configured refresh strategy.\u003C/p\u003E\n\u003Cp\u003EA rotating refresh mechanism can invalidate the previously used refresh credential and issue a replacement.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003ERefresh credential A\n |\n | Successful refresh\n v\nRefresh credential A consumed\nRefresh credential B issued\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis reduces the period during which an older refresh credential remains reusable.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Cstrong\u003Erotation behavior is not identical across every UltimateAuth mode or refresh strategy\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003ESome session continuation operations may update existing server-side state without replacing a separate refresh credential.\u003C/p\u003E\n\u003Cp\u003EThe effective behavior must follow the selected mode and its configured refresh strategy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-is-not-replay-prevention-by-itself\u0022\u003ERotation Is Not Replay Prevention by Itself\u003C/h3\u003E\n\u003Cp\u003ERotation limits credential reuse only when the server enforces the relevant consumption or replacement state.\u003C/p\u003E\n\u003Cp\u003EA secure rotating refresh mechanism must account for situations such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe same credential being submitted again.\u003C/li\u003E\n\u003Cli\u003EConcurrent refresh requests.\u003C/li\u003E\n\u003Cli\u003EA credential being reused after replacement.\u003C/li\u003E\n\u003Cli\u003EAn interrupted response after a successful rotation.\u003C/li\u003E\n\u003Cli\u003EAn attacker and legitimate client attempting to use the same credential.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact outcome depends on the implemented refresh strategy and concurrency guarantees.\u003C/p\u003E\n\u003Cp\u003ERotation should not be described as automatically eliminating every replay risk.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-token-reuse\u0022\u003ERefresh Token Reuse\u003C/h2\u003E\n\u003Cp\u003ERefresh token reuse occurs when a credential is presented after it has already been consumed, replaced, or otherwise invalidated.\u003C/p\u003E\n\u003Cp\u003EFor a single-use rotating refresh credential, this is a security-relevant event.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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 refreshes with A\n |\n v\nA replaced by B\n |\n v\nAnother request presents A\n |\n v\nReuse / invalid credential condition\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EA reuse attempt may indicate credential theft, but it can also arise from concurrent requests or client synchronization failures.\u003C/p\u003E\n\u003Cp\u003EThe refresh implementation must distinguish these situations according to its supported guarantees and policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-response\u0022\u003ESecurity Response\u003C/h3\u003E\n\u003Cp\u003EPossible security responses include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERejecting the invalid refresh attempt.\u003C/li\u003E\n\u003Cli\u003EInvalidating the associated refresh state.\u003C/li\u003E\n\u003Cli\u003ERevoking an affected session.\u003C/li\u003E\n\u003Cli\u003ERevoking a broader session chain where supported and appropriate.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese are distinct actions.\u003C/p\u003E\n\u003Cp\u003EThe presence of a reuse-detection mechanism does not imply that every reuse attempt automatically revokes the entire device-associated chain.\u003C/p\u003E\n\u003Cp\u003ESuch behavior must be explicitly supported by the configured refresh strategy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-credential-storage\u0022\u003ERefresh Credential Storage\u003C/h2\u003E\n\u003Cp\u003ERefresh credentials are generally more sensitive to long-term compromise because they may allow new access credentials to be obtained.\u003C/p\u003E\n\u003Cp\u003ETheir storage therefore deserves stronger protection than ordinary client-visible access metadata.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-side-storage\u0022\u003EServer-Side Storage\u003C/h3\u003E\n\u003Cp\u003EFor persisted opaque refresh credentials, storing a one-way verifier or hash instead of the raw credential can reduce the impact of a storage disclosure.\u003C/p\u003E\n\u003Cp\u003EHowever, the exact persistence model depends on the selected authentication mode and refresh implementation.\u003C/p\u003E\n\u003Cp\u003EIt should not be assumed that every authentication mode uses the same refresh storage structure.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-side-storage\u0022\u003EClient-Side Storage\u003C/h3\u003E\n\u003Cp\u003EThe appropriate client storage mechanism depends on the application environment.\u003C/p\u003E\n\u003Cp\u003EFor browser applications, HttpOnly cookies can prevent JavaScript from directly reading refresh credentials, although cookie-based refresh flows still require appropriate CSRF and origin protections.\u003C/p\u003E\n\u003Cp\u003ENative applications may use platform-provided secure storage where suitable.\u003C/p\u003E\n\u003Cp\u003ERefresh credentials should not be exposed through application logs, diagnostic output, URLs, or unrelated API responses.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022transport-security\u0022\u003ETransport Security\u003C/h3\u003E\n\u003Cp\u003EAuthentication credentials must be protected in transit.\u003C/p\u003E\n\u003Cp\u003EProduction authentication and refresh endpoints should use HTTPS.\u003C/p\u003E\n\u003Cp\u003EApplications should also avoid sending refresh credentials to services that do not need to receive them.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022claims-and-authorization-freshness\u0022\u003EClaims and Authorization Freshness\u003C/h2\u003E\n\u003Cp\u003EJWT claims represent information established when the token was issued.\u003C/p\u003E\n\u003Cp\u003EThey may include standard or application-specific information such 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\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\u003Esub Subject identifier\niss Issuer\naud Audience\nexp Expiration\niat Issued-at time\njti Token identifier\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EDepending on the application, a JWT may also include tenant information, roles, permissions, or session references.\u003C/p\u003E\n\u003Cp\u003EThese claims are not automatically updated when the underlying account changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022example-permission-removal\u0022\u003EExample: Permission Removal\u003C/h3\u003E\n\u003Cp\u003EConsider the following sequence:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EA JWT is issued containing an administrative permission.\u003C/li\u003E\n\u003Cli\u003EAn administrator removes that permission from the user.\u003C/li\u003E\n\u003Cli\u003EThe client continues presenting the previously issued JWT.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EIf the protected resource relies only on the signed claims in that JWT, the removed permission may remain effective until the token expires.\u003C/p\u003E\n\u003Cp\u003EThe signature remains valid because the token has not been modified.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-strategies\u0022\u003EAuthorization Strategies\u003C/h3\u003E\n\u003Cp\u003EApplications can address this limitation through their security architecture, including:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EShorter JWT lifetimes.\u003C/li\u003E\n\u003Cli\u003EReissuing claims during refresh.\u003C/li\u003E\n\u003Cli\u003EConsulting authoritative authorization state for sensitive operations.\u003C/li\u003E\n\u003Cli\u003EUsing server-authoritative authentication modes where immediate state enforcement is required.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe appropriate approach depends on the security requirements of the application.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E and \u003Ca href=\u0022/docs/plugin-domains/policies\u0022\u003EPolicies \u0026amp; Access Control\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022token-revocation\u0022\u003EToken Revocation\u003C/h2\u003E\n\u003Cp\u003ERevocation behavior differs fundamentally between opaque credentials and locally validated JWTs.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022opaque-credential-revocation\u0022\u003EOpaque Credential Revocation\u003C/h3\u003E\n\u003Cp\u003EAn opaque credential depends on authoritative validation.\u003C/p\u003E\n\u003Cp\u003EIf the corresponding server-managed authentication state is revoked, subsequent validation can reject the credential.\u003C/p\u003E\n\u003Cp\u003EThe precise scope depends on whether the security operation targets a session, chain, or account-level root.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022jwt-revocation\u0022\u003EJWT Revocation\u003C/h3\u003E\n\u003Cp\u003EA signed JWT cannot be made cryptographically invalid merely by deleting a server-side session record.\u003C/p\u003E\n\u003Cp\u003EIts signature and expiration remain unchanged.\u003C/p\u003E\n\u003Cp\u003ETo reject an otherwise valid JWT before expiration, the resource must enforce an additional condition, such as an authoritative revocation check or another supported invalidation mechanism.\u003C/p\u003E\n\u003Cp\u003EWithout that additional enforcement, a locally validated JWT may remain acceptable until expiration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-revocation\u0022\u003ERefresh Revocation\u003C/h3\u003E\n\u003Cp\u003ERevoking a refresh credential can prevent further successful refresh operations.\u003C/p\u003E\n\u003Cp\u003EHowever, that does not necessarily invalidate access tokens already issued before the revocation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAccess revocation and refresh revocation are separate security concerns.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-security-and-token-security\u0022\u003ESession Security and Token Security\u003C/h2\u003E\n\u003Cp\u003EToken security and session security are closely related, but they operate at different levels.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EConcern\u003C/th\u003E\n\u003Cth\u003EToken Security\u003C/th\u003E\n\u003Cth\u003ESession Security\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential format\u003C/td\u003E\n\u003Ctd\u003EOpaque or JWT\u003C/td\u003E\n\u003Ctd\u003ERoot, chain, and session state\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECryptographic validation\u003C/td\u003E\n\u003Ctd\u003EJWT signature and claims\u003C/td\u003E\n\u003Ctd\u003ENot a substitute for token signature validation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EExpiration\u003C/td\u003E\n\u003Ctd\u003EToken validity period\u003C/td\u003E\n\u003Ctd\u003ESession and chain lifecycle limits\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERotation\u003C/td\u003E\n\u003Ctd\u003EReplacement of authentication material\u003C/td\u003E\n\u003Ctd\u003EContinuity and associated state transitions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERevocation\u003C/td\u003E\n\u003Ctd\u003EWhether a presented credential remains acceptable\u003C/td\u003E\n\u003Ctd\u003EWhich authentication state has been invalidated\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDevice context\u003C/td\u003E\n\u003Ctd\u003EMay be associated with token use\u003C/td\u003E\n\u003Ctd\u003EManaged through applicable chain and session context\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA token may be valid according to its own format but unacceptable under authoritative session security rules.\u003C/p\u003E\n\u003Cp\u003EConversely, server-side session state may remain active while a particular access token has expired.\u003C/p\u003E\n\u003Cp\u003EBoth levels must be understood to reason correctly about the selected authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-security-misconceptions\u0022\u003ECommon Security Misconceptions\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-valid-signature-means-current-authorization\u0022\u003EA Valid Signature Means Current Authorization\u003C/h3\u003E\n\u003Cp\u003EA valid JWT signature proves that the token has not been modified and was signed by an accepted key.\u003C/p\u003E\n\u003Cp\u003EIt does not prove that the user\u0027s permissions or account status have remained unchanged since issuance.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-device-identifier-prevents-token-theft\u0022\u003EA Device Identifier Prevents Token Theft\u003C/h3\u003E\n\u003Cp\u003EA device identifier may support context consistency checks.\u003C/p\u003E\n\u003Cp\u003EIt does not independently prevent a stolen bearer credential from being replayed.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-rotation-makes-token-theft-impossible\u0022\u003ERefresh Rotation Makes Token Theft Impossible\u003C/h3\u003E\n\u003Cp\u003ERotation can reduce reuse opportunities and support detection of invalid reuse.\u003C/p\u003E\n\u003Cp\u003EIt does not prevent a stolen credential from being used before the legitimate client or server detects the compromise.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-invalidates-every-jwt-immediately\u0022\u003ELogout Invalidates Every JWT Immediately\u003C/h3\u003E\n\u003Cp\u003ELogout may revoke server-managed session or refresh state.\u003C/p\u003E\n\u003Cp\u003EPreviously issued JWT access tokens can remain valid until expiration unless protected resources enforce an additional invalidation mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022short-lived-tokens-eliminate-the-need-for-session-security\u0022\u003EShort-Lived Tokens Eliminate the Need for Session Security\u003C/h3\u003E\n\u003Cp\u003EShort lifetimes reduce the exposure window of a compromised token.\u003C/p\u003E\n\u003Cp\u003EThey do not replace account-level security policies, credential recovery protections, or session revocation controls where those capabilities are required.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-design-principles\u0022\u003ESecurity Design Principles\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s token model is best understood through a few principles.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EUse credentials only for their intended purpose.\u003C/strong\u003E Access and refresh credentials must not be interchangeable.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EValidate according to the selected mode.\u003C/strong\u003E Opaque credentials require authoritative validation; JWTs require correct cryptographic and claims validation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ETreat JWT claims as issuance-time information.\u003C/strong\u003E Current authorization state may differ from what an older token contains.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EKeep access credentials appropriately short-lived.\u003C/strong\u003E Token lifetime should reflect the application\u0027s acceptable exposure window.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EEnforce refresh continuation rules.\u003C/strong\u003E Rotation, expiration, consumption, and reuse behavior must be consistent with the configured strategy.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDistinguish token expiration from session revocation.\u003C/strong\u003E They invalidate authentication through different mechanisms.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not promise immediate JWT revocation without enforcement.\u003C/strong\u003E Server-side state changes affect locally validated tokens only when the resource checks the relevant state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth does not impose a single token model on every application.\u003C/p\u003E\n\u003Cp\u003EPureOpaque and Hybrid emphasize server-authoritative authentication state.\u003C/p\u003E\n\u003Cp\u003ESemiHybrid and PureJwt support locally validated JWT access, with different approaches to authentication continuation.\u003C/p\u003E\n\u003Cp\u003EThese choices affect performance, storage requirements, revocation behavior, and authorization freshness.\u003C/p\u003E\n\u003Cp\u003EThe purpose of the token security model is to make those tradeoffs explicit.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA token establishes access only while its applicable validation requirements remain satisfied. The strength and freshness of those requirements depend on the authentication mode.\u003C/strong\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/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand authoritative session validation and hierarchical revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Compare credential behavior across modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/session-lifecycle\u0022\u003ESession Model \u0026amp; Lifecycle\u003C/a\u003E \u2014 Explore session creation, continuation, and invalidation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E \u2014 Understand credential management and recovery security.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "authentication-state-and-credentials", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/device-management.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/device-management.json index 2fe0d598..0f8a3ca6 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/device-management.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/device-management.json @@ -1,7 +1,7 @@ { "Slug": "sessions-devices/device-management", "Title": "Device Management", - "Html": "\n\u003Cp\u003EAuthentication does not happen in isolation from the device or client environment in which it takes place.\u003C/p\u003E\n\u003Cp\u003EA user may authenticate from a desktop browser, a mobile application, or several different environments simultaneously. Each environment can have its own authentication state, activity history, and security requirements.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports \u003Cstrong\u003Edevice-aware session management\u003C/strong\u003E by associating authentication sessions with device context and organizing related sessions through \u003Ccode\u003ESessionChain\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis provides a foundation for device-level visibility, session continuity, and targeted security operations without treating device identity as a substitute for user authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-device-aware-authentication\u0022\u003EUnderstanding Device-Aware Authentication\u003C/h2\u003E\n\u003Cp\u003ETraditional token-based authentication can be implemented without explicitly modeling the client environment.\u003C/p\u003E\n\u003Cp\u003EA token is issued, validated, and eventually expires or is revoked.\u003C/p\u003E\n\u003Cp\u003EThat approach is sufficient for some applications, but it becomes limiting when an application needs to distinguish between different client environments belonging to the same user.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA user signs in from both a desktop browser and a mobile application.\u003C/li\u003E\n\u003Cli\u003EAn administrator needs to revoke sessions associated with one client environment.\u003C/li\u003E\n\u003Cli\u003EA session is refreshed repeatedly while remaining associated with the same device context.\u003C/li\u003E\n\u003Cli\u003EAn application needs to apply session limits or track activity across client environments.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth separates these concerns through its session and chain model.\u003C/p\u003E\n\u003Cp\u003EThe objective is not to prove the physical identity of a device. It is to provide a structured way to associate authentication activity with a recognizable client context.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-context-and-sessionchain\u0022\u003EDevice Context and SessionChain\u003C/h2\u003E\n\u003Cp\u003EThe central concept in UltimateAuth\u0027s device-aware model is \u003Ccode\u003ESessionChain\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA session represents authentication state.\u003C/p\u003E\n\u003Cp\u003EA \u003Ccode\u003ESessionChain\u003C/code\u003E groups related session activity under a persistent chain identity, which can be associated with a device or client environment.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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 Identity\n |\n \u002B-- SessionChain (Desktop Browser)\n | |\n | \u002B-- Session\n | \u002B-- Session / Rotation History\n |\n \u002B-- SessionChain (Mobile Application)\n |\n \u002B-- Session\n \u002B-- Session / Rotation History\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis diagram illustrates the relationship between identity, chain, and session. It does not imply that every authentication mode retains multiple simultaneous session records or stores a complete rotation history.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-chain-is-not-a-physical-device\u0022\u003EA Chain Is Not a Physical Device\u003C/h3\u003E\n\u003Cp\u003EA \u003Ccode\u003ESessionChain\u003C/code\u003E should not be understood as a physical hardware identifier.\u003C/p\u003E\n\u003Cp\u003EIt represents a logical continuity boundary for related authentication sessions.\u003C/p\u003E\n\u003Cp\u003EA single physical device may use different browsers, application installations, or isolated client storage environments.\u003C/p\u003E\n\u003Cp\u003EConversely, device identifiers supplied by clients are not necessarily reliable proof that requests originate from the same physical device.\u003C/p\u003E\n\u003Cp\u003EThe practical relationship is:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA device context can be associated with a SessionChain, but the chain itself is an authentication-session concept.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis distinction is important when designing security rules around device recognition and session revocation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-information\u0022\u003EDevice Information\u003C/h2\u003E\n\u003Cp\u003EDevice-aware session management can use information describing the client environment.\u003C/p\u003E\n\u003Cp\u003ERelevant information may include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EInformation\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\u003EDeviceId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifies a client-reported or application-managed device context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPlatform\u003C/td\u003E\n\u003Ctd\u003EDistinguishes environments such as web and mobile\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOperating system\u003C/td\u003E\n\u003Ctd\u003EProvides client environment metadata\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBrowser\u003C/td\u003E\n\u003Ctd\u003EDescribes the browser environment when applicable\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIP address\u003C/td\u003E\n\u003Ctd\u003EProvides network context for security and activity analysis\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe availability and reliability of this information depend on the client platform and the application\u0027s integration.\u003C/p\u003E\n\u003Cp\u003ENot every client can provide the same metadata.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deviceid-and-trust\u0022\u003EDeviceId and Trust\u003C/h3\u003E\n\u003Cp\u003EA \u003Ccode\u003EDeviceId\u003C/code\u003E is useful for identifying a client environment across requests or authentication operations.\u003C/p\u003E\n\u003Cp\u003EHowever, an identifier alone is not proof of device ownership.\u003C/p\u003E\n\u003Cp\u003EA value stored in browser storage, for example, may be copied, cleared, or replaced. An identifier generated by a mobile application may change after application data is removed.\u003C/p\u003E\n\u003Cp\u003EFor that reason, device recognition must not be confused with authentication.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s authentication and session validation rules remain responsible for determining whether a request is authorized to use the associated authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-fingerprinting\u0022\u003EDevice Fingerprinting\u003C/h3\u003E\n\u003Cp\u003EPlatform, operating system, browser, and network information may help describe a client environment.\u003C/p\u003E\n\u003Cp\u003EThese attributes are not inherently unique or stable.\u003C/p\u003E\n\u003Cp\u003EMultiple devices can share similar attributes, and a single device can present different attributes over time.\u003C/p\u003E\n\u003Cp\u003EThey should therefore be treated as contextual signals rather than a guaranteed device fingerprint.\u003C/p\u003E\n\u003Cp\u003EApplications requiring stronger device assurance need an appropriate proof or binding mechanism in addition to ordinary metadata.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-continuity\u0022\u003EDevice Continuity\u003C/h2\u003E\n\u003Cp\u003EDevice continuity describes how authentication activity remains associated with an existing client context.\u003C/p\u003E\n\u003Cp\u003EA session chain provides the logical structure for maintaining that continuity.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022first-authentication\u0022\u003EFirst Authentication\u003C/h3\u003E\n\u003Cp\u003EWhen an authentication flow establishes a new session, the session infrastructure determines the appropriate chain association.\u003C/p\u003E\n\u003Cp\u003EIf a new chain is required, the session is associated with that chain.\u003C/p\u003E\n\u003Cp\u003EThe resulting chain can represent the beginning of a device-associated authentication history.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022subsequent-authentication\u0022\u003ESubsequent Authentication\u003C/h3\u003E\n\u003Cp\u003EA later authentication from the same client environment may be associated with an existing chain when the configured matching and security conditions permit it.\u003C/p\u003E\n\u003Cp\u003EHowever, recognizing a device identifier does not automatically require the system to reuse a previous chain.\u003C/p\u003E\n\u003Cp\u003EChain association must respect the applicable lifecycle, validity, and security rules.\u003C/p\u003E\n\u003Cp\u003EFor example, a revoked chain should not become trusted again merely because a client presents the same device identifier.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-continuity-is-not-session-continuity\u0022\u003EDevice Continuity Is Not Session Continuity\u003C/h3\u003E\n\u003Cp\u003EA client environment can remain recognizable even when its previous authentication session has expired.\u003C/p\u003E\n\u003Cp\u003ELikewise, a new authentication session can be established without inheriting the validity of an earlier session.\u003C/p\u003E\n\u003Cp\u003EThe chain model allows related activity to be organized while keeping session validity subject to authentication and security rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022activity-tracking\u0022\u003EActivity Tracking\u003C/h2\u003E\n\u003Cp\u003EDevice-associated session management can maintain information about authentication activity.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhen a chain was created.\u003C/li\u003E\n\u003Cli\u003EWhen related authentication activity was last observed.\u003C/li\u003E\n\u003Cli\u003EHow often relevant session activity was recorded.\u003C/li\u003E\n\u003Cli\u003EHow many rotation operations were associated with the chain.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFields such as \u003Ccode\u003ELastSeenAt\u003C/code\u003E, \u003Ccode\u003ETouchCount\u003C/code\u003E, and \u003Ccode\u003ERotationCount\u003C/code\u003E help describe these aspects of the session lifecycle.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022touch-operations\u0022\u003ETouch Operations\u003C/h3\u003E\n\u003Cp\u003EA touch operation records qualifying session activity according to the configured session behavior.\u003C/p\u003E\n\u003Cp\u003EDepending on the authentication mode and session strategy, this may update activity metadata such as the last-seen timestamp or touch counter.\u003C/p\u003E\n\u003Cp\u003ETouch behavior should not be interpreted as a guarantee that every authenticated HTTP request updates persistent device activity.\u003C/p\u003E\n\u003Cp\u003EDifferent authentication modes have different requirements for consulting and updating server-side session state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-tracking\u0022\u003ERotation Tracking\u003C/h3\u003E\n\u003Cp\u003EToken or session rotation may replace authentication material while preserving the logical relationship with an existing chain.\u003C/p\u003E\n\u003Cp\u003EWhere rotation tracking is supported, the chain can maintain information about those transitions.\u003C/p\u003E\n\u003Cp\u003EThis makes it possible to distinguish the continuity of a client environment from the lifecycle of an individual credential or session.\u003C/p\u003E\n\u003Cp\u003ERotation counts are operational signals. They do not independently establish that a device is legitimate or that suspicious activity has occurred.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-and-device-revocation\u0022\u003ELogout and Device Revocation\u003C/h2\u003E\n\u003Cp\u003ELogout and chain revocation address different security scopes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003ELogout\u003C/h3\u003E\n\u003Cp\u003ELogout terminates the authentication state targeted by the logout operation.\u003C/p\u003E\n\u003Cp\u003EIts precise effect depends on the authentication mode, the credentials involved, and the configured session behavior.\u003C/p\u003E\n\u003Cp\u003EA logout operation does not necessarily mean that every session associated with the same device context has been revoked.\u003C/p\u003E\n\u003Cp\u003EIt also does not necessarily require deleting the associated chain record.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-revocation\u0022\u003EChain Revocation\u003C/h3\u003E\n\u003Cp\u003EChain revocation targets the logical group of sessions associated with a chain.\u003C/p\u003E\n\u003Cp\u003EIt provides a broader security operation than terminating a single session.\u003C/p\u003E\n\u003Cp\u003EThis is useful when an application needs to invalidate the authentication continuity associated with a particular client environment.\u003C/p\u003E\n\u003Cp\u003EFor example, if a user believes a mobile application session has been compromised, the application may revoke the corresponding chain rather than revoking unrelated sessions associated with other environments.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-does-not-establish-device-trust\u0022\u003ERevocation Does Not Establish Device Trust\u003C/h3\u003E\n\u003Cp\u003EA retained chain record is not equivalent to a trusted device.\u003C/p\u003E\n\u003Cp\u003ELikewise, removing or revoking a chain does not physically block a device from attempting to authenticate again.\u003C/p\u003E\n\u003Cp\u003EWhether a new authentication attempt is permitted depends on the authentication flow and the applicable security policies.\u003C/p\u003E\n\u003Cp\u003EThe chain model provides a boundary for managing authentication state; it is not a permanent device allowlist or hardware security mechanism.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-binding-and-security-boundaries\u0022\u003EDevice Binding and Security Boundaries\u003C/h2\u003E\n\u003Cp\u003EDevice-aware authentication can associate session state with device context.\u003C/p\u003E\n\u003Cp\u003EHowever, the strength of that association depends on what is actually validated.\u003C/p\u003E\n\u003Cp\u003EThere is an important distinction between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice metadata:\u003C/strong\u003E Information describing a client environment.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice identification:\u003C/strong\u003E An identifier used to recognize a client context.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice binding:\u003C/strong\u003E A security requirement connecting authentication material to a validated device-related condition.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice authentication:\u003C/strong\u003E Proof that a client possesses an appropriate device-bound secret or cryptographic key.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese concepts provide different levels of assurance.\u003C/p\u003E\n\u003Cp\u003EAn application must not assume that supplying a \u003Ccode\u003EDeviceId\u003C/code\u003E alone prevents stolen credentials from being reused elsewhere.\u003C/p\u003E\n\u003Cp\u003ESecurity guarantees depend on the binding and validation mechanisms enforced by the configured authentication infrastructure.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-management-across-authentication-modes\u0022\u003EDevice Management Across Authentication Modes\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports different authentication modes, and device-aware session behavior must be understood within those modes.\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\u003EDevice and session considerations\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureOpaque\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EServer-side session state is authoritative for protected requests, allowing session validity to be evaluated against server-managed state.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOpaque access and refresh credentials are associated with server-authoritative session state.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESemiHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EServer-side session state participates in lifecycle operations, while issued JWT access tokens may remain valid until expiration.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureJwt\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EProtected requests are primarily validated from JWT contents; server-side device and session enforcement depends on the configured supporting infrastructure.\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe presence of a device-associated chain does not imply identical enforcement behavior in every mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-and-jwt-validity\u0022\u003ERevocation and JWT Validity\u003C/h3\u003E\n\u003Cp\u003EIn modes that validate protected requests against authoritative server-side session state, session revocation can affect subsequent requests through that validation.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, a previously issued access token may remain valid until expiration if protected requests do not consult current revocation state.\u003C/p\u003E\n\u003Cp\u003ETherefore, revoking a device-associated chain must not be described as universally invalidating every previously issued access token immediately.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-and-device-limits\u0022\u003ESession and Device Limits\u003C/h2\u003E\n\u003Cp\u003EApplications may need to limit how many authentication sessions or client environments a user can maintain.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s session configuration provides the architectural place for managing supported session and chain constraints.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured capabilities, these constraints may concern:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe number of chains associated with a user.\u003C/li\u003E\n\u003Cli\u003EThe number of sessions associated with a chain.\u003C/li\u003E\n\u003Cli\u003ESession expiration and activity behavior.\u003C/li\u003E\n\u003Cli\u003EThe treatment of device context changes.\u003C/li\u003E\n\u003Cli\u003ERevocation and lifecycle rules.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact available options and enforcement behavior depend on the authentication mode and session configuration.\u003C/p\u003E\n\u003Cp\u003EDevice management should not be understood as a separate enforcement system operating independently of session management.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022operational-visibility\u0022\u003EOperational Visibility\u003C/h2\u003E\n\u003Cp\u003EDevice-associated session information can help applications present useful security information to users or administrators.\u003C/p\u003E\n\u003Cp\u003EFor example, an account management interface may display recognizable client environments and provide an option to revoke related sessions.\u003C/p\u003E\n\u003Cp\u003ESuch interfaces should distinguish between recorded information and verified facts.\u003C/p\u003E\n\u003Cp\u003EA browser name or IP address may help a user recognize an activity record, but it does not conclusively identify the person or physical device involved.\u003C/p\u003E\n\u003Cp\u003ELikewise, activity counters can support monitoring without independently providing anomaly detection.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides session and chain concepts that applications can use for these experiences, while the application\u0027s security and user-interface requirements determine how the information should be presented.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EDevice management in UltimateAuth is built on the relationship between identity, session, and chain.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe user identity identifies the account. The session represents authentication state. The SessionChain organizes related session continuity, potentially associated with a device context.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis model enables targeted session management and device-aware security operations without making device metadata an independent source of authentication trust.\u003C/p\u003E\n\u003Cp\u003EIt also preserves an important architectural boundary: device recognition, session validity, and authorization remain distinct security concerns.\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/sessions-devices/session-model\u0022\u003ESession Model\u003C/a\u003E \u2014 Understand sessions, chains, and their lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/session-security\u0022\u003ESession Security\u003C/a\u003E \u2014 Explore expiration, rotation, touch, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Compare session validation across authentication modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/request-lifecycle\u0022\u003ERequest Lifecycle\u003C/a\u003E \u2014 Understand how protected requests are authenticated and authorized.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EAuthentication does not happen in isolation from the device or client environment in which it takes place.\u003C/p\u003E\n\u003Cp\u003EA user may authenticate from a desktop browser, a mobile application, or several different environments simultaneously. Each environment can have its own authentication state, activity history, and security requirements.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports \u003Cstrong\u003Edevice-aware session management\u003C/strong\u003E by associating authentication sessions with device context and organizing related sessions through \u003Ccode\u003ESessionChain\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis provides a foundation for device-level visibility, session continuity, and targeted security operations without treating device identity as a substitute for user authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-device-aware-authentication\u0022\u003EUnderstanding Device-Aware Authentication\u003C/h2\u003E\n\u003Cp\u003ETraditional token-based authentication can be implemented without explicitly modeling the client environment.\u003C/p\u003E\n\u003Cp\u003EA token is issued, validated, and eventually expires or is revoked.\u003C/p\u003E\n\u003Cp\u003EThat approach is sufficient for some applications, but it becomes limiting when an application needs to distinguish between different client environments belonging to the same user.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA user signs in from both a desktop browser and a mobile application.\u003C/li\u003E\n\u003Cli\u003EAn administrator needs to revoke sessions associated with one client environment.\u003C/li\u003E\n\u003Cli\u003EA session is refreshed repeatedly while remaining associated with the same device context.\u003C/li\u003E\n\u003Cli\u003EAn application needs to apply session limits or track activity across client environments.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUltimateAuth separates these concerns through its session and chain model.\u003C/p\u003E\n\u003Cp\u003EThe objective is not to prove the physical identity of a device. It is to provide a structured way to associate authentication activity with a recognizable client context.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-context-and-sessionchain\u0022\u003EDevice Context and SessionChain\u003C/h2\u003E\n\u003Cp\u003EThe central concept in UltimateAuth\u0027s device-aware model is \u003Ccode\u003ESessionChain\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA session represents authentication state.\u003C/p\u003E\n\u003Cp\u003EA \u003Ccode\u003ESessionChain\u003C/code\u003E groups related session activity under a persistent chain identity, which can be associated with a device or client environment.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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 Identity\n |\n \u002B-- SessionChain (Desktop Browser)\n | |\n | \u002B-- Session\n | \u002B-- Session / Rotation History\n |\n \u002B-- SessionChain (Mobile Application)\n |\n \u002B-- Session\n \u002B-- Session / Rotation History\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis diagram illustrates the relationship between identity, chain, and session. It does not imply that every authentication mode retains multiple simultaneous session records or stores a complete rotation history.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022a-chain-is-not-a-physical-device\u0022\u003EA Chain Is Not a Physical Device\u003C/h3\u003E\n\u003Cp\u003EA \u003Ccode\u003ESessionChain\u003C/code\u003E should not be understood as a physical hardware identifier.\u003C/p\u003E\n\u003Cp\u003EIt represents a logical continuity boundary for related authentication sessions.\u003C/p\u003E\n\u003Cp\u003EA single physical device may use different browsers, application installations, or isolated client storage environments.\u003C/p\u003E\n\u003Cp\u003EConversely, device identifiers supplied by clients are not necessarily reliable proof that requests originate from the same physical device.\u003C/p\u003E\n\u003Cp\u003EThe practical relationship is:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA device context can be associated with a SessionChain, but the chain itself is an authentication-session concept.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis distinction is important when designing security rules around device recognition and session revocation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-information\u0022\u003EDevice Information\u003C/h2\u003E\n\u003Cp\u003EDevice-aware session management can use information describing the client environment.\u003C/p\u003E\n\u003Cp\u003ERelevant information may include:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EInformation\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\u003EDeviceId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifies a client-reported or application-managed device context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPlatform\u003C/td\u003E\n\u003Ctd\u003EDistinguishes environments such as web and mobile\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOperating system\u003C/td\u003E\n\u003Ctd\u003EProvides client environment metadata\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBrowser\u003C/td\u003E\n\u003Ctd\u003EDescribes the browser environment when applicable\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIP address\u003C/td\u003E\n\u003Ctd\u003EProvides network context for security and activity analysis\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe availability and reliability of this information depend on the client platform and the application\u0027s integration.\u003C/p\u003E\n\u003Cp\u003ENot every client can provide the same metadata.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022deviceid-and-trust\u0022\u003EDeviceId and Trust\u003C/h3\u003E\n\u003Cp\u003EA \u003Ccode\u003EDeviceId\u003C/code\u003E is useful for identifying a client environment across requests or authentication operations.\u003C/p\u003E\n\u003Cp\u003EHowever, an identifier alone is not proof of device ownership.\u003C/p\u003E\n\u003Cp\u003EA value stored in browser storage, for example, may be copied, cleared, or replaced. An identifier generated by a mobile application may change after application data is removed.\u003C/p\u003E\n\u003Cp\u003EFor that reason, device recognition must not be confused with authentication.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s authentication and session validation rules remain responsible for determining whether a request is authorized to use the associated authentication state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-fingerprinting\u0022\u003EDevice Fingerprinting\u003C/h3\u003E\n\u003Cp\u003EPlatform, operating system, browser, and network information may help describe a client environment.\u003C/p\u003E\n\u003Cp\u003EThese attributes are not inherently unique or stable.\u003C/p\u003E\n\u003Cp\u003EMultiple devices can share similar attributes, and a single device can present different attributes over time.\u003C/p\u003E\n\u003Cp\u003EThey should therefore be treated as contextual signals rather than a guaranteed device fingerprint.\u003C/p\u003E\n\u003Cp\u003EApplications requiring stronger device assurance need an appropriate proof or binding mechanism in addition to ordinary metadata.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-continuity\u0022\u003EDevice Continuity\u003C/h2\u003E\n\u003Cp\u003EDevice continuity describes how authentication activity remains associated with an existing client context.\u003C/p\u003E\n\u003Cp\u003EA session chain provides the logical structure for maintaining that continuity.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022first-authentication\u0022\u003EFirst Authentication\u003C/h3\u003E\n\u003Cp\u003EWhen an authentication flow establishes a new session, the session infrastructure determines the appropriate chain association.\u003C/p\u003E\n\u003Cp\u003EIf a new chain is required, the session is associated with that chain.\u003C/p\u003E\n\u003Cp\u003EThe resulting chain can represent the beginning of a device-associated authentication history.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022subsequent-authentication\u0022\u003ESubsequent Authentication\u003C/h3\u003E\n\u003Cp\u003EA later authentication from the same client environment may be associated with an existing chain when the configured matching and security conditions permit it.\u003C/p\u003E\n\u003Cp\u003EHowever, recognizing a device identifier does not automatically require the system to reuse a previous chain.\u003C/p\u003E\n\u003Cp\u003EChain association must respect the applicable lifecycle, validity, and security rules.\u003C/p\u003E\n\u003Cp\u003EFor example, a revoked chain should not become trusted again merely because a client presents the same device identifier.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-continuity-is-not-session-continuity\u0022\u003EDevice Continuity Is Not Session Continuity\u003C/h3\u003E\n\u003Cp\u003EA client environment can remain recognizable even when its previous authentication session has expired.\u003C/p\u003E\n\u003Cp\u003ELikewise, a new authentication session can be established without inheriting the validity of an earlier session.\u003C/p\u003E\n\u003Cp\u003EThe chain model allows related activity to be organized while keeping session validity subject to authentication and security rules.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022activity-tracking\u0022\u003EActivity Tracking\u003C/h2\u003E\n\u003Cp\u003EDevice-associated session management can maintain information about authentication activity.\u003C/p\u003E\n\u003Cp\u003EExamples include:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhen a chain was created.\u003C/li\u003E\n\u003Cli\u003EWhen related authentication activity was last observed.\u003C/li\u003E\n\u003Cli\u003EHow often relevant session activity was recorded.\u003C/li\u003E\n\u003Cli\u003EHow many rotation operations were associated with the chain.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFields such as \u003Ccode\u003ELastSeenAt\u003C/code\u003E, \u003Ccode\u003ETouchCount\u003C/code\u003E, and \u003Ccode\u003ERotationCount\u003C/code\u003E help describe these aspects of the session lifecycle.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022touch-operations\u0022\u003ETouch Operations\u003C/h3\u003E\n\u003Cp\u003EA touch operation records qualifying session activity according to the configured session behavior.\u003C/p\u003E\n\u003Cp\u003EDepending on the authentication mode and session strategy, this may update activity metadata such as the last-seen timestamp or touch counter.\u003C/p\u003E\n\u003Cp\u003ETouch behavior should not be interpreted as a guarantee that every authenticated HTTP request updates persistent device activity.\u003C/p\u003E\n\u003Cp\u003EDifferent authentication modes have different requirements for consulting and updating server-side session state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022rotation-tracking\u0022\u003ERotation Tracking\u003C/h3\u003E\n\u003Cp\u003EToken or session rotation may replace authentication material while preserving the logical relationship with an existing chain.\u003C/p\u003E\n\u003Cp\u003EWhere rotation tracking is supported, the chain can maintain information about those transitions.\u003C/p\u003E\n\u003Cp\u003EThis makes it possible to distinguish the continuity of a client environment from the lifecycle of an individual credential or session.\u003C/p\u003E\n\u003Cp\u003ERotation counts are operational signals. They do not independently establish that a device is legitimate or that suspicious activity has occurred.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout-and-device-revocation\u0022\u003ELogout and Device Revocation\u003C/h2\u003E\n\u003Cp\u003ELogout and chain revocation address different security scopes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022logout\u0022\u003ELogout\u003C/h3\u003E\n\u003Cp\u003ELogout terminates the authentication state targeted by the logout operation.\u003C/p\u003E\n\u003Cp\u003EIts precise effect depends on the authentication mode, the credentials involved, and the configured session behavior.\u003C/p\u003E\n\u003Cp\u003EA logout operation does not necessarily mean that every session associated with the same device context has been revoked.\u003C/p\u003E\n\u003Cp\u003EIt also does not necessarily require deleting the associated chain record.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-revocation\u0022\u003EChain Revocation\u003C/h3\u003E\n\u003Cp\u003EChain revocation targets the logical group of sessions associated with a chain.\u003C/p\u003E\n\u003Cp\u003EIt provides a broader security operation than terminating a single session.\u003C/p\u003E\n\u003Cp\u003EThis is useful when an application needs to invalidate the authentication continuity associated with a particular client environment.\u003C/p\u003E\n\u003Cp\u003EFor example, if a user believes a mobile application session has been compromised, the application may revoke the corresponding chain rather than revoking unrelated sessions associated with other environments.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-does-not-establish-device-trust\u0022\u003ERevocation Does Not Establish Device Trust\u003C/h3\u003E\n\u003Cp\u003EA retained chain record is not equivalent to a trusted device.\u003C/p\u003E\n\u003Cp\u003ELikewise, removing or revoking a chain does not physically block a device from attempting to authenticate again.\u003C/p\u003E\n\u003Cp\u003EWhether a new authentication attempt is permitted depends on the authentication flow and the applicable security policies.\u003C/p\u003E\n\u003Cp\u003EThe chain model provides a boundary for managing authentication state; it is not a permanent device allowlist or hardware security mechanism.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-binding-and-security-boundaries\u0022\u003EDevice Binding and Security Boundaries\u003C/h2\u003E\n\u003Cp\u003EDevice-aware authentication can associate session state with device context.\u003C/p\u003E\n\u003Cp\u003EHowever, the strength of that association depends on what is actually validated.\u003C/p\u003E\n\u003Cp\u003EThere is an important distinction between:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice metadata:\u003C/strong\u003E Information describing a client environment.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice identification:\u003C/strong\u003E An identifier used to recognize a client context.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice binding:\u003C/strong\u003E A security requirement connecting authentication material to a validated device-related condition.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDevice authentication:\u003C/strong\u003E Proof that a client possesses an appropriate device-bound secret or cryptographic key.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThese concepts provide different levels of assurance.\u003C/p\u003E\n\u003Cp\u003EAn application must not assume that supplying a \u003Ccode\u003EDeviceId\u003C/code\u003E alone prevents stolen credentials from being reused elsewhere.\u003C/p\u003E\n\u003Cp\u003ESecurity guarantees depend on the binding and validation mechanisms enforced by the configured authentication infrastructure.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-management-across-authentication-modes\u0022\u003EDevice Management Across Authentication Modes\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports different authentication modes, and device-aware session behavior must be understood within those modes.\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\u003EDevice and session considerations\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureOpaque\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EServer-side session state is authoritative for protected requests, allowing session validity to be evaluated against server-managed state.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EOpaque access and refresh credentials are associated with server-authoritative session state.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESemiHybrid\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EServer-side session state participates in lifecycle operations, while issued JWT access tokens may remain valid until expiration.\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPureJwt\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EProtected requests are primarily validated from JWT contents; server-side device and session enforcement depends on the configured supporting infrastructure.\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe presence of a device-associated chain does not imply identical enforcement behavior in every mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-and-jwt-validity\u0022\u003ERevocation and JWT Validity\u003C/h3\u003E\n\u003Cp\u003EIn modes that validate protected requests against authoritative server-side session state, session revocation can affect subsequent requests through that validation.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, a previously issued access token may remain valid until expiration if protected requests do not consult current revocation state.\u003C/p\u003E\n\u003Cp\u003ETherefore, revoking a device-associated chain must not be described as universally invalidating every previously issued access token immediately.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-and-device-limits\u0022\u003ESession and Device Limits\u003C/h2\u003E\n\u003Cp\u003EApplications may need to limit how many authentication sessions or client environments a user can maintain.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s session configuration provides the architectural place for managing supported session and chain constraints.\u003C/p\u003E\n\u003Cp\u003EDepending on the configured capabilities, these constraints may concern:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe number of chains associated with a user.\u003C/li\u003E\n\u003Cli\u003EThe number of sessions associated with a chain.\u003C/li\u003E\n\u003Cli\u003ESession expiration and activity behavior.\u003C/li\u003E\n\u003Cli\u003EThe treatment of device context changes.\u003C/li\u003E\n\u003Cli\u003ERevocation and lifecycle rules.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe exact available options and enforcement behavior depend on the authentication mode and session configuration.\u003C/p\u003E\n\u003Cp\u003EDevice management should not be understood as a separate enforcement system operating independently of session management.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022operational-visibility\u0022\u003EOperational Visibility\u003C/h2\u003E\n\u003Cp\u003EDevice-associated session information can help applications present useful security information to users or administrators.\u003C/p\u003E\n\u003Cp\u003EFor example, an account management interface may display recognizable client environments and provide an option to revoke related sessions.\u003C/p\u003E\n\u003Cp\u003ESuch interfaces should distinguish between recorded information and verified facts.\u003C/p\u003E\n\u003Cp\u003EA browser name or IP address may help a user recognize an activity record, but it does not conclusively identify the person or physical device involved.\u003C/p\u003E\n\u003Cp\u003ELikewise, activity counters can support monitoring without independently providing anomaly detection.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides session and chain concepts that applications can use for these experiences, while the application\u0027s security and user-interface requirements determine how the information should be presented.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EDevice management in UltimateAuth is built on the relationship between identity, session, and chain.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe user identity identifies the account. The session represents authentication state. The SessionChain organizes related session continuity, potentially associated with a device context.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis model enables targeted session management and device-aware security operations without making device metadata an independent source of authentication trust.\u003C/p\u003E\n\u003Cp\u003EIt also preserves an important architectural boundary: device recognition, session validity, and authorization remain distinct security concerns.\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/sessions-devices/session-lifecycle\u0022\u003ESession Model\u003C/a\u003E \u2014 Understand sessions, chains, and their lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security\u003C/a\u003E \u2014 Explore expiration, rotation, touch, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Compare session validation across authentication modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/request-lifecycle\u0022\u003ERequest Lifecycle\u003C/a\u003E \u2014 Understand how protected requests are authenticated and authorized.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "understanding-device-aware-authentication", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/session-lifecycle.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/session-lifecycle.json index 45f5c18d..c0782a55 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/session-lifecycle.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/sessions-devices/session-lifecycle.json @@ -1,7 +1,7 @@ { "Slug": "sessions-devices/session-lifecycle", "Title": "Session Model \u0026 Lifecycle", - "Html": "\n\u003Cp\u003EAuthentication is more than issuing a credential and checking its expiration.\u003C/p\u003E\n\u003Cp\u003EIn stateful authentication systems, successful authentication establishes security state that must be managed throughout its lifetime. Sessions may be refreshed, extended, revoked, or invalidated in response to changes in account security.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth models this state through a structured hierarchy:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERoot \u2192 Chain \u2192 Session\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis hierarchy separates account-level authentication security, device-associated session continuity, and individual authentication sessions.\u003C/p\u003E\n\u003Cp\u003EThe model provides the foundation for session management, security versioning, targeted revocation, and lifecycle enforcement.\u003C/p\u003E\n\u003Cp\u003EIts runtime behavior depends on the configured authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-the-session-hierarchy\u0022\u003EUnderstanding the Session Hierarchy\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth organizes server-managed authentication state into three levels.\u003C/p\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\u003ERepresents\u003C/th\u003E\n\u003Cth\u003EPrimary responsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESessionRoot\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAccount-level authentication security\u003C/td\u003E\n\u003Ctd\u003EGlobal session state and security versioning\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESessionChain\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERelated session continuity\u003C/td\u003E\n\u003Ctd\u003EDevice association, activity, and chain-level control\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 state\u003C/td\u003E\n\u003Ctd\u003ESession validity, expiration, and revocation\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EConceptually:\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 Identity\n |\n \u2514\u2500\u2500 SessionRoot\n |\n \u251C\u2500\u2500 SessionChain (Desktop Browser)\n | |\n | \u251C\u2500\u2500 Session\n | \u2514\u2500\u2500 Session\n |\n \u2514\u2500\u2500 SessionChain (Mobile Application)\n |\n \u2514\u2500\u2500 Session\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is a logical representation of the hierarchy. It does not imply that every authentication mode stores the same records or maintains multiple simultaneous sessions within each chain.\u003C/p\u003E\n\u003Cp\u003EEach level addresses a different security concern.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessionroot-account-level-security\u0022\u003ESessionRoot: Account-Level Security\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ESessionRoot\u003C/code\u003E represents the account-level security boundary for server-managed authentication sessions.\u003C/p\u003E\n\u003Cp\u003EIt associates the authentication session hierarchy with a user identity and maintains security information relevant across that identity\u0027s sessions.\u003C/p\u003E\n\u003Cp\u003EThe root is not the user account itself.\u003C/p\u003E\n\u003Cp\u003EThe Users domain may own the user\u0027s identifiers, profile, and lifecycle. The session root belongs to authentication security management.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-responsibilities\u0022\u003ERoot Responsibilities\u003C/h3\u003E\n\u003Cp\u003EA session root provides a common boundary for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAssociating session chains with an identity.\u003C/li\u003E\n\u003Cli\u003EMaintaining account-level session security state.\u003C/li\u003E\n\u003Cli\u003ECoordinating global session invalidation.\u003C/li\u003E\n\u003Cli\u003ESupporting security version checks.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe distinction is important because changes to authentication security do not necessarily require changes to the user entity.\u003C/p\u003E\n\u003Cp\u003EFor example, invalidating existing sessions should not require recreating the user or changing the user\u0027s identifiers.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-versioning\u0022\u003ESecurity Versioning\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth\u0027s session model supports security versioning.\u003C/p\u003E\n\u003Cp\u003EA root can maintain a security version representing the current account-level authentication security state.\u003C/p\u003E\n\u003Cp\u003ESessions can retain the corresponding version from their creation.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003ERoot.SecurityVersion = 4\nSession.SecurityVersionAtCreation = 4\n\nResult: Version matches\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter an account-level security invalidation:\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\u003ERoot.SecurityVersion = 5\nSession.SecurityVersionAtCreation = 4\n\nResult: Version mismatch\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen the applicable session validation checks detect a version mismatch, the older session is no longer valid.\u003C/p\u003E\n\u003Cp\u003EThis allows the authentication system to invalidate previously established session state through a shared security boundary.\u003C/p\u003E\n\u003Cp\u003EThe effect on already-issued JWT access tokens still depends on whether protected requests consult current server-side security state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessionchain-session-continuity\u0022\u003ESessionChain: Session Continuity\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ESessionChain\u003C/code\u003E represents a logical grouping of related authentication sessions.\u003C/p\u003E\n\u003Cp\u003EA chain can be associated with a particular device or client environment, allowing session activity to be managed within a recognizable continuity boundary.\u003C/p\u003E\n\u003Cp\u003EFor example, a user may have separate chains for a desktop browser and a mobile application.\u003C/p\u003E\n\u003Cp\u003EHowever, a chain is not a physical device identity or a guarantee that the device has been cryptographically verified.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-responsibilities\u0022\u003EChain Responsibilities\u003C/h3\u003E\n\u003Cp\u003EA session chain can provide a boundary for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAssociating related sessions.\u003C/li\u003E\n\u003Cli\u003EMaintaining device or client context.\u003C/li\u003E\n\u003Cli\u003ERecording relevant activity.\u003C/li\u003E\n\u003Cli\u003ETracking session rotation.\u003C/li\u003E\n\u003Cli\u003EApplying chain-level security operations.\u003C/li\u003E\n\u003Cli\u003ERevoking related sessions together.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis allows UltimateAuth to distinguish between the lifecycle of an individual session and the continuity of the environment associated with it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-association\u0022\u003EDevice Association\u003C/h3\u003E\n\u003Cp\u003EA chain may include device-related information such as a device identifier, platform, operating system, or browser.\u003C/p\u003E\n\u003Cp\u003EThese attributes help describe the client environment.\u003C/p\u003E\n\u003Cp\u003EThey must not be treated as independent proof of device ownership.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E for the device association model and its security boundaries.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-individual-authentication-state\u0022\u003ESession: Individual Authentication State\u003C/h2\u003E\n\u003Cp\u003EA \u003Ccode\u003ESession\u003C/code\u003E represents an individual unit of server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003EIt is associated with a chain and participates in the validity rules established by the authentication mode and session configuration.\u003C/p\u003E\n\u003Cp\u003EA session may contain information relevant to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EIts associated identity and chain.\u003C/li\u003E\n\u003Cli\u003ECreation and expiration.\u003C/li\u003E\n\u003Cli\u003ERevocation status.\u003C/li\u003E\n\u003Cli\u003ESecurity version at creation.\u003C/li\u003E\n\u003Cli\u003EAuthentication lifecycle and validity.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA session is not necessarily equivalent to an access token.\u003C/p\u003E\n\u003Cp\u003EIn opaque authentication modes, a client credential may reference server-managed session state.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, an issued access token may be validated locally without consulting the session hierarchy on every protected request.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe token is authentication material. The session is a security-state concept.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EKeeping these concepts separate is essential for understanding UltimateAuth\u0027s authentication modes.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-creation\u0022\u003ESession Creation\u003C/h2\u003E\n\u003Cp\u003EA session lifecycle begins when an authentication flow successfully establishes the required authentication state.\u003C/p\u003E\n\u003Cp\u003EFor modes using the server-managed session hierarchy, the process conceptually involves:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EResolving the authenticated user identity.\u003C/li\u003E\n\u003Cli\u003EResolving or establishing the corresponding session root.\u003C/li\u003E\n\u003Cli\u003EDetermining the appropriate session chain.\u003C/li\u003E\n\u003Cli\u003ECreating or updating the required session state.\u003C/li\u003E\n\u003Cli\u003EIssuing the authentication material required by the selected mode.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe authentication flow must satisfy its verification and security requirements before establishing a valid authenticated session.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-resolution\u0022\u003ERoot Resolution\u003C/h3\u003E\n\u003Cp\u003EAn existing account-level session root can be reused when appropriate.\u003C/p\u003E\n\u003Cp\u003EIf the required root does not exist, the session infrastructure can establish it as part of session creation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-resolution\u0022\u003EChain Resolution\u003C/h3\u003E\n\u003Cp\u003EThe session infrastructure determines whether authentication should be associated with an existing chain or a new chain.\u003C/p\u003E\n\u003Cp\u003EA previously observed device identifier does not automatically establish that an existing chain is valid or eligible for reuse.\u003C/p\u003E\n\u003Cp\u003EThe decision must respect the applicable chain lifecycle and security conditions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-establishment\u0022\u003ESession Establishment\u003C/h3\u003E\n\u003Cp\u003EOnce the appropriate security context has been established, the authentication system creates or updates the session state required by the authentication mode.\u003C/p\u003E\n\u003Cp\u003EThe resulting client credential is then issued according to that mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022active-session-usage\u0022\u003EActive Session Usage\u003C/h2\u003E\n\u003Cp\u003EAfter authentication, the client presents the appropriate authentication material when accessing protected resources.\u003C/p\u003E\n\u003Cp\u003EWhat happens next depends on the configured mode.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, protected requests can be evaluated against current server-managed session state.\u003C/p\u003E\n\u003Cp\u003EIn locally validated JWT modes, protected requests may be authenticated using the token\u0027s cryptographic validity and claims without loading the current session record.\u003C/p\u003E\n\u003Cp\u003EThis distinction affects session activity tracking, expiration enforcement, and revocation behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022touch-operations\u0022\u003ETouch Operations\u003C/h3\u003E\n\u003Cp\u003EA touch operation records qualifying session activity.\u003C/p\u003E\n\u003Cp\u003EDepending on the session strategy, it may update information such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ELastSeenAt\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ETouchCount\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003ERelevant session activity or expiration state\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ETouch behavior is not necessarily performed for every HTTP request.\u003C/p\u003E\n\u003Cp\u003EApplications should not assume that an authenticated request always results in a persistent session update.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022sliding-expiration\u0022\u003ESliding Expiration\u003C/h3\u003E\n\u003Cp\u003ESliding expiration allows eligible authentication state to remain valid as qualifying activity occurs.\u003C/p\u003E\n\u003Cp\u003EUnlike a fixed expiration, sliding expiration can extend a session\u0027s usable lifetime according to configured rules.\u003C/p\u003E\n\u003Cp\u003EHowever, sliding expiration does not necessarily remove absolute lifetime restrictions.\u003C/p\u003E\n\u003Cp\u003EA session may still become invalid when a maximum permitted lifetime is reached.\u003C/p\u003E\n\u003Cp\u003EThe exact behavior depends on the selected authentication mode and its session configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-and-rotation\u0022\u003ERefresh and Rotation\u003C/h2\u003E\n\u003Cp\u003ERefresh and rotation are related to session lifecycle but are not identical operations.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERefresh\u003C/strong\u003E is an authentication operation used to continue or renew authentication state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERotation\u003C/strong\u003E replaces authentication material or session-related credentials according to the configured security strategy.\u003C/p\u003E\n\u003Cp\u003EThe relationship between these operations varies by authentication mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque\u0022\u003EPureOpaque\u003C/h3\u003E\n\u003Cp\u003EPureOpaque uses server-authoritative opaque session authentication.\u003C/p\u003E\n\u003Cp\u003EIt does not require a separate access-token and refresh-token pair.\u003C/p\u003E\n\u003Cp\u003ESession continuity and activity management are handled through the applicable opaque session lifecycle.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003EHybrid\u003C/h3\u003E\n\u003Cp\u003EHybrid uses distinct opaque access and refresh credentials backed by server-authoritative session state.\u003C/p\u003E\n\u003Cp\u003ERefresh operations can renew authentication material while preserving the appropriate session or chain relationship.\u003C/p\u003E\n\u003Cp\u003EThe exact rotation behavior follows the configured refresh strategy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid\u0022\u003ESemiHybrid\u003C/h3\u003E\n\u003Cp\u003ESemiHybrid uses JWT access tokens while maintaining server-side session state for lifecycle operations such as refresh and logout.\u003C/p\u003E\n\u003Cp\u003ERefresh may issue new JWT access material and update the relevant server-managed state.\u003C/p\u003E\n\u003Cp\u003EAlready-issued access tokens may remain valid until expiration because protected requests do not necessarily consult that state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022purejwt\u0022\u003EPureJwt\u003C/h3\u003E\n\u003Cp\u003EPureJwt primarily relies on stateless JWT validation for protected requests.\u003C/p\u003E\n\u003Cp\u003EA refresh mechanism may use an independent refresh store, but the server-managed Root \u2192 Chain \u2192 Session hierarchy must not be assumed to govern every protected request.\u003C/p\u003E\n\u003Cp\u003EThis is an important distinction between the framework\u0027s common authentication concepts and the persistence requirements of individual modes.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-expiration\u0022\u003ESession Expiration\u003C/h2\u003E\n\u003Cp\u003ESession expiration defines when authentication state is no longer eligible for continued use.\u003C/p\u003E\n\u003Cp\u003EDepending on the authentication mode and configuration, expiration may involve:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EExpiration mechanism\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\u003Cstrong\u003EFixed lifetime\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication state expires after a defined duration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EIdle timeout\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication state expires after insufficient qualifying activity\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESliding expiration\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEligible activity extends the usable lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAbsolute expiration\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication state cannot extend beyond a maximum lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese mechanisms are not necessarily all enabled or applicable in every authentication mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022expired-versus-revoked\u0022\u003EExpired Versus Revoked\u003C/h3\u003E\n\u003Cp\u003EExpiration and revocation represent different reasons for invalidation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EExpiration\u003C/strong\u003E occurs because a configured time-based condition has been reached.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERevocation\u003C/strong\u003E occurs because authentication state has been explicitly invalidated through a security operation.\u003C/p\u003E\n\u003Cp\u003EBoth can make a session unusable, but they represent different lifecycle events and may be recorded or enforced differently.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-revocation\u0022\u003ESession Revocation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s hierarchical session model supports security operations at different levels.\u003C/p\u003E\n\u003Cp\u003EThe scope of revocation determines how broadly authentication state is affected.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-level-revocation\u0022\u003ESession-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ESession-level revocation targets an individual session.\u003C/p\u003E\n\u003Cp\u003EIt is appropriate when a specific authentication instance must be invalidated without necessarily affecting other sessions associated with the same user.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-level-revocation\u0022\u003EChain-Level Revocation\u003C/h3\u003E\n\u003Cp\u003EChain-level revocation targets the session continuity represented by a chain.\u003C/p\u003E\n\u003Cp\u003EIt allows related sessions to be invalidated together.\u003C/p\u003E\n\u003Cp\u003EThis can be useful when a particular client environment is suspected of compromise or when a user wants to terminate sessions associated with one device context.\u003C/p\u003E\n\u003Cp\u003ERevoking a chain does not mean that the corresponding physical device is permanently blocked from authenticating again.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-level-revocation\u0022\u003ERoot-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ERoot-level revocation affects the account-level authentication security boundary.\u003C/p\u003E\n\u003Cp\u003EIt provides a mechanism for invalidating authentication state associated with the user\u0027s session hierarchy.\u003C/p\u003E\n\u003Cp\u003ESecurity version changes can support this operation by making previously created sessions fail subsequent version validation.\u003C/p\u003E\n\u003Cp\u003EThis is useful for broad security responses, including operations that require invalidating all applicable existing sessions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-scope\u0022\u003ERevocation Scope\u003C/h3\u003E\n\u003Cp\u003EConceptually:\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 revocation\n \u2514\u2500\u2500 One session\n\nChain revocation\n \u2514\u2500\u2500 Related sessions in one chain\n\nRoot revocation\n \u2514\u2500\u2500 Account-level session hierarchy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe effective invalidation of protected requests depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EIn particular, server-side revocation does not automatically invalidate a previously issued JWT that is still cryptographically valid and is not checked against current revocation state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-security-and-credential-changes\u0022\u003ESession Security and Credential Changes\u003C/h2\u003E\n\u003Cp\u003ESession lifecycle is closely related to credential security.\u003C/p\u003E\n\u003Cp\u003EA password change or recovery operation may require existing authentication sessions to be preserved or revoked.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports session handling policies for credential changes and resets, including:\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\u003EBehavior\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 applicable 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\u003EThese policies allow applications to choose the security response appropriate to the operation.\u003C/p\u003E\n\u003Cp\u003EFor example, an authenticated password change and an anonymous password recovery operation may require different session handling.\u003C/p\u003E\n\u003Cp\u003EThe Credentials domain initiates the relevant security behavior, while the session infrastructure applies the corresponding lifecycle operations.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-configuration\u0022\u003ESession Configuration\u003C/h2\u003E\n\u003Cp\u003ESession behavior is configured through UltimateAuth\u0027s session options, including \u003Ccode\u003EUAuthSessionOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe configuration model governs supported aspects of session lifecycle such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession lifetimes.\u003C/li\u003E\n\u003Cli\u003EActivity and expiration behavior.\u003C/li\u003E\n\u003Cli\u003ERefresh and rotation strategies.\u003C/li\u003E\n\u003Cli\u003ESession and chain limits.\u003C/li\u003E\n\u003Cli\u003EDevice-related session constraints.\u003C/li\u003E\n\u003Cli\u003ERevocation and lifecycle enforcement.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ENot every option applies to every authentication mode.\u003C/p\u003E\n\u003Cp\u003EFor example, a configuration setting that controls server-side session activity may not affect the validation of a stateless JWT access token.\u003C/p\u003E\n\u003Cp\u003EApplications should configure session behavior with the selected authentication mode and its enforcement boundaries in mind.\u003C/p\u003E\n\u003Cp\u003EDetailed configuration examples belong in the configuration reference and How-to Guides.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates authentication identity, session continuity, and individual authentication state.\u003C/p\u003E\n\u003Cp\u003EThe Root \u2192 Chain \u2192 Session hierarchy expresses those responsibilities at different security levels:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ERoot\u003C/strong\u003E provides account-level authentication security.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EChain\u003C/strong\u003E organizes related session continuity.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession\u003C/strong\u003E represents individual server-managed authentication state.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis structure supports targeted session management without requiring all security operations to act on every session belonging to a user.\u003C/p\u003E\n\u003Cp\u003EAt the same time, UltimateAuth does not assume that every authentication mode requires identical server-side state.\u003C/p\u003E\n\u003Cp\u003EThe hierarchy is most directly relevant to session-managed authentication, while JWT-based modes preserve their own validation and lifecycle characteristics.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication establishes trust. Session management determines how that trust is maintained, renewed, and invalidated over time.\u003C/strong\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/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E \u2014 Device context, chain association, and device-aware session management.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/sessions-devices/session-security\u0022\u003ESession Security\u003C/a\u003E \u2014 Session expiration, rotation, revocation, and security controls.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/authentication-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Understand how session behavior differs between modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/request-lifecycle\u0022\u003ERequest Lifecycle\u003C/a\u003E \u2014 Learn how protected requests validate authentication state.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003EAuthentication is more than issuing a credential and checking its expiration.\u003C/p\u003E\n\u003Cp\u003EIn stateful authentication systems, successful authentication establishes security state that must be managed throughout its lifetime. Sessions may be refreshed, extended, revoked, or invalidated in response to changes in account security.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth models this state through a structured hierarchy:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERoot \u2192 Chain \u2192 Session\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis hierarchy separates account-level authentication security, device-associated session continuity, and individual authentication sessions.\u003C/p\u003E\n\u003Cp\u003EThe model provides the foundation for session management, security versioning, targeted revocation, and lifecycle enforcement.\u003C/p\u003E\n\u003Cp\u003EIts runtime behavior depends on the configured authentication mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-the-session-hierarchy\u0022\u003EUnderstanding the Session Hierarchy\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth organizes server-managed authentication state into three levels.\u003C/p\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\u003ERepresents\u003C/th\u003E\n\u003Cth\u003EPrimary responsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESessionRoot\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAccount-level authentication security\u003C/td\u003E\n\u003Ctd\u003EGlobal session state and security versioning\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESessionChain\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERelated session continuity\u003C/td\u003E\n\u003Ctd\u003EDevice association, activity, and chain-level control\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 state\u003C/td\u003E\n\u003Ctd\u003ESession validity, expiration, and revocation\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EConceptually:\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 Identity\n |\n \u2514\u2500\u2500 SessionRoot\n |\n \u251C\u2500\u2500 SessionChain (Desktop Browser)\n | |\n | \u251C\u2500\u2500 Session\n | \u2514\u2500\u2500 Session\n |\n \u2514\u2500\u2500 SessionChain (Mobile Application)\n |\n \u2514\u2500\u2500 Session\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis is a logical representation of the hierarchy. It does not imply that every authentication mode stores the same records or maintains multiple simultaneous sessions within each chain.\u003C/p\u003E\n\u003Cp\u003EEach level addresses a different security concern.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessionroot-account-level-security\u0022\u003ESessionRoot: Account-Level Security\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ESessionRoot\u003C/code\u003E represents the account-level security boundary for server-managed authentication sessions.\u003C/p\u003E\n\u003Cp\u003EIt associates the authentication session hierarchy with a user identity and maintains security information relevant across that identity\u0027s sessions.\u003C/p\u003E\n\u003Cp\u003EThe root is not the user account itself.\u003C/p\u003E\n\u003Cp\u003EThe Users domain may own the user\u0027s identifiers, profile, and lifecycle. The session root belongs to authentication security management.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-responsibilities\u0022\u003ERoot Responsibilities\u003C/h3\u003E\n\u003Cp\u003EA session root provides a common boundary for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAssociating session chains with an identity.\u003C/li\u003E\n\u003Cli\u003EMaintaining account-level session security state.\u003C/li\u003E\n\u003Cli\u003ECoordinating global session invalidation.\u003C/li\u003E\n\u003Cli\u003ESupporting security version checks.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe distinction is important because changes to authentication security do not necessarily require changes to the user entity.\u003C/p\u003E\n\u003Cp\u003EFor example, invalidating existing sessions should not require recreating the user or changing the user\u0027s identifiers.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-versioning\u0022\u003ESecurity Versioning\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth\u0027s session model supports security versioning.\u003C/p\u003E\n\u003Cp\u003EA root can maintain a security version representing the current account-level authentication security state.\u003C/p\u003E\n\u003Cp\u003ESessions can retain the corresponding version from their creation.\u003C/p\u003E\n\u003Cp\u003EConceptually:\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\u003ERoot.SecurityVersion = 4\nSession.SecurityVersionAtCreation = 4\n\nResult: Version matches\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter an account-level security invalidation:\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\u003ERoot.SecurityVersion = 5\nSession.SecurityVersionAtCreation = 4\n\nResult: Version mismatch\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen the applicable session validation checks detect a version mismatch, the older session is no longer valid.\u003C/p\u003E\n\u003Cp\u003EThis allows the authentication system to invalidate previously established session state through a shared security boundary.\u003C/p\u003E\n\u003Cp\u003EThe effect on already-issued JWT access tokens still depends on whether protected requests consult current server-side security state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022sessionchain-session-continuity\u0022\u003ESessionChain: Session Continuity\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003ESessionChain\u003C/code\u003E represents a logical grouping of related authentication sessions.\u003C/p\u003E\n\u003Cp\u003EA chain can be associated with a particular device or client environment, allowing session activity to be managed within a recognizable continuity boundary.\u003C/p\u003E\n\u003Cp\u003EFor example, a user may have separate chains for a desktop browser and a mobile application.\u003C/p\u003E\n\u003Cp\u003EHowever, a chain is not a physical device identity or a guarantee that the device has been cryptographically verified.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-responsibilities\u0022\u003EChain Responsibilities\u003C/h3\u003E\n\u003Cp\u003EA session chain can provide a boundary for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAssociating related sessions.\u003C/li\u003E\n\u003Cli\u003EMaintaining device or client context.\u003C/li\u003E\n\u003Cli\u003ERecording relevant activity.\u003C/li\u003E\n\u003Cli\u003ETracking session rotation.\u003C/li\u003E\n\u003Cli\u003EApplying chain-level security operations.\u003C/li\u003E\n\u003Cli\u003ERevoking related sessions together.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis allows UltimateAuth to distinguish between the lifecycle of an individual session and the continuity of the environment associated with it.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022device-association\u0022\u003EDevice Association\u003C/h3\u003E\n\u003Cp\u003EA chain may include device-related information such as a device identifier, platform, operating system, or browser.\u003C/p\u003E\n\u003Cp\u003EThese attributes help describe the client environment.\u003C/p\u003E\n\u003Cp\u003EThey must not be treated as independent proof of device ownership.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E for the device association model and its security boundaries.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-individual-authentication-state\u0022\u003ESession: Individual Authentication State\u003C/h2\u003E\n\u003Cp\u003EA \u003Ccode\u003ESession\u003C/code\u003E represents an individual unit of server-managed authentication state.\u003C/p\u003E\n\u003Cp\u003EIt is associated with a chain and participates in the validity rules established by the authentication mode and session configuration.\u003C/p\u003E\n\u003Cp\u003EA session may contain information relevant to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EIts associated identity and chain.\u003C/li\u003E\n\u003Cli\u003ECreation and expiration.\u003C/li\u003E\n\u003Cli\u003ERevocation status.\u003C/li\u003E\n\u003Cli\u003ESecurity version at creation.\u003C/li\u003E\n\u003Cli\u003EAuthentication lifecycle and validity.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA session is not necessarily equivalent to an access token.\u003C/p\u003E\n\u003Cp\u003EIn opaque authentication modes, a client credential may reference server-managed session state.\u003C/p\u003E\n\u003Cp\u003EIn JWT-based modes, an issued access token may be validated locally without consulting the session hierarchy on every protected request.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe token is authentication material. The session is a security-state concept.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EKeeping these concepts separate is essential for understanding UltimateAuth\u0027s authentication modes.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-creation\u0022\u003ESession Creation\u003C/h2\u003E\n\u003Cp\u003EA session lifecycle begins when an authentication flow successfully establishes the required authentication state.\u003C/p\u003E\n\u003Cp\u003EFor modes using the server-managed session hierarchy, the process conceptually involves:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EResolving the authenticated user identity.\u003C/li\u003E\n\u003Cli\u003EResolving or establishing the corresponding session root.\u003C/li\u003E\n\u003Cli\u003EDetermining the appropriate session chain.\u003C/li\u003E\n\u003Cli\u003ECreating or updating the required session state.\u003C/li\u003E\n\u003Cli\u003EIssuing the authentication material required by the selected mode.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe authentication flow must satisfy its verification and security requirements before establishing a valid authenticated session.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-resolution\u0022\u003ERoot Resolution\u003C/h3\u003E\n\u003Cp\u003EAn existing account-level session root can be reused when appropriate.\u003C/p\u003E\n\u003Cp\u003EIf the required root does not exist, the session infrastructure can establish it as part of session creation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-resolution\u0022\u003EChain Resolution\u003C/h3\u003E\n\u003Cp\u003EThe session infrastructure determines whether authentication should be associated with an existing chain or a new chain.\u003C/p\u003E\n\u003Cp\u003EA previously observed device identifier does not automatically establish that an existing chain is valid or eligible for reuse.\u003C/p\u003E\n\u003Cp\u003EThe decision must respect the applicable chain lifecycle and security conditions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-establishment\u0022\u003ESession Establishment\u003C/h3\u003E\n\u003Cp\u003EOnce the appropriate security context has been established, the authentication system creates or updates the session state required by the authentication mode.\u003C/p\u003E\n\u003Cp\u003EThe resulting client credential is then issued according to that mode.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022active-session-usage\u0022\u003EActive Session Usage\u003C/h2\u003E\n\u003Cp\u003EAfter authentication, the client presents the appropriate authentication material when accessing protected resources.\u003C/p\u003E\n\u003Cp\u003EWhat happens next depends on the configured mode.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, protected requests can be evaluated against current server-managed session state.\u003C/p\u003E\n\u003Cp\u003EIn locally validated JWT modes, protected requests may be authenticated using the token\u0027s cryptographic validity and claims without loading the current session record.\u003C/p\u003E\n\u003Cp\u003EThis distinction affects session activity tracking, expiration enforcement, and revocation behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022touch-operations\u0022\u003ETouch Operations\u003C/h3\u003E\n\u003Cp\u003EA touch operation records qualifying session activity.\u003C/p\u003E\n\u003Cp\u003EDepending on the session strategy, it may update information such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003ELastSeenAt\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ETouchCount\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003ERelevant session activity or expiration state\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ETouch behavior is not necessarily performed for every HTTP request.\u003C/p\u003E\n\u003Cp\u003EApplications should not assume that an authenticated request always results in a persistent session update.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022sliding-expiration\u0022\u003ESliding Expiration\u003C/h3\u003E\n\u003Cp\u003ESliding expiration allows eligible authentication state to remain valid as qualifying activity occurs.\u003C/p\u003E\n\u003Cp\u003EUnlike a fixed expiration, sliding expiration can extend a session\u0027s usable lifetime according to configured rules.\u003C/p\u003E\n\u003Cp\u003EHowever, sliding expiration does not necessarily remove absolute lifetime restrictions.\u003C/p\u003E\n\u003Cp\u003EA session may still become invalid when a maximum permitted lifetime is reached.\u003C/p\u003E\n\u003Cp\u003EThe exact behavior depends on the selected authentication mode and its session configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022refresh-and-rotation\u0022\u003ERefresh and Rotation\u003C/h2\u003E\n\u003Cp\u003ERefresh and rotation are related to session lifecycle but are not identical operations.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERefresh\u003C/strong\u003E is an authentication operation used to continue or renew authentication state.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERotation\u003C/strong\u003E replaces authentication material or session-related credentials according to the configured security strategy.\u003C/p\u003E\n\u003Cp\u003EThe relationship between these operations varies by authentication mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pureopaque\u0022\u003EPureOpaque\u003C/h3\u003E\n\u003Cp\u003EPureOpaque uses server-authoritative opaque session authentication.\u003C/p\u003E\n\u003Cp\u003EIt does not require a separate access-token and refresh-token pair.\u003C/p\u003E\n\u003Cp\u003ESession continuity and activity management are handled through the applicable opaque session lifecycle.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hybrid\u0022\u003EHybrid\u003C/h3\u003E\n\u003Cp\u003EHybrid uses distinct opaque access and refresh credentials backed by server-authoritative session state.\u003C/p\u003E\n\u003Cp\u003ERefresh operations can renew authentication material while preserving the appropriate session or chain relationship.\u003C/p\u003E\n\u003Cp\u003EThe exact rotation behavior follows the configured refresh strategy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022semihybrid\u0022\u003ESemiHybrid\u003C/h3\u003E\n\u003Cp\u003ESemiHybrid uses JWT access tokens while maintaining server-side session state for lifecycle operations such as refresh and logout.\u003C/p\u003E\n\u003Cp\u003ERefresh may issue new JWT access material and update the relevant server-managed state.\u003C/p\u003E\n\u003Cp\u003EAlready-issued access tokens may remain valid until expiration because protected requests do not necessarily consult that state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022purejwt\u0022\u003EPureJwt\u003C/h3\u003E\n\u003Cp\u003EPureJwt primarily relies on stateless JWT validation for protected requests.\u003C/p\u003E\n\u003Cp\u003EA refresh mechanism may use an independent refresh store, but the server-managed Root \u2192 Chain \u2192 Session hierarchy must not be assumed to govern every protected request.\u003C/p\u003E\n\u003Cp\u003EThis is an important distinction between the framework\u0027s common authentication concepts and the persistence requirements of individual modes.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-expiration\u0022\u003ESession Expiration\u003C/h2\u003E\n\u003Cp\u003ESession expiration defines when authentication state is no longer eligible for continued use.\u003C/p\u003E\n\u003Cp\u003EDepending on the authentication mode and configuration, expiration may involve:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EExpiration mechanism\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\u003Cstrong\u003EFixed lifetime\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication state expires after a defined duration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EIdle timeout\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication state expires after insufficient qualifying activity\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ESliding expiration\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EEligible activity extends the usable lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAbsolute expiration\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication state cannot extend beyond a maximum lifetime\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese mechanisms are not necessarily all enabled or applicable in every authentication mode.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022expired-versus-revoked\u0022\u003EExpired Versus Revoked\u003C/h3\u003E\n\u003Cp\u003EExpiration and revocation represent different reasons for invalidation.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EExpiration\u003C/strong\u003E occurs because a configured time-based condition has been reached.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ERevocation\u003C/strong\u003E occurs because authentication state has been explicitly invalidated through a security operation.\u003C/p\u003E\n\u003Cp\u003EBoth can make a session unusable, but they represent different lifecycle events and may be recorded or enforced differently.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-revocation\u0022\u003ESession Revocation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth\u0027s hierarchical session model supports security operations at different levels.\u003C/p\u003E\n\u003Cp\u003EThe scope of revocation determines how broadly authentication state is affected.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-level-revocation\u0022\u003ESession-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ESession-level revocation targets an individual session.\u003C/p\u003E\n\u003Cp\u003EIt is appropriate when a specific authentication instance must be invalidated without necessarily affecting other sessions associated with the same user.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022chain-level-revocation\u0022\u003EChain-Level Revocation\u003C/h3\u003E\n\u003Cp\u003EChain-level revocation targets the session continuity represented by a chain.\u003C/p\u003E\n\u003Cp\u003EIt allows related sessions to be invalidated together.\u003C/p\u003E\n\u003Cp\u003EThis can be useful when a particular client environment is suspected of compromise or when a user wants to terminate sessions associated with one device context.\u003C/p\u003E\n\u003Cp\u003ERevoking a chain does not mean that the corresponding physical device is permanently blocked from authenticating again.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-level-revocation\u0022\u003ERoot-Level Revocation\u003C/h3\u003E\n\u003Cp\u003ERoot-level revocation affects the account-level authentication security boundary.\u003C/p\u003E\n\u003Cp\u003EIt provides a mechanism for invalidating authentication state associated with the user\u0027s session hierarchy.\u003C/p\u003E\n\u003Cp\u003ESecurity version changes can support this operation by making previously created sessions fail subsequent version validation.\u003C/p\u003E\n\u003Cp\u003EThis is useful for broad security responses, including operations that require invalidating all applicable existing sessions.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-scope\u0022\u003ERevocation Scope\u003C/h3\u003E\n\u003Cp\u003EConceptually:\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 revocation\n \u2514\u2500\u2500 One session\n\nChain revocation\n \u2514\u2500\u2500 Related sessions in one chain\n\nRoot revocation\n \u2514\u2500\u2500 Account-level session hierarchy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe effective invalidation of protected requests depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EIn particular, server-side revocation does not automatically invalidate a previously issued JWT that is still cryptographically valid and is not checked against current revocation state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-security-and-credential-changes\u0022\u003ESession Security and Credential Changes\u003C/h2\u003E\n\u003Cp\u003ESession lifecycle is closely related to credential security.\u003C/p\u003E\n\u003Cp\u003EA password change or recovery operation may require existing authentication sessions to be preserved or revoked.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports session handling policies for credential changes and resets, including:\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\u003EBehavior\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 applicable 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\u003EThese policies allow applications to choose the security response appropriate to the operation.\u003C/p\u003E\n\u003Cp\u003EFor example, an authenticated password change and an anonymous password recovery operation may require different session handling.\u003C/p\u003E\n\u003Cp\u003EThe Credentials domain initiates the relevant security behavior, while the session infrastructure applies the corresponding lifecycle operations.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-configuration\u0022\u003ESession Configuration\u003C/h2\u003E\n\u003Cp\u003ESession behavior is configured through UltimateAuth\u0027s session options, including \u003Ccode\u003EUAuthSessionOptions\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe configuration model governs supported aspects of session lifecycle such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession lifetimes.\u003C/li\u003E\n\u003Cli\u003EActivity and expiration behavior.\u003C/li\u003E\n\u003Cli\u003ERefresh and rotation strategies.\u003C/li\u003E\n\u003Cli\u003ESession and chain limits.\u003C/li\u003E\n\u003Cli\u003EDevice-related session constraints.\u003C/li\u003E\n\u003Cli\u003ERevocation and lifecycle enforcement.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ENot every option applies to every authentication mode.\u003C/p\u003E\n\u003Cp\u003EFor example, a configuration setting that controls server-side session activity may not affect the validation of a stateless JWT access token.\u003C/p\u003E\n\u003Cp\u003EApplications should configure session behavior with the selected authentication mode and its enforcement boundaries in mind.\u003C/p\u003E\n\u003Cp\u003EDetailed configuration examples belong in the configuration reference and How-to Guides.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates authentication identity, session continuity, and individual authentication state.\u003C/p\u003E\n\u003Cp\u003EThe Root \u2192 Chain \u2192 Session hierarchy expresses those responsibilities at different security levels:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ERoot\u003C/strong\u003E provides account-level authentication security.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EChain\u003C/strong\u003E organizes related session continuity.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003ESession\u003C/strong\u003E represents individual server-managed authentication state.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis structure supports targeted session management without requiring all security operations to act on every session belonging to a user.\u003C/p\u003E\n\u003Cp\u003EAt the same time, UltimateAuth does not assume that every authentication mode requires identical server-side state.\u003C/p\u003E\n\u003Cp\u003EThe hierarchy is most directly relevant to session-managed authentication, while JWT-based modes preserve their own validation and lifecycle characteristics.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAuthentication establishes trust. Session management determines how that trust is maintained, renewed, and invalidated over time.\u003C/strong\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/sessions-devices/device-management\u0022\u003EDevice Management\u003C/a\u003E \u2014 Device context, chain association, and device-aware session management.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security\u003C/a\u003E \u2014 Session expiration, rotation, revocation, and security controls.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E \u2014 Understand how session behavior differs between modes.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/request-lifecycle\u0022\u003ERequest Lifecycle\u003C/a\u003E \u2014 Learn how protected requests validate authentication state.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "understanding-the-session-hierarchy", diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/wwwroot/app.css b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/wwwroot/app.css index b505f1c4..ea777af5 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/wwwroot/app.css +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm/wwwroot/app.css @@ -430,7 +430,7 @@ h1:focus { .docs-content code { font-family: Consolas, monospace; - font-size: 0.9rem; + font-size: 0.9em; } .docs-content p code { @@ -742,3 +742,35 @@ h1:focus { color: #b4d989; border-color: #5b8664; } + +.docs-content blockquote { + margin: 0.85rem 0 1.1rem; + padding: 0 0 0 0.85rem; + border: none; + border-left: 2px solid var(--mud-palette-lines-default); + border-radius: 0; + background: transparent; + color: var(--mud-palette-text-secondary); + font-size: 0.85rem; + font-weight: 400; + line-height: 1.55; +} + + .docs-content blockquote p { + margin: 0; + } + + .docs-content blockquote p + p { + margin-top: 0.4rem; + } + + .docs-content blockquote strong { + color: inherit; + font-weight: 600; + } + + .docs-content blockquote a { + color: inherit; + text-decoration: underline; + text-underline-offset: 2px; + } From 23447a14f622ea048827a87b4627d1856bcc9dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Fri, 16 Oct 2026 21:09:07 +0300 Subject: [PATCH 5/7] Footer and more home sections --- .../Layout/MainLayout.razor | 48 ++++++++++ .../Pages/DocsLandingPage.razor | 4 +- .../Pages/DocsSidebar.razor | 77 +++++++++++++++- .../Pages/Home.razor | 92 +++++++++++++++++++ .../wwwroot/app.css | 16 ++++ 5 files changed, 232 insertions(+), 5 deletions(-) diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Layout/MainLayout.razor b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Layout/MainLayout.razor index 943d43cf..415e3674 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Layout/MainLayout.razor +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Layout/MainLayout.razor @@ -77,6 +77,54 @@ @Body + + + + + + + + + + + + UltimateAuth + + + Open-source unified Auth for modern .NET. + + + + + Docs + Samples + GitHub + Discord + + + + + + + + + Β© @DateTime.Now.Year CodeBeam. + + + + Built for the .NET community. + + + + + + +
diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsLandingPage.razor b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsLandingPage.razor index 830dd6cb..62ec35e3 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsLandingPage.razor +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsLandingPage.razor @@ -126,9 +126,7 @@ refresh rotation, and the policy pipeline. - + Open Security Docs diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsSidebar.razor b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsSidebar.razor index 2f207df7..c0879aa2 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsSidebar.razor +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsSidebar.razor @@ -13,7 +13,8 @@ DOCUMENTATION @* UAuth Docs *@ - + + } @@ -40,7 +41,8 @@ Overview @foreach (var group in _groups) { - + @foreach (var item in group.Value) { @item.Title @@ -63,9 +65,54 @@ [Parameter] public bool Inline { get; set; } + private readonly Dictionary _expandedGroups = new(); + + private string? _activeGroup; + + private bool IsGroupExpanded(string group) + { + return _expandedGroups.TryGetValue(group, out var expanded) + ? expanded + : group == _activeGroup; + } + + private void SetGroupExpanded(string group, bool expanded) + { + _expandedGroups[group] = expanded; + StateHasChanged(); + } + + private void UpdateActiveGroup() + { + if (_groups is null) + return; + + var currentPath = Nav.ToBaseRelativePath(Nav.Uri) + .Split('?', '#')[0] + .Trim('/'); + + var activeItem = _groups + .SelectMany(g => g.Value) + .FirstOrDefault(item => + currentPath.Equals( + $"docs/{item.Slug.Trim('/')}", + StringComparison.OrdinalIgnoreCase)); + + var newGroup = activeItem?.Group; + + if (newGroup == _activeGroup) + return; + + _activeGroup = newGroup; + + if (newGroup is not null) + _expandedGroups[newGroup] = true; + } + protected override void OnInitialized() { _subscription = PersistentState.RegisterOnPersisting(PersistSidebarAsync); + Nav.LocationChanged += OnLocationChanged; } protected override async Task OnInitializedAsync() @@ -92,6 +139,7 @@ g => g.Key, g => g.OrderBy(x => x.Order).ToList() ); + UpdateActiveGroup(); } catch (HttpRequestException ex) { @@ -105,6 +153,15 @@ } } + private void OnLocationChanged(object? sender, LocationChangedEventArgs e) + { + _ = InvokeAsync(() => + { + UpdateActiveGroup(); + StateHasChanged(); + }); + } + private List? _items; private Task PersistSidebarAsync() @@ -137,8 +194,24 @@ return System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(group.Replace("-", " ")); } + private bool HasExpandedGroups => _groups is { Count: > 0 } && _groups.Keys.Any(IsGroupExpanded); + + private void ToggleAllGroups() + { + if (_groups is null) + return; + + bool expand = !HasExpandedGroups; + + foreach (var group in _groups.Keys) + { + _expandedGroups[group] = expand; + } + } + public void Dispose() { + Nav.LocationChanged -= OnLocationChanged; _subscription.Dispose(); } diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/Home.razor b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/Home.razor index 5be34301..e00fb76c 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/Home.razor +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/Home.razor @@ -90,6 +90,98 @@ + +
+ + + + + + + Explore UltimateAuth + + + + From documentation to working examples β€” + discover how UltimateAuth fits into your applications. + + + + + + + + + + + + Documentation + + + + Understand the architecture, authentication + flows, security model and integration patterns. + + + + Read the Docs + + + + + + + + + + + + Working Samples + + + + Explore real implementations across Blazor Server, + WebAssembly, UAuthHub and Resource APIs. + + + + Explore Samples + + + + + + + + + Explore the Source on GitHub + + + + + +