mcpbeat

Managing Blazor Server Authentication

microsoft/managing-blazor-server-authentication

> Manages authentication in Blazor Server applications with ASP.NET Core Identity. Solves critical operations failing silently in component handlers, login/logout not working in interactive mode, SignInManager returning null, cascading auth state parameter errors. Configures cascading authentication state and minimal API endpoints for login/logout/register (required for Interactive Server Mode). Covers Blazor auth UI (AuthorizeView, HTML form login, LoginName via @context.User.Identity?.Name). Also covers OWIN to ASP.NET Core middleware migration. Use when setting up authentication in Blazor Server, troubleshooting auth issues, encountering build errors for System.Web.Security types, or fixing runtime authentication failures in Blazor Server circuits.

11k tokens
context cost
the whole folder, loaded on every use
3
files
instructions only
0
copies elsewhere
how many repositories repackaged it
17
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/microsoft/upgrade-agent-plugins --skill managing-blazor-server-authentication

What comes with it

26 542 bytes besides the instruction
ref/cookie-auth-pattern.md
ref/endpoint-templates.md

The instruction itself

25 sections, as written by the author

Managing Authentication in Blazor Server

Manages authentication in Blazor Server applications with ASP.NET Core Identity. Covers critical Blazor Server auth patterns required for Interactive Server Mode (cookie auth via HTTP endpoints, cascading state) and Blazor auth UI patterns for WebForms migration.

Reference guide for complementary patterns. The workflow steps below contain explicit instructions on when to invoke each skill.

| Skill | Use For |

|-------|---------|

| migrating-webforms-to-blazor-server | Initial WebForms to Blazor migration (project setup, Routes, markup conversion) → Prerequisites |

| migrating-aspnet-identity | Core Identity migration (IdentityDbContext, middleware, OWIN cleanup) → Step 1 |

| migrating-owin-cookie-auth | OWIN cookie authentication without Identity (alternative path) → See OWIN reference section |

| managing-blazor-server-data-access | Data access and state management patterns in Blazor Server → Related |

| migrating-mvc-configuration | Web.config to appsettings.json migration → Related |


Workflow

Configure Blazor Server authentication in order:

Configuration Progress:
- [ ] Step 1: Configure core Identity infrastructure
- [ ] Step 2: Implement cookie auth pattern for Interactive Server Mode
- [ ] Step 3  Implement Blazor auth UI
- [ ] Step 4: Set up authorization patterns

Step 1: Migrate Core Identity Infrastructure

Execute the migrating-aspnet-identity skill to migrate core ASP.NET Identity infrastructure. Complete all steps in that skill, then return here for Blazor Server-specific authentication patterns. That skill handles:

  • Installing required Identity packages (Microsoft.AspNetCore.Identity.EntityFrameworkCore, etc.)
  • Converting IdentityDbContext (constructor changes, namespace updates)
  • Registering DbContext and Identity services in Program.cs
  • Adding authentication/authorization middleware (UseAuthentication(), UseAuthorization())
  • Updating UserManager and SignInManager usage
  • Cleaning up OWIN code

The process is identical whether migrating from ASP.NET MVC or Web Forms Identity.

After completing the Identity migration skill, add this Blazor Server-specific configuration:

builder.Services.AddCascadingAuthenticationState();

> CRITICAL: When using <Routes @rendermode="InteractiveServer" />, HttpContext is NULL during WebSocket circuits. Cookie-based authentication operations (login, register, logout) cannot be performed via Blazor component event handlers. They will silently fail.

Required pattern: Use standard HTML <form method="post"> elements that submit to minimal API endpoints via full HTTP POST requests. The endpoint performs the auth operation over a real HTTP connection and redirects back to a Blazor page.

For complete explanation of why this is necessary, troubleshooting common issues, and alternative approaches, read ref/cookie-auth-pattern.md.

For ready-to-use endpoint code (login, register, logout, password reset, external providers), read ref/endpoint-templates.md.

Quick example:

@* Login.razor *@
<form method="post" action="/Account/LoginHandler">
    <input type="email" name="email" required />
    <input type="password" name="password" required />
    <button type="submit">Log in</button>
</form>
// Program.cs
app.MapPost("/Account/LoginHandler", async (HttpContext context, SignInManager<IdentityUser> signInManager) =>
{
    var form = await context.Request.ReadFormAsync();
    var result = await signInManager.PasswordSignInAsync(form["email"].ToString(), form["password"].ToString(), false, false);
    return result.Succeeded ? Results.Redirect("/") : Results.Redirect("/Account/Login?error=Invalid+login+attempt");
}).DisableAntiforgery();

Step 3: Implement Blazor Auth UI

Use this step when migrating Web Forms auth controls (asp:Login, asp:LoginView, asp:LoginStatus, asp:LoginName) to Blazor UI.

If the application already has working auth UI, or uses scaffolded Identity Razor Pages under /Areas/Identity/, skip this step.

The cookie auth pattern from Step 2 handles the actual auth operations; this step covers the display layer.

AuthorizeView for Conditional Content

@* Show different content for authenticated vs anonymous users *@
<AuthorizeView>
    <NotAuthorized>
        <a href="/Account/Login">Log in</a>
        <a href="/Account/Register">Register</a>
    </NotAuthorized>
    <Authorized>
        Welcome, @context.User.Identity?.Name!
        <form method="post" action="/Account/Logout" class="d-inline">
            <button type="submit" class="btn btn-link">Log out</button>
        </form>
    </Authorized>
</AuthorizeView>

Login Form

Use a standard HTML <form method="post"> that submits to a minimal API endpoint (see Step 2):

@page "/Account/Login"

<h2>Log in</h2>

@if (!string.IsNullOrEmpty(errorMessage))
{
    <div class="alert alert-danger">@errorMessage</div>
}

<form method="post" action="/Account/LoginHandler">
    <div class="mb-3">
        <label for="email">Email</label>
        <input type="email" id="email" name="email" required class="form-control" />
    </div>
    <div class="mb-3">
        <label for="password">Password</label>
        <input type="password" id="password" name="password" required class="form-control" />
    </div>
    <button type="submit" class="btn btn-primary">Log in</button>
    <a href="/Account/Register">Register as new user</a>
</form>

@code {
    [SupplyParameterFromQuery] public string? Error { get; set; }
    private string? errorMessage;

    protected override void OnInitialized() => errorMessage = Error;
}

Registration Form

@page "/Account/Register"

<h2>Create a new account</h2>

@if (!string.IsNullOrEmpty(errorMessage))
{
    <div class="alert alert-danger">@errorMessage</div>
}

<form method="post" action="/Account/RegisterHandler">
    <div class="mb-3">
        <label for="email">Email</label>
        <input type="email" id="email" name="email" required class="form-control" />
    </div>
    <div class="mb-3">
        <label for="password">Password</label>
        <input type="password" id="password" name="password" required class="form-control" />
    </div>
    <div class="mb-3">
        <label for="confirmPassword">Confirm Password</label>
        <input type="password" id="confirmPassword" name="confirmPassword" required class="form-control" />
    </div>
    <button type="submit" class="btn btn-primary">Register</button>
</form>

@code {
    [SupplyParameterFromQuery] public string? Error { get; set; }
    private string? errorMessage;

    protected override void OnInitialized() => errorMessage = Error;
}

Change Password Form

@page "/Account/ChangePassword"
@attribute [Authorize]

<h2>Change Password</h2>

@if (!string.IsNullOrEmpty(errorMessage))
{
    <div class="alert alert-danger">@errorMessage</div>
}
@if (!string.IsNullOrEmpty(successMessage))
{
    <div class="alert alert-success">@successMessage</div>
}

<form method="post" action="/Account/ChangePasswordHandler">
    <div class="mb-3">
        <label for="currentPassword">Current Password</label>
        <input type="password" id="currentPassword" name="currentPassword" required class="form-control" />
    </div>
    <div class="mb-3">
        <label for="newPassword">New Password</label>
        <input type="password" id="newPassword" name="newPassword" required class="form-control" />
    </div>
    <div class="mb-3">
        <label for="confirmPassword">Confirm New Password</label>
        <input type="password" id="confirmPassword" name="confirmPassword" required class="form-control" />
    </div>
    <button type="submit" class="btn btn-primary">Change Password</button>
</form>

@code {
    [SupplyParameterFromQuery] public string? Error { get; set; }
    [SupplyParameterFromQuery] public string? Success { get; set; }

    private string? errorMessage;
    private string? successMessage;

    protected override void OnInitialized()
    {
        errorMessage = Error;
        successMessage = Success;
    }
}

Web Forms Control → Blazor Mapping

| Web Forms | Blazor |

|-----------|---------------|

| <asp:Login> | <form method="post"> with email/password inputs → minimal API endpoint |

| <asp:LoginView> | <AuthorizeView> with <NotAuthorized> / <Authorized> templates |

| <asp:LoginName> | @context.User.Identity?.Name inside <AuthorizeView> |

| <asp:LoginStatus> | Links inside <AuthorizeView> (login link for anonymous, logout form for authenticated) |

| <asp:CreateUserWizard> | Custom registration form with <form method="post"> → minimal API endpoint |

| <asp:ChangePassword> | Custom form with <form method="post"> → minimal API endpoint |


Step 4: Migrate Authorization Patterns

Web.config Authorization → Blazor Authorization

<!-- Web Forms — Web.config -->
<location path="Admin">
    <system.web>
        <authorization>
            <allow roles="Administrator" />
            <deny users="*" />
        </authorization>
    </system.web>
</location>
@* Blazor — Admin pages use [Authorize] attribute *@
@page "/Admin"
@attribute [Authorize(Roles = "Administrator")]

<h1>Admin Panel</h1>

LoginView Conditional Content

<!-- Web Forms -->
<asp:LoginView runat="server">
    <AnonymousTemplate>
        <a href="~/Account/Login">Log in</a>
    </AnonymousTemplate>
    <LoggedInTemplate>
        Welcome, <asp:LoginName runat="server" />!
        <asp:LoginStatus runat="server" LogoutAction="Redirect" LogoutPageUrl="~/" />
    </LoggedInTemplate>
</asp:LoginView>
@* Blazor — Native AuthorizeView *@
<AuthorizeView>
    <NotAuthorized>
        <a href="/Account/Login">Log in</a>
    </NotAuthorized>
    <Authorized>
        Welcome, @context.User.Identity?.Name!
        <form method="post" action="/Account/Logout" class="d-inline">
            <button type="submit" class="btn btn-link">Log out</button>
        </form>
    </Authorized>
</AuthorizeView>

Role-Based Content

<!-- Web Forms -->
<asp:LoginView runat="server">
    <RoleGroups>
        <asp:RoleGroup Roles="Administrator">
            <ContentTemplate><a href="~/Admin">Admin Panel</a></ContentTemplate>
        </asp:RoleGroup>
    </RoleGroups>
</asp:LoginView>
@* Blazor *@
<AuthorizeView Roles="Administrator">
    <a href="/Admin">Admin Panel</a>
</AuthorizeView>

Step 5: Update Authentication State Access

Code-Behind Auth Patterns

// Web Forms — code-behind
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
    var userName = HttpContext.Current.User.Identity.Name;
    var isAdmin = HttpContext.Current.User.IsInRole("Administrator");
}

// Web Forms — OWIN
var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>();
var user = manager.FindById(User.Identity.GetUserId());
// Blazor — inject auth state
@inject AuthenticationStateProvider AuthStateProvider

@code {
    private string? userName;
    private bool isAdmin;

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;
        userName = user.Identity?.Name;
        isAdmin = user.IsInRole("Administrator");
    }
}

Cascading Auth Parameter (Simpler)

// Blazor — cascading parameter (requires AddCascadingAuthenticationState)
[CascadingParameter]
private Task<AuthenticationState>? AuthState { get; set; }

protected override async Task OnInitializedAsync()
{
    if (AuthState != null)
    {
        var state = await AuthState;
        var user = state.User;
    }
}

OWIN to ASP.NET Core Identity Reference

Quick reference for OWIN Identity patterns already handled by the migrating-aspnet-identity skill:

| Web Forms (OWIN Startup.cs) | Blazor (Program.cs) |

|------------------------------|---------------------|

| app.UseCookieAuthentication(...) | builder.Services.AddAuthentication().AddCookie(...) |

| app.UseExternalSignInCookie(...) | builder.Services.AddAuthentication().AddGoogle/Facebook(...) |

| ConfigureAuth(app) in Startup.Auth.cs | Configuration in Program.cs services section |

| app.CreatePerOwinContext<ApplicationUserManager>(...) | builder.Services.AddIdentity<ApplicationUser, IdentityRole>() |

| SecurityStampValidator.OnValidateIdentity(...) | Built into ASP.NET Core Identity automatically |

> Note: For projects using OWIN cookie authentication without ASP.NET Identity, use the migrating-owin-cookie-auth skill instead.


Reference Documents

For detailed implementation patterns and code examples:

  • ref/cookie-auth-pattern.md — Complete explanation of why HttpContext NULL errors in WebSocket circuits, the form → minimal API pattern, DisableAntiforgery requirement, troubleshooting, and alternative approaches
  • ref/endpoint-templates.md — Ready-to-use minimal API endpoint code for login, register, logout, password reset, external providers (Google/Microsoft), and testing examples

Common Identity Gotchas

No HttpContext.Current

Blazor Server has no HttpContext.Current. Use dependency injection:

// WRONG: HttpContext.Current.User
// RIGHT: Inject AuthenticationStateProvider or use [CascadingParameter]

Blazor Server login/logout MUST use HTTP endpoints (not component-based), because cookies are set on HTTP responses — see ref/cookie-auth-pattern.md.

SignalR Circuit vs HTTP Request

Authentication state is captured when the circuit starts. If the user's session expires mid-circuit, they remain "authenticated" until the page refreshes. Use RevalidatingServerAuthenticationStateProvider for periodic revalidation.

Blazor Identity UI Scaffolding

For a complete Identity UI (login, register, manage profile), scaffold it:

dotnet aspnet-codegenerator identity -dc ApplicationDbContext --files "Account.Login;Account.Register;Account.Logout"

This generates Razor Pages (not components) under /Areas/Identity/. They coexist with Blazor.

How to use it

Copy the folder

Take microsoft/managing-blazor-server-authentication from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.