diff --git a/README.md b/README.md index 9ad88bb5..6ad17168 100644 --- a/README.md +++ b/README.md @@ -19,20 +19,26 @@ ## 📑 Table of Contents -- [🗺 Roadmap](#-roadmap) -- [🌟 Why UltimateAuth](#-why-ultimateauth) -- [🚀 Quick Start](#-quick-start) -- [💡 Usage](#-usage) -- [📘 Documentation](#-documentation) -- [🤝 Contributing](#-contributing) -- [⭐ Acknowledgements](#-acknowledgements) +## Table of Contents + + +## Table of Contents + +- [Roadmap](#roadmap) +- [Why UltimateAuth](#why-ultimateauth) +- [Quick Start](#quick-start) +- [Usage](#usage) +- [Documentation](#documentation) +- [Upgrading & Breaking Changes](#upgrading--breaking-changes) +- [Community & Contributing](#community--contributing) +- [Acknowledgements](#acknowledgements) --- UltimateAuth is an open-source auth framework with platform-level capabilities that unifies secure session, cookie and token based Auth, modern PKCE flows, Blazor/Maui-ready client experiences - eliminating the complexity of traditional Auth systems while providing a clean, lightweight, extensible and developer-first architecture. --- -## 🗺 Roadmap +## Roadmap | Phase | Version | Scope | Status | Release Date | | ----------------------- | ------------- | ----------------------------------------- | -------------- | ------------ | @@ -67,7 +73,7 @@ Whether you're using UltimateAuth, exploring the project, or interested in contr --- -## 🌟 Why UltimateAuth +## Why UltimateAuth The Six-Point Principles ### 1) Unified Authentication System @@ -124,7 +130,7 @@ Traditional auth solutions struggle here — UltimateAuth embraces it. --- -# 🚀 Quick Start +# Quick Start > ⏱ Get started in minutes > > **This Quick Start uses a Blazor Server application with in-memory persistence.** @@ -251,7 +257,7 @@ For a **complete, step-by-step setup with all required configuration, components --- -## 💡 Usage +## Usage **One Client. Your Auth Application API.** @@ -324,7 +330,7 @@ Start with the simple API. Drop down to UltimateAuth's extensibility points when --- -## 📘 Documentation +## Documentation Two documentation experiences are provided: @@ -336,7 +342,19 @@ Create accounts, simulate devices, test auth flows, and observe UltimateAuth in --- -## 🤝 Community & Contributing +## Upgrading & Breaking Changes + +UltimateAuth aims to keep upgrades straightforward. When a release introduces breaking changes, we provide concise migration guides explaining what needs to be updated. + +**Upgrading from an earlier version?** + +Check the [Migration Guides](https://ultimateauth.com/docs/migration/index) for version-specific instructions. + +For new features and improvements, see the [GitHub Releases](https://github.com/CodeBeamOrg/UltimateAuth/releases). + +--- + +## Community & Contributing UltimateAuth is built by developers, for developers — and everyone is welcome to be part of its journey. @@ -354,7 +372,7 @@ Every great open-source project starts with people willing to build something to --- -## ⭐ Acknowledgements +## Acknowledgements UltimateAuth is built with love by CodeBeam and shaped by real-world .NET development — for teams who want authentication to be secure, predictable, extensible, and a joy to use. diff --git a/docs/content/_groups.json b/docs/content/_groups.json index e702badb..d13c0b2e 100644 --- a/docs/content/_groups.json +++ b/docs/content/_groups.json @@ -6,5 +6,7 @@ "plugin-domains": 5, "client": 6, "configuration": 7, - "security": 8 + "security": 8, + "how-to-guides": 9, + "migration": 10 } \ No newline at end of file diff --git a/docs/content/how-to-guides/index.md b/docs/content/how-to-guides/index.md new file mode 100644 index 00000000..2737c6dc --- /dev/null +++ b/docs/content/how-to-guides/index.md @@ -0,0 +1,13 @@ +--- +title: How-to Guides +order: 1 +group: how-to-guides +--- + +# How-to Guides + +Practical, task-oriented guides for building applications with UltimateAuth. + +## Available Guides + +- [Protect a Page](/docs/how-to-guides/protect-a-page) — Secure Blazor pages and UI sections using UltimateAuth and ASP.NET Core authorization. diff --git a/docs/content/how-to-guides/protect-a-page.md b/docs/content/how-to-guides/protect-a-page.md new file mode 100644 index 00000000..3dedd021 --- /dev/null +++ b/docs/content/how-to-guides/protect-a-page.md @@ -0,0 +1,850 @@ +--- +title: Protect a Page +order: 2 +group: how-to-guides +--- + +# Protect a Page + +Learn how to protect Blazor pages and UI sections using UltimateAuth and ASP.NET Core authorization. + +UltimateAuth provides declarative authorization for routed Blazor pages through `[UAuthAuthorize]`, conditional rendering through `UAuthStateView`, and interoperability with ASP.NET Core's `[Authorize]` attribute. + +In this guide, you'll learn how to: + +- Require authentication before a page can be accessed. +- Restrict access using roles and permissions. +- Combine authorization requirements with different match modes. +- Require an active session. +- Protect individual UI sections. +- Use ASP.NET Core authorization alongside UltimateAuth. +- Configure authorization with built-in and custom routers. +- Understand the role of `UAuthApp` and `UAuthComponentBase`. + +> **Security:** Blazor authorization controls which UI components are rendered. Protected API endpoints, data, and server operations must enforce authorization independently on the server. Client-side authorization must never be the only security boundary. + +## Prerequisites + +You should have a working UltimateAuth Blazor application with authentication configured. + +If you haven't created one yet, start with the [QuickStart](/docs/getting-started/quickstart). + +The examples in this guide use: + +```razor +@using CodeBeam.UltimateAuth.Client +@using CodeBeam.UltimateAuth.Client.Blazor +@using CodeBeam.UltimateAuth.Core.Contracts +@using Microsoft.AspNetCore.Authorization +``` + +You can add these namespaces to your application's `_Imports.razor` file to make them available across components. + +## 1. Protect a Page with UAuthAuthorize + +The simplest way to protect a routed Blazor page is to add the `[UAuthAuthorize]` attribute. + +Create `Pages/Dashboard.razor`: + +```razor +@page "/dashboard" +@attribute [UAuthAuthorize] + +

Dashboard

+ +

You are authenticated.

+``` + +That's it. + +When a user navigates to `/dashboard`, UltimateAuth evaluates the page's authorization requirements before rendering the page component. + +- **Authenticated user with an acceptable session state:** The page can be rendered. +- **Anonymous user:** Authorization is denied. +- **Inactive session:** Authorization is denied when an inactive session state is available and `RequireActive` is enabled. + +By default, `[UAuthAuthorize]` requires an authenticated user and an active session where session-state information is present. + +No explicit role or permission is required. + +### Does the page need to inherit from UAuthComponentBase? + +**No.** + +The page above works without inheriting from any UltimateAuth base class. + +Authorization is enforced by `UAuthAuthorizeRouteView`, not by the page's base class. + +This also means an unauthorized routed page is not instantiated by UltimateAuth's route view. + +## 2. Require a Role + +Use the `Roles` property to restrict access to users with a specific role. + +```razor +@page "/admin" +@attribute [UAuthAuthorize(Roles = "Admin")] + +

Administration

+ +

Only administrators can access this page.

+``` + +The user must be authenticated and have the `Admin` role. + +You can also declare multiple roles: + +```razor +@page "/management" +@attribute [UAuthAuthorize(Roles = "Admin,Manager")] + +

Management

+``` + +With the default `Category` match mode, having **either `Admin` or `Manager`** is sufficient, provided the other configured authorization categories also succeed. + +Role names must correspond to the roles recognized by UltimateAuth's authentication state. + +## 3. Require a Permission + +For more granular authorization, use `Permissions`. + +```razor +@page "/users" +@attribute [UAuthAuthorize(Permissions = "users.read.self")] + +

Users

+ +

You have permission to view users.

+``` + +UltimateAuth checks the requested permission against the current `UAuthState`. + +You can declare multiple permissions: + +```razor +@page "/reports" +@attribute [UAuthAuthorize(Permissions = "reports.read.self,reports.manage.admin")] + +

Reports

+``` + +By default, the user needs at least one of these permissions. + +Permission strings must match the permissions represented in the authenticated user's UltimateAuth state. + +> **Tip:** Prefer permissions for operations such as reading reports, managing users, or editing records. Roles are useful for broader organizational responsibilities, while permissions can express specific capabilities. + +## 4. Combine Roles and Permissions + +You can specify both `Roles` and `Permissions`. + +```razor +@page "/administration/users" +@attribute [UAuthAuthorize(Roles = "Admin", Permissions = "users.manage.admin")] + +

User Management

+``` + +With the default `MatchMode = Category`, the user must satisfy: + +**Admin role AND users.manage permission** + +This is because `Category` requires at least one match from each configured category. + +If multiple roles and permissions are provided: + +```razor +@page "/management/users" +@attribute [UAuthAuthorize(Roles = "Admin,Manager", Permissions = "users.read.self,users.manage.admin")] +``` + +The requirement becomes: + +**(Admin OR Manager) AND (users.read.self OR users.manage.admin)** + +This is the default authorization behavior. + +## 5. Understand Match Modes + +UltimateAuth supports three authorization matching modes: + +| Match Mode | Behavior | +|---|---| +| `Category` | At least one matching value in each configured category | +| `Any` | At least one configured role or permission must match | +| `All` | Every configured role and permission must match | + +The default is `AuthorizationMatchMode.Category`. + +### Category — the default + +```razor +@page "/category-example" +@attribute [UAuthAuthorize(Roles = "Admin,Manager", Permissions = "reports.read,reports.write", MatchMode = AuthorizationMatchMode.Category)] +``` + +The user must have: + +- `Admin` **or** `Manager`. +- `reports.read` **or** `reports.write`. + +Both configured categories must succeed. + +Use `Category` when roles and permissions represent independent requirements but alternatives within each category are acceptable. + +### Any — one match is enough + +```razor +@page "/any-example" +@attribute [UAuthAuthorize(Roles = "Admin,Auditor", Permissions = "reports.read.admin", MatchMode = AuthorizationMatchMode.Any)] +``` + +The user needs at least one of: + +- `Admin` role. +- `Auditor` role. +- `reports.read.admin` permission. + +This is useful for pages with several alternative authorization paths. + +Be careful when combining high-privilege roles and lower-privilege permissions with `Any`: a single matching permission grants access. + +### All — every value is required + +```razor +@page "/all-example" +@attribute [UAuthAuthorize(Roles = "Admin,SecurityOfficer", Permissions = "audit.read.self,audit.export.admin", MatchMode = AuthorizationMatchMode.All)] +``` + +The user must have: + +- `Admin` role. +- `SecurityOfficer` role. +- `audit.read.self` permission. +- `audit.export.admin` permission. + +Use `All` when every declared requirement is mandatory. + +### Choosing a match mode + +| Requirement | Recommended mode | +|---|---| +| Any authenticated user | Parameterless attribute | +| One required role | Default `Category` | +| Any of several roles | Default `Category` | +| One role AND one permission | Default `Category` | +| Role OR permission | `Any` | +| All listed roles AND permissions | `All` | + +If roles and permissions are omitted entirely, an authenticated user satisfies the authorization requirement regardless of match mode, subject to the session-state requirement. + +## 6. Require an Active Session + +By default, `[UAuthAuthorize]` uses: + +```csharp +RequireActive = true +``` + +The default configuration can be written explicitly: + +```razor +@page "/account/settings" +@attribute [UAuthAuthorize(RequireActive = true)] + +

Account Settings

+``` + +When UltimateAuth has session-state information and the session is not active, authorization returns an inactive result. + +If your application needs to allow an authenticated user regardless of a reported non-active session state, you can disable this requirement: + +```razor +@page "/session-information" +@attribute [UAuthAuthorize(RequireActive = false)] + +

Session Information

+``` + +This changes only the session-state condition. Authentication is still required. + +> **Important:** `RequireActive` evaluates the session state currently available to the Blazor client. It does not independently validate a session against the server for every render. Server-side session validation and revocation enforcement remain necessary. + +When no session-state value is available, the default evaluator does not reject the user solely because that value is absent. + +## 7. Protect Part of a Page with UAuthStateView + +Sometimes you want the page to remain publicly accessible while protecting only a specific section. + +Use `UAuthStateView` for conditional rendering. + +```razor +@page "/products" + +

Products

+ +

Everyone can view this page.

+ + + + + + +

Administrative tools are unavailable.

+
+
+``` + +The page itself remains accessible to everyone. + +However, the administrative section is only rendered for an authorized user. + +`UAuthStateView` uses the same `IUAuthAuthorizationEvaluator` as `UAuthAuthorizeRouteView`, so matching roles, permissions, and match modes have consistent semantics. + +### Basic authentication-only section + +```razor + + +

Welcome back!

+
+ +

Sign in to access your account features.

+
+
+``` + +As with parameterless `[UAuthAuthorize]`, authentication is required, along with an acceptable session state when `RequireActive` is true. + +### Protect a section with permissions + +```razor + + + + + +

You cannot refund orders.

+
+
+``` + +### Combine requirements in a section + +```razor + + +

Order management tools are available.

+
+ +

Access denied.

+
+
+``` + +This uses the same `Category` semantics as the routed page attribute. + +### Access the current authentication state + +The `Authorized` fragment receives the current `UAuthState` as its context. + +```razor + + +

Hello, @state.Identity?.DisplayName!

+
+ +

Please sign in.

+
+
+``` + +This allows authorized content to access identity information without requiring a custom base class. + +## 8. Handle Unauthorized, Inactive, and Authorizing States + +`UAuthStateView` supports multiple rendering states. + +```razor + + +

Administrator access granted.

+
+ + +

You do not have permission to access this section.

+
+ + +

Your session is not active.

+
+ + +

Checking your permissions...

+
+
+``` + +| Fragment | When displayed | +|---|---| +| `Authorized` | Authorization succeeds | +| `NotAuthorized` | Authorization is denied | +| `Inactive` | A non-active session is detected and `RequireActive` is true | +| `Authorizing` | Authorization evaluation is in progress | + +If `Inactive` is not supplied, the component falls back to the `NotAuthorized` fragment. + +If `Authorizing` is not supplied, unauthorized content is used while a new authorization result is pending. + +During reevaluation, previously authorized section content is removed before an asynchronous policy evaluation completes. This prevents stale authorized content from remaining visible during that pending evaluation. + +## 9. Use an ASP.NET Core Authorization Policy + +`UAuthStateView` supports the `Policy` parameter. + +First, register a policy using ASP.NET Core authorization: + +```csharp +builder.Services.AddAuthorizationCore(options => +{ + options.AddPolicy("CanViewReports", policy => policy.RequireClaim("reports_access", "granted")); +}); +``` + +Then use that policy in a UI section: + +```razor + + +

Reports are available.

+
+ +

Reports access is restricted.

+
+
+``` + +The policy is evaluated through ASP.NET Core's `IAuthorizationService`, using the claims principal produced from the current UltimateAuth state. + +You can also combine a policy with roles and permissions: + +```razor + + +

All requirements passed.

+
+
+``` + +For `All`, every configured requirement must succeed. + +For `Any`, the policy is another possible successful condition. + +For `Category`, the policy must succeed alongside the configured role and permission categories. + +> **Current API distinction:** `[UAuthAuthorize]` currently exposes `Roles`, `Permissions`, `MatchMode`, and `RequireActive`, but not `Policy`. Use `UAuthStateView` for policy-based section rendering, or ASP.NET Core `[Authorize(Policy = "...")]` for policy-protected routed pages. + +## 10. Protect a Page with ASP.NET Core Authorize + +UltimateAuth integrates with ASP.NET Core authorization. + +You can use the standard `[Authorize]` attribute on routed Blazor pages. + +```razor +@page "/account" +@attribute [Authorize] + +

Account

+ +

This page requires ASP.NET Core authorization.

+``` + +Or require a role: + +```razor +@page "/administration" +@attribute [Authorize(Roles = "Admin")] + +

Administration

+``` + +Or a registered policy: + +```razor +@page "/reports" +@attribute [Authorize(Policy = "CanViewReports")] + +

Reports

+``` + +These checks are performed through ASP.NET Core's `AuthorizeRouteView` and its authentication and authorization infrastructure. + +UltimateAuth provides a Blazor `AuthenticationStateProvider` that integrates UltimateAuth authentication state with ASP.NET Core. + +### Which attribute should I use? + +| Feature | `[UAuthAuthorize]` | `[Authorize]` | +|---|---|---| +| Requires authentication | Yes | Yes | +| Roles | Yes | Yes | +| UltimateAuth permissions | Yes | Via separately configured ASP.NET Core policies or handlers | +| `AuthorizationMatchMode` | Yes | No direct equivalent property | +| `RequireActive` | Yes | No direct equivalent property | +| Named ASP.NET Core policy | Not currently | Yes | +| Routed Blazor page | Yes | Yes | + +Use `[UAuthAuthorize]` when working directly with UltimateAuth roles, permissions, and session-aware requirements. + +Use `[Authorize]` when relying on ASP.NET Core authorization policies, requirements, or existing authorization infrastructure. + +### Combine both attributes + +You can declare both on the same routed page: + +```razor +@page "/secure-reports" + +@attribute [UAuthAuthorize(Permissions = "reports.read")] +@attribute [Authorize(Roles = "Admin")] + +

Secure Reports

+``` + +The page is rendered only when **both authorization systems allow access**. + +UltimateAuth evaluates `[UAuthAuthorize]`, and the underlying ASP.NET Core `AuthorizeRouteView` evaluates `[Authorize]`. + +The requirements are effectively combined using AND semantics across the two mechanisms. + +> **Note:** ASP.NET Core `[Authorize]` uses the configured ASP.NET Core authentication state. `[UAuthAuthorize]` uses `UAuthState`. UltimateAuth integrates these models, but custom authentication providers must keep their states consistent. + +## 11. How UAuthApp Protects Routes + +`UAuthApp` is the root Blazor integration component for UltimateAuth. + +It provides: + +- Client runtime initialization. +- The current `UAuthState` as a cascading parameter. +- ASP.NET Core cascading authentication state. +- Session lifecycle coordination. +- Runtime initialization readiness. +- Optional built-in routing. + +### Built-in router + +If you use: + +```razor + +``` + +UltimateAuth automatically configures its router to use `UAuthAuthorizeRouteView`. + +You do not need to add a separate `AuthorizeRouteView` or `UAuthAuthorizeRouteView`. + +The built-in router supports both `[UAuthAuthorize]` and ASP.NET Core `[Authorize]`. + +It also supports an application-level `NotAuthorized` fragment: + +```razor + + +

Access Denied

+

You are not authorized to access this page.

+
+
+``` + +This fragment is displayed when route authorization fails. + +### Runtime initialization + +`UAuthApp` initializes the client authentication state before allowing UltimateAuth-protected routes to be rendered. + +This is important because authentication state may not be immediately available when a Blazor application first starts, particularly when restoring a session. + +During initialization or asynchronous authorization evaluation, `UAuthAuthorizeRouteView` can display an `Authorizing` fragment. + +If one is not supplied, the protected route content remains unrendered while authorization is pending. + +### Manual and Reactive render modes + +`UAuthApp` supports `UAuthRenderMode.Manual` and `UAuthRenderMode.Reactive`. + +These control how UltimateAuth state changes affect general component rendering. + +Route authorization itself subscribes to relevant authentication state changes, and `UAuthStateView` also reacts to such changes. + +You do not need to enable reactive rendering merely to make `[UAuthAuthorize]` work. + +## 12. Protect Pages with a Custom Router + +If your application uses its own Blazor `Router`, set `UseBuiltInRouter="false"` on `UAuthApp`. + +Then use `UAuthAuthorizeRouteView` inside the router's `Found` fragment. + +```razor + + + + + + +

Access Denied

+

You cannot access this page.

+
+ + +

Checking access...

+
+ +
+ + +
+
+
+``` + +`UAuthAuthorizeRouteView` wraps ASP.NET Core's `AuthorizeRouteView` internally. + +This means it supports both authorization systems, while checking UltimateAuth-specific requirements before the protected page component is instantiated. + +**Do not replace it with a plain `RouteView` or `AuthorizeRouteView` when your routed pages depend on `[UAuthAuthorize]`.** + +A plain ASP.NET Core `AuthorizeRouteView` does not evaluate the UltimateAuth-specific attribute by itself. + +If your custom router uses additional route assemblies, pass the appropriate `AdditionalAssemblies` to the `Router` as needed. + +## 13. Do I Need UAuthComponentBase? + +**Not for authorization.** + +Consider this page: + +```razor +@page "/settings" +@attribute [UAuthAuthorize] + +

Settings

+``` + +It is protected when routed through `UAuthAuthorizeRouteView`, even though it doesn't inherit from `UAuthComponentBase`. + +You can still use `UAuthComponentBase` when you need access to the UltimateAuth state lifecycle. + +```razor +@page "/profile" +@attribute [UAuthAuthorize] +@inherits UAuthComponentBase + +

Profile

+ +

User: @AuthState.Identity?.DisplayName

+ +@code { + protected override Task HandleAuthStateChangedAsync( + UAuthStateChangeReason reason) + { + // React to authentication state changes. + return Task.CompletedTask; + } +} +``` + +`UAuthComponentBase` provides: + +- Access to cascading `UAuthState`. +- Authentication state change notifications. +- Automatic rerendering when the state changes, by default. +- An asynchronous lifecycle hook. +- Event subscription cleanup on disposal. + +However, it no longer performs page authorization itself. + +### Can I use UAuthAuthorize on a regular child component? + +The attribute only declares metadata. It does not enforce authorization on its own. + +For example: + +```razor +@attribute [UAuthAuthorize(Roles = "Admin")] + +
Administrative tools
+``` + +If this is an ordinary child component rendered directly inside another page, `[UAuthAuthorize]` is not automatically evaluated by `UAuthAuthorizeRouteView`. + +Route authorization is performed for routed page components. + +For conditional content inside a page, use `UAuthStateView` instead. + +## 14. Advanced: Customize Authorization Evaluation + +UltimateAuth uses the `IUAuthAuthorizationEvaluator` service for Blazor authorization. + +The default evaluator is registered by: + +```csharp +builder.Services.AddUltimateAuthClientBlazor(); +``` + +It evaluates: + +- Authentication status. +- Session state when required. +- Roles. +- Permissions. +- ASP.NET Core policies supplied through authorization requirements. +- The configured match mode. + +The evaluator is registered with `TryAddScoped`, allowing applications to provide their own implementation through dependency injection. + +If you need custom application-specific authorization behavior, you can implement `IUAuthAuthorizationEvaluator`. + +A replacement evaluator receives the current `UAuthState` and a `UAuthAuthorizationRequirement` and returns a `UAuthAuthorizationResult`. + +A custom implementation should preserve secure defaults, especially for anonymous users, inactive sessions, and failed authorization checks. + +Keep authorization evaluation separate from component rendering and navigation logic. + +## 15. Important Security Considerations + +### Blazor UI authorization is not API authorization + +An unauthorized user should not see or instantiate a protected routed page. However, hiding UI does not secure the HTTP endpoint or underlying data source. + +For example: + +```razor +@page "/users" +@attribute [UAuthAuthorize(Roles = "Admin")] +``` + +This protects the Blazor page through UltimateAuth's router. + +It does **not** automatically protect an unrelated API endpoint such as: + +```text +GET /api/users +``` + +That endpoint must independently validate the caller and enforce the required authorization. + +For Blazor Server, the same principle applies to sensitive server-side operations, even when they are invoked without an HTTP call from the component. + +### UAuthAuthorize is currently a Blazor attribute + +The current `[UAuthAuthorize]` attribute is interpreted by UltimateAuth's Blazor authorization pipeline. + +It does not, by itself, secure ASP.NET Core controllers or Minimal API endpoints. + +Use the server-side authorization mechanisms appropriate for those endpoints. + +### Revocation and role changes + +`UAuthAuthorizeRouteView` and `UAuthStateView` respond to authentication state changes. + +When the available `UAuthState` changes, authorization can be reevaluated. + +However, a remote role change or server-side session revocation is not necessarily observed immediately by the Blazor client. + +Server-side authorization must remain authoritative, even while a client retains stale state. + +### Authorization during navigation + +A protected routed page is not instantiated until UltimateAuth authorization succeeds. + +When authorization becomes invalid, the route view stops rendering the protected page. + +This is different from implementing a redirect inside the page's `OnInitialized` or `OnAfterRender` method, where the component may already have been instantiated. + +### Do not rely on hidden controls for security + +`UAuthStateView` is useful for showing or hiding buttons, navigation entries, and sections. + +Always authorize the underlying operation independently. + +For example, hiding a `Delete User` button does not remove the need to authorize the actual delete request. + +## 16. Troubleshooting + +### UAuthAuthorize does not prevent access + +First, make sure you're using **UltimateAuth v0.1.1 or later**. + +Earlier versions relied on `UAuthComponentBase` for authorization checks. Starting with v0.1.1, `[UAuthAuthorize]` is enforced at the routing level through `UAuthAuthorizeRouteView`. + +If authorization is not working as expected: + +1. Verify that `CodeBeam.UltimateAuth.Client.Blazor` is version `0.1.1` or later. +2. If you're using the built-in router, make sure `UseBuiltInRouter="true"` is configured on `UAuthApp`. +3. If you're using a custom router, replace `RouteView` or `AuthorizeRouteView` with `UAuthAuthorizeRouteView`. +4. Make sure the page is routed within the `UAuthApp` context. +5. Confirm that the user meets the authentication, role, permission, and session-state requirements. + +**Upgrading from v0.1.0?** See [Migrating to v0.1.1](/docs/migration/v0.1.1) for the required changes. + +### The protected page appears blank during startup + +UltimateAuth may still be initializing or evaluating authorization. + +With a custom router, provide an `Authorizing` fragment for a loading indicator. + +### UAuthStateView reports a missing cascading UAuthState + +Make sure the component is rendered inside `UAuthApp`, or another host that correctly provides the required cascading UltimateAuth state. + +### ASP.NET Core authorization reports a missing AuthenticationState + +Make sure ASP.NET Core's authentication state infrastructure is available. + +`UAuthApp` provides a `CascadingAuthenticationState` within its child content. + +If you create a custom host outside `UAuthApp`, you must provide the appropriate ASP.NET Core cascading authentication state yourself. + +### Roles or permissions are unexpectedly denied + +Confirm that: + +- The user is authenticated. +- The state contains the expected role or permission. +- The correct match mode is selected. +- The user's session state satisfies `RequireActive`. +- The client state has been updated after any server-side changes. + +### I need a policy directly on a routed page + +Use ASP.NET Core's `[Authorize(Policy = "...")]` attribute. + +The current `[UAuthAuthorize]` attribute does not expose a `Policy` property. + +## Summary + +| Scenario | Recommended approach | +|---|---| +| Protect an entire Blazor page | `[UAuthAuthorize]` | +| Require an UltimateAuth role | `[UAuthAuthorize(Roles = "...")]` | +| Require an UltimateAuth permission | `[UAuthAuthorize(Permissions = "...")]` | +| Combine roles and permissions | `MatchMode` | +| Require ASP.NET Core policy authorization | `[Authorize(Policy = "...")]` | +| Combine ASP.NET Core and UltimateAuth | Use both attributes | +| Protect an individual UI section | `UAuthStateView` | +| Use built-in routing | `UAuthApp UseBuiltInRouter="true"` | +| Use custom routing | `UAuthAuthorizeRouteView` | +| React to authentication lifecycle changes | `UAuthComponentBase` | +| Protect an API or server operation | Enforce authorization on the server | + +UltimateAuth's Blazor authorization model allows you to declare requirements at the page level, conditionally render protected sections, and integrate with ASP.NET Core authorization without coupling page protection to a specific base class. + +## Next Steps + +- [QuickStart](/docs/getting-started/quickstart) — Create a working UltimateAuth application. +- [Authentication Model](/docs/fundamentals/auth-model) — Understand UltimateAuth authentication concepts. +- [Migration to v0.1.1](/docs/migration/v0.1.1) — Update custom routers and existing Blazor authorization code. diff --git a/docs/content/migration/index.md b/docs/content/migration/index.md new file mode 100644 index 00000000..0cd8f9d0 --- /dev/null +++ b/docs/content/migration/index.md @@ -0,0 +1,15 @@ +--- +title: Migration Guides +order: 1 +group: migration +--- + +# Migration Guides + +Version-specific instructions for upgrading UltimateAuth applications. + +Migration guides focus on breaking changes, behavior changes, and required application updates. + +## Available Guides + +- [Migrating to v0.1.1](/docs/migration/v0.1.1) — Blazor route authorization and component lifecycle changes. diff --git a/docs/content/migration/v0.1.1.md b/docs/content/migration/v0.1.1.md new file mode 100644 index 00000000..f53a3250 --- /dev/null +++ b/docs/content/migration/v0.1.1.md @@ -0,0 +1,76 @@ +--- +title: Migrating to v0.1.1 +order: 2 +group: migration +--- + +# Migrating to v0.1.1 + +UltimateAuth v0.1.1 improves Blazor page authorization by moving `[UAuthAuthorize]` enforcement to the routing layer. + +**Most applications require no changes.** If you're using the built-in `UAuthApp` router, authorization works automatically. + +Only the following cases require attention. + +## 1. Custom Routers + +If you use a custom Blazor router, replace `AuthorizeRouteView` with `UAuthAuthorizeRouteView`. + +**Before:** + +```razor + +``` + +**After:** + +```razor + +``` + +This enables `[UAuthAuthorize]` enforcement while preserving ASP.NET Core `[Authorize]` support. + +Make sure your custom router is rendered inside `UAuthApp`. + +## 2. UAuthComponentBase Overrides + +`UAuthComponentBase` no longer handles authorization or automatic redirects. + +If you previously overrode `OnUnauthorized()` or `OnForbidden()`, replace that behavior with the `NotAuthorized` fragment on `UAuthApp` (built-in router) or `UAuthAuthorizeRouteView` (custom router). + +The authentication state change hook is now asynchronous. + +**Before:** + +```csharp +protected override void HandleAuthStateChanged(UAuthStateChangeReason reason) +{ +} +``` + +**After:** + +```csharp +protected override Task HandleAuthStateChangedAsync(UAuthStateChangeReason reason) +{ + return Task.CompletedTask; +} +``` + +Pages no longer need to inherit from `UAuthComponentBase` to use `[UAuthAuthorize]`. + +## 3. Session Requirement + +`[UAuthAuthorize]` now defaults to `RequireActive = true`. + +If your application intentionally allows authenticated users with a reported inactive session state, explicitly use: + +```razor +@attribute [UAuthAuthorize(RequireActive = false)] +``` + +Otherwise, no change is needed. + +--- + +For usage examples, see [Protect a Page](/docs/how-to-guides/protect-a-page). diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsContent.razor b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsContent.razor index 9650497f..d1c9fcfe 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsContent.razor +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsContent.razor @@ -8,7 +8,7 @@ @if (_notFound) { - + Page not found @@ -26,7 +26,7 @@ } else if (_doc is not null) { - + @_doc.Title diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsLandingPage.razor b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsLandingPage.razor index 62ec35e3..79b76a4c 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsLandingPage.razor +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/Pages/DocsLandingPage.razor @@ -3,171 +3,173 @@ - + - - UltimateAuth Docs - The modern way to understand authentication — unified, simple and powerful. + + + + ULTIMATEAUTH DOCUMENTATION + + + + Authentication, unified. + - - Learn the architecture, flows, client APIs, security model, and integration patterns of UltimateAuth: + + Everything you need to build, integrate, and understand authentication with UltimateAuth. + - - - Start Here - + + + + New to UltimateAuth? Start here. + + - If you are new to UltimateAuth, start with Getting Started and - Fundamentals. If you care about usage, continue with Client & API. - If you are evaluating architecture or enterprise adoption, - continue with Auth Flows, Security, and Plugin Domains. + Build your first working authentication application with a guided Quick Start. + Get familiar with the framework before exploring its architecture. + + + + Start Quick Start + + + + Getting Started Guide + + + - - - Getting Started + + + + + + Build & Integrate + - Set up UltimateAuth quickly, understand the first login flow, - and move into real-world application setup. + Set up UltimateAuth for Blazor Server, + WebAssembly, Resource APIs, and real-world + application architectures. - - Open Getting Started + + Explore Setup Guides - - - Fundamentals + + + + Understand the Architecture - Understand the authentication model, flow-based execution, - auth modes, client profiles, and runtime architecture. + Learn the unified authentication model, flows, authentication modes, client profiles, + and runtime behavior. - - Open Fundamentals + + Explore Fundamentals + - - - - Auth Flows + + + Explore by Topic + + + Looking for something specific? Go directly to the documentation section you need. + + + + + + + + Authentication Flows + - Explore login, refresh, logout, session lifecycle, - token behavior, and device-aware authentication flows. + Login, refresh, logout, and session lifecycle. - - - Open Auth Flows - + + Explore Auth Flows → + - - - - Client & API - + + + + + Client & API + - Explore the usage, the service client layer of UltimateAuth. - Everything from basic login/logout to advanced session management should call with client. + Client APIs, integration, and application usage. - - - Open Client & API - + + Explore Client APIs → + - - - - Plugin Domains - + + + + + Plugin Domains + - Explore the user, credential, and authorization modules that not required for core Auth, but essential. + Users, credentials, authorization, and policies. - - - Open Plugin Domains - + + Explore Plugin Domains → + - - - - + + + + Security - - Review the session security model, token behavior, - refresh rotation, and the policy pipeline. + Session security, token protection, refresh rotation, and security policies. - - - Open Security Docs - + + Explore Security → + - - - - Coming Next - - - - The documentation experience will continue to expand with richer navigation, - improved code rendering, and dedicated reference surfaces. - - - - API Reference - Interactive authentication sandbox - Sample-driven guides - Integration recipes for Blazor, WASM, MAUI, MVC, and APIs - - - - - - - Browse Documentation - - - - View Markdown Source + + + View Documentation Source diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json index 12c6baeb..a326d4da 100644 --- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json @@ -257,5 +257,33 @@ "Order": 4, "Group": "security", "GroupOrder": 8 + }, + { + "Title": "How-to Guides", + "Slug": "how-to-guides/index", + "Order": 1, + "Group": "how-to-guides", + "GroupOrder": 9 + }, + { + "Title": "Protect a Page", + "Slug": "how-to-guides/protect-a-page", + "Order": 2, + "Group": "how-to-guides", + "GroupOrder": 9 + }, + { + "Title": "Migration Guides", + "Slug": "migration/index", + "Order": 1, + "Group": "migration", + "GroupOrder": 10 + }, + { + "Title": "Migrating to v0.1.1", + "Slug": "migration/v0.1.1", + "Order": 2, + "Group": "migration", + "GroupOrder": 10 } ] \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/how-to-guides/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/how-to-guides/index.json new file mode 100644 index 00000000..faf0a0c4 --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/how-to-guides/index.json @@ -0,0 +1,14 @@ +{ + "Slug": "how-to-guides/index", + "Title": "How-to Guides", + "Html": "\n\u003Cp\u003EPractical, task-oriented guides for building applications with UltimateAuth.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022available-guides\u0022\u003EAvailable Guides\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/how-to-guides/protect-a-page\u0022\u003EProtect a Page\u003C/a\u003E \u2014 Secure Blazor pages and UI sections using UltimateAuth and ASP.NET Core authorization.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Headings": [ + { + "Id": "available-guides", + "Text": "Available Guides", + "Level": 0 + } + ], + "SourceHash": "D45E8C30B26AF54F802D9926DA15D933AA6342A064BE1D54F90087F8F64A49AA", + "BuilderVersion": "1" +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/how-to-guides/protect-a-page.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/how-to-guides/protect-a-page.json new file mode 100644 index 00000000..2079660f --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/how-to-guides/protect-a-page.json @@ -0,0 +1,234 @@ +{ + "Slug": "how-to-guides/protect-a-page", + "Title": "Protect a Page", + "Html": "\n\u003Cp\u003ELearn how to protect Blazor pages and UI sections using UltimateAuth and ASP.NET Core authorization.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides declarative authorization for routed Blazor pages through \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E, conditional rendering through \u003Ccode\u003EUAuthStateView\u003C/code\u003E, and interoperability with ASP.NET Core\u0027s \u003Ccode\u003E[Authorize]\u003C/code\u003E attribute.\u003C/p\u003E\n\u003Cp\u003EIn this guide, you\u0027ll learn how to:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ERequire authentication before a page can be accessed.\u003C/li\u003E\n\u003Cli\u003ERestrict access using roles and permissions.\u003C/li\u003E\n\u003Cli\u003ECombine authorization requirements with different match modes.\u003C/li\u003E\n\u003Cli\u003ERequire an active session.\u003C/li\u003E\n\u003Cli\u003EProtect individual UI sections.\u003C/li\u003E\n\u003Cli\u003EUse ASP.NET Core authorization alongside UltimateAuth.\u003C/li\u003E\n\u003Cli\u003EConfigure authorization with built-in and custom routers.\u003C/li\u003E\n\u003Cli\u003EUnderstand the role of \u003Ccode\u003EUAuthApp\u003C/code\u003E and \u003Ccode\u003EUAuthComponentBase\u003C/code\u003E.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003ESecurity:\u003C/strong\u003E Blazor authorization controls which UI components are rendered. Protected API endpoints, data, and server operations must enforce authorization independently on the server. Client-side authorization must never be the only security boundary.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022prerequisites\u0022\u003EPrerequisites\u003C/h2\u003E\n\u003Cp\u003EYou should have a working UltimateAuth Blazor application with authentication configured.\u003C/p\u003E\n\u003Cp\u003EIf you haven\u0027t created one yet, start with the \u003Ca href=\u0022/docs/getting-started/quickstart\u0022\u003EQuickStart\u003C/a\u003E.\u003C/p\u003E\n\u003Cp\u003EThe examples in this guide use:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\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@using CodeBeam.UltimateAuth.Core.Contracts\n@using Microsoft.AspNetCore.Authorization\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EYou can add these namespaces to your application\u0027s \u003Ccode\u003E_Imports.razor\u003C/code\u003E file to make them available across components.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-a-page-with-uauthauthorize\u0022\u003E1. Protect a Page with UAuthAuthorize\u003C/h2\u003E\n\u003Cp\u003EThe simplest way to protect a routed Blazor page is to add the \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E attribute.\u003C/p\u003E\n\u003Cp\u003ECreate \u003Ccode\u003EPages/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\u0026lt;h1\u0026gt;Dashboard\u0026lt;/h1\u0026gt;\n\n\u0026lt;p\u0026gt;You are authenticated.\u0026lt;/p\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThat\u0027s it.\u003C/p\u003E\n\u003Cp\u003EWhen a user navigates to \u003Ccode\u003E/dashboard\u003C/code\u003E, UltimateAuth evaluates the page\u0027s authorization requirements before rendering the page component.\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EAuthenticated user with an acceptable session state:\u003C/strong\u003E The page can be rendered.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EAnonymous user:\u003C/strong\u003E Authorization is denied.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EInactive session:\u003C/strong\u003E Authorization is denied when an inactive session state is available and \u003Ccode\u003ERequireActive\u003C/code\u003E is enabled.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EBy default, \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E requires an authenticated user and an active session where session-state information is present.\u003C/p\u003E\n\u003Cp\u003ENo explicit role or permission is required.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022does-the-page-need-to-inherit-from-uauthcomponentbase\u0022\u003EDoes the page need to inherit from UAuthComponentBase?\u003C/h3\u003E\n\u003Cp\u003E\u003Cstrong\u003ENo.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThe page above works without inheriting from any UltimateAuth base class.\u003C/p\u003E\n\u003Cp\u003EAuthorization is enforced by \u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E, not by the page\u0027s base class.\u003C/p\u003E\n\u003Cp\u003EThis also means an unauthorized routed page is not instantiated by UltimateAuth\u0027s route view.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022require-a-role\u0022\u003E2. Require a Role\u003C/h2\u003E\n\u003Cp\u003EUse the \u003Ccode\u003ERoles\u003C/code\u003E property to restrict access to users with a specific 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@page \u0026quot;/admin\u0026quot;\n@attribute [UAuthAuthorize(Roles = \u0026quot;Admin\u0026quot;)]\n\n\u0026lt;h1\u0026gt;Administration\u0026lt;/h1\u0026gt;\n\n\u0026lt;p\u0026gt;Only administrators can access this page.\u0026lt;/p\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe user must be authenticated and have the \u003Ccode\u003EAdmin\u003C/code\u003E role.\u003C/p\u003E\n\u003Cp\u003EYou can also declare multiple roles:\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;/management\u0026quot;\n@attribute [UAuthAuthorize(Roles = \u0026quot;Admin,Manager\u0026quot;)]\n\n\u0026lt;h1\u0026gt;Management\u0026lt;/h1\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWith the default \u003Ccode\u003ECategory\u003C/code\u003E match mode, having \u003Cstrong\u003Eeither \u003Ccode\u003EAdmin\u003C/code\u003E or \u003Ccode\u003EManager\u003C/code\u003E\u003C/strong\u003E is sufficient, provided the other configured authorization categories also succeed.\u003C/p\u003E\n\u003Cp\u003ERole names must correspond to the roles recognized by UltimateAuth\u0027s authentication state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022require-a-permission\u0022\u003E3. Require a Permission\u003C/h2\u003E\n\u003Cp\u003EFor more granular authorization, use \u003Ccode\u003EPermissions\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;/users\u0026quot;\n@attribute [UAuthAuthorize(Permissions = \u0026quot;users.read.self\u0026quot;)]\n\n\u0026lt;h1\u0026gt;Users\u0026lt;/h1\u0026gt;\n\n\u0026lt;p\u0026gt;You have permission to view users.\u0026lt;/p\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUltimateAuth checks the requested permission against the current \u003Ccode\u003EUAuthState\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EYou can declare multiple 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\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;/reports\u0026quot;\n@attribute [UAuthAuthorize(Permissions = \u0026quot;reports.read.self,reports.manage.admin\u0026quot;)]\n \n\u0026lt;h1\u0026gt;Reports\u0026lt;/h1\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EBy default, the user needs at least one of these permissions.\u003C/p\u003E\n\u003Cp\u003EPermission strings must match the permissions represented in the authenticated user\u0027s UltimateAuth state.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003ETip:\u003C/strong\u003E Prefer permissions for operations such as reading reports, managing users, or editing records. Roles are useful for broader organizational responsibilities, while permissions can express specific capabilities.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022combine-roles-and-permissions\u0022\u003E4. Combine Roles and Permissions\u003C/h2\u003E\n\u003Cp\u003EYou can specify both \u003Ccode\u003ERoles\u003C/code\u003E and \u003Ccode\u003EPermissions\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;/administration/users\u0026quot;\n@attribute [UAuthAuthorize(Roles = \u0026quot;Admin\u0026quot;, Permissions = \u0026quot;users.manage.admin\u0026quot;)]\n\n\u0026lt;h1\u0026gt;User Management\u0026lt;/h1\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWith the default \u003Ccode\u003EMatchMode = Category\u003C/code\u003E, the user must satisfy:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAdmin role AND users.manage permission\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis is because \u003Ccode\u003ECategory\u003C/code\u003E requires at least one match from each configured category.\u003C/p\u003E\n\u003Cp\u003EIf multiple roles and permissions are provided:\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;/management/users\u0026quot;\n@attribute [UAuthAuthorize(Roles = \u0026quot;Admin,Manager\u0026quot;, Permissions = \u0026quot;users.read.self,users.manage.admin\u0026quot;)]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe requirement becomes:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003E(Admin OR Manager) AND (users.read.self OR users.manage.admin)\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis is the default authorization behavior.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understand-match-modes\u0022\u003E5. Understand Match Modes\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports three authorization matching modes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EMatch Mode\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\u003ECategory\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAt least one matching value in each configured category\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAny\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAt least one configured role or permission must match\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EEvery configured role and permission must match\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe default is \u003Ccode\u003EAuthorizationMatchMode.Category\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022category-the-default\u0022\u003ECategory \u2014 the default\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\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;/category-example\u0026quot;\n@attribute [UAuthAuthorize(Roles = \u0026quot;Admin,Manager\u0026quot;, Permissions = \u0026quot;reports.read,reports.write\u0026quot;, MatchMode = AuthorizationMatchMode.Category)]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe user must have:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EAdmin\u003C/code\u003E \u003Cstrong\u003Eor\u003C/strong\u003E \u003Ccode\u003EManager\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Ereports.read\u003C/code\u003E \u003Cstrong\u003Eor\u003C/strong\u003E \u003Ccode\u003Ereports.write\u003C/code\u003E.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EBoth configured categories must succeed.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECategory\u003C/code\u003E when roles and permissions represent independent requirements but alternatives within each category are acceptable.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022any-one-match-is-enough\u0022\u003EAny \u2014 one match is enough\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@page \u0026quot;/any-example\u0026quot;\n@attribute [UAuthAuthorize(Roles = \u0026quot;Admin,Auditor\u0026quot;, Permissions = \u0026quot;reports.read.admin\u0026quot;, MatchMode = AuthorizationMatchMode.Any)]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe user needs at least one of:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EAdmin\u003C/code\u003E role.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAuditor\u003C/code\u003E role.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Ereports.read.admin\u003C/code\u003E permission.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThis is useful for pages with several alternative authorization paths.\u003C/p\u003E\n\u003Cp\u003EBe careful when combining high-privilege roles and lower-privilege permissions with \u003Ccode\u003EAny\u003C/code\u003E: a single matching permission grants access.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022all-every-value-is-required\u0022\u003EAll \u2014 every value is required\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@page \u0026quot;/all-example\u0026quot;\n@attribute [UAuthAuthorize(Roles = \u0026quot;Admin,SecurityOfficer\u0026quot;, Permissions = \u0026quot;audit.read.self,audit.export.admin\u0026quot;, MatchMode = AuthorizationMatchMode.All)]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe user must have:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EAdmin\u003C/code\u003E role.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003ESecurityOfficer\u003C/code\u003E role.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eaudit.read.self\u003C/code\u003E permission.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003Eaudit.export.admin\u003C/code\u003E permission.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EUse \u003Ccode\u003EAll\u003C/code\u003E when every declared requirement is mandatory.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-a-match-mode\u0022\u003EChoosing a match mode\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ERequirement\u003C/th\u003E\n\u003Cth\u003ERecommended mode\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAny authenticated user\u003C/td\u003E\n\u003Ctd\u003EParameterless attribute\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOne required role\u003C/td\u003E\n\u003Ctd\u003EDefault \u003Ccode\u003ECategory\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAny of several roles\u003C/td\u003E\n\u003Ctd\u003EDefault \u003Ccode\u003ECategory\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EOne role AND one permission\u003C/td\u003E\n\u003Ctd\u003EDefault \u003Ccode\u003ECategory\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERole OR permission\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EAny\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAll listed roles AND permissions\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EAll\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EIf roles and permissions are omitted entirely, an authenticated user satisfies the authorization requirement regardless of match mode, subject to the session-state requirement.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022require-an-active-session\u0022\u003E6. Require an Active Session\u003C/h2\u003E\n\u003Cp\u003EBy default, \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E uses:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003ERequireActive = true\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe default configuration can be written explicitly:\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;/account/settings\u0026quot;\n@attribute [UAuthAuthorize(RequireActive = true)]\n\n\u0026lt;h1\u0026gt;Account Settings\u0026lt;/h1\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EWhen UltimateAuth has session-state information and the session is not active, authorization returns an inactive result.\u003C/p\u003E\n\u003Cp\u003EIf your application needs to allow an authenticated user regardless of a reported non-active session state, you can disable this requirement:\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;/session-information\u0026quot;\n@attribute [UAuthAuthorize(RequireActive = false)]\n\n\u0026lt;h1\u0026gt;Session Information\u0026lt;/h1\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis changes only the session-state condition. Authentication is still required.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E \u003Ccode\u003ERequireActive\u003C/code\u003E evaluates the session state currently available to the Blazor client. It does not independently validate a session against the server for every render. Server-side session validation and revocation enforcement remain necessary.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cp\u003EWhen no session-state value is available, the default evaluator does not reject the user solely because that value is absent.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-part-of-a-page-with-uauthstateview\u0022\u003E7. Protect Part of a Page with UAuthStateView\u003C/h2\u003E\n\u003Cp\u003ESometimes you want the page to remain publicly accessible while protecting only a specific section.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003EUAuthStateView\u003C/code\u003E for conditional rendering.\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;/products\u0026quot;\n\n\u0026lt;h1\u0026gt;Products\u0026lt;/h1\u0026gt;\n\n\u0026lt;p\u0026gt;Everyone can view this page.\u0026lt;/p\u0026gt;\n\n\u0026lt;UAuthStateView Roles=\u0026quot;Admin\u0026quot;\u0026gt;\n \u0026lt;Authorized\u0026gt;\n \u0026lt;button\u0026gt;Edit Products\u0026lt;/button\u0026gt;\n \u0026lt;/Authorized\u0026gt;\n \u0026lt;NotAuthorized\u0026gt;\n \u0026lt;p\u0026gt;Administrative tools are unavailable.\u0026lt;/p\u0026gt;\n \u0026lt;/NotAuthorized\u0026gt;\n\u0026lt;/UAuthStateView\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe page itself remains accessible to everyone.\u003C/p\u003E\n\u003Cp\u003EHowever, the administrative section is only rendered for an authorized user.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthStateView\u003C/code\u003E uses the same \u003Ccode\u003EIUAuthAuthorizationEvaluator\u003C/code\u003E as \u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E, so matching roles, permissions, and match modes have consistent semantics.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic-authentication-only-section\u0022\u003EBasic authentication-only section\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\u0026lt;UAuthStateView\u0026gt;\n \u0026lt;Authorized\u0026gt;\n \u0026lt;p\u0026gt;Welcome back!\u0026lt;/p\u0026gt;\n \u0026lt;/Authorized\u0026gt;\n \u0026lt;NotAuthorized\u0026gt;\n \u0026lt;p\u0026gt;Sign in to access your account features.\u0026lt;/p\u0026gt;\n \u0026lt;/NotAuthorized\u0026gt;\n\u0026lt;/UAuthStateView\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAs with parameterless \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E, authentication is required, along with an acceptable session state when \u003Ccode\u003ERequireActive\u003C/code\u003E is true.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-a-section-with-permissions\u0022\u003EProtect a section with 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\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;UAuthStateView Permissions=\u0026quot;orders.refund.self\u0026quot;\u0026gt;\n \u0026lt;Authorized\u0026gt;\n \u0026lt;button\u0026gt;Refund Order\u0026lt;/button\u0026gt;\n \u0026lt;/Authorized\u0026gt;\n \u0026lt;NotAuthorized\u0026gt;\n \u0026lt;p\u0026gt;You cannot refund orders.\u0026lt;/p\u0026gt;\n \u0026lt;/NotAuthorized\u0026gt;\n\u0026lt;/UAuthStateView\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022combine-requirements-in-a-section\u0022\u003ECombine requirements in a section\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\u0026lt;UAuthStateView Roles=\u0026quot;Admin,Manager\u0026quot; Permissions=\u0026quot;orders.read.self,orders.manage.admin\u0026quot; MatchMode=\u0026quot;AuthorizationMatchMode.Category\u0026quot;\u0026gt;\n \u0026lt;Authorized\u0026gt;\n \u0026lt;p\u0026gt;Order management tools are available.\u0026lt;/p\u0026gt;\n \u0026lt;/Authorized\u0026gt;\n \u0026lt;NotAuthorized\u0026gt;\n \u0026lt;p\u0026gt;Access denied.\u0026lt;/p\u0026gt;\n \u0026lt;/NotAuthorized\u0026gt;\n\u0026lt;/UAuthStateView\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis uses the same \u003Ccode\u003ECategory\u003C/code\u003E semantics as the routed page attribute.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022access-the-current-authentication-state\u0022\u003EAccess the current authentication state\u003C/h3\u003E\n\u003Cp\u003EThe \u003Ccode\u003EAuthorized\u003C/code\u003E fragment receives the current \u003Ccode\u003EUAuthState\u003C/code\u003E as its 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\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;UAuthStateView\u0026gt;\n \u0026lt;Authorized Context=\u0026quot;state\u0026quot;\u0026gt;\n \u0026lt;p\u0026gt;Hello, @state.Identity?.DisplayName!\u0026lt;/p\u0026gt;\n \u0026lt;/Authorized\u0026gt;\n \u0026lt;NotAuthorized\u0026gt;\n \u0026lt;p\u0026gt;Please sign in.\u0026lt;/p\u0026gt;\n \u0026lt;/NotAuthorized\u0026gt;\n\u0026lt;/UAuthStateView\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis allows authorized content to access identity information without requiring a custom base class.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handle-unauthorized-inactive-and-authorizing-states\u0022\u003E8. Handle Unauthorized, Inactive, and Authorizing States\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthStateView\u003C/code\u003E supports multiple rendering states.\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;UAuthStateView Roles=\u0026quot;Admin\u0026quot;\n RequireActive=\u0026quot;true\u0026quot;\u0026gt;\n \u0026lt;Authorized\u0026gt;\n \u0026lt;p\u0026gt;Administrator access granted.\u0026lt;/p\u0026gt;\n \u0026lt;/Authorized\u0026gt;\n\n \u0026lt;NotAuthorized\u0026gt;\n \u0026lt;p\u0026gt;You do not have permission to access this section.\u0026lt;/p\u0026gt;\n \u0026lt;/NotAuthorized\u0026gt;\n\n \u0026lt;Inactive Context=\u0026quot;state\u0026quot;\u0026gt;\n \u0026lt;p\u0026gt;Your session is not active.\u0026lt;/p\u0026gt;\n \u0026lt;/Inactive\u0026gt;\n\n \u0026lt;Authorizing\u0026gt;\n \u0026lt;p\u0026gt;Checking your permissions...\u0026lt;/p\u0026gt;\n \u0026lt;/Authorizing\u0026gt;\n\u0026lt;/UAuthStateView\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\u003EFragment\u003C/th\u003E\n\u003Cth\u003EWhen displayed\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAuthorized\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthorization succeeds\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENotAuthorized\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthorization is denied\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EInactive\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EA non-active session is detected and \u003Ccode\u003ERequireActive\u003C/code\u003E is true\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAuthorizing\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthorization evaluation is in progress\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EIf \u003Ccode\u003EInactive\u003C/code\u003E is not supplied, the component falls back to the \u003Ccode\u003ENotAuthorized\u003C/code\u003E fragment.\u003C/p\u003E\n\u003Cp\u003EIf \u003Ccode\u003EAuthorizing\u003C/code\u003E is not supplied, unauthorized content is used while a new authorization result is pending.\u003C/p\u003E\n\u003Cp\u003EDuring reevaluation, previously authorized section content is removed before an asynchronous policy evaluation completes. This prevents stale authorized content from remaining visible during that pending evaluation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-an-asp.net-core-authorization-policy\u0022\u003E9. Use an ASP.NET Core Authorization Policy\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthStateView\u003C/code\u003E supports the \u003Ccode\u003EPolicy\u003C/code\u003E parameter.\u003C/p\u003E\n\u003Cp\u003EFirst, register a policy using ASP.NET Core authorization:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddAuthorizationCore(options =\u0026gt;\n{\n options.AddPolicy(\u0026quot;CanViewReports\u0026quot;, policy =\u0026gt; policy.RequireClaim(\u0026quot;reports_access\u0026quot;, \u0026quot;granted\u0026quot;));\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThen use that policy in a UI section:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\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;UAuthStateView Policy=\u0026quot;CanViewReports\u0026quot;\u0026gt;\n \u0026lt;Authorized\u0026gt;\n \u0026lt;p\u0026gt;Reports are available.\u0026lt;/p\u0026gt;\n \u0026lt;/Authorized\u0026gt;\n \u0026lt;NotAuthorized\u0026gt;\n \u0026lt;p\u0026gt;Reports access is restricted.\u0026lt;/p\u0026gt;\n \u0026lt;/NotAuthorized\u0026gt;\n\u0026lt;/UAuthStateView\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe policy is evaluated through ASP.NET Core\u0027s \u003Ccode\u003EIAuthorizationService\u003C/code\u003E, using the claims principal produced from the current UltimateAuth state.\u003C/p\u003E\n\u003Cp\u003EYou can also combine a policy with roles and 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\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;UAuthStateView Roles=\u0026quot;Admin\u0026quot; Permissions=\u0026quot;reports.read\u0026quot; Policy=\u0026quot;CanViewReports\u0026quot; MatchMode=\u0026quot;AuthorizationMatchMode.All\u0026quot;\u0026gt;\n \u0026lt;Authorized\u0026gt;\n \u0026lt;p\u0026gt;All requirements passed.\u0026lt;/p\u0026gt;\n \u0026lt;/Authorized\u0026gt;\n\u0026lt;/UAuthStateView\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor \u003Ccode\u003EAll\u003C/code\u003E, every configured requirement must succeed.\u003C/p\u003E\n\u003Cp\u003EFor \u003Ccode\u003EAny\u003C/code\u003E, the policy is another possible successful condition.\u003C/p\u003E\n\u003Cp\u003EFor \u003Ccode\u003ECategory\u003C/code\u003E, the policy must succeed alongside the configured role and permission categories.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003ECurrent API distinction:\u003C/strong\u003E \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E currently exposes \u003Ccode\u003ERoles\u003C/code\u003E, \u003Ccode\u003EPermissions\u003C/code\u003E, \u003Ccode\u003EMatchMode\u003C/code\u003E, and \u003Ccode\u003ERequireActive\u003C/code\u003E, but not \u003Ccode\u003EPolicy\u003C/code\u003E. Use \u003Ccode\u003EUAuthStateView\u003C/code\u003E for policy-based section rendering, or ASP.NET Core \u003Ccode\u003E[Authorize(Policy = \u0026quot;...\u0026quot;)]\u003C/code\u003E for policy-protected routed pages.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-a-page-with-asp.net-core-authorize\u0022\u003E10. Protect a Page with ASP.NET Core Authorize\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth integrates with ASP.NET Core authorization.\u003C/p\u003E\n\u003Cp\u003EYou can use the standard \u003Ccode\u003E[Authorize]\u003C/code\u003E attribute on routed Blazor pages.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\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;/account\u0026quot;\n@attribute [Authorize]\n\n\u0026lt;h1\u0026gt;Account\u0026lt;/h1\u0026gt;\n\n\u0026lt;p\u0026gt;This page requires ASP.NET Core authorization.\u0026lt;/p\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOr require 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\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;/administration\u0026quot;\n@attribute [Authorize(Roles = \u0026quot;Admin\u0026quot;)]\n\n\u0026lt;h1\u0026gt;Administration\u0026lt;/h1\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOr a registered 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\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;/reports\u0026quot;\n@attribute [Authorize(Policy = \u0026quot;CanViewReports\u0026quot;)]\n\n\u0026lt;h1\u0026gt;Reports\u0026lt;/h1\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese checks are performed through ASP.NET Core\u0027s \u003Ccode\u003EAuthorizeRouteView\u003C/code\u003E and its authentication and authorization infrastructure.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides a Blazor \u003Ccode\u003EAuthenticationStateProvider\u003C/code\u003E that integrates UltimateAuth authentication state with ASP.NET Core.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022which-attribute-should-i-use\u0022\u003EWhich attribute should I use?\u003C/h3\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\u003E\u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E\u003C/th\u003E\n\u003Cth\u003E\u003Ccode\u003E[Authorize]\u003C/code\u003E\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERequires authentication\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERoles\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUltimateAuth permissions\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EVia separately configured ASP.NET Core policies or handlers\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EAuthorizationMatchMode\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003ENo direct equivalent property\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERequireActive\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003ENo direct equivalent property\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ENamed ASP.NET Core policy\u003C/td\u003E\n\u003Ctd\u003ENot currently\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERouted Blazor page\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E when working directly with UltimateAuth roles, permissions, and session-aware requirements.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003E[Authorize]\u003C/code\u003E when relying on ASP.NET Core authorization policies, requirements, or existing authorization infrastructure.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022combine-both-attributes\u0022\u003ECombine both attributes\u003C/h3\u003E\n\u003Cp\u003EYou can declare both on the same routed 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\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;/secure-reports\u0026quot;\n\n@attribute [UAuthAuthorize(Permissions = \u0026quot;reports.read\u0026quot;)]\n@attribute [Authorize(Roles = \u0026quot;Admin\u0026quot;)]\n\n\u0026lt;h1\u0026gt;Secure Reports\u0026lt;/h1\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe page is rendered only when \u003Cstrong\u003Eboth authorization systems allow access\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth evaluates \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E, and the underlying ASP.NET Core \u003Ccode\u003EAuthorizeRouteView\u003C/code\u003E evaluates \u003Ccode\u003E[Authorize]\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe requirements are effectively combined using AND semantics across the two mechanisms.\u003C/p\u003E\n\u003Cblockquote\u003E\n\u003Cp\u003E\u003Cstrong\u003ENote:\u003C/strong\u003E ASP.NET Core \u003Ccode\u003E[Authorize]\u003C/code\u003E uses the configured ASP.NET Core authentication state. \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E uses \u003Ccode\u003EUAuthState\u003C/code\u003E. UltimateAuth integrates these models, but custom authentication providers must keep their states consistent.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022how-uauthapp-protects-routes\u0022\u003E11. How UAuthApp Protects Routes\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthApp\u003C/code\u003E is the root Blazor integration component for UltimateAuth.\u003C/p\u003E\n\u003Cp\u003EIt provides:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EClient runtime initialization.\u003C/li\u003E\n\u003Cli\u003EThe current \u003Ccode\u003EUAuthState\u003C/code\u003E as a cascading parameter.\u003C/li\u003E\n\u003Cli\u003EASP.NET Core cascading authentication state.\u003C/li\u003E\n\u003Cli\u003ESession lifecycle coordination.\u003C/li\u003E\n\u003Cli\u003ERuntime initialization readiness.\u003C/li\u003E\n\u003Cli\u003EOptional built-in routing.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022built-in-router\u0022\u003EBuilt-in router\u003C/h3\u003E\n\u003Cp\u003EIf you use:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\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\u003EUltimateAuth automatically configures its router to use \u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EYou do not need to add a separate \u003Ccode\u003EAuthorizeRouteView\u003C/code\u003E or \u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe built-in router supports both \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E and ASP.NET Core \u003Ccode\u003E[Authorize]\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIt also supports an application-level \u003Ccode\u003ENotAuthorized\u003C/code\u003E fragment:\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;\n AppAssembly=\u0026quot;typeof(Program).Assembly\u0026quot;\n DefaultLayout=\u0026quot;typeof(Layout.MainLayout)\u0026quot;\u0026gt;\n \u0026lt;NotAuthorized\u0026gt;\n \u0026lt;h1\u0026gt;Access Denied\u0026lt;/h1\u0026gt;\n \u0026lt;p\u0026gt;You are not authorized to access this page.\u0026lt;/p\u0026gt;\n \u0026lt;/NotAuthorized\u0026gt;\n\u0026lt;/UAuthApp\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis fragment is displayed when route authorization fails.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022runtime-initialization\u0022\u003ERuntime initialization\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthApp\u003C/code\u003E initializes the client authentication state before allowing UltimateAuth-protected routes to be rendered.\u003C/p\u003E\n\u003Cp\u003EThis is important because authentication state may not be immediately available when a Blazor application first starts, particularly when restoring a session.\u003C/p\u003E\n\u003Cp\u003EDuring initialization or asynchronous authorization evaluation, \u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E can display an \u003Ccode\u003EAuthorizing\u003C/code\u003E fragment.\u003C/p\u003E\n\u003Cp\u003EIf one is not supplied, the protected route content remains unrendered while authorization is pending.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022manual-and-reactive-render-modes\u0022\u003EManual and Reactive render modes\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthApp\u003C/code\u003E supports \u003Ccode\u003EUAuthRenderMode.Manual\u003C/code\u003E and \u003Ccode\u003EUAuthRenderMode.Reactive\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThese control how UltimateAuth state changes affect general component rendering.\u003C/p\u003E\n\u003Cp\u003ERoute authorization itself subscribes to relevant authentication state changes, and \u003Ccode\u003EUAuthStateView\u003C/code\u003E also reacts to such changes.\u003C/p\u003E\n\u003Cp\u003EYou do not need to enable reactive rendering merely to make \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E work.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-pages-with-a-custom-router\u0022\u003E12. Protect Pages with a Custom Router\u003C/h2\u003E\n\u003Cp\u003EIf your application uses its own Blazor \u003Ccode\u003ERouter\u003C/code\u003E, set \u003Ccode\u003EUseBuiltInRouter=\u0026quot;false\u0026quot;\u003C/code\u003E on \u003Ccode\u003EUAuthApp\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThen use \u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E inside the router\u0027s \u003Ccode\u003EFound\u003C/code\u003E fragment.\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;false\u0026quot;\u0026gt;\n \u0026lt;Router AppAssembly=\u0026quot;typeof(Program).Assembly\u0026quot;\u0026gt;\n \u0026lt;Found Context=\u0026quot;routeData\u0026quot;\u0026gt;\n \u0026lt;UAuthAuthorizeRouteView RouteData=\u0026quot;routeData\u0026quot; DefaultLayout=\u0026quot;typeof(Layout.MainLayout)\u0026quot;\u0026gt;\n\n \u0026lt;NotAuthorized\u0026gt;\n \u0026lt;h1\u0026gt;Access Denied\u0026lt;/h1\u0026gt;\n \u0026lt;p\u0026gt;You cannot access this page.\u0026lt;/p\u0026gt;\n \u0026lt;/NotAuthorized\u0026gt;\n\n \u0026lt;Authorizing\u0026gt;\n \u0026lt;p\u0026gt;Checking access...\u0026lt;/p\u0026gt;\n \u0026lt;/Authorizing\u0026gt;\n\n \u0026lt;/UAuthAuthorizeRouteView\u0026gt;\n\n \u0026lt;FocusOnNavigate RouteData=\u0026quot;routeData\u0026quot; Selector=\u0026quot;h1\u0026quot; /\u0026gt;\n \u0026lt;/Found\u0026gt;\n \u0026lt;/Router\u0026gt;\n\u0026lt;/UAuthApp\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E wraps ASP.NET Core\u0027s \u003Ccode\u003EAuthorizeRouteView\u003C/code\u003E internally.\u003C/p\u003E\n\u003Cp\u003EThis means it supports both authorization systems, while checking UltimateAuth-specific requirements before the protected page component is instantiated.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not replace it with a plain \u003Ccode\u003ERouteView\u003C/code\u003E or \u003Ccode\u003EAuthorizeRouteView\u003C/code\u003E when your routed pages depend on \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EA plain ASP.NET Core \u003Ccode\u003EAuthorizeRouteView\u003C/code\u003E does not evaluate the UltimateAuth-specific attribute by itself.\u003C/p\u003E\n\u003Cp\u003EIf your custom router uses additional route assemblies, pass the appropriate \u003Ccode\u003EAdditionalAssemblies\u003C/code\u003E to the \u003Ccode\u003ERouter\u003C/code\u003E as needed.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-i-need-uauthcomponentbase\u0022\u003E13. Do I Need UAuthComponentBase?\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003ENot for authorization.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EConsider this 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\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;/settings\u0026quot;\n@attribute [UAuthAuthorize]\n\n\u0026lt;h1\u0026gt;Settings\u0026lt;/h1\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIt is protected when routed through \u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E, even though it doesn\u0027t inherit from \u003Ccode\u003EUAuthComponentBase\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EYou can still use \u003Ccode\u003EUAuthComponentBase\u003C/code\u003E when you need access to the UltimateAuth state lifecycle.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\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;/profile\u0026quot;\n@attribute [UAuthAuthorize]\n@inherits UAuthComponentBase\n\n\u0026lt;h1\u0026gt;Profile\u0026lt;/h1\u0026gt;\n\n\u0026lt;p\u0026gt;User: @AuthState.Identity?.DisplayName\u0026lt;/p\u0026gt;\n\n@code {\n protected override Task HandleAuthStateChangedAsync(\n UAuthStateChangeReason reason)\n {\n // React to authentication state changes.\n return Task.CompletedTask;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthComponentBase\u003C/code\u003E provides:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAccess to cascading \u003Ccode\u003EUAuthState\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EAuthentication state change notifications.\u003C/li\u003E\n\u003Cli\u003EAutomatic rerendering when the state changes, by default.\u003C/li\u003E\n\u003Cli\u003EAn asynchronous lifecycle hook.\u003C/li\u003E\n\u003Cli\u003EEvent subscription cleanup on disposal.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EHowever, it no longer performs page authorization itself.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022can-i-use-uauthauthorize-on-a-regular-child-component\u0022\u003ECan I use UAuthAuthorize on a regular child component?\u003C/h3\u003E\n\u003Cp\u003EThe attribute only declares metadata. It does not enforce authorization on its own.\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\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@attribute [UAuthAuthorize(Roles = \u0026quot;Admin\u0026quot;)]\n\n\u0026lt;div\u0026gt;Administrative tools\u0026lt;/div\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIf this is an ordinary child component rendered directly inside another page, \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E is not automatically evaluated by \u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ERoute authorization is performed for routed page components.\u003C/p\u003E\n\u003Cp\u003EFor conditional content inside a page, use \u003Ccode\u003EUAuthStateView\u003C/code\u003E instead.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022advanced-customize-authorization-evaluation\u0022\u003E14. Advanced: Customize Authorization Evaluation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses the \u003Ccode\u003EIUAuthAuthorizationEvaluator\u003C/code\u003E service for Blazor authorization.\u003C/p\u003E\n\u003Cp\u003EThe default evaluator is registered 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\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EIt evaluates:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EAuthentication status.\u003C/li\u003E\n\u003Cli\u003ESession state when required.\u003C/li\u003E\n\u003Cli\u003ERoles.\u003C/li\u003E\n\u003Cli\u003EPermissions.\u003C/li\u003E\n\u003Cli\u003EASP.NET Core policies supplied through authorization requirements.\u003C/li\u003E\n\u003Cli\u003EThe configured match mode.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe evaluator is registered with \u003Ccode\u003ETryAddScoped\u003C/code\u003E, allowing applications to provide their own implementation through dependency injection.\u003C/p\u003E\n\u003Cp\u003EIf you need custom application-specific authorization behavior, you can implement \u003Ccode\u003EIUAuthAuthorizationEvaluator\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA replacement evaluator receives the current \u003Ccode\u003EUAuthState\u003C/code\u003E and a \u003Ccode\u003EUAuthAuthorizationRequirement\u003C/code\u003E and returns a \u003Ccode\u003EUAuthAuthorizationResult\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA custom implementation should preserve secure defaults, especially for anonymous users, inactive sessions, and failed authorization checks.\u003C/p\u003E\n\u003Cp\u003EKeep authorization evaluation separate from component rendering and navigation logic.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022important-security-considerations\u0022\u003E15. Important Security Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-ui-authorization-is-not-api-authorization\u0022\u003EBlazor UI authorization is not API authorization\u003C/h3\u003E\n\u003Cp\u003EAn unauthorized user should not see or instantiate a protected routed page. However, hiding UI does not secure the HTTP endpoint or underlying data source.\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\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;/users\u0026quot;\n@attribute [UAuthAuthorize(Roles = \u0026quot;Admin\u0026quot;)]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis protects the Blazor page through UltimateAuth\u0027s router.\u003C/p\u003E\n\u003Cp\u003EIt does \u003Cstrong\u003Enot\u003C/strong\u003E automatically protect an unrelated API endpoint such as:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EGET /api/users\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThat endpoint must independently validate the caller and enforce the required authorization.\u003C/p\u003E\n\u003Cp\u003EFor Blazor Server, the same principle applies to sensitive server-side operations, even when they are invoked without an HTTP call from the component.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022uauthauthorize-is-currently-a-blazor-attribute\u0022\u003EUAuthAuthorize is currently a Blazor attribute\u003C/h3\u003E\n\u003Cp\u003EThe current \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E attribute is interpreted by UltimateAuth\u0027s Blazor authorization pipeline.\u003C/p\u003E\n\u003Cp\u003EIt does not, by itself, secure ASP.NET Core controllers or Minimal API endpoints.\u003C/p\u003E\n\u003Cp\u003EUse the server-side authorization mechanisms appropriate for those endpoints.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revocation-and-role-changes\u0022\u003ERevocation and role changes\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E and \u003Ccode\u003EUAuthStateView\u003C/code\u003E respond to authentication state changes.\u003C/p\u003E\n\u003Cp\u003EWhen the available \u003Ccode\u003EUAuthState\u003C/code\u003E changes, authorization can be reevaluated.\u003C/p\u003E\n\u003Cp\u003EHowever, a remote role change or server-side session revocation is not necessarily observed immediately by the Blazor client.\u003C/p\u003E\n\u003Cp\u003EServer-side authorization must remain authoritative, even while a client retains stale state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authorization-during-navigation\u0022\u003EAuthorization during navigation\u003C/h3\u003E\n\u003Cp\u003EA protected routed page is not instantiated until UltimateAuth authorization succeeds.\u003C/p\u003E\n\u003Cp\u003EWhen authorization becomes invalid, the route view stops rendering the protected page.\u003C/p\u003E\n\u003Cp\u003EThis is different from implementing a redirect inside the page\u0027s \u003Ccode\u003EOnInitialized\u003C/code\u003E or \u003Ccode\u003EOnAfterRender\u003C/code\u003E method, where the component may already have been instantiated.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-not-rely-on-hidden-controls-for-security\u0022\u003EDo not rely on hidden controls for security\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthStateView\u003C/code\u003E is useful for showing or hiding buttons, navigation entries, and sections.\u003C/p\u003E\n\u003Cp\u003EAlways authorize the underlying operation independently.\u003C/p\u003E\n\u003Cp\u003EFor example, hiding a \u003Ccode\u003EDelete User\u003C/code\u003E button does not remove the need to authorize the actual delete request.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022troubleshooting\u0022\u003E16. Troubleshooting\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022uauthauthorize-does-not-prevent-access\u0022\u003EUAuthAuthorize does not prevent access\u003C/h3\u003E\n\u003Cp\u003EFirst, make sure you\u0027re using \u003Cstrong\u003EUltimateAuth v0.1.1 or later\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EEarlier versions relied on \u003Ccode\u003EUAuthComponentBase\u003C/code\u003E for authorization checks. Starting with v0.1.1, \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E is enforced at the routing level through \u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIf authorization is not working as expected:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EVerify that \u003Ccode\u003ECodeBeam.UltimateAuth.Client.Blazor\u003C/code\u003E is version \u003Ccode\u003E0.1.1\u003C/code\u003E or later.\u003C/li\u003E\n\u003Cli\u003EIf you\u0027re using the built-in router, make sure \u003Ccode\u003EUseBuiltInRouter=\u0026quot;true\u0026quot;\u003C/code\u003E is configured on \u003Ccode\u003EUAuthApp\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EIf you\u0027re using a custom router, replace \u003Ccode\u003ERouteView\u003C/code\u003E or \u003Ccode\u003EAuthorizeRouteView\u003C/code\u003E with \u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EMake sure the page is routed within the \u003Ccode\u003EUAuthApp\u003C/code\u003E context.\u003C/li\u003E\n\u003Cli\u003EConfirm that the user meets the authentication, role, permission, and session-state requirements.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003E\u003Cstrong\u003EUpgrading from v0.1.0?\u003C/strong\u003E See \u003Ca href=\u0022/docs/migration/v0.1.1\u0022\u003EMigrating to v0.1.1\u003C/a\u003E for the required changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022the-protected-page-appears-blank-during-startup\u0022\u003EThe protected page appears blank during startup\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth may still be initializing or evaluating authorization.\u003C/p\u003E\n\u003Cp\u003EWith a custom router, provide an \u003Ccode\u003EAuthorizing\u003C/code\u003E fragment for a loading indicator.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022uauthstateview-reports-a-missing-cascading-uauthstate\u0022\u003EUAuthStateView reports a missing cascading UAuthState\u003C/h3\u003E\n\u003Cp\u003EMake sure the component is rendered inside \u003Ccode\u003EUAuthApp\u003C/code\u003E, or another host that correctly provides the required cascading UltimateAuth state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022asp.net-core-authorization-reports-a-missing-authenticationstate\u0022\u003EASP.NET Core authorization reports a missing AuthenticationState\u003C/h3\u003E\n\u003Cp\u003EMake sure ASP.NET Core\u0027s authentication state infrastructure is available.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthApp\u003C/code\u003E provides a \u003Ccode\u003ECascadingAuthenticationState\u003C/code\u003E within its child content.\u003C/p\u003E\n\u003Cp\u003EIf you create a custom host outside \u003Ccode\u003EUAuthApp\u003C/code\u003E, you must provide the appropriate ASP.NET Core cascading authentication state yourself.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022roles-or-permissions-are-unexpectedly-denied\u0022\u003ERoles or permissions are unexpectedly denied\u003C/h3\u003E\n\u003Cp\u003EConfirm that:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EThe user is authenticated.\u003C/li\u003E\n\u003Cli\u003EThe state contains the expected role or permission.\u003C/li\u003E\n\u003Cli\u003EThe correct match mode is selected.\u003C/li\u003E\n\u003Cli\u003EThe user\u0027s session state satisfies \u003Ccode\u003ERequireActive\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EThe client state has been updated after any server-side changes.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022i-need-a-policy-directly-on-a-routed-page\u0022\u003EI need a policy directly on a routed page\u003C/h3\u003E\n\u003Cp\u003EUse ASP.NET Core\u0027s \u003Ccode\u003E[Authorize(Policy = \u0026quot;...\u0026quot;)]\u003C/code\u003E attribute.\u003C/p\u003E\n\u003Cp\u003EThe current \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E attribute does not expose a \u003Ccode\u003EPolicy\u003C/code\u003E property.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022summary\u0022\u003ESummary\u003C/h2\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EScenario\u003C/th\u003E\n\u003Cth\u003ERecommended approach\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EProtect an entire Blazor page\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERequire an UltimateAuth role\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E[UAuthAuthorize(Roles = \u0026quot;...\u0026quot;)]\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERequire an UltimateAuth permission\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E[UAuthAuthorize(Permissions = \u0026quot;...\u0026quot;)]\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECombine roles and permissions\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EMatchMode\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERequire ASP.NET Core policy authorization\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003E[Authorize(Policy = \u0026quot;...\u0026quot;)]\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECombine ASP.NET Core and UltimateAuth\u003C/td\u003E\n\u003Ctd\u003EUse both attributes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EProtect an individual UI section\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthStateView\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUse built-in routing\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthApp UseBuiltInRouter=\u0026quot;true\u0026quot;\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUse custom routing\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EReact to authentication lifecycle changes\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EUAuthComponentBase\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EProtect an API or server operation\u003C/td\u003E\n\u003Ctd\u003EEnforce authorization on the server\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EUltimateAuth\u0027s Blazor authorization model allows you to declare requirements at the page level, conditionally render protected sections, and integrate with ASP.NET Core authorization without coupling page protection to a specific base class.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/getting-started/quickstart\u0022\u003EQuickStart\u003C/a\u003E \u2014 Create a working UltimateAuth application.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/fundamentals/auth-model\u0022\u003EAuthentication Model\u003C/a\u003E \u2014 Understand UltimateAuth authentication concepts.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/migration/v0.1.1\u0022\u003EMigration to v0.1.1\u003C/a\u003E \u2014 Update custom routers and existing Blazor authorization code.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Headings": [ + { + "Id": "prerequisites", + "Text": "Prerequisites", + "Level": 0 + }, + { + "Id": "protect-a-page-with-uauthauthorize", + "Text": "1. Protect a Page with UAuthAuthorize", + "Level": 0 + }, + { + "Id": "does-the-page-need-to-inherit-from-uauthcomponentbase", + "Text": "Does the page need to inherit from UAuthComponentBase?", + "Level": 1 + }, + { + "Id": "require-a-role", + "Text": "2. Require a Role", + "Level": 0 + }, + { + "Id": "require-a-permission", + "Text": "3. Require a Permission", + "Level": 0 + }, + { + "Id": "combine-roles-and-permissions", + "Text": "4. Combine Roles and Permissions", + "Level": 0 + }, + { + "Id": "understand-match-modes", + "Text": "5. Understand Match Modes", + "Level": 0 + }, + { + "Id": "category-the-default", + "Text": "Category \u2014 the default", + "Level": 1 + }, + { + "Id": "any-one-match-is-enough", + "Text": "Any \u2014 one match is enough", + "Level": 1 + }, + { + "Id": "all-every-value-is-required", + "Text": "All \u2014 every value is required", + "Level": 1 + }, + { + "Id": "choosing-a-match-mode", + "Text": "Choosing a match mode", + "Level": 1 + }, + { + "Id": "require-an-active-session", + "Text": "6. Require an Active Session", + "Level": 0 + }, + { + "Id": "protect-part-of-a-page-with-uauthstateview", + "Text": "7. Protect Part of a Page with UAuthStateView", + "Level": 0 + }, + { + "Id": "basic-authentication-only-section", + "Text": "Basic authentication-only section", + "Level": 1 + }, + { + "Id": "protect-a-section-with-permissions", + "Text": "Protect a section with permissions", + "Level": 1 + }, + { + "Id": "combine-requirements-in-a-section", + "Text": "Combine requirements in a section", + "Level": 1 + }, + { + "Id": "access-the-current-authentication-state", + "Text": "Access the current authentication state", + "Level": 1 + }, + { + "Id": "handle-unauthorized-inactive-and-authorizing-states", + "Text": "8. Handle Unauthorized, Inactive, and Authorizing States", + "Level": 0 + }, + { + "Id": "use-an-asp.net-core-authorization-policy", + "Text": "9. Use an ASP.NET Core Authorization Policy", + "Level": 0 + }, + { + "Id": "protect-a-page-with-asp.net-core-authorize", + "Text": "10. Protect a Page with ASP.NET Core Authorize", + "Level": 0 + }, + { + "Id": "which-attribute-should-i-use", + "Text": "Which attribute should I use?", + "Level": 1 + }, + { + "Id": "combine-both-attributes", + "Text": "Combine both attributes", + "Level": 1 + }, + { + "Id": "how-uauthapp-protects-routes", + "Text": "11. How UAuthApp Protects Routes", + "Level": 0 + }, + { + "Id": "built-in-router", + "Text": "Built-in router", + "Level": 1 + }, + { + "Id": "runtime-initialization", + "Text": "Runtime initialization", + "Level": 1 + }, + { + "Id": "manual-and-reactive-render-modes", + "Text": "Manual and Reactive render modes", + "Level": 1 + }, + { + "Id": "protect-pages-with-a-custom-router", + "Text": "12. Protect Pages with a Custom Router", + "Level": 0 + }, + { + "Id": "do-i-need-uauthcomponentbase", + "Text": "13. Do I Need UAuthComponentBase?", + "Level": 0 + }, + { + "Id": "can-i-use-uauthauthorize-on-a-regular-child-component", + "Text": "Can I use UAuthAuthorize on a regular child component?", + "Level": 1 + }, + { + "Id": "advanced-customize-authorization-evaluation", + "Text": "14. Advanced: Customize Authorization Evaluation", + "Level": 0 + }, + { + "Id": "important-security-considerations", + "Text": "15. Important Security Considerations", + "Level": 0 + }, + { + "Id": "blazor-ui-authorization-is-not-api-authorization", + "Text": "Blazor UI authorization is not API authorization", + "Level": 1 + }, + { + "Id": "uauthauthorize-is-currently-a-blazor-attribute", + "Text": "UAuthAuthorize is currently a Blazor attribute", + "Level": 1 + }, + { + "Id": "revocation-and-role-changes", + "Text": "Revocation and role changes", + "Level": 1 + }, + { + "Id": "authorization-during-navigation", + "Text": "Authorization during navigation", + "Level": 1 + }, + { + "Id": "do-not-rely-on-hidden-controls-for-security", + "Text": "Do not rely on hidden controls for security", + "Level": 1 + }, + { + "Id": "troubleshooting", + "Text": "16. Troubleshooting", + "Level": 0 + }, + { + "Id": "uauthauthorize-does-not-prevent-access", + "Text": "UAuthAuthorize does not prevent access", + "Level": 1 + }, + { + "Id": "the-protected-page-appears-blank-during-startup", + "Text": "The protected page appears blank during startup", + "Level": 1 + }, + { + "Id": "uauthstateview-reports-a-missing-cascading-uauthstate", + "Text": "UAuthStateView reports a missing cascading UAuthState", + "Level": 1 + }, + { + "Id": "asp.net-core-authorization-reports-a-missing-authenticationstate", + "Text": "ASP.NET Core authorization reports a missing AuthenticationState", + "Level": 1 + }, + { + "Id": "roles-or-permissions-are-unexpectedly-denied", + "Text": "Roles or permissions are unexpectedly denied", + "Level": 1 + }, + { + "Id": "i-need-a-policy-directly-on-a-routed-page", + "Text": "I need a policy directly on a routed page", + "Level": 1 + }, + { + "Id": "summary", + "Text": "Summary", + "Level": 0 + }, + { + "Id": "next-steps", + "Text": "Next Steps", + "Level": 0 + } + ], + "SourceHash": "DCAF757286008CAF750DF1425284DB7AAD17EAAF6B7FEA009089C1D6DEBEB91F", + "BuilderVersion": "1" +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/migration/index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/migration/index.json new file mode 100644 index 00000000..c595f397 --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/migration/index.json @@ -0,0 +1,14 @@ +{ + "Slug": "migration/index", + "Title": "Migration Guides", + "Html": "\n\u003Cp\u003EVersion-specific instructions for upgrading UltimateAuth applications.\u003C/p\u003E\n\u003Cp\u003EMigration guides focus on breaking changes, behavior changes, and required application updates.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022available-guides\u0022\u003EAvailable Guides\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/migration/v0.1.1\u0022\u003EMigrating to v0.1.1\u003C/a\u003E \u2014 Blazor route authorization and component lifecycle changes.\u003C/li\u003E\n\u003C/ul\u003E\n", + "Headings": [ + { + "Id": "available-guides", + "Text": "Available Guides", + "Level": 0 + } + ], + "SourceHash": "FA6FAFE02BA9EF2656EBA1C8531CEC3F43A2666D05A61B6E2979BD96B3593D8B", + "BuilderVersion": "1" +} \ No newline at end of file diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/migration/v0.1.1.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/migration/v0.1.1.json new file mode 100644 index 00000000..5b5ae49d --- /dev/null +++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/migration/v0.1.1.json @@ -0,0 +1,24 @@ +{ + "Slug": "migration/v0.1.1", + "Title": "Migrating to v0.1.1", + "Html": "\n\u003Cp\u003EUltimateAuth v0.1.1 improves Blazor page authorization by moving \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E enforcement to the routing layer.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EMost applications require no changes.\u003C/strong\u003E If you\u0027re using the built-in \u003Ccode\u003EUAuthApp\u003C/code\u003E router, authorization works automatically.\u003C/p\u003E\n\u003Cp\u003EOnly the following cases require attention.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022custom-routers\u0022\u003E1. Custom Routers\u003C/h2\u003E\n\u003Cp\u003EIf you use a custom Blazor router, replace \u003Ccode\u003EAuthorizeRouteView\u003C/code\u003E with \u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EBefore:\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\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;AuthorizeRouteView RouteData=\u0026quot;routeData\u0026quot; DefaultLayout=\u0026quot;typeof(MainLayout)\u0026quot; /\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EAfter:\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\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;UAuthAuthorizeRouteView RouteData=\u0026quot;routeData\u0026quot; DefaultLayout=\u0026quot;typeof(MainLayout)\u0026quot; /\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis enables \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E enforcement while preserving ASP.NET Core \u003Ccode\u003E[Authorize]\u003C/code\u003E support.\u003C/p\u003E\n\u003Cp\u003EMake sure your custom router is rendered inside \u003Ccode\u003EUAuthApp\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022uauthcomponentbase-overrides\u0022\u003E2. UAuthComponentBase Overrides\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthComponentBase\u003C/code\u003E no longer handles authorization or automatic redirects.\u003C/p\u003E\n\u003Cp\u003EIf you previously overrode \u003Ccode\u003EOnUnauthorized()\u003C/code\u003E or \u003Ccode\u003EOnForbidden()\u003C/code\u003E, replace that behavior with the \u003Ccode\u003ENotAuthorized\u003C/code\u003E fragment on \u003Ccode\u003EUAuthApp\u003C/code\u003E (built-in router) or \u003Ccode\u003EUAuthAuthorizeRouteView\u003C/code\u003E (custom router).\u003C/p\u003E\n\u003Cp\u003EThe authentication state change hook is now asynchronous.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EBefore:\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\u003Eprotected override void HandleAuthStateChanged(UAuthStateChangeReason reason)\n{\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EAfter:\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\u003Eprotected override Task HandleAuthStateChangedAsync(UAuthStateChangeReason reason)\n{\n return Task.CompletedTask;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EPages no longer need to inherit from \u003Ccode\u003EUAuthComponentBase\u003C/code\u003E to use \u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-requirement\u0022\u003E3. Session Requirement\u003C/h2\u003E\n\u003Cp\u003E\u003Ccode\u003E[UAuthAuthorize]\u003C/code\u003E now defaults to \u003Ccode\u003ERequireActive = true\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIf your application intentionally allows authenticated users with a reported inactive session state, explicitly use:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\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@attribute [UAuthAuthorize(RequireActive = false)]\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOtherwise, no change is needed.\u003C/p\u003E\n\u003Chr\u003E\n\u003Cp\u003EFor usage examples, see \u003Ca href=\u0022/docs/how-to-guides/protect-a-page\u0022\u003EProtect a Page\u003C/a\u003E.\u003C/p\u003E\n", + "Headings": [ + { + "Id": "custom-routers", + "Text": "1. Custom Routers", + "Level": 0 + }, + { + "Id": "uauthcomponentbase-overrides", + "Text": "2. UAuthComponentBase Overrides", + "Level": 0 + }, + { + "Id": "session-requirement", + "Text": "3. Session Requirement", + "Level": 0 + } + ], + "SourceHash": "27406465426DBB34AD27694EB7C388BBCE987897BBC5B2E241605D76F450B5D6", + "BuilderVersion": "1" +} \ No newline at end of file