From 1201f6797d1025010ee07f6fea8b36a07269b9db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Sun, 18 Oct 2026 19:07:09 +0300 Subject: [PATCH 1/3] Improve open-source project readiness and compliance --- docs/content/getting-started/quickstart.md | 25 +- .../docs/auth-flows/device-management.json | 97 ----- .../wwwroot/docs/auth-flows/index.json | 87 ----- .../wwwroot/docs/auth-flows/login-flow.json | 4 +- .../wwwroot/docs/auth-flows/logout-flow.json | 4 +- .../wwwroot/docs/auth-flows/overview.json | 4 +- .../wwwroot/docs/auth-flows/refresh-flow.json | 4 +- .../docs/auth-flows/session-lifecycle.json | 102 ------ .../docs/auth-flows/token-behavior.json | 117 ------ .../docs/client-sdk/authentication.json | 4 +- .../docs/client-sdk/authorization.json | 4 +- .../wwwroot/docs/client-sdk/credentials.json | 4 +- .../wwwroot/docs/client-sdk/identifiers.json | 4 +- .../wwwroot/docs/client-sdk/overview.json | 4 +- .../docs/client-sdk/session-management.json | 4 +- .../docs/client-sdk/user-management.json | 4 +- .../wwwroot/docs/client/authentication.json | 332 ------------------ .../wwwroot/docs/client/authorization.json | 322 ----------------- .../wwwroot/docs/client/credentials.json | 307 ---------------- .../wwwroot/docs/client/identifiers.json | 287 --------------- .../wwwroot/docs/client/index.json | 97 ----- .../wwwroot/docs/client/overview.json | 97 ----- .../docs/client/session-management.json | 197 ----------- .../wwwroot/docs/client/user-management.json | 312 ---------------- .../configuration/advanced-configuration.json | 4 +- .../docs/configuration/client-options.json | 4 +- .../configuration/configuration-overview.json | 72 ---- .../configuration/configuration-sources.json | 87 ----- .../wwwroot/docs/configuration/index.json | 217 ------------ .../wwwroot/docs/configuration/overview.json | 4 +- .../docs/configuration/server-options.json | 4 +- .../wwwroot/docs/fundamentals/auth-model.json | 4 +- .../wwwroot/docs/fundamentals/auth-modes.json | 4 +- .../docs/fundamentals/client-profiles.json | 4 +- .../docs/fundamentals/flow-based-auth.json | 4 +- .../wwwroot/docs/fundamentals/index.json | 6 - .../wwwroot/docs/fundamentals/overview.json | 4 +- .../docs/fundamentals/request-lifecycle.json | 4 +- .../fundamentals/runtime-architecture.json | 4 +- .../docs/getting-started/ecosystem.json | 4 +- .../wwwroot/docs/getting-started/index.json | 4 +- .../docs/getting-started/quickstart.json | 19 +- .../getting-started/real-world-setup.json | 4 +- .../plugin-domains/authorization-domain.json | 157 --------- .../docs/plugin-domains/authorization.json | 4 +- .../plugin-domains/credential-domain.json | 112 ------ .../docs/plugin-domains/credentials.json | 4 +- .../wwwroot/docs/plugin-domains/index.json | 42 --- .../wwwroot/docs/plugin-domains/overview.json | 4 +- .../wwwroot/docs/plugin-domains/policies.json | 4 +- .../docs/plugin-domains/users-domain.json | 82 ----- .../wwwroot/docs/plugin-domains/users.json | 4 +- .../wwwroot/docs/readme.json | 47 --- .../docs/security/access-token-behavior.json | 97 ----- .../docs/security/authorization-pipeline.json | 4 +- .../docs/security/policy-pipeline.json | 77 ---- .../docs/security/refresh-rotation.json | 4 +- .../docs/security/session-security-model.json | 192 ---------- .../docs/security/session-security.json | 4 +- .../wwwroot/docs/security/token-security.json | 4 +- .../sessions-devices/device-management.json | 4 +- .../sessions-devices/session-lifecycle.json | 4 +- .../Program.cs | 69 ++-- 63 files changed, 180 insertions(+), 3619 deletions(-) delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/device-management.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/session-lifecycle.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/token-behavior.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/index.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/overview.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/session-management.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/user-management.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-overview.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-sources.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/index.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization-domain.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credential-domain.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/index.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/users-domain.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/readme.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/access-token-behavior.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/policy-pipeline.json delete mode 100644 docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security-model.json diff --git a/docs/content/getting-started/quickstart.md b/docs/content/getting-started/quickstart.md index 7eb68c3a..18dc311c 100644 --- a/docs/content/getting-started/quickstart.md +++ b/docs/content/getting-started/quickstart.md @@ -75,8 +75,7 @@ using CodeBeam.UltimateAuth.Server.Extensions; After creating the builder, register UltimateAuth: ```csharp -builder.Services - .AddUltimateAuthServer() +builder.Services.AddUltimateAuthServer() .AddUltimateAuthInMemory(); builder.Services.AddUltimateAuthClientBlazor(); @@ -152,15 +151,22 @@ 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 +## 6. Add Usings + +Add this in `_Imports.razor` + +```razor +@using CodeBeam.UltimateAuth.Client +@using CodeBeam.UltimateAuth.Client.Blazor +``` + +## 7. Initialize the UltimateAuth Application Open `Components/Routes.razor`. Replace its contents with: ```razor -@using CodeBeam.UltimateAuth.Client.Blazor - ``` @@ -172,15 +178,6 @@ 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. Add Usings - -Add this in `_Imports.razor` - -```razor -@using CodeBeam.UltimateAuth.Client -@using CodeBeam.UltimateAuth.Client.Blazor -``` - ## 8. Create Your Login Page Change `Components/Pages/Home.razor`: 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 deleted file mode 100644 index dbb6e101..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/device-management.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "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\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\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", - "Text": "\uD83E\uDDE0 Why Device Matters", - "Level": 0 - }, - { - "Id": "core-concept-chain-device", - "Text": "\uD83E\uDDE9 Core Concept: Chain = Device", - "Level": 0 - }, - { - "Id": "what-defines-a-device", - "Text": "\uD83D\uDD17 What Defines a Device?", - "Level": 0 - }, - { - "Id": "device-lifecycle", - "Text": "\uD83D\uDD04 Device Lifecycle", - "Level": 0 - }, - { - "Id": "first-login", - "Text": "1\uFE0F\u20E3 First Login", - "Level": 1 - }, - { - "Id": "subsequent-logins", - "Text": "2\uFE0F\u20E3 Subsequent Logins", - "Level": 1 - }, - { - "Id": "activity-touch", - "Text": "3\uFE0F\u20E3 Activity (Touch)", - "Level": 1 - }, - { - "Id": "token-rotation", - "Text": "4\uFE0F\u20E3 Token Rotation", - "Level": 1 - }, - { - "Id": "logout", - "Text": "5\uFE0F\u20E3 Logout", - "Level": 1 - }, - { - "Id": "revoke", - "Text": "6\uFE0F\u20E3 Revoke", - "Level": 1 - }, - { - "Id": "security-model", - "Text": "\uD83D\uDD10 Security Model", - "Level": 0 - }, - { - "Id": "device-binding", - "Text": "\uD83D\uDD17 Device Binding", - "Level": 1 - }, - { - "Id": "rotation-tracking", - "Text": "\uD83D\uDD01 Rotation Tracking", - "Level": 1 - }, - { - "Id": "revoke-cascade", - "Text": "\uD83D\uDEA8 Revoke Cascade", - "Level": 1 - }, - { - "Id": "configuration", - "Text": "\u2699\uFE0F Configuration", - "Level": 0 - }, - { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", - "Level": 0 - }, - { - "Id": "key-takeaways", - "Text": "\uD83D\uDCCC Key Takeaways", - "Level": 0 - }, - { - "Id": "next-step", - "Text": "\u27A1\uFE0F 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/auth-flows/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json deleted file mode 100644 index 8ca77d6a..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/index.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "Slug": "auth-flows/index", - "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/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 d60840a2..3cf683ab 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 @@ -93,5 +93,7 @@ "Text": "Next step", "Level": 0 } - ] + ], + "SourceHash": "95831894EDF3C21F6C20A367A9445C3173282799B50704499768BEAD96F51864", + "BuilderVersion": "1" } \ No newline at end of file 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 2abd4d91..2dad947d 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 @@ -153,5 +153,7 @@ "Text": "\u27A1\uFE0F Next step", "Level": 0 } - ] + ], + "SourceHash": "16E601FE603117928E01E64029AC1BDF0F5AD93F8108808F07817D0A2EB608CE", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/overview.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/overview.json index bd907ffc..4ec11523 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/overview.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/overview.json @@ -83,5 +83,7 @@ "Text": "Next step", "Level": 0 } - ] + ], + "SourceHash": "A2EB72E76BD9E836BEF7240D2D1FD72BF16C85F7D73C85160CFD908E689DDD4E", + "BuilderVersion": "1" } \ No newline at end of file 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 f67944ec..de5c82f6 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 @@ -73,5 +73,7 @@ "Text": "Next step", "Level": 0 } - ] + ], + "SourceHash": "A154282349B0F5C598E5E716B0A66A4480444FE2330BE60FCA79AA1C1D7CD04F", + "BuilderVersion": "1" } \ No newline at end of file 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 deleted file mode 100644 index 4902cf5e..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/session-lifecycle.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "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\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\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", - "Text": "\uD83E\uDDE0 Core Model", - "Level": 0 - }, - { - "Id": "root-identity-authority", - "Text": "\uD83D\uDD39 Root (Identity Authority)", - "Level": 1 - }, - { - "Id": "chain-device-context", - "Text": "\uD83D\uDCF1 Chain (Device Context)", - "Level": 1 - }, - { - "Id": "session-authentication-instance", - "Text": "\uD83D\uDD11 Session (Authentication Instance)", - "Level": 1 - }, - { - "Id": "relationship", - "Text": "\uD83D\uDD17 Relationship", - "Level": 0 - }, - { - "Id": "lifecycle-overview", - "Text": "\uD83D\uDD04 Lifecycle Overview", - "Level": 0 - }, - { - "Id": "creation-login", - "Text": "1\uFE0F\u20E3 Creation (Login)", - "Level": 1 - }, - { - "Id": "active-usage", - "Text": "2\uFE0F\u20E3 Active Usage", - "Level": 1 - }, - { - "Id": "refresh", - "Text": "3\uFE0F\u20E3 Refresh", - "Level": 1 - }, - { - "Id": "expiration", - "Text": "4\uFE0F\u20E3 Expiration", - "Level": 1 - }, - { - "Id": "revocation", - "Text": "5\uFE0F\u20E3 Revocation", - "Level": 1 - }, - { - "Id": "security-model", - "Text": "\uD83D\uDD10 Security Model", - "Level": 0 - }, - { - "Id": "security-versioning", - "Text": "\uD83D\uDD12 Security Versioning", - "Level": 1 - }, - { - "Id": "device-binding", - "Text": "\uD83D\uDD17 Device Binding", - "Level": 1 - }, - { - "Id": "rotation-tracking", - "Text": "\uD83D\uDD01 Rotation Tracking", - "Level": 1 - }, - { - "Id": "lifecycle-configuration", - "Text": "\u2699\uFE0F Lifecycle Configuration", - "Level": 1 - }, - { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", - "Level": 0 - }, - { - "Id": "key-takeaways", - "Text": "\uD83D\uDCCC Key Takeaways", - "Level": 0 - }, - { - "Id": "next-step", - "Text": "\u27A1\uFE0F 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/auth-flows/token-behavior.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/token-behavior.json deleted file mode 100644 index 1d7ebc7c..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/auth-flows/token-behavior.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "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\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", - "Text": "\uD83E\uDDE0 Rethinking Tokens", - "Level": 0 - }, - { - "Id": "token-types", - "Text": "\uD83E\uDDE9 Token Types", - "Level": 0 - }, - { - "Id": "opaque-tokens", - "Text": "\uD83D\uDD39 Opaque Tokens", - "Level": 1 - }, - { - "Id": "jwt-json-web-tokens", - "Text": "\uD83D\uDD39 JWT (JSON Web Tokens)", - "Level": 1 - }, - { - "Id": "mode-based-behavior", - "Text": "\u2696\uFE0F Mode-Based Behavior", - "Level": 0 - }, - { - "Id": "access-tokens", - "Text": "\uD83D\uDD11 Access Tokens", - "Level": 0 - }, - { - "Id": "characteristics", - "Text": "Characteristics", - "Level": 1 - }, - { - "Id": "important", - "Text": "Important", - "Level": 1 - }, - { - "Id": "refresh-tokens", - "Text": "\uD83D\uDD04 Refresh Tokens", - "Level": 0 - }, - { - "Id": "characteristics-1", - "Text": "Characteristics", - "Level": 1 - }, - { - "Id": "lifecycle", - "Text": "Lifecycle", - "Level": 1 - }, - { - "Id": "security-model", - "Text": "\uD83D\uDD10 Security Model", - "Level": 0 - }, - { - "Id": "rotation", - "Text": "\uD83D\uDD01 Rotation", - "Level": 1 - }, - { - "Id": "reuse-detection", - "Text": "\uD83D\uDEA8 Reuse Detection", - "Level": 1 - }, - { - "Id": "session-binding", - "Text": "\uD83D\uDD17 Session Binding", - "Level": 1 - }, - { - "Id": "hashed-storage", - "Text": "\uD83D\uDD12 Hashed Storage", - "Level": 1 - }, - { - "Id": "token-issuance", - "Text": "\uD83D\uDD04 Token Issuance", - "Level": 0 - }, - { - "Id": "access-token", - "Text": "Access Token", - "Level": 1 - }, - { - "Id": "refresh-token", - "Text": "Refresh Token", - "Level": 1 - }, - { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", - "Level": 0 - }, - { - "Id": "key-takeaways", - "Text": "\uD83D\uDCCC Key Takeaways", - "Level": 0 - }, - { - "Id": "next-step", - "Text": "\u27A1\uFE0F 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 index ebbd097d..3b4c2c4d 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authentication.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authentication.json @@ -328,5 +328,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "E08764ABC9FE77E47DEDD39DFB7BC9C174BD3482E71CF20D8547489902D0DC6D", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authorization.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authorization.json index 0c229dc2..aec3e496 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authorization.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/authorization.json @@ -318,5 +318,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "D6F142925A628E00731B2C2A8DF9DF60EAEB3CCD27C9A3D5C9D458376FF87651", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/credentials.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/credentials.json index 50c161bb..1a9590e6 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/credentials.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/credentials.json @@ -303,5 +303,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "DF1EFE42FAB4410FFCA4565BFB89489FAA283C600459D65A80F5FE5BE0DF1044", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/identifiers.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/identifiers.json index f2fe60ac..12fae5b6 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/identifiers.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/identifiers.json @@ -283,5 +283,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "8E4DEB0C994EC32D3379CC565FF9A18B7A30AAC11FBE6C2668F2CBC0D1DC0226", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/overview.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/overview.json index 6c3172b0..1b79ad60 100644 --- 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 @@ -93,5 +93,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "152FDA6808038B8192736EF34354F65C42565CF545396E6B52A7B831E8213610", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/session-management.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/session-management.json index 6beb6297..0026de63 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/session-management.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/session-management.json @@ -193,5 +193,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "FF79BEDC480EB6D1EB699C30604B98F72D86F0BE4187AF401B25DE17FBDE5252", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/user-management.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/user-management.json index 86a8c9ea..e07a1979 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/user-management.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/user-management.json @@ -308,5 +308,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "B8C4309BCFECF2ED5F4AE1CFDB7DD6303B0633F8576568BFA0D5497EE2E42DCF", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json deleted file mode 100644 index 7c5594ef..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authentication.json +++ /dev/null @@ -1,332 +0,0 @@ -{ - "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/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/authorization.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json deleted file mode 100644 index 959ef349..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/authorization.json +++ /dev/null @@ -1,322 +0,0 @@ -{ - "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\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/credentials.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json deleted file mode 100644 index b61389b9..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/credentials.json +++ /dev/null @@ -1,307 +0,0 @@ -{ - "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\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/identifiers.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json deleted file mode 100644 index 3bffd6b0..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/identifiers.json +++ /dev/null @@ -1,287 +0,0 @@ -{ - "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\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/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/index.json deleted file mode 100644 index 35d18cac..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/index.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "Slug": "client/index", - "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-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/overview.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/overview.json deleted file mode 100644 index 66047fef..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/overview.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "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 deleted file mode 100644 index 1bdf75ba..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/session-management.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "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-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/user-management.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/user-management.json deleted file mode 100644 index de32652a..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client/user-management.json +++ /dev/null @@ -1,312 +0,0 @@ -{ - "Slug": "client/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/configuration/advanced-configuration.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/advanced-configuration.json index e775f62d..c9383db7 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 @@ -373,5 +373,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "2350219DC5DA89C4803843184F261E65BEA3A29968116F65EF33C9A13E00DC1B", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/client-options.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/client-options.json index e2476278..800caaef 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 @@ -408,5 +408,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "B983498C65C1D5EC6AF5BDD7D78AD78955164848475FF72ADC1D6495E1A1183B", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-overview.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-overview.json deleted file mode 100644 index 2bd287de..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-overview.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "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\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-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", - "Text": "\u26A0\uFE0F A Common Misunderstanding", - "Level": 0 - }, - { - "Id": "layered-configuration-model", - "Text": "\uD83E\uDDE9 Layered Configuration Model", - "Level": 0 - }, - { - "Id": "core-behavior-definition", - "Text": "\uD83D\uDD39 Core (Behavior Definition)", - "Level": 1 - }, - { - "Id": "server-application-configuration", - "Text": "\uD83D\uDD39 Server (Application Configuration)", - "Level": 1 - }, - { - "Id": "client-runtime-behavior", - "Text": "\uD83D\uDD39 Client (Runtime Behavior)", - "Level": 1 - }, - { - "Id": "runtime-configuration-the-key-idea", - "Text": "\u26A1 Runtime Configuration (The Key Idea)", - "Level": 0 - }, - { - "Id": "how-it-works", - "Text": "\uD83E\uDDE0 How It Works", - "Level": 1 - }, - { - "Id": "from-static-to-dynamic", - "Text": "\uD83D\uDD04 From Static to Dynamic", - "Level": 0 - }, - { - "Id": "why-this-matters", - "Text": "\uD83C\uDFAF Why This Matters", - "Level": 0 - }, - { - "Id": "safety-by-design", - "Text": "\uD83D\uDEE1 Safety by Design", - "Level": 0 - }, - { - "Id": "core-vs-effective", - "Text": "\u2699\uFE0F Core vs Effective", - "Level": 0 - }, - { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", - "Level": 0 - }, - { - "Id": "next-step", - "Text": "\u27A1\uFE0F 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/configuration/configuration-sources.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-sources.json deleted file mode 100644 index 1a195360..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/configuration-sources.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "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\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-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", - "Text": "\uD83E\uDDE0 Two Ways to Configure", - "Level": 0 - }, - { - "Id": "code-program.cs", - "Text": "\uD83D\uDD39 Code (Program.cs)", - "Level": 1 - }, - { - "Id": "configuration-files-appsettings.json", - "Text": "\uD83D\uDD39 Configuration Files (appsettings.json)", - "Level": 1 - }, - { - "Id": "precedence-rules", - "Text": "\u2696\uFE0F Precedence Rules", - "Level": 0 - }, - { - "Id": "environment-based-configuration", - "Text": "\uD83C\uDF0D Environment-Based Configuration", - "Level": 0 - }, - { - "Id": "recommended-strategy", - "Text": "\uD83E\uDDE9 Recommended Strategy", - "Level": 0 - }, - { - "Id": "use-program.cs-for", - "Text": "\u2714 Use Program.cs for:", - "Level": 1 - }, - { - "Id": "use-appsettings-for", - "Text": "\u2714 Use appsettings for:", - "Level": 1 - }, - { - "Id": "safety-validation", - "Text": "\uD83D\uDEE1 Safety \u0026 Validation", - "Level": 0 - }, - { - "Id": "common-pitfalls", - "Text": "\u26A0\uFE0F Common Pitfalls", - "Level": 0 - }, - { - "Id": "assuming-code-overrides-config", - "Text": "\u274C Assuming code overrides config", - "Level": 1 - }, - { - "Id": "hardcoding-production-values", - "Text": "\u274C Hardcoding production values", - "Level": 1 - }, - { - "Id": "mixing-environments-unintentionally", - "Text": "\u274C Mixing environments unintentionally", - "Level": 1 - }, - { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", - "Level": 0 - }, - { - "Id": "key-takeaways", - "Text": "\uD83D\uDCCC Key Takeaways", - "Level": 0 - }, - { - "Id": "next-step", - "Text": "\u27A1\uFE0F 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/configuration/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json deleted file mode 100644 index 0ae66497..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/index.json +++ /dev/null @@ -1,217 +0,0 @@ -{ - "Slug": "configuration/index", - "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/configuration/overview.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/overview.json index c74f4be5..eccab71e 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/overview.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/overview.json @@ -213,5 +213,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "42602ECDDEC509FEAAEF5F7835FCE10B05B3FCAC730541040EF2E96C21180A86", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/server-options.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/configuration/server-options.json index 62cc23a3..399d4a59 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 @@ -538,5 +538,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "35AE1CE661A0DCC3570C2FF5F8F104352B0D607D3B3BDB3C2286C0D9B78052EC", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/auth-model.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/auth-model.json index 2f1c136a..eb71c661 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 @@ -158,5 +158,7 @@ "Text": "Next step", "Level": 0 } - ] + ], + "SourceHash": "77930C2897B345BFA0A34AB076BD4F3FD71EDF2974774053A818333FA1606E32", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/auth-modes.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/auth-modes.json index 98396abd..4b3a54e1 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 @@ -183,5 +183,7 @@ "Text": "Summary", "Level": 0 } - ] + ], + "SourceHash": "4DC1B8CE3E0AF60E3F0A66E58B12D70A167388FC09F554788A4B0DEDA21A7488", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/client-profiles.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/client-profiles.json index 6ab377c3..ebd8d50e 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 @@ -148,5 +148,7 @@ "Text": "Next step", "Level": 0 } - ] + ], + "SourceHash": "BD551E01000F1180F1BC1E3B20F70A758151252EB688C8A7882291BB2E040265", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/flow-based-auth.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/flow-based-auth.json index 44faf37a..6586b2fa 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 @@ -88,5 +88,7 @@ "Text": "Next step", "Level": 0 } - ] + ], + "SourceHash": "6371366D09791BCDFD207180BECD70AE6AE37CEC2D3979ED0510C4F47C56B4AB", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/index.json deleted file mode 100644 index d5c1b23a..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/index.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "Slug": "fundamentals/index", - "Title": "Fundamental 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/overview.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/overview.json index 6c2c249f..709fbd29 100644 --- 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 @@ -2,5 +2,7 @@ "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": [] + "Headings": [], + "SourceHash": "3A80E10B41EA9EB7B196C0D220166A111301C24D569FD70B17DD9108B2DEDFBC", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/request-lifecycle.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/request-lifecycle.json index c9d67fc4..a8d92595 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 @@ -108,5 +108,7 @@ "Text": "Next step", "Level": 0 } - ] + ], + "SourceHash": "C1C30AF56D8F793C08D750F9CE5749999D4C128EB80CE5B56E210AC32F573184", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/runtime-architecture.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/fundamentals/runtime-architecture.json index 129bacea..12eedbff 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 @@ -163,5 +163,7 @@ "Text": "Next step", "Level": 0 } - ] + ], + "SourceHash": "C5CDB1C29C2513D7903159DB78D2EECD64069A5BC28A248D87EE79BC3B48B98C", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/ecosystem.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/ecosystem.json index fda41be2..5b599679 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 @@ -198,5 +198,7 @@ "Text": "The Rule of Thumb", "Level": 0 } - ] + ], + "SourceHash": "925A6E9BEDF786703D57622BE34403833B73C81D632FB1C11782C18BDEF9C70A", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/index.json index 16a5c545..1af6c0ea 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 @@ -93,5 +93,7 @@ "Text": "Authentication is a lifecycle, not a login endpoint.", "Level": 1 } - ] + ], + "SourceHash": "F7A457DB94D9E4B4383CDE2C557C052B4E7AFBFD779257A5B795C4DF364D4F86", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json index b0efdf49..16eb652d 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/quickstart.json @@ -1,7 +1,7 @@ { "Slug": "getting-started/quickstart", "Title": "QuickStart", - "Html": "\n\u003Cp\u003E\u003Cstrong\u003EYour first UltimateAuth application, in just a few steps.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis guide walks you through creating a Blazor Server application, adding UltimateAuth with in-memory storage, and signing in with a sample account.\u003C/p\u003E\n\u003Cp\u003EYou\u0027ll start with a minimal setup. No database configuration, migrations, or custom authentication infrastructure required.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022prerequisites\u0022\u003EPrerequisites\u003C/h2\u003E\n\u003Cp\u003EYou\u0027ll need:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E.NET SDK (8 and higher, latest recommended)\u003C/li\u003E\n\u003Cli\u003EA code editor such as Visual Studio, VS Code, or Rider\u003C/li\u003E\n\u003Cli\u003EBasic familiarity with Blazor\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis guide uses \u003Cstrong\u003EBlazor Web App with Interactive Server rendering\u003C/strong\u003E and UltimateAuth\u0027s in-memory persistence bundle.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-blazor-application\u0022\u003E1. Create a Blazor Application\u003C/h2\u003E\n\u003Cp\u003EOpen a terminal and create a new 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/real-world-setup#persistent-storage-with-entity-framework-core\u0022\u003EEntity Framework Core Setup\u003C/a\u003E\u003C/strong\u003E \u2014 Move from in-memory storage to persistent authentication data.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Html": "\n\u003Cp\u003E\u003Cstrong\u003EYour first UltimateAuth application, in just a few steps.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis guide walks you through creating a Blazor Server application, adding UltimateAuth with in-memory storage, and signing in with a sample account.\u003C/p\u003E\n\u003Cp\u003EYou\u0027ll start with a minimal setup. No database configuration, migrations, or custom authentication infrastructure required.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022prerequisites\u0022\u003EPrerequisites\u003C/h2\u003E\n\u003Cp\u003EYou\u0027ll need:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E.NET SDK (8 and higher, latest recommended)\u003C/li\u003E\n\u003Cli\u003EA code editor such as Visual Studio, VS Code, or Rider\u003C/li\u003E\n\u003Cli\u003EBasic familiarity with Blazor\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis guide uses \u003Cstrong\u003EBlazor Web App with Interactive Server rendering\u003C/strong\u003E and UltimateAuth\u0027s in-memory persistence bundle.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-a-blazor-application\u0022\u003E1. Create a Blazor Application\u003C/h2\u003E\n\u003Cp\u003EOpen a terminal and create a new Blazor Interactive Server project:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet new blazor -n UltimateAuthDemo -int Server -ai\ncd UltimateAuthDemo\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis creates a Blazor Web App with Interactive Server components.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-ultimateauth\u0022\u003E2. Install UltimateAuth\u003C/h2\u003E\n\u003Cp\u003EInstall the Blazor client adapter and the in-memory bundle:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.InMemory.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor this tutorial, also install the sample seed package:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Sample.Seed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe packages serve different purposes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPackage\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EClient.Blazor\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EBlazor components, authentication state, and client integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EInMemory.Bundle\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIn-memory implementations for authentication domains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ESample.Seed\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDevelopment accounts and sample authorization data\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe in-memory bundle includes the relevant persistence implementations, so you do not need to register each store individually.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-ultimateauth-services\u0022\u003E3. Configure UltimateAuth Services\u003C/h2\u003E\n\u003Cp\u003EOpen \u003Ccode\u003EProgram.cs\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EAdd the following namespaces:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.Blazor;\nusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.InMemory;\nusing CodeBeam.UltimateAuth.Sample.Seed.Extensions;\nusing CodeBeam.UltimateAuth.Server.Extensions;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter creating the builder, register UltimateAuth:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthInMemory();\n\nbuilder.Services.AddUltimateAuthClientBlazor();\n\nbuilder.Services.AddUltimateAuthSampleSeed();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThat\u0027s the core service configuration.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E registers the authentication server.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthInMemory()\u003C/code\u003E provides in-memory stores.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthClientBlazor()\u003C/code\u003E registers the Blazor client integration.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthSampleSeed()\u003C/code\u003E prepares the development seed services.\u003C/p\u003E\n\u003Cp\u003EYou don\u0027t need to call \u003Ccode\u003EAddUltimateAuth()\u003C/code\u003E or \u003Ccode\u003EAddUltimateAuthClient()\u003C/code\u003E separately.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-http-pipeline\u0022\u003E4. Configure the HTTP Pipeline\u003C/h2\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\u003C/code\u003E, configure the application pipeline:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar app = builder.Build();\n\nif (!app.Environment.IsDevelopment())\n{\n app.UseExceptionHandler(\u0026quot;/Error\u0026quot;, createScopeForErrors: true);\n app.UseHsts();\n}\n\napp.UseHttpsRedirection();\napp.UseStaticFiles();\n\napp.UseUltimateAuthWithAspNetCore();\napp.UseAntiforgery();\napp.MapUltimateAuthEndpoints();\n\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveServerRenderMode()\n .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient());\n\nif (app.Environment.IsDevelopment())\n{\n await app.SeedUltimateAuthAsync();\n}\n\napp.Run();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EKeep the \u003Ccode\u003EAddRazorComponents().AddInteractiveServerComponents()\u003C/code\u003E registration generated by the Blazor project template.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EUseUltimateAuthWithAspNetCore()\u003C/code\u003E integrates UltimateAuth\u0027s middleware with ASP.NET Core authentication and authorization.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EMapUltimateAuthEndpoints()\u003C/code\u003E registers the authentication endpoints.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthRoutes()\u003C/code\u003E includes UltimateAuth\u0027s Blazor client route assemblies in endpoint discovery.\u003C/p\u003E\n\u003Cp\u003EThe development-only seed call creates the sample users before the application begins accepting requests.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-the-ultimateauth-script\u0022\u003E5. Add the UltimateAuth Script\u003C/h2\u003E\n\u003Cp\u003EOpen \u003Ccode\u003EComponents/App.razor\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EKeep the existing Blazor script and add UltimateAuth\u0027s JavaScript file:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EHTML\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-html\u0022\u003E\u0026lt;script src=\u0026quot;_content/CodeBeam.UltimateAuth.Client.Blazor/uauth.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EPlace these near the end of the document body.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s Blazor integration uses this script for browser-side functionality.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-usings\u0022\u003E6. Add Usings\u003C/h2\u003E\n\u003Cp\u003EAdd this in \u003Ccode\u003E_Imports.razor\u003C/code\u003E\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@using CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022initialize-the-ultimateauth-application\u0022\u003E7. Initialize the UltimateAuth Application\u003C/h2\u003E\n\u003Cp\u003EOpen \u003Ccode\u003EComponents/Routes.razor\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EReplace its contents with:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E\u0026lt;UAuthApp UseBuiltInRouter=\u0026quot;true\u0026quot; AppAssembly=\u0026quot;typeof(Program).Assembly\u0026quot; DefaultLayout=\u0026quot;typeof(Layout.MainLayout)\u0026quot; /\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthApp\u003C/code\u003E is the root integration component for UltimateAuth in Blazor.\u003C/p\u003E\n\u003Cp\u003EIt initializes the client runtime, provides authentication state to child components, and coordinates the authenticated session lifecycle.\u003C/p\u003E\n\u003Cp\u003EWith \u003Ccode\u003EUseBuiltInRouter=\u0026quot;true\u0026quot;\u003C/code\u003E, it also provides the application\u0027s router.\u003C/p\u003E\n\u003Cp\u003EYou don\u0027t need to create a separate \u003Ccode\u003ERouter\u003C/code\u003E or \u003Ccode\u003ECascadingAuthenticationState\u003C/code\u003E component for this setup.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-your-login-page\u0022\u003E8. Create Your Login Page\u003C/h2\u003E\n\u003Cp\u003EChange \u003Ccode\u003EComponents/Pages/Home.razor\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@page \u0026quot;/\u0026quot;\n@page \u0026quot;/login\u0026quot;\n@attribute [UAuthLoginPage]\n@inherits UAuthPageBase\n\n\u0026lt;div class=\u0026quot;container mt-5\u0026quot; style=\u0026quot;max-width: 400px;\u0026quot;\u0026gt;\n \u0026lt;h3\u0026gt;Sign in\u0026lt;/h3\u0026gt;\n \u0026lt;UAuthLoginForm Identifier=\u0026quot;@_identifier\u0026quot; Secret=\u0026quot;@_password\u0026quot; ReturnUrl=\u0026quot;/dashboard\u0026quot;\u0026gt;\n \u0026lt;div class=\u0026quot;d-block mb-3\u0026quot;\u0026gt;\n \u0026lt;label\u0026gt;Username\u0026lt;/label\u0026gt;\n \u0026lt;input @bind=\u0026quot;_identifier\u0026quot; class=\u0026quot;form-control w-100\u0026quot; autocomplete=\u0026quot;username\u0026quot; /\u0026gt;\n \u0026lt;/div\u0026gt;\n\n \u0026lt;div class=\u0026quot;d-block mb-3\u0026quot;\u0026gt;\n \u0026lt;label\u0026gt;Password\u0026lt;/label\u0026gt;\n \u0026lt;input type=\u0026quot;password\u0026quot; @bind=\u0026quot;_password\u0026quot; class=\u0026quot;form-control w-100\u0026quot; autocomplete=\u0026quot;current-password\u0026quot; /\u0026gt;\n \u0026lt;/div\u0026gt;\n\n \u0026lt;button class=\u0026quot;btn btn-primary w-100\u0026quot; type=\u0026quot;submit\u0026quot;\u0026gt;Sign in\u0026lt;/button\u0026gt;\n \u0026lt;/UAuthLoginForm\u0026gt;\n\u0026lt;/div\u0026gt;\n\n@code {\n private string _identifier = \u0026quot;\u0026quot;;\n private string _password = \u0026quot;\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthLoginForm\u003C/code\u003E handles the login submission through UltimateAuth\u0027s client flow.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003E[UAuthLoginPage]\u003C/code\u003E identifies this component as the application\u0027s login page.\u003C/p\u003E\n\u003Cp\u003EOnly designate one login page for this purpose.\u003C/p\u003E\n\u003Cp\u003EThe form above is intentionally minimal. You can replace the inputs with your preferred UI components later.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-protected-page\u0022\u003E9. Add a Protected Page\u003C/h2\u003E\n\u003Cp\u003ECreate \u003Ccode\u003EComponents/Pages/Dashboard.razor\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@page \u0026quot;/dashboard\u0026quot;\n@attribute [UAuthAuthorize]\n@inherits UAuthPageBase\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Dashboard\u0026lt;/h3\u0026gt;\n\n\u0026lt;p\u0026gt;You have reached an authenticated page.\u0026lt;/p\u0026gt;\n\u0026lt;UAuthStateView Roles=\u0026quot;Admin\u0026quot;\u0026gt;You have admin privileges.\u0026lt;/UAuthStateView\u0026gt;\n\u0026lt;p\u0026gt;User: @AuthState?.Identity?.DisplayName\u0026lt;/p\u0026gt;\n\n\u0026lt;button @onclick=\u0026quot;Logout\u0026quot; class=\u0026quot;btn btn-primary\u0026quot;\u0026gt;Logout\u0026lt;/button\u0026gt;\n\n@code {\n private async Task Logout()\n {\n await UAuthClient.Flows.LogoutAsync();\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E declares an UltimateAuth authorization requirement for the page.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E Client-side route protection is not a substitute for server-side authorization. Protected APIs and resources must enforce authorization on the server.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022run-the-application\u0022\u003E10. Run the Application\u003C/h2\u003E\n\u003Cp\u003EStart the application:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet run\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOpen the HTTPS address displayed in your terminal, then navigate to:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E/login\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe sample seed package creates two development accounts:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EUsername\u003C/th\u003E\n\u003Cth\u003EPassword\u003C/th\u003E\n\u003Cth\u003ERole\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Eadmin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Eadmin\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAdministrator\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003Euser\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Euser\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EStandard user\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ESign in with:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EUsername: admin\nPassword: admin\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter successful authentication, navigate to:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E/dashboard\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EYou now have a Blazor application using UltimateAuth authentication.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022your-first-ultimateauth-application-is-running\u0022\u003E\uD83C\uDF89 Your First UltimateAuth Application Is Running\u003C/h2\u003E\n\u003Cp\u003EWith this setup, you have:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAn authentication server integrated into ASP.NET Core.\u003C/li\u003E\n\u003Cli\u003EIn-memory user, credential, session, token, and authorization storage.\u003C/li\u003E\n\u003Cli\u003EA Blazor authentication client.\u003C/li\u003E\n\u003Cli\u003EA working login form.\u003C/li\u003E\n\u003Cli\u003EAuthentication state initialization.\u003C/li\u003E\n\u003Cli\u003ESession lifecycle coordination.\u003C/li\u003E\n\u003Cli\u003ESample users and authorization data.\u003C/li\u003E\n\u003Cli\u003EA page declaring an authentication requirement.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EYou did not need to create your own password hashing, token issuance, session validation, or authentication middleware implementations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-happened-when-you-logged-in\u0022\u003EWhat Happened When You Logged In?\u003C/h2\u003E\n\u003Cp\u003EFor this Blazor Server scenario, UltimateAuth uses the applicable session-oriented authentication model.\u003C/p\u003E\n\u003Cp\u003EThe login flow validates the credentials and establishes authentication state on the server.\u003C/p\u003E\n\u003Cp\u003EWhere a new session is created, it participates in UltimateAuth\u0027s root, chain, and session lifecycle.\u003C/p\u003E\n\u003Cp\u003EThe Blazor client then coordinates authentication state for the application.\u003C/p\u003E\n\u003Cp\u003EYou can explore these operations in detail later through the Authentication Flows and Session Management documentation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-in-memory-storage\u0022\u003EImportant: In-Memory Storage\u003C/h2\u003E\n\u003Cp\u003EThis QuickStart intentionally uses in-memory persistence.\u003C/p\u003E\n\u003Cp\u003EIt is ideal for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELearning UltimateAuth.\u003C/li\u003E\n\u003Cli\u003ELocal development.\u003C/li\u003E\n\u003Cli\u003ETesting authentication flows.\u003C/li\u003E\n\u003Cli\u003EBuilding prototypes.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003E\u003Cstrong\u003EIn-memory authentication data is not durable.\u003C/strong\u003E Users, credentials, sessions, and other stored state may be lost when the application restarts.\u003C/p\u003E\n\u003Cp\u003EThe sample credentials are also for development only. Do not deploy the seeded accounts or their example passwords in a production application.\u003C/p\u003E\n\u003Cp\u003EFor production use, configure durable persistence, appropriate secrets and signing keys, HTTPS, and the security settings required by your deployment.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/getting-started/ecosystem\u0022\u003EEcosystem\u003C/a\u003E\u003C/strong\u003E \u2014 Explore packages, integrations, and supported environments.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E\u003C/strong\u003E \u2014 Configure sessions, tokens, login policies, and security behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E\u003C/strong\u003E \u2014 Customize Blazor integration and authentication flows.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/auth-flows/overview\u0022\u003EAuthentication Flows\u003C/a\u003E\u003C/strong\u003E \u2014 Understand login, refresh, logout, and revocation.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003E\u003Ca href=\u0022/docs/getting-started/real-world-setup#persistent-storage-with-entity-framework-core\u0022\u003EEntity Framework Core Setup\u003C/a\u003E\u003C/strong\u003E \u2014 Move from in-memory storage to persistent authentication data.\u003C/li\u003E\n\u003C/ul\u003E\n", "Headings": [ { "Id": "prerequisites", @@ -33,24 +33,29 @@ "Text": "5. Add the UltimateAuth Script", "Level": 0 }, + { + "Id": "add-usings", + "Text": "6. Add Usings", + "Level": 0 + }, { "Id": "initialize-the-ultimateauth-application", - "Text": "6. Initialize the UltimateAuth Application", + "Text": "7. Initialize the UltimateAuth Application", "Level": 0 }, { "Id": "create-your-login-page", - "Text": "7. Create Your Login Page", + "Text": "8. Create Your Login Page", "Level": 0 }, { "Id": "add-a-protected-page", - "Text": "8. Add a Protected Page", + "Text": "9. Add a Protected Page", "Level": 0 }, { "Id": "run-the-application", - "Text": "9. Run the Application", + "Text": "10. Run the Application", "Level": 0 }, { @@ -73,5 +78,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "CB240CCA978FE45AD65EA09C882CE905FF7424CB6531541B1DEDAE55948572FE", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/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 d1ea0238..d828b7b9 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 @@ -308,5 +308,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "B62B0756A775A119E94E9873797C6AADF4B14F3BDC983D1788EC0FE33C63A08A", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization-domain.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization-domain.json deleted file mode 100644 index 2752df50..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization-domain.json +++ /dev/null @@ -1,157 +0,0 @@ -{ - "Slug": "plugin-domains/authorization-domain", - "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/authorization.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/authorization.json index 7a58645b..ed014038 100644 --- 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 @@ -153,5 +153,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "AF418595856AD36637B3600FB1D0FC9F390A09548F4140E9E387FD014C29BD4F", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credential-domain.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credential-domain.json deleted file mode 100644 index 1aa72c47..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credential-domain.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "Slug": "plugin-domains/credential-domain", - "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/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": "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/credentials.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credentials.json index 8605b5d0..6e4d4195 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credentials.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credentials.json @@ -108,5 +108,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "F0E1E773A693F7405C202D30CDB37AF1154B0EA97BCE385786157DF1B6E20D24", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/index.json deleted file mode 100644 index 89c89e36..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/index.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "Slug": "plugin-domains/index", - "Title": "Plugin Domains", - "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": "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/overview.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/overview.json index dd659e0b..c03faed4 100644 --- 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 @@ -38,5 +38,7 @@ "Text": "Explore the Domains", "Level": 0 } - ] + ], + "SourceHash": "38525D5C02A3A42ABD860AF9D3BA23D1FB8C0854CDAFB33A2C6251E6BA86BFA7", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/policies.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/policies.json index b69c2e67..761fccd8 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 @@ -128,5 +128,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "C47F024A8CF724F133B482142F79DA9A0159119CA682B2DBB2C660F9AE8E192F", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/users-domain.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/users-domain.json deleted file mode 100644 index 5a6f45dc..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/users-domain.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "Slug": "plugin-domains/users-domain", - "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/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": "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/plugin-domains/users.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/users.json index d0323db1..d46a6373 100644 --- 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 @@ -78,5 +78,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "13C75CB97B677253340246EBBE289AA49D6D519BFDB598CA00CE46E7A19126F9", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/readme.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/readme.json deleted file mode 100644 index 98a6eac6..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/readme.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Slug": "readme", - "Title": "\uD83D\uDD10 UltimateAuth Docs", - "Html": "\n\u003Cp\u003EThe modern, unified auth framework with platform-level abilities for .NET - Reimagined.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022sections\u0022\u003ESections\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth docs consists of 7 sections. There are suggested read line for starters:\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022getting-started\u0022\u003E1) Getting Started\u003C/h3\u003E\n\u003Cp\u003EStart here to integrate UltimateAuth into your application:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EGetting Started Guide\u003C/li\u003E\n\u003Cli\u003ESetup with different client profiles\u003C/li\u003E\n\u003Cli\u003EBasic usage\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022fundamentals\u0022\u003E2) Fundamentals\u003C/h3\u003E\n\u003Cp\u003EUnderstand how UltimateAuth works internally:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECore Concepts\u003C/li\u003E\n\u003Cli\u003ESession Architecture (Root \u2192 Chain \u2192 Session)\u003C/li\u003E\n\u003Cli\u003EToken Model\u003C/li\u003E\n\u003Cli\u003EAuthorization Model\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022auth-flows\u0022\u003E3) Auth Flows\u003C/h3\u003E\n\u003Cp\u003EExplore authentication lifecycle:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ELogin Flow\u003C/li\u003E\n\u003Cli\u003ERefresh Flow\u003C/li\u003E\n\u003Cli\u003ELogout Flow\u003C/li\u003E\n\u003Cli\u003ESession Lifecycle\u003C/li\u003E\n\u003Cli\u003EToken Behavior\u003C/li\u003E\n\u003Cli\u003EDevice Management\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022plugin-domains\u0022\u003E4) Plugin Domains\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth is built as modular domains:\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\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-extensibility\u0022\u003E5) Configuration \u0026amp; Extensibility\u003C/h3\u003E\n\u003Cp\u003ECustomize behavior and integrate with your system:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EServer Options\u003C/li\u003E\n\u003Cli\u003EClient Options\u003C/li\u003E\n\u003Cli\u003EConfiguration Sources\u003C/li\u003E\n\u003Cli\u003EAdvanced Configuration\u003C/li\u003E\n\u003Cli\u003EExtending Domains \u0026amp; Stores\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-api\u0022\u003E6) Client \u0026amp; API\u003C/h3\u003E\n\u003Cul\u003E\n\u003Cli\u003EReal world usage of UltimateAuth\u003C/li\u003E\n\u003Cli\u003EAPI and code examples of authentication, sessions, users, credentials and authorization\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-advanced\u0022\u003E7) Security \u0026amp; Advanced\u003C/h3\u003E\n\u003Cp\u003EDeep dive into the security model:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ESession Security Model\u003C/li\u003E\n\u003Cli\u003ERefresh Token Rotation\u003C/li\u003E\n\u003Cli\u003EAccess Token Behavior\u003C/li\u003E\n\u003Cli\u003EPolicy Pipeline\u003C/li\u003E\n\u003C/ul\u003E\n", - "Headings": [ - { - "Id": "sections", - "Text": "Sections", - "Level": 0 - }, - { - "Id": "getting-started", - "Text": "1) Getting Started", - "Level": 1 - }, - { - "Id": "fundamentals", - "Text": "2) Fundamentals", - "Level": 1 - }, - { - "Id": "auth-flows", - "Text": "3) Auth Flows", - "Level": 1 - }, - { - "Id": "plugin-domains", - "Text": "4) Plugin Domains", - "Level": 1 - }, - { - "Id": "configuration-extensibility", - "Text": "5) Configuration \u0026 Extensibility", - "Level": 1 - }, - { - "Id": "client-api", - "Text": "6) Client \u0026 API", - "Level": 1 - }, - { - "Id": "security-advanced", - "Text": "7) Security \u0026 Advanced", - "Level": 1 - } - ] -} \ No newline at end of file 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 deleted file mode 100644 index cc83e67b..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/access-token-behavior.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "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\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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", - "Text": "\uD83E\uDDE0 Core Principle", - "Level": 0 - }, - { - "Id": "token-types", - "Text": "\uD83D\uDD11 Token Types", - "Level": 0 - }, - { - "Id": "opaque-tokens", - "Text": "\uD83D\uDD39 Opaque Tokens", - "Level": 1 - }, - { - "Id": "jwt-tokens", - "Text": "\uD83D\uDD39 JWT Tokens", - "Level": 1 - }, - { - "Id": "mode-dependent-behavior", - "Text": "\u2699\uFE0F Mode-Dependent Behavior", - "Level": 0 - }, - { - "Id": "pureopaque", - "Text": "\uD83D\uDFE2 PureOpaque", - "Level": 1 - }, - { - "Id": "hybrid", - "Text": "\uD83D\uDFE1 Hybrid", - "Level": 1 - }, - { - "Id": "semihybrid", - "Text": "\uD83D\uDFE0 SemiHybrid", - "Level": 1 - }, - { - "Id": "purejwt", - "Text": "\uD83D\uDD35 PureJwt", - "Level": 1 - }, - { - "Id": "lifetime-strategy", - "Text": "\u23F1 Lifetime Strategy", - "Level": 0 - }, - { - "Id": "refresh-interaction", - "Text": "\uD83D\uDD04 Refresh Interaction", - "Level": 0 - }, - { - "Id": "claims-model", - "Text": "\uD83E\uDDFE Claims Model", - "Level": 0 - }, - { - "Id": "important-implication", - "Text": "\u26A0\uFE0F Important Implication", - "Level": 0 - }, - { - "Id": "security-boundaries", - "Text": "\uD83D\uDEE1 Security Boundaries", - "Level": 0 - }, - { - "Id": "why-this-matters", - "Text": "\uD83D\uDD25 Why This Matters", - "Level": 0 - }, - { - "Id": "design-tradeoff", - "Text": "\u26A0\uFE0F Design Tradeoff", - "Level": 0 - }, - { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", - "Level": 0 - }, - { - "Id": "next-step", - "Text": "\u27A1\uFE0F 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/security/authorization-pipeline.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/authorization-pipeline.json index 0a08921f..f09f6f13 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 @@ -163,5 +163,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "11AF093DB571457E519CECB1BD55B6F0D587DD1D831F6E574E4F070B3438A4CD", + "BuilderVersion": "1" } \ No newline at end of file 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 deleted file mode 100644 index db451763..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/policy-pipeline.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "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\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 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", - "Text": "\uD83E\uDDE0 Core Principle", - "Level": 0 - }, - { - "Id": "high-level-flow", - "Text": "\uD83D\uDD01 High-Level Flow", - "Level": 0 - }, - { - "Id": "accesscontext", - "Text": "\uD83E\uDDE9 AccessContext", - "Level": 0 - }, - { - "Id": "step-1-context-enrichment", - "Text": "\uD83D\uDD04 Step 1: Context Enrichment", - "Level": 0 - }, - { - "Id": "step-2-invariants", - "Text": "\uD83D\uDEE1 Step 2: Invariants", - "Level": 0 - }, - { - "Id": "step-3-global-policies", - "Text": "\uD83C\uDF10 Step 3: Global Policies", - "Level": 0 - }, - { - "Id": "step-4-runtime-policies", - "Text": "\uD83C\uDFAF Step 4: Runtime Policies", - "Level": 0 - }, - { - "Id": "step-5-decision-engine", - "Text": "\u2696\uFE0F Step 5: Decision Engine", - "Level": 0 - }, - { - "Id": "decision-rules", - "Text": "Decision Rules", - "Level": 1 - }, - { - "Id": "permission-integration", - "Text": "\uD83D\uDD10 Permission Integration", - "Level": 0 - }, - { - "Id": "why-this-matters", - "Text": "\uD83D\uDD25 Why This Matters", - "Level": 0 - }, - { - "Id": "design-tradeoff", - "Text": "\u26A0\uFE0F Design Tradeoff", - "Level": 0 - }, - { - "Id": "mental-model", - "Text": "\uD83E\uDDE0 Mental Model", - "Level": 0 - }, - { - "Id": "summary", - "Text": "\u27A1\uFE0F Summary", - "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 6b16d9af..79720b39 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 @@ -138,5 +138,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "A61B9A86C27B78D8CA03EFACAFE25AA055927E5D50CBC133B05CD6FA35ABD8EE", + "BuilderVersion": "1" } \ No newline at end of file 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 deleted file mode 100644 index a264287e..00000000 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security-model.json +++ /dev/null @@ -1,192 +0,0 @@ -{ - "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-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", - "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/session-security.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/session-security.json index 1d65cf71..f2c99d02 100644 --- 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 @@ -188,5 +188,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "194AA028AF9E80E803D5B86D7E14A9664B52DEBA8C5FAFC97E96A3D6CCD8E36C", + "BuilderVersion": "1" } \ 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 393c76fc..214278e9 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 @@ -228,5 +228,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "B4BA17260F7930DCAB85E5331354187F1B4C82B25291166BEC1DC28B8E709C03", + "BuilderVersion": "1" } \ 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 index 0f8a3ca6..35e69993 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 @@ -123,5 +123,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "29A80E248452FE72B5342F01C930525F9E7AEBAA54C89135E920CE360BFF88BF", + "BuilderVersion": "1" } \ 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 index c0782a55..95b35aec 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 @@ -158,5 +158,7 @@ "Text": "Next Steps", "Level": 0 } - ] + ], + "SourceHash": "9E2453DEE6714F4FBD585903C857D8ED6CE4ED58E764E89762CC833DB7AC1B55", + "BuilderVersion": "1" } \ No newline at end of file diff --git a/src/utilities/CodeBeam.UltimateAuth.DocsBuilder/Program.cs b/src/utilities/CodeBeam.UltimateAuth.DocsBuilder/Program.cs index 2696fa50..547c2999 100644 --- a/src/utilities/CodeBeam.UltimateAuth.DocsBuilder/Program.cs +++ b/src/utilities/CodeBeam.UltimateAuth.DocsBuilder/Program.cs @@ -2,10 +2,11 @@ using Markdig; using System.Text.Json; using System.Text.RegularExpressions; -using System.Xml; Console.WriteLine("=== DocsBuilder START ==="); +const string BuilderVersion = "1"; + var solutionRootDir = TryGetSolutionDir(args) ?? FindSolutionRootFallback(); Console.WriteLine($"SolutionRoot: {solutionRootDir}"); @@ -86,16 +87,33 @@ GroupOrder = groupOrder }); - var inputLastWrite = File.GetLastWriteTimeUtc(file); + var sourceHash = Convert.ToHexString( + System.Security.Cryptography.SHA256.HashData( + System.Text.Encoding.UTF8.GetBytes(markdown) + ) + ); if (File.Exists(outputPath)) { - var outputLastWrite = File.GetLastWriteTimeUtc(outputPath); - - if (outputLastWrite >= inputLastWrite) + try { - Console.WriteLine($"⏩ Skipped: {relativePath}"); - continue; + var existingJson = await File.ReadAllTextAsync(outputPath); + var existingDoc = + JsonSerializer.Deserialize(existingJson); + + if (existingDoc is not null && + existingDoc.SourceHash == sourceHash && + existingDoc.BuilderVersion == BuilderVersion && + existingDoc.Slug == slug && + existingDoc.Title == title) + { + Console.WriteLine($"⏩ Skipped: {relativePath}"); + continue; + } + } + catch (JsonException) + { + // Invalid cache: regenerate. } } @@ -115,7 +133,7 @@ if (!string.IsNullOrWhiteSpace(outputFolder)) Directory.CreateDirectory(outputFolder); - var doc = new DocOutput(slug, title, html, headings); + var doc = new DocOutput(slug, title, html, headings, sourceHash, BuilderVersion); var json = JsonSerializer.Serialize(doc, new JsonSerializerOptions { @@ -125,6 +143,23 @@ await File.WriteAllTextAsync(outputPath, json); } +// Remove generated JSON files whose Markdown sources no longer exist. +var expectedOutputs = markdownFiles + .Select(file => Path.GetFullPath(Path.Combine(websiteDocsOutputDir, Path.ChangeExtension(Path.GetRelativePath(sourceDocsDir, file), ".json")))) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + +foreach (var outputFile in Directory.GetFiles(websiteDocsOutputDir, "*.json", SearchOption.AllDirectories)) +{ + if (Path.GetFileName(outputFile).Equals("docs-index.json", StringComparison.OrdinalIgnoreCase)) + continue; + + if (!expectedOutputs.Contains(Path.GetFullPath(outputFile))) + { + File.Delete(outputFile); + Console.WriteLine($"🗑 Removed: {Path.GetRelativePath(websiteDocsOutputDir, outputFile)}"); + } +} + var ordered = allDocs .OrderBy(x => x.GroupOrder) .ThenBy(x => x.Group) @@ -325,23 +360,15 @@ static string WrapCodeBlocks(string html) _ => language.ToUpperInvariant() }; - var wrapper = HtmlNode.CreateNode( - "
" - ); + var wrapper = HtmlNode.CreateNode("
"); - var header = HtmlNode.CreateNode( - "
" - ); + var header = HtmlNode.CreateNode("
"); - var languageLabel = HtmlNode.CreateNode( - "" - ); + var languageLabel = HtmlNode.CreateNode(""); languageLabel.InnerHtml = HtmlEntity.Entitize(label); - var copyButton = HtmlNode.CreateNode( - "" - ); + var copyButton = HtmlNode.CreateNode(""); header.AppendChild(languageLabel); header.AppendChild(copyButton); @@ -355,7 +382,7 @@ static string WrapCodeBlocks(string html) return doc.DocumentNode.InnerHtml; } -internal sealed record DocOutput(string Slug, string Title, string Html, List Headings); +internal sealed record DocOutput(string Slug, string Title, string Html, List Headings, string SourceHash, string BuilderVersion); internal sealed class DocIndexItem { From bb1472f6832bec8e283139d898989858b7cee949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Sun, 18 Oct 2026 22:35:41 +0300 Subject: [PATCH 2/3] Moving To AwesomeAssertions From FluentAssertions --- .github/workflows/nuget-publish.yml | 15 +++++++++++++-- README.md | 6 +++++- ...m.UltimateAuth.Tests.Integration.EfCore.csproj | 2 +- .../UserCreationAtomicityTests.cs | 2 +- .../UserQueryPaginationTests.cs | 2 +- .../AuthorizationManagementTests.cs | 2 +- ...CodeBeam.UltimateAuth.Tests.Integration.csproj | 2 +- .../CredentialManagementTests.cs | 2 +- .../HttpContextJsonContractTests.cs | 2 +- .../LoginTests.cs | 2 +- .../LogoutAdminTests.cs | 2 +- .../LogoutTests.cs | 2 +- .../PkceFlowIntegrationTests.cs | 2 +- .../RefreshTests.cs | 2 +- .../ResourceApi/ResourceApiAuthenticationTests.cs | 2 +- .../ResourceApi/ResourceApiAuthorizationTests.cs | 2 +- .../ResourceApiTenantIsolationTests.cs | 2 +- .../SdkEnpointContractTests.cs | 2 +- .../SessionAdminTests.cs | 2 +- .../SessionSlidingExpirationTests.cs | 2 +- .../SessionTests.cs | 2 +- .../UAuthHubSampleSmokeTests.cs | 2 +- .../UserCreationAtomicityTests.cs | 2 +- .../UserIdentifierTests.cs | 4 ++-- .../UserLifecycleTests.cs | 2 +- .../UserProfileTests.cs | 2 +- ...thenticationSecurityStateStoreContractTests.cs | 2 +- .../AuthorizationEndpointHandlerTests.cs | 2 +- .../Authorization/AuthorizationServiceTests.cs | 2 +- .../Authorization/CompiledPermissionSetTests.cs | 2 +- .../Authorization/RoleServiceTests.cs | 2 +- .../Authorization/Store/RoleStoreContractTests.cs | 2 +- .../Store/UserRoleStoreContractTests.cs | 2 +- .../Authorization/UserRoleServiceTests.cs | 2 +- .../Bundle/UAuthDbContextTests.cs | 2 +- .../Bundle/UAuthEfCoreOptionsTests.cs | 2 +- ...imateAuthEntityFrameworkCoreExtensionsTests.cs | 2 +- .../Bunit/UAuthAppTests.cs | 2 +- .../Bunit/UAuthLoginFormTests.cs | 2 +- .../Bunit/UAuthStateViewTests.cs | 2 +- .../Client/AuthStateSnapshotFactoryTests.cs | 2 +- .../Client/Blazor/UAuthAppLifecycleTests.cs | 2 +- .../Client/Blazor/UAuthComponentBaseTests.cs | 2 +- .../Client/Blazor/UAuthHubLayoutBaseTests.cs | 2 +- .../Client/Blazor/UAuthHubPageBaseTests.cs | 2 +- .../Blazor/UAuthLoginFormSubmissionTests.cs | 2 +- .../Client/Blazor/UAuthLoginFormTests.cs | 2 +- .../Client/Blazor/UAuthPageBaseTests.cs | 2 +- .../Client/Blazor/UAuthRequestClientTests.cs | 2 +- .../Client/Blazor/UAuthStateViewTests.cs | 2 +- .../Client/ClientOptionsValidatorTests.cs | 2 +- .../Client/ClientProductInfoTests.cs | 2 +- .../Client/ClientProfileTests.cs | 2 +- .../Client/UAuthClientAuthorizationTests.cs | 2 +- .../Client/UAuthClientCredentialsTests.cs | 2 +- .../Client/UAuthClientEventConsistencyTests.cs | 2 +- .../Client/UAuthClientSessionTests.cs | 2 +- .../Client/UAuthClientUserTests.cs | 2 +- .../Client/UAuthFlowClientTests.cs | 2 +- .../Client/UAuthLoginPageDiscoveryTests.cs | 2 +- .../Client/UAuthLoginRedirectTests.cs | 2 +- .../Client/UAuthResultMapperTests.cs | 2 +- .../Client/UAuthStateManagerTests.cs | 2 +- .../Client/UAuthStateTests.cs | 2 +- .../CodeBeam.UltimateAuth.Tests.Unit.csproj | 2 +- .../Core/ClaimsSnapshotTests.cs | 2 +- .../Core/ConfigurationGuardsTests.cs | 2 +- .../Core/CoreProductInfoTests.cs | 2 +- .../Core/OptionValidatorTests.cs | 2 +- .../Core/PaginationTests.cs | 2 +- .../Credentials/ChangePasswordTests.cs | 2 +- .../Credentials/CredentialEndpointHandlerTests.cs | 2 +- .../CredentialManagementServiceTests.cs | 2 +- .../Credentials/ResetPasswordTests.cs | 2 +- .../Store/PasswordCredentialStoreContractTests.cs | 2 +- .../EfCoreSessionStoreTests.cs | 2 +- .../InMemory/InMemoryVersionedStoreTests.cs | 2 +- .../Policies/AccessPoliciesTests.cs | 2 +- .../Policies/PolicyBuilderTests.cs | 2 +- .../Policies/PolicyTests.cs | 2 +- .../ResourceApi/RemoteSessionValidatorTests.cs | 2 +- .../UAuthResourceApiRegistrationTests.cs | 2 +- .../Samples/HomeTests.cs | 2 +- .../Security/Argon2PasswordHasherTest.cs | 2 +- .../Argon2ServiceCollectionExtensionsTest.cs | 2 +- .../Server/AccessPipelineTests.cs | 2 +- .../Server/AuthenticationSecurityManagerTests.cs | 2 +- .../Server/ClientBaseAddressProviderTests.cs | 2 +- .../Server/CookieInfrastructureTests.cs | 2 +- .../Server/CredentialInfrastructureTests.cs | 2 +- .../Server/CredentialResponseWriterTests.cs | 2 +- .../Server/EffectiveServerOptionsProviderTests.cs | 2 +- .../Server/HandleHubTests.cs | 2 +- .../Server/HubCredentialResolverTests.cs | 2 +- .../Server/HubFlowReaderTests.cs | 2 +- .../Server/HubFlowServiceTests.cs | 2 +- .../Server/IdentifierNormalizerTests.cs | 2 +- .../Server/JwtTokenGeneratorTests.cs | 2 +- .../Server/LoginEndpointHandlerTests.cs | 2 +- .../Server/LoginOrchestratorTests.cs | 2 +- .../Server/LogoutEndpointHandlerTests.cs | 2 +- .../Server/PkceEndpointHandlerTests.cs | 2 +- .../Server/PkceServiceTests.cs | 2 +- .../Server/PkceTests.cs | 2 +- .../Server/RedirectTests.cs | 2 +- .../Server/RefreshFlowServiceTests.cs | 2 +- .../Server/RefreshTokenRotationServiceTests.cs | 2 +- .../Server/ResourceAccessContextBuilderTests.cs | 2 +- .../Server/ReturnUrlParserTests.cs | 2 +- .../Server/ServerOptionsValidatorTests.cs | 2 +- .../Server/SessionApplicationServiceTests.cs | 2 +- .../Server/SessionEndpointHandlerTests.cs | 2 +- .../Server/SessionTouchServiceTests.cs | 2 +- .../Server/TryLoginTests.cs | 2 +- .../Server/UAuthAuthenticationExtensionsTests.cs | 2 +- .../Server/UAuthAuthorizationHandlerTests.cs | 2 +- .../Server/UAuthFlowServiceTests.cs | 2 +- .../Server/UAuthPolicyProviderTests.cs | 2 +- .../UAuthResourceAccessOrchestratorTests.cs | 2 +- .../UAuthResourceAuthenticationHandlerTests.cs | 2 +- .../Server/UAuthServerProductInfoTests.cs | 2 +- .../Server/UAuthSessionQueryServiceTests.cs | 2 +- .../Server/UAuthSessionValidatorTests.cs | 2 +- .../Server/ValidateCredentialResolverTests.cs | 2 +- .../Server/ValidateEndpointHandlerTests.cs | 2 +- .../Sessions/BearerSessionIdResolverTests.cs | 2 +- .../Sessions/CompositeSessionIdResolverTests.cs | 2 +- .../Sessions/CookieSessionIdResolverTests.cs | 2 +- .../Sessions/HeaderSessionIdResolverTests.cs | 2 +- .../Sessions/HttpContextSessionExtensionsTests.cs | 2 +- .../Sessions/QuerySessionIdResolverTests.cs | 2 +- .../Sessions/SessionContextAccessorTests.cs | 2 +- .../Sessions/SessionTests.cs | 2 +- .../Store/SessionStoreChainContractTests.cs | 2 +- .../Store/SessionStoreLifecycleContractTests.cs | 2 +- .../Store/SessionStoreRootContractTests.cs | 2 +- .../Store/SessionStoreSessionContractTests.cs | 2 +- .../Sessions/UAuthSessionIssuerTests.cs | 2 +- .../Tokens/RefreshTokenStoreContractTests.cs | 2 +- .../Tokens/UAuthTokenIssuerTests.cs | 2 +- .../Query/UserSummaryQueryStoreContractTests.cs | 2 +- .../Store/UserIdentifierStoreContractTests.cs | 2 +- .../Store/UserLifecycleStoreContractTests.cs | 2 +- .../Users/Store/UserProfileStoreContractTests.cs | 2 +- .../Users/UserApplicationServiceTests.cs | 2 +- .../Users/UserCreateValidatorTests.cs | 2 +- .../Users/UserEndpointHandlerTests.cs | 2 +- .../UserIdentifierApplicationServiceTests.cs | 2 +- .../Users/UserIdentifierValidationTests.cs | 2 +- .../Users/UserProfileValidatorTests.cs | 2 +- .../Utility/JsonConverterTests.cs | 2 +- 151 files changed, 168 insertions(+), 153 deletions(-) diff --git a/.github/workflows/nuget-publish.yml b/.github/workflows/nuget-publish.yml index 7fc7768e..6377d652 100644 --- a/.github/workflows/nuget-publish.yml +++ b/.github/workflows/nuget-publish.yml @@ -60,10 +60,12 @@ jobs: /p:IncludeSymbols=true \ /p:SymbolPackageFormat=snupkg - - name: Verify packages + - name: Verify packages and symbols shell: bash run: | + set -euo pipefail shopt -s nullglob + packages=(./nupkgs/*.nupkg) if (( ${#packages[@]} == 0 )); then @@ -78,9 +80,18 @@ jobs: echo "::error::Unexpected package version: $f" exit 1 fi + + symbols="${f%.nupkg}.snupkg" + + if [[ ! -s "$symbols" ]]; then + echo "::error::Missing or empty symbols package: $symbols" + exit 1 + fi + + echo "Validated: $f + $symbols" done - echo "Validated ${#packages[@]} packages for version $VERSION" + echo "Validated ${#packages[@]} packages and symbols." - name: NuGet login (Trusted Publishing) uses: NuGet/login@v1 diff --git a/README.md b/README.md index 67c0b52a..9ad88bb5 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,14 @@ ![GitHub stars](https://img.shields.io/github/stars/CodeBeamOrg/UltimateAuth?style=flat&logo=github) ![Last Commit](https://img.shields.io/github/last-commit/CodeBeamOrg/UltimateAuth?branch=dev&logo=github) ![License](https://img.shields.io/github/license/CodeBeamOrg/UltimateAuth) -[![Discord](https://img.shields.io/discord/1459498792192839774?color=%237289da&label=Discord&logo=discord&logoColor=%237289da&style=flat-square)](https://discord.gg/QscA86dXSR) +[![Discord](https://img.shields.io/badge/Discord-Join-5865F2?style=flat-square&logo=discord&logoColor=white)](https://discord.gg/QscA86dXSR) [![codecov](https://codecov.io/gh/CodeBeamOrg/UltimateAuth/branch/dev/graph/badge.svg)](https://codecov.io/gh/CodeBeamOrg/UltimateAuth) [![NuGet version](https://img.shields.io/nuget/v/CodeBeam.UltimateAuth.Core?color=512bd4&label=nuget%20version&logo=nuget&style=flat-square)](https://www.nuget.org/packages/CodeBeam.UltimateAuth.Core) [![NuGet downloads](https://img.shields.io/nuget/dt/CodeBeam.UltimateAuth.Core?color=512bd4&label=nuget%20downloads&logo=nuget&style=flat-square)](https://www.nuget.org/packages/CodeBeam.UltimateAuth.Core) + + ## 📑 Table of Contents diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration.EfCore/CodeBeam.UltimateAuth.Tests.Integration.EfCore.csproj b/tests/CodeBeam.UltimateAuth.Tests.Integration.EfCore/CodeBeam.UltimateAuth.Tests.Integration.EfCore.csproj index c5f95081..d282dbc5 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration.EfCore/CodeBeam.UltimateAuth.Tests.Integration.EfCore.csproj +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration.EfCore/CodeBeam.UltimateAuth.Tests.Integration.EfCore.csproj @@ -9,7 +9,7 @@ - + diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration.EfCore/UserCreationAtomicityTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration.EfCore/UserCreationAtomicityTests.cs index e7099981..61190150 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration.EfCore/UserCreationAtomicityTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration.EfCore/UserCreationAtomicityTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Users.Contracts; using CodeBeam.UltimateAuth.Users.Reference; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; namespace CodeBeam.UltimateAuth.Tests.Integration.EfCore.Users; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration.EfCore/UserQueryPaginationTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration.EfCore/UserQueryPaginationTests.cs index c8d3b429..67b0454c 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration.EfCore/UserQueryPaginationTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration.EfCore/UserQueryPaginationTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Users.Contracts; using CodeBeam.UltimateAuth.Users.Reference; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; namespace CodeBeam.UltimateAuth.Tests.Integration.EfCore.Users; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/AuthorizationManagementTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/AuthorizationManagementTests.cs index 4e8d0096..ed912363 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/AuthorizationManagementTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/AuthorizationManagementTests.cs @@ -7,7 +7,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Tests.Integration.Helpers; using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using System.Net; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/CodeBeam.UltimateAuth.Tests.Integration.csproj b/tests/CodeBeam.UltimateAuth.Tests.Integration/CodeBeam.UltimateAuth.Tests.Integration.csproj index db10f6e6..956c4a59 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/CodeBeam.UltimateAuth.Tests.Integration.csproj +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/CodeBeam.UltimateAuth.Tests.Integration.csproj @@ -9,7 +9,7 @@ - + diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/CredentialManagementTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/CredentialManagementTests.cs index ab9450e4..95540fef 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/CredentialManagementTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/CredentialManagementTests.cs @@ -7,7 +7,7 @@ using CodeBeam.UltimateAuth.Credentials.Contracts; using CodeBeam.UltimateAuth.Credentials.Reference; using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/HttpContextJsonContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/HttpContextJsonContractTests.cs index c43bdac8..22ea0305 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/HttpContextJsonContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/HttpContextJsonContractTests.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Authorization.Contracts; using CodeBeam.UltimateAuth.Server.Extensions; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/LoginTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/LoginTests.cs index 0f94b67c..71fae4e0 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/LoginTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/LoginTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; using System.Net; using System.Net.Http.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/LogoutAdminTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/LogoutAdminTests.cs index 61deb287..2b34bdc5 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/LogoutAdminTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/LogoutAdminTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Defaults; using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; using System.Net; using System.Net.Http.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/LogoutTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/LogoutTests.cs index 3896b0e8..ba1b5f25 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/LogoutTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/LogoutTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; using System.Net; using System.Net.Http.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/PkceFlowIntegrationTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/PkceFlowIntegrationTests.cs index 6781cd5b..ae851ccf 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/PkceFlowIntegrationTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/PkceFlowIntegrationTests.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Defaults; using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; using System.Net; using System.Net.Http.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/RefreshTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/RefreshTests.cs index 93c56ff5..6ea68db0 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/RefreshTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/RefreshTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using System.Net; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/ResourceApi/ResourceApiAuthenticationTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/ResourceApi/ResourceApiAuthenticationTests.cs index 05cbb8d9..9d4c94b3 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/ResourceApi/ResourceApiAuthenticationTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/ResourceApi/ResourceApiAuthenticationTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Server; -using FluentAssertions; +using AwesomeAssertions; using Moq; using System.Net; using System.Net.Http.Headers; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/ResourceApi/ResourceApiAuthorizationTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/ResourceApi/ResourceApiAuthorizationTests.cs index 81dd27c5..520ef517 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/ResourceApi/ResourceApiAuthorizationTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/ResourceApi/ResourceApiAuthorizationTests.cs @@ -6,7 +6,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Policies.Abstractions; using CodeBeam.UltimateAuth.Server; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/ResourceApi/ResourceApiTenantIsolationTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/ResourceApi/ResourceApiTenantIsolationTests.cs index ef23ac53..bd199cd8 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/ResourceApi/ResourceApiTenantIsolationTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/ResourceApi/ResourceApiTenantIsolationTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Server; -using FluentAssertions; +using AwesomeAssertions; using Moq; using System.Net; using System.Net.Http.Headers; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/SdkEnpointContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/SdkEnpointContractTests.cs index 92a5d23b..2dbcd496 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/SdkEnpointContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/SdkEnpointContractTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Credentials.Contracts; using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/SessionAdminTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/SessionAdminTests.cs index 94834b9b..14f710a9 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/SessionAdminTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/SessionAdminTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Defaults; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; using System.Net; using System.Net.Http.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/SessionSlidingExpirationTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/SessionSlidingExpirationTests.cs index ce08b0a6..fb0fc9dd 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/SessionSlidingExpirationTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/SessionSlidingExpirationTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/SessionTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/SessionTests.cs index 474ec758..65bb06c6 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/SessionTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/SessionTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; using System.Net; using System.Net.Http.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/UAuthHubSampleSmokeTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/UAuthHubSampleSmokeTests.cs index f224fa9a..f657bc1d 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/UAuthHubSampleSmokeTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/UAuthHubSampleSmokeTests.cs @@ -1,5 +1,5 @@ using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; public sealed class UAuthHubSampleSmokeTests : IClassFixture diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/UserCreationAtomicityTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/UserCreationAtomicityTests.cs index 322ba131..071e052c 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/UserCreationAtomicityTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/UserCreationAtomicityTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; using CodeBeam.UltimateAuth.Users.Contracts; using CodeBeam.UltimateAuth.Users.Reference; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; namespace CodeBeam.UltimateAuth.Tests.Integration; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/UserIdentifierTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/UserIdentifierTests.cs index 287c15a9..82ce3a0f 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/UserIdentifierTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/UserIdentifierTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Users; using CodeBeam.UltimateAuth.Users.Contracts; using CodeBeam.UltimateAuth.Users.Reference; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using System.Net; @@ -253,7 +253,7 @@ public async Task IdentifierMutation_WhenIdBelongsToAnotherUser_ShouldReturnNotF }); var after = await store.GetByUserAsync(victim.UserKey); - using var assertions = new FluentAssertions.Execution.AssertionScope(); + using var assertions = new AwesomeAssertions.Execution.AssertionScope(); response.StatusCode.Should().Be(HttpStatusCode.NotFound); after.Should().BeEquivalentTo(before, "rejected operations must not mutate the victim or their primary identifiers"); diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/UserLifecycleTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/UserLifecycleTests.cs index 3cb5251e..bca72585 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/UserLifecycleTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/UserLifecycleTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; using CodeBeam.UltimateAuth.Users.Contracts; using CodeBeam.UltimateAuth.Users.Reference; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Integration/UserProfileTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Integration/UserProfileTests.cs index eac094f6..45dd55d9 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Integration/UserProfileTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Integration/UserProfileTests.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Tests.Integration.Infrastructure; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Mvc.Testing; using System.Net; using System.Net.Http.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authentication/AuthenticationSecurityStateStoreContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authentication/AuthenticationSecurityStateStoreContractTests.cs index 0f05a231..b3523c18 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authentication/AuthenticationSecurityStateStoreContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authentication/AuthenticationSecurityStateStoreContractTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Core.Security; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.Authentication.Contracts; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/AuthorizationEndpointHandlerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/AuthorizationEndpointHandlerTests.cs index 28bd0552..e357de36 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/AuthorizationEndpointHandlerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/AuthorizationEndpointHandlerTests.cs @@ -7,7 +7,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Server.Auth; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Moq; using System.Text.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/AuthorizationServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/AuthorizationServiceTests.cs index 6e0fe9c7..727c3797 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/AuthorizationServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/AuthorizationServiceTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Core.Errors; using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/CompiledPermissionSetTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/CompiledPermissionSetTests.cs index b24715ac..45998114 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/CompiledPermissionSetTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/CompiledPermissionSetTests.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Authorization; using CodeBeam.UltimateAuth.Authorization.Contracts; using CodeBeam.UltimateAuth.Core.MultiTenancy; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/RoleServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/RoleServiceTests.cs index dadb2d58..f8c0caae 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/RoleServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/RoleServiceTests.cs @@ -7,7 +7,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/Store/RoleStoreContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/Store/RoleStoreContractTests.cs index 04f647f1..b76b6146 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/Store/RoleStoreContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/Store/RoleStoreContractTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Core.Errors; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Contracts.Authorization; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/Store/UserRoleStoreContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/Store/UserRoleStoreContractTests.cs index 425cb690..1723e3bf 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/Store/UserRoleStoreContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/Store/UserRoleStoreContractTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Core.Errors; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.Authorization.Contracts; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/UserRoleServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/UserRoleServiceTests.cs index bc11a48b..b1ea60c5 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/UserRoleServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Authorization/UserRoleServiceTests.cs @@ -9,7 +9,7 @@ using CodeBeam.UltimateAuth.Core.Options; using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Bundle/UAuthDbContextTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Bundle/UAuthDbContextTests.cs index 5fff3bdb..86e5a127 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Bundle/UAuthDbContextTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Bundle/UAuthDbContextTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; using CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; using CodeBeam.UltimateAuth.Users.EntityFrameworkCore; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Tests.Unit.EntityFrameworkCore; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Bundle/UAuthEfCoreOptionsTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Bundle/UAuthEfCoreOptionsTests.cs index 65d899eb..62bb7c5b 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Bundle/UAuthEfCoreOptionsTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Bundle/UAuthEfCoreOptionsTests.cs @@ -1,5 +1,5 @@ using CodeBeam.UltimateAuth.EntityFrameworkCore; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.EntityFrameworkCore; namespace CodeBeam.UltimateAuth.Tests.Unit.EntityFrameworkCore; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Bundle/UltimateAuthEntityFrameworkCoreExtensionsTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Bundle/UltimateAuthEntityFrameworkCoreExtensionsTests.cs index babbafe1..827c9512 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Bundle/UltimateAuthEntityFrameworkCoreExtensionsTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Bundle/UltimateAuthEntityFrameworkCoreExtensionsTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; using CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore; using CodeBeam.UltimateAuth.Users.EntityFrameworkCore; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Bunit/UAuthAppTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Bunit/UAuthAppTests.cs index fa05c157..56020d90 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Bunit/UAuthAppTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Bunit/UAuthAppTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Client.Blazor; using CodeBeam.UltimateAuth.Client.Infrastructure; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Bunit/UAuthLoginFormTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Bunit/UAuthLoginFormTests.cs index bff645be..46e6bd12 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Bunit/UAuthLoginFormTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Bunit/UAuthLoginFormTests.cs @@ -7,7 +7,7 @@ using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.Errors; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Bunit/UAuthStateViewTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Bunit/UAuthStateViewTests.cs index 277146e7..0b046015 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Bunit/UAuthStateViewTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Bunit/UAuthStateViewTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/AuthStateSnapshotFactoryTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/AuthStateSnapshotFactoryTests.cs index e2acf3aa..973c98fc 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/AuthStateSnapshotFactoryTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/AuthStateSnapshotFactoryTests.cs @@ -6,7 +6,7 @@ using CodeBeam.UltimateAuth.Server.Auth; using CodeBeam.UltimateAuth.Users; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Moq; using System.Text.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthAppLifecycleTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthAppLifecycleTests.cs index dc12cb93..1883ef1d 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthAppLifecycleTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthAppLifecycleTests.cs @@ -7,7 +7,7 @@ using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthComponentBaseTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthComponentBaseTests.cs index 29e4837c..40dd863a 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthComponentBaseTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthComponentBaseTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using System.Security.Claims; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthHubLayoutBaseTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthHubLayoutBaseTests.cs index 3e68262b..02d61ba1 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthHubLayoutBaseTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthHubLayoutBaseTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Client.Blazor; using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.Domain; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthHubPageBaseTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthHubPageBaseTests.cs index 2ebe17d8..b4aad2c1 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthHubPageBaseTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthHubPageBaseTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.Defaults; using CodeBeam.UltimateAuth.Core.Domain; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthLoginFormSubmissionTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthLoginFormSubmissionTests.cs index 033ef102..ec25c253 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthLoginFormSubmissionTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthLoginFormSubmissionTests.cs @@ -8,7 +8,7 @@ using CodeBeam.UltimateAuth.Core.Defaults; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.Errors; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthLoginFormTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthLoginFormTests.cs index 50832445..4d17c72d 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthLoginFormTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthLoginFormTests.cs @@ -8,7 +8,7 @@ using CodeBeam.UltimateAuth.Core.Defaults; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.Options; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthPageBaseTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthPageBaseTests.cs index 532da561..b8517836 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthPageBaseTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthPageBaseTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Client.Blazor; using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthRequestClientTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthRequestClientTests.cs index d138cbf3..b2aeea08 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthRequestClientTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthRequestClientTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Client.Infrastructure; using CodeBeam.UltimateAuth.Client.Options; using CodeBeam.UltimateAuth.Core.Options; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Options; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthStateViewTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthStateViewTests.cs index 4ff25daf..f1a6819c 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthStateViewTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/Blazor/UAuthStateViewTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/ClientOptionsValidatorTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/ClientOptionsValidatorTests.cs index c897c7ae..bbe8b102 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/ClientOptionsValidatorTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/ClientOptionsValidatorTests.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Client.Options; using CodeBeam.UltimateAuth.Core.Options; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/ClientProductInfoTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/ClientProductInfoTests.cs index 3f717360..9c0d1c32 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/ClientProductInfoTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/ClientProductInfoTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Client.Runtime; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.Options; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Options; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/ClientProfileTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/ClientProfileTests.cs index 7dbeb1df..85e882e7 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/ClientProfileTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/ClientProfileTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Client.Options; using CodeBeam.UltimateAuth.Core.Options; using CodeBeam.UltimateAuth.Core.Runtime; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientAuthorizationTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientAuthorizationTests.cs index fa697b12..7590a37e 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientAuthorizationTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientAuthorizationTests.cs @@ -6,7 +6,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientCredentialsTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientCredentialsTests.cs index e4997a11..e35a07a2 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientCredentialsTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientCredentialsTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Credentials.Contracts; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientEventConsistencyTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientEventConsistencyTests.cs index 6acbe060..096f82bc 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientEventConsistencyTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientEventConsistencyTests.cs @@ -6,7 +6,7 @@ using CodeBeam.UltimateAuth.Client.Options; using CodeBeam.UltimateAuth.Client.Services; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Options; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientSessionTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientSessionTests.cs index ab42fc8f..25e5e6c5 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientSessionTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientSessionTests.cs @@ -7,7 +7,7 @@ using CodeBeam.UltimateAuth.Client.Services; using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Options; using Moq; using System.Text.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientUserTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientUserTests.cs index b7c319ff..40bb55b3 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientUserTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthClientUserTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Moq; using System.Text.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthFlowClientTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthFlowClientTests.cs index 6ac2c9b0..d979b72e 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthFlowClientTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthFlowClientTests.cs @@ -11,7 +11,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Options; using Moq; using System.Text.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthLoginPageDiscoveryTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthLoginPageDiscoveryTests.cs index 2a8a2855..dd0b7bb5 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthLoginPageDiscoveryTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthLoginPageDiscoveryTests.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Client; using CodeBeam.UltimateAuth.Client.Blazor.Infrastructure; using CodeBeam.UltimateAuth.Client.Infrastructure; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.Client.Infrastructure; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthLoginRedirectTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthLoginRedirectTests.cs index 7650562c..1aa94ff2 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthLoginRedirectTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthLoginRedirectTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Client.Blazor; using CodeBeam.UltimateAuth.Client.Infrastructure; using CodeBeam.UltimateAuth.Core.Defaults; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthResultMapperTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthResultMapperTests.cs index 475ab82e..3fa289ae 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthResultMapperTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthResultMapperTests.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Client.Contracts; using CodeBeam.UltimateAuth.Client.Errors; using CodeBeam.UltimateAuth.Client.Infrastructure; -using FluentAssertions; +using AwesomeAssertions; using System.Text.Json; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthStateManagerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthStateManagerTests.cs index 1ee40234..0f4f8883 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthStateManagerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthStateManagerTests.cs @@ -6,7 +6,7 @@ using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthStateTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthStateTests.cs index 8bc728dd..06dde860 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthStateTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Client/UAuthStateTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; -using FluentAssertions; +using AwesomeAssertions; using System.Security.Claims; using Xunit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/CodeBeam.UltimateAuth.Tests.Unit.csproj b/tests/CodeBeam.UltimateAuth.Tests.Unit/CodeBeam.UltimateAuth.Tests.Unit.csproj index 9a20db38..6940539a 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/CodeBeam.UltimateAuth.Tests.Unit.csproj +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/CodeBeam.UltimateAuth.Tests.Unit.csproj @@ -13,7 +13,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/ClaimsSnapshotTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/ClaimsSnapshotTests.cs index 6877d718..7f526328 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/ClaimsSnapshotTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/ClaimsSnapshotTests.cs @@ -1,5 +1,5 @@ using CodeBeam.UltimateAuth.Core.Domain; -using FluentAssertions; +using AwesomeAssertions; using System.Security.Claims; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/ConfigurationGuardsTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/ConfigurationGuardsTests.cs index e94a7109..4c509fff 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/ConfigurationGuardsTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/ConfigurationGuardsTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Options; using CodeBeam.UltimateAuth.Core.Runtime; using CodeBeam.UltimateAuth.Server.Extensions; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/CoreProductInfoTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/CoreProductInfoTests.cs index 0056fb0e..9e23501c 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/CoreProductInfoTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/CoreProductInfoTests.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Extensions; using CodeBeam.UltimateAuth.Core.Options; using CodeBeam.UltimateAuth.Core.Runtime; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using System.Reflection; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/OptionValidatorTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/OptionValidatorTests.cs index f2c6ac31..f9be1990 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/OptionValidatorTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/OptionValidatorTests.cs @@ -1,5 +1,5 @@ using CodeBeam.UltimateAuth.Core.Options; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/PaginationTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/PaginationTests.cs index 6654a14a..6136c8f1 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/PaginationTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Core/PaginationTests.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Options; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.Core; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/ChangePasswordTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/ChangePasswordTests.cs index 189c192c..1af5ece3 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/ChangePasswordTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/ChangePasswordTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Core.Defaults; using CodeBeam.UltimateAuth.Credentials.Contracts; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/CredentialEndpointHandlerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/CredentialEndpointHandlerTests.cs index 221e2435..ad37f9cd 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/CredentialEndpointHandlerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/CredentialEndpointHandlerTests.cs @@ -6,7 +6,7 @@ using CodeBeam.UltimateAuth.Credentials.Reference; using CodeBeam.UltimateAuth.Server.Auth; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/CredentialManagementServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/CredentialManagementServiceTests.cs index 356222f5..c0551c78 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/CredentialManagementServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/CredentialManagementServiceTests.cs @@ -13,7 +13,7 @@ using CodeBeam.UltimateAuth.Tests.Unit.Helpers; using CodeBeam.UltimateAuth.Users; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Options; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/ResetPasswordTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/ResetPasswordTests.cs index 0c8db7da..52aafab0 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/ResetPasswordTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/ResetPasswordTests.cs @@ -8,7 +8,7 @@ using CodeBeam.UltimateAuth.Credentials.Reference; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; using CodeBeam.UltimateAuth.Users; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; using System.Text.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/Store/PasswordCredentialStoreContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/Store/PasswordCredentialStoreContractTests.cs index 6afdaf5a..c5901281 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/Store/PasswordCredentialStoreContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Credentials/Store/PasswordCredentialStoreContractTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Credentials.Contracts; using CodeBeam.UltimateAuth.Credentials.Reference; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.Credentials.Contracts; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreSessionStoreTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreSessionStoreTests.cs index fee1f22c..d2c319d8 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreSessionStoreTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/EntityFrameworkCore/EfCoreSessionStoreTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Data.Sqlite; using System.Security.Claims; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/InMemory/InMemoryVersionedStoreTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/InMemory/InMemoryVersionedStoreTests.cs index f8d64643..73385ead 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/InMemory/InMemoryVersionedStoreTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/InMemory/InMemoryVersionedStoreTests.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.InMemory; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.InMemory; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Policies/AccessPoliciesTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Policies/AccessPoliciesTests.cs index 8d5f79c9..1dc64189 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Policies/AccessPoliciesTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Policies/AccessPoliciesTests.cs @@ -7,7 +7,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Policies; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Policies/PolicyBuilderTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Policies/PolicyBuilderTests.cs index 613a90f1..b01504b1 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Policies/PolicyBuilderTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Policies/PolicyBuilderTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Policies; using CodeBeam.UltimateAuth.Policies.Registry; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Policies/PolicyTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Policies/PolicyTests.cs index 8f25e3f0..18088136 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Policies/PolicyTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Policies/PolicyTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Policies.Registry; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; namespace CodeBeam.UltimateAuth.Tests.Unit.Policies; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/ResourceApi/RemoteSessionValidatorTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/ResourceApi/RemoteSessionValidatorTests.cs index 718b93b5..2b184062 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/ResourceApi/RemoteSessionValidatorTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/ResourceApi/RemoteSessionValidatorTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Core.Options; using CodeBeam.UltimateAuth.Server.ResourceApi; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using System.Net; using System.Net.Http.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/ResourceApi/UAuthResourceApiRegistrationTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/ResourceApi/UAuthResourceApiRegistrationTests.cs index 275aa81f..5ab5ab59 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/ResourceApi/UAuthResourceApiRegistrationTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/ResourceApi/UAuthResourceApiRegistrationTests.cs @@ -11,7 +11,7 @@ using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Server.ResourceApi; using CodeBeam.UltimateAuth.Server.Runtime; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Configuration; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Samples/HomeTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Samples/HomeTests.cs index 2d62992c..d656634d 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Samples/HomeTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Samples/HomeTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Client.Infrastructure; using CodeBeam.UltimateAuth.Sample.UAuthHub.Components.Layout; using CodeBeam.UltimateAuth.Sample.UAuthHub.Components.Pages; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Components; namespace CodeBeam.UltimateAuth.Tests.Unit.Samples.UAuthHub; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Security/Argon2PasswordHasherTest.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Security/Argon2PasswordHasherTest.cs index e2e1414f..93760522 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Security/Argon2PasswordHasherTest.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Security/Argon2PasswordHasherTest.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Errors; using CodeBeam.UltimateAuth.Credentials.Contracts; using CodeBeam.UltimateAuth.Security.Argon2; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Options; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Security/Argon2ServiceCollectionExtensionsTest.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Security/Argon2ServiceCollectionExtensionsTest.cs index 836b3acf..0bb4bd2f 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Security/Argon2ServiceCollectionExtensionsTest.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Security/Argon2ServiceCollectionExtensionsTest.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Abstractions; using CodeBeam.UltimateAuth.Security.Argon2; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/AccessPipelineTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/AccessPipelineTests.cs index dc733da2..d809cda5 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/AccessPipelineTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/AccessPipelineTests.cs @@ -9,7 +9,7 @@ using CodeBeam.UltimateAuth.Policies.Abstractions; using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit.Server; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/AuthenticationSecurityManagerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/AuthenticationSecurityManagerTests.cs index 3359f009..2e7664b9 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/AuthenticationSecurityManagerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/AuthenticationSecurityManagerTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Core.Security; using CodeBeam.UltimateAuth.Server.Security; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit.Server.Security; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ClientBaseAddressProviderTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ClientBaseAddressProviderTests.cs index 9141aa56..e295bb3e 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ClientBaseAddressProviderTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ClientBaseAddressProviderTests.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/CookieInfrastructureTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/CookieInfrastructureTests.cs index cfef81d3..8eb1aa58 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/CookieInfrastructureTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/CookieInfrastructureTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; namespace CodeBeam.UltimateAuth.Tests.Unit.Server; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/CredentialInfrastructureTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/CredentialInfrastructureTests.cs index 72d0d4fa..f22ba779 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/CredentialInfrastructureTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/CredentialInfrastructureTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Options; using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Options; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/CredentialResponseWriterTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/CredentialResponseWriterTests.cs index dbc6b7dc..6340448d 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/CredentialResponseWriterTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/CredentialResponseWriterTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/EffectiveServerOptionsProviderTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/EffectiveServerOptionsProviderTests.cs index ad1b9187..a72b3362 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/EffectiveServerOptionsProviderTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/EffectiveServerOptionsProviderTests.cs @@ -7,7 +7,7 @@ using CodeBeam.UltimateAuth.Server.Extensions; using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HandleHubTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HandleHubTests.cs index e2fb0182..04a46f32 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HandleHubTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HandleHubTests.cs @@ -6,7 +6,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Server.Stores; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HubCredentialResolverTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HubCredentialResolverTests.cs index 94507805..98ad80d4 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HubCredentialResolverTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HubCredentialResolverTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Stores; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HubFlowReaderTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HubFlowReaderTests.cs index ab924b2c..a32b437e 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HubFlowReaderTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HubFlowReaderTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Stores; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HubFlowServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HubFlowServiceTests.cs index 77e764b2..41fe0264 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HubFlowServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/HubFlowServiceTests.cs @@ -8,7 +8,7 @@ using CodeBeam.UltimateAuth.Server.Services; using CodeBeam.UltimateAuth.Server.Stores; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Options; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/IdentifierNormalizerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/IdentifierNormalizerTests.cs index f3577924..19744fc7 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/IdentifierNormalizerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/IdentifierNormalizerTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Options; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/JwtTokenGeneratorTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/JwtTokenGeneratorTests.cs index 0a24ed6c..79e09088 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/JwtTokenGeneratorTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/JwtTokenGeneratorTests.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Server.Infrastructure; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using System.Text.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/LoginEndpointHandlerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/LoginEndpointHandlerTests.cs index bfbb51c0..3caa7a41 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/LoginEndpointHandlerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/LoginEndpointHandlerTests.cs @@ -12,7 +12,7 @@ using CodeBeam.UltimateAuth.Server.Stores; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; using CodeBeam.UltimateAuth.Users; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.Extensions.Options; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/LoginOrchestratorTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/LoginOrchestratorTests.cs index 9dfa9ddf..3402774d 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/LoginOrchestratorTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/LoginOrchestratorTests.cs @@ -10,7 +10,7 @@ using CodeBeam.UltimateAuth.Server.Flows; using CodeBeam.UltimateAuth.Server.Services; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/LogoutEndpointHandlerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/LogoutEndpointHandlerTests.cs index b93f2618..e81606e1 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/LogoutEndpointHandlerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/LogoutEndpointHandlerTests.cs @@ -12,7 +12,7 @@ using CodeBeam.UltimateAuth.Server.Services; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/PkceEndpointHandlerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/PkceEndpointHandlerTests.cs index ff12d3be..de656606 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/PkceEndpointHandlerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/PkceEndpointHandlerTests.cs @@ -11,7 +11,7 @@ using CodeBeam.UltimateAuth.Server.Services; using CodeBeam.UltimateAuth.Server.Stores; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/PkceServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/PkceServiceTests.cs index 7246bf2f..845460e6 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/PkceServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/PkceServiceTests.cs @@ -12,7 +12,7 @@ using CodeBeam.UltimateAuth.Server.Services; using CodeBeam.UltimateAuth.Server.Stores; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Options; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/PkceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/PkceTests.cs index c2b97929..c5119182 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/PkceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/PkceTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Server.Services; using CodeBeam.UltimateAuth.Server.Stores; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/RedirectTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/RedirectTests.cs index b6cb8033..4e8a1896 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/RedirectTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/RedirectTests.cs @@ -8,7 +8,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.DependencyInjection; using System.Text; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/RefreshFlowServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/RefreshFlowServiceTests.cs index 997462c2..76be15d8 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/RefreshFlowServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/RefreshFlowServiceTests.cs @@ -11,7 +11,7 @@ using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Server.Services; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit.Server; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/RefreshTokenRotationServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/RefreshTokenRotationServiceTests.cs index fa7131c5..8d26537d 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/RefreshTokenRotationServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/RefreshTokenRotationServiceTests.cs @@ -10,7 +10,7 @@ using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Server.Services; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; using System.Text.Json; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ResourceAccessContextBuilderTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ResourceAccessContextBuilderTests.cs index c3eb38c0..2a128759 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ResourceAccessContextBuilderTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ResourceAccessContextBuilderTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Server.Authorization; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ReturnUrlParserTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ReturnUrlParserTests.cs index 71b34e5c..25197a38 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ReturnUrlParserTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ReturnUrlParserTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ServerOptionsValidatorTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ServerOptionsValidatorTests.cs index dd98580b..27f478c1 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ServerOptionsValidatorTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ServerOptionsValidatorTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Core.Options; using CodeBeam.UltimateAuth.Server.Extensions; using CodeBeam.UltimateAuth.Server.Options; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/SessionApplicationServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/SessionApplicationServiceTests.cs index eae2f55f..99cfacef 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/SessionApplicationServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/SessionApplicationServiceTests.cs @@ -8,7 +8,7 @@ using CodeBeam.UltimateAuth.Server.Services; using CodeBeam.UltimateAuth.Sessions.InMemory; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit.Server; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/SessionEndpointHandlerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/SessionEndpointHandlerTests.cs index fd272709..8fbc07bb 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/SessionEndpointHandlerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/SessionEndpointHandlerTests.cs @@ -6,7 +6,7 @@ using CodeBeam.UltimateAuth.Server.Endpoints; using CodeBeam.UltimateAuth.Server.Services; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/SessionTouchServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/SessionTouchServiceTests.cs index 13682b85..cae98eea 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/SessionTouchServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/SessionTouchServiceTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Server.Flows; using CodeBeam.UltimateAuth.Sessions.InMemory; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/TryLoginTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/TryLoginTests.cs index e337339b..c8bcf379 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/TryLoginTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/TryLoginTests.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Server.Flows; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthAuthenticationExtensionsTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthAuthenticationExtensionsTests.cs index 95a738c4..b6554a9a 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthAuthenticationExtensionsTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthAuthenticationExtensionsTests.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Defaults; using CodeBeam.UltimateAuth.Server.Authentication; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthAuthorizationHandlerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthAuthorizationHandlerTests.cs index 4d0d6da9..b5dbd9bc 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthAuthorizationHandlerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthAuthorizationHandlerTests.cs @@ -6,7 +6,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Server.Authorization; using CodeBeam.UltimateAuth.Server.Infrastructure; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthFlowServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthFlowServiceTests.cs index 1c911f4b..2a49a93f 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthFlowServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthFlowServiceTests.cs @@ -10,7 +10,7 @@ using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Server.Services; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit.Server; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthPolicyProviderTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthPolicyProviderTests.cs index 7142cce9..dd1124f5 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthPolicyProviderTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthPolicyProviderTests.cs @@ -1,5 +1,5 @@ using CodeBeam.UltimateAuth.Server.Authorization; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Options; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthResourceAccessOrchestratorTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthResourceAccessOrchestratorTests.cs index d005bf41..efb80fc9 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthResourceAccessOrchestratorTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthResourceAccessOrchestratorTests.cs @@ -7,7 +7,7 @@ using CodeBeam.UltimateAuth.Policies.Abstractions; using CodeBeam.UltimateAuth.Server.Authorization; using CodeBeam.UltimateAuth.Server.Infrastructure; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthResourceAuthenticationHandlerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthResourceAuthenticationHandlerTests.cs index e7395f83..ab40b778 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthResourceAuthenticationHandlerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthResourceAuthenticationHandlerTests.cs @@ -9,7 +9,7 @@ using CodeBeam.UltimateAuth.Server.Authentication; using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Options; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthServerProductInfoTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthServerProductInfoTests.cs index ed7b7e05..f9155e74 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthServerProductInfoTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthServerProductInfoTests.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Server.Runtime; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Options; using System.Reflection; using System.Runtime.InteropServices; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthSessionQueryServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthSessionQueryServiceTests.cs index 9ed2252b..8b7855ad 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthSessionQueryServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthSessionQueryServiceTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Server.Auth; using CodeBeam.UltimateAuth.Server.Services; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit.Server; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthSessionValidatorTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthSessionValidatorTests.cs index 2f6a06b0..e781ebcf 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthSessionValidatorTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/UAuthSessionValidatorTests.cs @@ -6,7 +6,7 @@ using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Server.Services; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Options; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ValidateCredentialResolverTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ValidateCredentialResolverTests.cs index 16a7195f..03b96852 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ValidateCredentialResolverTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ValidateCredentialResolverTests.cs @@ -7,7 +7,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ValidateEndpointHandlerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ValidateEndpointHandlerTests.cs index 675a71ec..53d7161b 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ValidateEndpointHandlerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Server/ValidateEndpointHandlerTests.cs @@ -9,7 +9,7 @@ using CodeBeam.UltimateAuth.Server.Endpoints; using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/BearerSessionIdResolverTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/BearerSessionIdResolverTests.cs index fabbf816..65485d9e 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/BearerSessionIdResolverTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/BearerSessionIdResolverTests.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Server.Infrastructure; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/CompositeSessionIdResolverTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/CompositeSessionIdResolverTests.cs index 799037b2..a6b16ad8 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/CompositeSessionIdResolverTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/CompositeSessionIdResolverTests.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/CookieSessionIdResolverTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/CookieSessionIdResolverTests.cs index 60a4005a..19574df2 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/CookieSessionIdResolverTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/CookieSessionIdResolverTests.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/HeaderSessionIdResolverTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/HeaderSessionIdResolverTests.cs index ce0faef7..a1021802 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/HeaderSessionIdResolverTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/HeaderSessionIdResolverTests.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/HttpContextSessionExtensionsTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/HttpContextSessionExtensionsTests.cs index ff544812..e29db74c 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/HttpContextSessionExtensionsTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/HttpContextSessionExtensionsTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Server.Extensions; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/QuerySessionIdResolverTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/QuerySessionIdResolverTests.cs index 8c2e6cc3..c88b1f15 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/QuerySessionIdResolverTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/QuerySessionIdResolverTests.cs @@ -1,6 +1,6 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/SessionContextAccessorTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/SessionContextAccessorTests.cs index 219f50c4..c7853603 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/SessionContextAccessorTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/SessionContextAccessorTests.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Core.Contracts; using CodeBeam.UltimateAuth.Core.Defaults; using CodeBeam.UltimateAuth.Server.Infrastructure; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/SessionTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/SessionTests.cs index 0011259e..cd433dba 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/SessionTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/SessionTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Server.Services; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreChainContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreChainContractTests.cs index a03cec9f..ef08049b 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreChainContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreChainContractTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Core.Errors; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.Sessions.Contracts; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreLifecycleContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreLifecycleContractTests.cs index e2bc8e60..9d6eca75 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreLifecycleContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreLifecycleContractTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.Sessions.Contracts; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreRootContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreRootContractTests.cs index fa9ad3c1..91a75392 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreRootContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreRootContractTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.Errors; using CodeBeam.UltimateAuth.Core.MultiTenancy; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.Sessions.Contracts; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreSessionContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreSessionContractTests.cs index 1a86d210..f0323c8d 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreSessionContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/Store/SessionStoreSessionContractTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Core.Errors; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.Sessions.Contracts; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/UAuthSessionIssuerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/UAuthSessionIssuerTests.cs index 8d89ece3..04224572 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/UAuthSessionIssuerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Sessions/UAuthSessionIssuerTests.cs @@ -9,7 +9,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; using System.Security; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Tokens/RefreshTokenStoreContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Tokens/RefreshTokenStoreContractTests.cs index ab7e783c..bf95ebf9 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Tokens/RefreshTokenStoreContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Tokens/RefreshTokenStoreContractTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Core.Errors; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.Tokens.Contracts; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Tokens/UAuthTokenIssuerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Tokens/UAuthTokenIssuerTests.cs index 0f094f11..453fe9cc 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Tokens/UAuthTokenIssuerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Tokens/UAuthTokenIssuerTests.cs @@ -6,7 +6,7 @@ using CodeBeam.UltimateAuth.Server.Auth; using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Tests.Unit.Helpers; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/Query/UserSummaryQueryStoreContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/Query/UserSummaryQueryStoreContractTests.cs index b26395b7..f774d5c3 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/Query/UserSummaryQueryStoreContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/Query/UserSummaryQueryStoreContractTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.Users.Contracts; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/UserIdentifierStoreContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/UserIdentifierStoreContractTests.cs index d735cb71..62ae9d02 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/UserIdentifierStoreContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/UserIdentifierStoreContractTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Users.Contracts; using CodeBeam.UltimateAuth.Users.Reference; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.Users.Contracts; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/UserLifecycleStoreContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/UserLifecycleStoreContractTests.cs index 52fcbc33..9b3bb133 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/UserLifecycleStoreContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/UserLifecycleStoreContractTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Users.Contracts; using CodeBeam.UltimateAuth.Users.Reference; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.Users.Contracts; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/UserProfileStoreContractTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/UserProfileStoreContractTests.cs index 8f517d58..522d4c29 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/UserProfileStoreContractTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/Store/UserProfileStoreContractTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Users.Contracts; using CodeBeam.UltimateAuth.Users.Reference; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit.Users.Contracts; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserApplicationServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserApplicationServiceTests.cs index c8481e93..c0206bf4 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserApplicationServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserApplicationServiceTests.cs @@ -10,7 +10,7 @@ using CodeBeam.UltimateAuth.Users; using CodeBeam.UltimateAuth.Users.Contracts; using CodeBeam.UltimateAuth.Users.Reference; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Options; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserCreateValidatorTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserCreateValidatorTests.cs index 5af89c05..64aad5cd 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserCreateValidatorTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserCreateValidatorTests.cs @@ -4,7 +4,7 @@ using CodeBeam.UltimateAuth.Tests.Unit.Helpers; using CodeBeam.UltimateAuth.Users; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Moq; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserEndpointHandlerTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserEndpointHandlerTests.cs index 31577bbc..1b5c1fbc 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserEndpointHandlerTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserEndpointHandlerTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Tests.Unit.Helpers; using CodeBeam.UltimateAuth.Users.Contracts; using CodeBeam.UltimateAuth.Users.Reference; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; using Moq; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserIdentifierApplicationServiceTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserIdentifierApplicationServiceTests.cs index ae12ee5d..4f31ff61 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserIdentifierApplicationServiceTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserIdentifierApplicationServiceTests.cs @@ -5,7 +5,7 @@ using CodeBeam.UltimateAuth.Tests.Unit.Helpers; using CodeBeam.UltimateAuth.Users.Contracts; using CodeBeam.UltimateAuth.Users.Reference; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.DependencyInjection; using static CodeBeam.UltimateAuth.Core.Defaults.UAuthActions; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserIdentifierValidationTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserIdentifierValidationTests.cs index 52a4fec9..3b17c43d 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserIdentifierValidationTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserIdentifierValidationTests.cs @@ -2,7 +2,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Server.Options; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using Microsoft.Extensions.Options; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserProfileValidatorTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserProfileValidatorTests.cs index 6a2a0910..f54acbb6 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserProfileValidatorTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Users/UserProfileValidatorTests.cs @@ -1,7 +1,7 @@ using CodeBeam.UltimateAuth.Server.Infrastructure; using CodeBeam.UltimateAuth.Users; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; namespace CodeBeam.UltimateAuth.Tests.Unit; diff --git a/tests/CodeBeam.UltimateAuth.Tests.Unit/Utility/JsonConverterTests.cs b/tests/CodeBeam.UltimateAuth.Tests.Unit/Utility/JsonConverterTests.cs index 0187d335..6f03b484 100644 --- a/tests/CodeBeam.UltimateAuth.Tests.Unit/Utility/JsonConverterTests.cs +++ b/tests/CodeBeam.UltimateAuth.Tests.Unit/Utility/JsonConverterTests.cs @@ -3,7 +3,7 @@ using CodeBeam.UltimateAuth.Core.Domain; using CodeBeam.UltimateAuth.Core.MultiTenancy; using CodeBeam.UltimateAuth.Users.Contracts; -using FluentAssertions; +using AwesomeAssertions; using System.Text.Json; namespace CodeBeam.UltimateAuth.Tests.Unit.Core; From 054b7d259db9ed73651726459ded3dc91a2e938f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Can=20Karag=C3=B6z?= Date: Sun, 18 Oct 2026 23:59:18 +0300 Subject: [PATCH 3/3] Copyright & Contributing Text Changes --- CONTRIBUTING.md | 12 ++++++++++++ COPYRIGHT.md | 13 +++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 COPYRIGHT.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b693c62..67cbcb91 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,6 +82,18 @@ UltimateAuth is licensed under the [Apache License 2.0](LICENSE.txt). By contrib If a separate contributor agreement is introduced, its requirements will be documented before enforcement. +## Contributor License Agreement (CLA) + +UltimateAuth welcomes contributions from the open-source community. + +Contributors retain copyright ownership of their original contributions unless otherwise agreed in writing. + +By submitting a contribution, contributors agree that their contributions are provided under the project's Apache License, Version 2.0, and confirm that they have the necessary rights to submit them. + +UltimateAuth intends to adopt the .NET Foundation Contributor License Agreement (CLA) process if accepted as a .NET Foundation project. + +Once the CLA process is implemented, contributors may be required to sign the applicable agreement before their pull requests can be merged. + ## A welcoming community Be respectful, assume good intentions, and help others learn. Different experience levels and perspectives make the project better. diff --git a/COPYRIGHT.md b/COPYRIGHT.md new file mode 100644 index 00000000..f3b63496 --- /dev/null +++ b/COPYRIGHT.md @@ -0,0 +1,13 @@ +# Copyright and Ownership + +Copyright © 2026 Mehmet Can Karagöz and contributors. + +UltimateAuth is an open-source project developed and maintained under the CodeBeam name. + +CodeBeam serves as the project's publishing identity and GitHub organization and is not a separate legal entity. + +The original source code of UltimateAuth is copyrighted by Mehmet Can Karagöz. Contributors retain copyright ownership of their respective original contributions unless otherwise agreed in writing. + +Contributions are incorporated into UltimateAuth under the Apache License, Version 2.0, and any applicable Contributor License Agreement (CLA). + +See [LICENSE.txt](LICENSE.txt) for the project's license terms and [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines. \ No newline at end of file