{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/configure-auth",
  "version": "1.0.1",
  "name": "configure-auth",
  "description": "Add authentication and authorization to a Blazor Web App, accounting for the app's render mode. USE WHEN the user needs [Authorize] on pages, AuthorizeView, role or policy-based access, login/logout Identity pages, or AuthenticationStateProvider. Also USE WHEN auth state is null after WebAssembly loads, SignInManager throws in an interactive component, <NotAuthorized> content never renders in static SSR, or HttpContext.User is null in an interactive component. DO NOT USE for general component authoring (see author-component), for prerendering concerns unrelated to auth (see support-prerendering), or for managing non-auth cascading state (see coordinate-components).",
  "system_prompt_fragment": "# Configure Auth\n\n## Step 1 — Read AGENTS.md\n\nRead `AGENTS.md` at the workspace root for the project's interactivity mode and scope before making changes.\n\n## Step 2 — Register auth services in Program.cs\n\n```csharp\n// Program.cs (server project)\nbuilder.Services.AddCascadingAuthenticationState();\nbuilder.Services.AddAuthorization();\n```\n\nFor ASP.NET Core Identity add the Identity services:\n\n```csharp\nbuilder.Services.AddAuthentication(options =>\n{\n    options.DefaultScheme = IdentityConstants.ApplicationScheme;\n    options.DefaultSignInScheme = IdentityConstants.ExternalScheme;\n})\n.AddIdentityCookies();\n\nbuilder.Services.AddIdentityCore<ApplicationUser>()\n    .AddRoles<IdentityRole>()\n    .AddEntityFrameworkStores<ApplicationDbContext>()\n    .AddSignInManager()\n    .AddDefaultTokenProviders();\n```\n\n## Step 3 — Wire App.razor for auth and render mode\n\nThe `App.razor` component must use `AuthorizeRouteView` and conditionally apply the render mode so that pages excluded from interactive routing render statically.\n\n```razor\n<!DOCTYPE html>\n<html>\n<head>\n    <HeadOutlet @rendermode=\"RenderModeForPage\" />\n</head>\n<body>\n    <Routes @rendermode=\"RenderModeForPage\" />\n    <script src=\"_framework/blazor.web.js\"></script>\n</body>\n</html>\n\n@code {\n    [CascadingParameter]\n    public HttpContext HttpContext { get; set; } = default!;\n\n    private IComponentRenderMode? RenderModeForPage =>\n        HttpContext.AcceptsInteractiveRouting()\n            ? InteractiveServer   // replace with the app's render mode\n            : null;\n}\n```\n\nIn `Routes.razor` (or wherever the router lives), use `AuthorizeRouteView`:\n\n```razor\n<Router AppAssembly=\"typeof(Program).Assembly\">\n    <Found Context=\"routeData\">\n        <AuthorizeRouteView RouteData=\"routeData\"\n                            DefaultLayout=\"typeof(Layout.MainLayout)\">\n            <NotAuthorized>\n                @if (context.User.Identity?.IsAuthenticated != true)\n                {\n                    <RedirectToLogin />\n                }\n                else\n                {\n                    <p>You are not authorized to access this resource.</p>\n                }\n            </NotAuthorized>\n        </AuthorizeRouteView>\n        <FocusOnNavigate RouteData=\"routeData\" Selector=\"h1\" />\n    </Found>\n</Router>\n```\n\n## Step 4 — Protect pages and components\n\n### [Authorize] attribute on pages\n\n```razor\n@page \"/admin\"\n@attribute [Authorize]\n```\n\nWith roles or policies:\n\n```razor\n@attribute [Authorize(Roles = \"Admin\")]\n@attribute [Authorize(Policy = \"RequireManager\")]\n```\n\n### AuthorizeView for conditional UI\n\n```razor\n<AuthorizeView>\n    <Authorized>Welcome, @context.User.Identity?.Name!</Authorized>\n    <NotAuthorized><a href=\"Account/Login\">Log in</a></NotAuthorized>\n</AuthorizeView>\n```\n\nRole/policy variants:\n\n```razor\n<AuthorizeView Roles=\"Admin,Manager\">\n    <Authorized>Admin content here</Authorized>\n</AuthorizeView>\n```\n\n### Access auth state in code\n\n```csharp\n[CascadingParameter]\nprivate Task<AuthenticationState>? AuthState { get; set; }\n\nprotected override async Task OnInitializedAsync()\n{\n    if (AuthState is not null)\n    {\n        var state = await AuthState;\n        var isAdmin = state.User.IsInRole(\"Admin\");\n    }\n}\n```\n\n## Step 5 — Identity pages must stay static SSR\n\n`SignInManager` and `UserManager` use `HttpContext` internally and **throw in interactive components**. Identity pages (login, register, manage) must render as static SSR.\n\nIn a **globally interactive** app, mark every Identity page:\n\n```razor\n@page \"/Account/Login\"\n@attribute [ExcludeFromInteractiveRouting]\n```\n\nThis forces a full-page navigation (exits the interactive circuit) so the page renders through the static SSR pipeline with a real `HttpContext`.\n\n`App.razor` must use `AcceptsInteractiveRouting()` (Step 3) to return `null` for these pages — otherwise the framework still tries to render them interactively.\n\nIn a **per-page** app, Identity pages are static by default (no `@rendermode` directive), so `[ExcludeFromInteractiveRouting]` is not needed.\n\n## Step 6 — Auth state in WebAssembly / Auto mode\n\nWebAssembly components run in the browser and have no `HttpContext`. Auth state must be serialized from the server during prerendering and deserialized on the client.\n\n**Server `Program.cs`:**\n\n```csharp\nbuilder.Services.AddAuthenticationStateSerialization();\n```\n\n**Client `.Client/Program.cs`:**\n\n```csharp\nbuilder.Services.AddAuthenticationStateDeserialization();\n```\n\nWithout these calls, `Task<AuthenticationState>` resolves to an anonymous user after WebAssembly takes over from prerendering.\n\n`AddAuthenticationStateSerialization` accepts options to include role and claim data:\n\n```csharp\nbuilder.Services.AddAuthenticationStateSerialization(options =>\n    options.SerializeAllClaims = true);\n```\n\n## Render Mode × Auth Matrix\n\n| Render mode | HttpContext.User | SignInManager | Auth state source | Key requirement |\n|---|---|---|---|---|\n| Static SSR | Available | Works | Server pipeline | Use middleware for redirects, `<NotAuthorized>` does NOT render |\n| Server (interactive) | NOT available | Throws | `CascadingAuthenticationState` | Use `[Authorize]` + `AuthorizeView`, not `HttpContext` |\n| WebAssembly | NOT available | Throws | Serialized from server | `AddAuthenticationStateSerialization` / `Deserialization` |\n| Auto | NOT available after WASM | Throws | Serialized from server | Same as WebAssembly; register in **both** Program.cs files |\n\n## Common Mistakes\n\n| Mistake | Symptom | Fix |\n|---------|---------|-----|\n| Using `HttpContext.User` in interactive component | Null or stale claims | Use `[CascadingParameter] Task<AuthenticationState>` |\n| `SignInManager` in interactive component | `InvalidOperationException` | Move to static SSR page with `[ExcludeFromInteractiveRouting]` |\n| Missing `AddAuthenticationStateSerialization` | Anonymous user after WASM loads | Add to server Program.cs; add `Deserialization` to client Program.cs |\n| `<NotAuthorized>` in static SSR layout | Content never shown | Static SSR uses middleware pipeline; redirect via `LoginPath` or `RedirectToLogin` component |\n| Global interactivity without `AcceptsInteractiveRouting` | Identity pages crash | Add `AcceptsInteractiveRouting()` check in App.razor (Step 3) |\n| Missing `AddCascadingAuthenticationState()` | `Task<AuthenticationState>` is null | Register in Program.cs (Step 2) |",
  "applicable_domains": [
    "code",
    "dotnet",
    "engineering"
  ],
  "invocation": [
    "/configure-auth"
  ],
  "tags": [
    "dotnet-blazor",
    "dotnet",
    "csharp",
    "microsoft"
  ],
  "authored_by": "anthropics",
  "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-blazor/skills/configure-auth/SKILL.md",
  "lifecycle": "stable",
  "category": "dotnet",
  "provenance": {
    "source": "dotnet/skills",
    "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-blazor/skills/configure-auth/SKILL.md",
    "author": "Microsoft / .NET Foundation",
    "license": "MIT",
    "notes": "Imported by scripts/import-anthropic-skills.py."
  }
}