microsoft/migrating-owin-to-aspnet-core
> Migrates OWIN/Katana middleware, authentication, pipeline components, and SignalR 2.x to native ASP.NET Core equivalents. Use when projects reference Microsoft.Owin, IAppBuilder, OwinMiddleware, Microsoft.Owin.Host.SystemWeb, OWIN-based OAuth/cookie/bearer authentication, OWIN startup classes, or SignalR 2.x hubs mapped via OWIN. Triggers for "migrate OWIN", "remove OWIN", "replace OWIN middleware", "convert OWIN pipeline", "migrate Katana", "convert OWIN auth", "upgrade SignalR", "replace OWIN startup", or when assessment signals include UsesOwin, UsesKatana, UsesOwinAuth, UsesOwinMiddleware, or UsesAppBuilder.
npx skills add https://github.com/microsoft/upgrade-agent-plugins --skill migrating-owin-to-aspnet-core
Migrate ASP.NET MVC applications that rely on OWIN/Katana for middleware hosting, authentication, and SignalR from the Katana pipeline to native ASP.NET Core middleware. Katana (Microsoft.Owin.Host.SystemWeb) ran an OWIN pipeline inside IIS before the MVC pipeline — ASP.NET Core unifies both into a single middleware pipeline, making the OWIN layer unnecessary. Covers all OWIN middleware patterns: custom OwinMiddleware subclasses, IAppBuilder pipeline configuration, OWIN authentication schemes, and SignalR 2.x hub migration.
Migration Progress:
- [ ] Step 1: Audit OWIN/Katana usage
- [ ] Step 2: Migrate OWIN startup to Program.cs
- [ ] Step 3: Convert OWIN authentication to Core auth
- [ ] Step 4: Migrate SignalR 2.x to ASP.NET Core SignalR
- [ ] Step 5: Convert custom OWIN middleware
- [ ] Step 6: Remove Katana packages and clean up
Search the project for Katana and OWIN dependencies. If none are found, inform the user and skip this skill.
Look for:
[assembly: OwinStartup(typeof(...))] attribute in Startup.cs or AssemblyInfoStartup.Configuration(IAppBuilder app) or Startup.ConfigureAuth(IAppBuilder app) methodsMicrosoft.Owin.Host.SystemWeb package reference (Katana IIS host)Microsoft.Owin.Security.* packages (OWIN auth middleware)Microsoft.AspNet.SignalR and Microsoft.AspNet.SignalR.Owin packagesapp.MapSignalR() calls in OWIN startupStartup.Auth.cs partial class filesCategorize findings into three groups:
IAppBuilder configurationThe Katana startup class splits across Startup.cs and often Startup.Auth.cs. ASP.NET Core consolidates this into Program.cs.
Before — Katana startup with IAppBuilder:
[assembly: OwinStartup(typeof(MyApp.Startup))]
namespace MyApp
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.MapSignalR();
// Custom OWIN middleware
app.Use<RequestLoggingMiddleware>();
}
}
}
After — ASP.NET Core Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Service registrations (auth, SignalR, etc.) go here
builder.Services.AddAuthentication(/* ... */);
builder.Services.AddSignalR();
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Middleware pipeline
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<RequestLoggingMiddleware>();
app.MapHub<ChatHub>("/chatHub");
app.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Remove the [assembly: OwinStartup] attribute and the old Startup class once all configuration has moved to Program.cs.
OWIN authentication middleware registers inline on IAppBuilder. ASP.NET Core splits authentication into service registration (builder.Services) and middleware (app.Use*). Convert each OWIN auth scheme:
Before — OWIN cookie auth:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = TimeSpan.FromDays(14)
});
After — ASP.NET Core cookie auth:
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/Login";
options.ExpireTimeSpan = TimeSpan.FromDays(14);
});
Before — OWIN bearer auth:
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(tokenValidationParameters, issuer)
});
After — ASP.NET Core JWT bearer:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://myissuer.example.com",
ValidateAudience = true,
ValidAudience = "my-api",
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(keyBytes)
};
});
Before — OWIN external sign-in:
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseGoogleAuthentication(clientId: "...", clientSecret: "...");
After — ASP.NET Core external auth:
builder.Services.AddAuthentication()
.AddCookie()
.AddGoogle(options =>
{
options.ClientId = builder.Configuration["Auth:Google:ClientId"];
options.ClientSecret = builder.Configuration["Auth:Google:ClientSecret"];
});
If the project hosts its own OAuth token endpoint via app.UseOAuthAuthorizationServer(), this has no built-in ASP.NET Core equivalent. Replace with a dedicated identity server library (OpenIddict or Duende IdentityServer). This is a significant architectural change — flag it to the user and provide guidance:
OAuthAuthorizationServerOptionsSignalR 2.x (OWIN-hosted) and ASP.NET Core SignalR share concepts but differ in API surface, client library, and connection lifecycle.
Before — SignalR 2.x hub:
using Microsoft.AspNet.SignalR;
public class ChatHub : Hub
{
public void Send(string name, string message)
{
Clients.All.broadcastMessage(name, message);
}
public override Task OnConnected()
{
Groups.Add(Context.ConnectionId, "general");
return base.OnConnected();
}
}
After — ASP.NET Core SignalR hub:
using Microsoft.AspNetCore.SignalR;
public class ChatHub : Hub
{
public async Task Send(string name, string message)
{
await Clients.All.SendAsync("BroadcastMessage", name, message);
}
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "general");
await base.OnConnectedAsync();
}
}
Key API differences:
Clients.All.broadcastMessage(...) → Clients.All.SendAsync("BroadcastMessage", ...)SendAsync invocationsOnConnected → OnConnectedAsync, OnDisconnected(bool) → OnDisconnectedAsync(Exception)Groups.Add() → Groups.AddToGroupAsync()Before — OWIN startup:
app.MapSignalR();
// or with custom path:
app.MapSignalR("/messaging", new HubConfiguration());
After — Program.cs:
builder.Services.AddSignalR();
// ...
app.MapHub<ChatHub>("/chatHub");
Each hub is mapped individually in ASP.NET Core instead of a single MapSignalR() call. Map each hub class to its own route path.
Before — SignalR 2.x static resolver:
var context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
context.Clients.All.broadcastMessage("system", "Hello");
After — ASP.NET Core dependency injection:
public class NotificationService
{
private readonly IHubContext<ChatHub> _hubContext;
public NotificationService(IHubContext<ChatHub> hubContext)
{
_hubContext = hubContext;
}
public async Task NotifyAll(string message)
{
await _hubContext.Clients.All.SendAsync("BroadcastMessage", "system", message);
}
}
Replace the old jQuery-based SignalR client with the @microsoft/signalr npm package:
Before — SignalR 2.x client:
<script src="~/Scripts/jquery.signalR-2.4.3.min.js"></script>
<script src="~/signalr/hubs"></script>
<script>
var hub = $.connection.chatHub;
hub.client.broadcastMessage = function (name, message) { /* ... */ };
$.connection.hub.start();
</script>
After — ASP.NET Core SignalR client:
<script src="~/lib/microsoft/signalr/dist/browser/signalr.min.js"></script>
<script>
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub")
.build();
connection.on("BroadcastMessage", (name, message) => { /* ... */ });
connection.start();
</script>
The auto-generated /signalr/hubs proxy no longer exists. Client method names are registered explicitly with connection.on().
For each custom OwinMiddleware subclass, convert to ASP.NET Core middleware. The core pattern change is constructor injection of RequestDelegate replacing the OwinMiddleware base class:
Before — OWIN middleware:
public class RequestTimingMiddleware : OwinMiddleware
{
public RequestTimingMiddleware(OwinMiddleware next) : base(next) { }
public override async Task Invoke(IOwinContext context)
{
var sw = Stopwatch.StartNew();
await Next.Invoke(context);
sw.Stop();
context.Response.Headers.Add("X-Timing", new[] { sw.ElapsedMilliseconds.ToString() });
}
}
After — ASP.NET Core middleware:
public class RequestTimingMiddleware
{
private readonly RequestDelegate _next;
public RequestTimingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var sw = Stopwatch.StartNew();
await _next(context);
sw.Stop();
context.Response.Headers["X-Timing"] = sw.ElapsedMilliseconds.ToString();
}
}
Register in Program.cs with app.UseMiddleware<RequestTimingMiddleware>();.
Microsoft.OwinMicrosoft.Owin.Host.SystemWebMicrosoft.Owin.SecurityMicrosoft.Owin.Security.CookiesMicrosoft.Owin.Security.OAuthMicrosoft.Owin.Security.Google (and other provider-specific packages)Microsoft.AspNet.SignalRMicrosoft.AspNet.SignalR.OwinOwinStartup.cs and Startup.Auth.cs if all logic has moved to Program.cs[assembly: OwinStartup(...)] attribute from AssemblyInfo or Startup.csjquery.signalR-*.js) from Scripts/ or wwwroot/using Microsoft.Owin and using Microsoft.AspNet.SignalR directives and remove them| OWIN / Katana | ASP.NET Core |
|---------------|--------------|
| IAppBuilder | IApplicationBuilder (via WebApplication) |
| app.Use() (OWIN delegate) | app.Use() (Core delegate) or app.UseMiddleware<T>() |
| OwinMiddleware | Middleware class with RequestDelegate |
| IOwinContext | HttpContext |
| Startup.Configuration(IAppBuilder) | Program.cs pipeline |
| [assembly: OwinStartup] | Not needed — Program.cs is the entry point |
| app.UseCookieAuthentication() | AddAuthentication().AddCookie() |
| app.UseOAuthBearerAuthentication() | AddAuthentication().AddJwtBearer() |
| app.UseExternalSignInCookie() | AddAuthentication().AddCookie() + .AddGoogle() / etc. |
| app.UseOAuthAuthorizationServer() | OpenIddict or Duende IdentityServer |
| app.MapSignalR() | app.MapHub<T>("/path") |
| GlobalHost.ConnectionManager | IHubContext<T> via DI |
| Hub.Clients.All.method() | Hub.Clients.All.SendAsync("Method", ...) |
| Groups.Add() | Groups.AddToGroupAsync() |
Microsoft.Owin.*, Owin, or Microsoft.AspNet.SignalR package references remainIAppBuilder, IOwinContext, OwinMiddleware, or OwinStartup references in codeAddAuthentication() service patternMicrosoft.AspNetCore.SignalR with SendAsync pattern@microsoft/signalr instead of jquery.signalRRequestDelegate and InvokeAsync(HttpContext) patternProgram.csTake microsoft/migrating-owin-to-aspnet-core from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
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.