Bwfc Identity Migration logo

Bwfc Identity Migration

Organization
FritzAndFriends
bwfc-identity-migration

**WORKFLOW SKILL** — Migrate ASP.NET Web Forms Identity and Membership authentication to Blazor Server Identity. Covers OWIN→Core middleware, login/register/logout minimal API endpoints, BWFC login controls, cookie auth under Interactive Server, and role-based authorization. WHEN: "migrate identity", "login page migration", "OWIN to core", "cookie auth blazor", "LoginView migration". INVOKES: dotnet CLI for identity scaffolding. FOR SINGLE OPERATIONS: use bwfc-migration for markup, bwfc-data-migration for EF.

Overview

PublisherFritzAndFriends
RepositoryBlazorWebFormsComponents
Skill namebwfc-identity-migration
Stars
449
Forks
77
Bundled files
1
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 1 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by FritzAndFriends on GitHub. Read the source before you install it.

Installation

Install the Bwfc Identity Migration AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/FritzAndFriends/BlazorWebFormsComponents.git /tmp/BlazorWebFormsComponents
mkdir -p .claude/skills
cp -r /tmp/BlazorWebFormsComponents/migration-toolkit/skills/bwfc-identity-migration .claude/skills/bwfc-identity-migration
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Bwfc Identity Migration in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Bwfc Identity Migration on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Bwfc Identity Migration is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Web Forms Identity → Blazor Identity Migration

Overview

Web Forms Auth SystemEraBlazor Migration Path
ASP.NET Identity (OWIN)2013+ASP.NET Core Identity (closest match)
ASP.NET Membership2005-2013Core Identity (schema migration required)
FormsAuthentication2002-2005Core Identity or cookie auth

Related: /bwfc-migration (markup), /bwfc-data-migration (EF/architecture)

Critical Rules

  1. Cookie auth requires HTTP endpoints — login/register/logout MUST use <form method="post"> + minimal API endpoints. Component event handlers (@onclick) silently fail for cookie operations in interactive mode.
  2. NEVER replace LoginView with AuthorizeView — BWFC LoginView uses AuthenticationStateProvider natively with same template names (AnonymousTemplate, LoggedInTemplate).
  3. DisableAntiforgery required — Blazor forms don't include antiforgery tokens. All auth endpoints must call .DisableAntiforgery().
  4. Session-based auth data worksSession["key"] via WebFormsPageBase.Session / SessionShim. Don't inject IHttpContextAccessor.

Quick Reference

BWFC Login Controls (drop-in replacements)

<Login />, <LoginName />, <LoginStatus />, <LoginView>, <CreateUserWizard />, <ChangePassword />, <PasswordRecovery />

Auth Endpoint Pattern

csharp
// Program.cs — login via HTTP POST (required for cookie auth)
app.MapPost("/Account/LoginHandler", async (HttpContext ctx, SignInManager<IdentityUser> sm) =>
{
    var form = await ctx.Request.ReadFormAsync();
    var result = await sm.PasswordSignInAsync(form["email"]!, form["password"]!, false, false);
    return result.Succeeded ? Results.Redirect("/") : Results.Redirect("/Account/Login?error=failed");
}).DisableAntiforgery();

Companion Documents

DocumentContent
IDENTITY-PATTERNS.mdCookie auth details, identity config steps, BWFC login controls, authorization patterns, endpoint templates, OWIN mapping, gotchas

L2 Break-Fix Playbook

Step 1: Add Identity Packages

bash
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Identity.UI

Step 2: Configure Identity in Program.cs

csharp
// Program.cs
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
    options.SignIn.RequireConfirmedAccount = false;
    options.Password.RequiredLength = 6;
})
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

// Required for Blazor Server
builder.Services.AddCascadingAuthenticationState();

// Middleware pipeline (ORDER MATTERS)
app.UseAuthentication();
app.UseAuthorization();

Step 3: Create ApplicationUser and DbContext

csharp
// Models/ApplicationUser.cs
using Microsoft.AspNetCore.Identity;

public class ApplicationUser : IdentityUser
{
    // Add custom properties from your Web Forms ApplicationUser
}

// Data/ApplicationDbContext.cs
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options) { }
}

Step 4: Migrate the Database Schema

If migrating from ASP.NET Identity (OWIN), the schema is similar but not identical:

bash
dotnet ef migrations add IdentityMigration
dotnet ef database update

Key schema differences:

ASP.NET Identity (OWIN)ASP.NET Core Identity
AspNetUsers.Id (string GUID)Same
AspNetUsers.PasswordHashSame format — passwords are compatible
AspNetUserClaimsSame
AspNetUserRolesSame
AspNetRolesSame
__MigrationHistory__EFMigrationsHistory

Important: ASP.NET Identity v2 password hashes (from Web Forms) are compatible with ASP.NET Core Identity. Users will NOT need to reset passwords.

If migrating from Membership (older):

  • Use the Microsoft.AspNetCore.Identity.MicrosoftAccountMigration or a custom SQL migration script
  • Membership password hashes are NOT compatible — users will need password resets

Step 5: Migrate Login Pages

BWFC Login Controls

BWFC provides login controls that match Web Forms markup. These controls use native Blazor AuthenticationStateProvider internally — they are not shims and do not need to be replaced with AuthorizeView.

Important: BWFC login controls require builder.Services.AddBlazorWebFormsComponents() in Program.cs and builder.Services.AddCascadingAuthenticationState() for authentication state propagation.

Web FormsBWFCNotes
<asp:Login runat="server" /><Login />Login form with username/password
<asp:LoginName runat="server" /><LoginName />Displays authenticated username
<asp:LoginStatus runat="server" /><LoginStatus />Login/Logout toggle link
<asp:LoginView runat="server"><LoginView>Shows different content for anon vs auth users
<asp:CreateUserWizard runat="server" /><CreateUserWizard />Registration form
<asp:ChangePassword runat="server" /><ChangePassword />Password change form
<asp:PasswordRecovery runat="server" /><PasswordRecovery />Password reset flow

Login Page Migration Example

html
<!-- Web Forms — Login.aspx -->
<%@ Page Title="Log in" MasterPageFile="~/Site.Master" ... %>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
    <h2>Log in</h2>
    <asp:Login ID="LoginCtrl" runat="server"
        ViewStateMode="Disabled"
        RenderOuterTable="false">
        <LayoutTemplate>
            <asp:TextBox ID="UserName" runat="server" CssClass="form-control" />
            <asp:RequiredFieldValidator ControlToValidate="UserName"
                ErrorMessage="Required" runat="server" />
            <asp:TextBox ID="Password" TextMode="Password" runat="server" />
            <asp:Button Text="Log in" CommandName="Login" runat="server" />
        </LayoutTemplate>
    </asp:Login>
</asp:Content>
razor
@* Blazor — Login.razor *@
@page "/Account/Login"

<h2>Log in</h2>
<Login RenderOuterTable="false">
    <LayoutTemplate>
        <TextBox @bind-Text="model.UserName" CssClass="form-control" />
        <RequiredFieldValidator ControlToValidate="UserName" ErrorMessage="Required" />
        <TextBox TextMode="Password" @bind-Text="model.Password" />
        <Button Text="Log in" CommandName="Login" />
    </LayoutTemplate>
</Login>

Step 6: Migrate Authorization Patterns

Web.config Authorization → Blazor Authorization

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

<h1>Admin Panel</h1>

LoginView Conditional Content

html
<!-- 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>
razor
@* Blazor — BWFC LoginView (recommended — preserves markup and uses AuthenticationStateProvider natively) *@
<LoginView>
    <AnonymousTemplate>
        <a href="/Account/Login">Log in</a>
    </AnonymousTemplate>
    <LoggedInTemplate>
        Welcome, <LoginName />!
        <LoginStatus LogoutAction="Redirect" LogoutPageUrl="/" />
    </LoggedInTemplate>
</LoginView>

Note: BWFC's LoginView internally injects AuthenticationStateProvider and evaluates authentication state natively — it is NOT a shim that needs to be replaced. Keep it for markup compatibility. If you prefer native Blazor syntax long-term:

razor
@* Blazor — Native AuthorizeView (alternative — different template names) *@
<AuthorizeView>
    <NotAuthorized>
        <a href="/Account/Login">Log in</a>
    </NotAuthorized>
    <Authorized>
        Welcome, @context.User.Identity?.Name!
        <a href="/Account/Logout">Log out</a>
    </Authorized>
</AuthorizeView>

⚠️ NEVER replace LoginView with AuthorizeView. The BWFC LoginView component is a fully functional Blazor component that injects AuthenticationStateProvider natively. It uses the SAME template names as asp:LoginView (AnonymousTemplate, LoggedInTemplate). The migration script handles this automatically — do NOT convert to AuthorizeView manually. Use AuthorizeView only when you need Blazor-native role-based content (<AuthorizeView Roles="...">) with no Web Forms equivalent.

Role-Based Content

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

Step 7: Migrate Authentication State Access

Code-Behind Auth Patterns

csharp
// 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());
csharp
// 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)

csharp
// 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;
    }
}

Session-Based Auth Data

Note: WebFormsPageBase provides a Session property (via SessionShim) that works in Blazor Server interactive mode. If your Web Forms code stores auth-related data in session (e.g., Session["userCheckoutCompleted"], Session["token"], Session["cartId"]), you can access it the same way in migrated Blazor code:

csharp
// Web Forms
Session["userCheckoutCompleted"] = true;
if (Session["token"] is string token) { ... }

// Blazor (same pattern, SessionShim handles it)
Session["userCheckoutCompleted"] = true;
if (Session["token"] is string token) { ... }

SessionShim uses IHttpContextAccessor and ISession under the hood, but you should not inject IHttpContextAccessor directly when WebFormsPageBase.Session already provides the same access pattern. Keep the shim-based approach during initial migration — session-to-DI conversion is an optional L3 optimization step, not a Layer 1 or Layer 2 requirement.


OWIN Middleware → ASP.NET Core Middleware

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.csConfiguration in Program.cs services section
app.CreatePerOwinContext<ApplicationUserManager>(...)builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
SecurityStampValidator.OnValidateIdentity(...)Built into ASP.NET Core Identity automatically

Ready-to-Use Endpoint Templates

These minimal API endpoints handle authentication operations that must run over HTTP (not WebSocket). Add them to Program.cs after building the app but before app.Run(). Each endpoint reads form data, performs the auth operation, and redirects back to a Blazor page.

Important: Every endpoint below calls .DisableAntiforgery() because Blazor-rendered <form> elements do not include antiforgery tokens. See DisableAntiforgery Requirement.

Login Endpoint

csharp
// Program.cs — authenticates user and sets auth cookie via HTTP response
app.MapPost("/Account/LoginHandler", async (HttpContext context, SignInManager<IdentityUser> signInManager) =>
{
    var form = await context.Request.ReadFormAsync();
    var email = form["email"].ToString();
    var password = form["password"].ToString();

    var result = await signInManager.PasswordSignInAsync(email, password, isPersistent: false, lockoutOnFailure: false);
    if (result.Succeeded)
        return Results.Redirect("/");
    return Results.Redirect("/Account/Login?error=Invalid+login+attempt");
}).DisableAntiforgery();

Blazor form that submits to this endpoint:

razor
@page "/Account/Login"

<h2>Log in</h2>
@if (!string.IsNullOrEmpty(errorMessage))
{
    <div class="text-danger">@errorMessage</div>
}
<form method="post" action="/Account/LoginHandler">
    <div>
        <label>Email</label>
        <input type="email" name="email" required />
    </div>
    <div>
        <label>Password</label>
        <input type="password" name="password" required />
    </div>
    <button type="submit">Log in</button>
</form>

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

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

Register Endpoint

csharp
// Program.cs — creates user, signs in, and redirects
app.MapPost("/Account/RegisterHandler", async (HttpContext context,
    UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager) =>
{
    var form = await context.Request.ReadFormAsync();
    var email = form["email"].ToString();
    var password = form["password"].ToString();

    var user = new IdentityUser { UserName = email, Email = email };
    var result = await userManager.CreateAsync(user, password);
    if (result.Succeeded)
    {
        await signInManager.SignInAsync(user, isPersistent: false);
        return Results.Redirect("/");
    }
    var errors = string.Join("; ", result.Errors.Select(e => e.Description));
    return Results.Redirect($"/Account/Register?error={Uri.EscapeDataString(errors)}");
}).DisableAntiforgery();

Logout Endpoint

csharp
// Program.cs — signs out and redirects to home page
app.MapPost("/Account/Logout", async (SignInManager<IdentityUser> signInManager) =>
{
    await signInManager.SignOutAsync();
    return Results.Redirect("/");
}).DisableAntiforgery();

Blazor logout form (use instead of a link):

razor
<form method="post" action="/Account/Logout">
    <button type="submit" class="nav-link btn btn-link">Log out</button>
</form>

Note: Do NOT use <a href="/Account/Logout"> — Blazor's enhanced navigation will intercept the click and attempt client-side navigation instead of a real HTTP POST. Use a <form method="post"> with a submit button, or add data-enhance-nav="false" to the link. See Blazor Enhanced Navigation in the data migration skill.


DisableAntiforgery Requirement

Important: When using <form method="post"> to submit to minimal API endpoints from Blazor-rendered pages, the endpoint MUST call .DisableAntiforgery() because Blazor's HTML rendering does not include antiforgery tokens. Without this, the request will fail with a 400 Bad Request.

csharp
// ✅ CORRECT — DisableAntiforgery() required for Blazor form submissions
app.MapPost("/Account/LoginHandler", handler).DisableAntiforgery();

// ❌ WRONG — will reject POST from Blazor-rendered forms
app.MapPost("/Account/LoginHandler", handler);

If you need antiforgery protection, you must manually include the token in the form using @inject Microsoft.AspNetCore.Antiforgery.IAntiforgery and render a hidden field. For most migration scenarios, .DisableAntiforgery() is the pragmatic choice.


Common Identity Gotchas

No HttpContext.Current

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

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

Cookie Auth Requires HTTP Endpoints

Blazor Server login/logout MUST use HTTP endpoints (not component-based), because cookies are set on HTTP responses:

csharp
// Program.cs — Add login/logout endpoints
app.MapPost("/Account/Logout", async (SignInManager<ApplicationUser> signInManager) =>
{
    await signInManager.SignOutAsync();
    return Results.Redirect("/");
});

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:

bash
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.

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Bwfc Identity Migration AI skill do?

**WORKFLOW SKILL** — Migrate ASP.NET Web Forms Identity and Membership authentication to Blazor Server Identity. Covers OWIN→Core middleware, login/register/logout minimal API endpoints, BWFC login controls, cookie auth under Interactive Server, and role-based authorization. WHEN: "migrate identity", "login page migration", "OWIN to core", "cookie auth blazor", "LoginView migration". INVOKES: dotnet CLI for identity scaffolding. FOR SINGLE OPERATIONS: use bwfc-migration for markup, bwfc-data-migration for EF.

Why use Bwfc Identity Migration on TypingMind?

Because you install it once and use it with any model. Bwfc Identity Migration is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Bwfc Identity Migration in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/FritzAndFriends/BlazorWebFormsComponents/tree/dev/migration-toolkit/skills/bwfc-identity-migration. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Bwfc Identity Migration?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Bwfc Identity Migration?

As many as you like. As long as a model supports skills, you can use Bwfc Identity Migration with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Bwfc Identity Migration AI skill free?

Yes. It is published on GitHub by FritzAndFriends under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇