Authentication, Identity, and OWIN
|
This section documents ASP.NET MVC 5.3.x, ASP.NET Web API 2.2, ASP.NET Web Pages 3, OWIN/Katana, SignalR 2,
and ASP.NET Identity 2 — all running on .NET Framework 4.8.1 — the This is not ASP.NET Core MVC. ASP.NET MVC 5 is the pre-Core, This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
This page documents OWIN/Katana and ASP.NET Identity 2.x on .NET Framework 4.8.1 — not ASP.NET Core Identity, a rewritten library with a different API surface (see Authentication and ASP.NET Core Identity under ASP.NET Core).
Forms authentication vs. OWIN cookie authentication
MVC 5’s template moved away from the older Forms Authentication module (<authentication mode="Forms"> in
web.config, FormsAuthentication.SetAuthCookie) to OWIN cookie authentication
(Microsoft.Owin.Security.Cookies), which issues and validates the auth cookie through OWIN middleware instead
of a System.Web HttpModule. This is what let ASP.NET Identity 2 support claims-based principals
(ClaimsPrincipal) and external logins through the same middleware pipeline used for local accounts, rather
than bolting them onto Forms Authentication’s simple username-only cookie.
The OWIN/Katana pipeline, Startup.cs, IAppBuilder
Katana is Microsoft’s OWIN implementation for System.Web/IIS; [assembly: OwinStartup] tells it which
class’s Configuration(IAppBuilder) method to run at startup, independently of Global.asax’s
`Application_Start:
// App_Start/Startup.Auth.cs (a partial class; the other half is App_Start/Startup.cs)
[assembly: OwinStartup(typeof(MyApp.Startup))]
namespace MyApp
{
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator
.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
TimeSpan.FromMinutes(30),
(manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
{
ClientId = "...", ClientSecret = "..."
});
}
}
}
IAppBuilder.Use/UseCookieAuthentication/etc. build a middleware chain conceptually identical to ASP.NET
Core’s app.Use(…) (see Request Pipeline and
Middleware under ASP.NET Core) but predating it — OWIN was the middleware model .NET Framework had before
ASP.NET Core existed, and Web API 2’s OWIN self-host (see ASP.NET Web
API 2) and SignalR 2 (see SignalR 2) both build on this same pipeline.
ASP.NET Identity 2.x
| Type | Role |
|---|---|
|
The user entity — extend with app-specific properties. |
|
The role entity. |
|
Create/find/update users, hash and verify passwords, manage claims/roles/tokens. |
|
Create/find/delete roles. |
|
Password sign-in, two-factor sign-in, and external-login sign-in, wired to the OWIN cookie middleware above. |
|
An EF6 |
|
The persistence abstraction |
public class ApplicationUser : IdentityUser
{
public string DisplayName { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("DefaultConnection") { }
public static ApplicationDbContext Create() => new ApplicationDbContext();
}
var result = await UserManager.CreateAsync(
new ApplicationUser { UserName = model.Email, Email = model.Email }, model.Password);
if (result.Succeeded)
{
var user = await UserManager.FindByNameAsync(model.Email);
var confirmToken = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
// email the confirmation link containing confirmToken
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
Password hashing (PBKDF2 by default via IPasswordHasher), password/user-name validators (IPasswordValidator,
IUserValidator, configured in App_Start/IdentityConfig.cs), email/phone confirmation, two-factor
authentication (GenerateTwoFactorTokenAsync), account lockout (MaxFailedAccessAttemptsBeforeLockout), and
claims (UserManager.AddClaimAsync) are all UserManager/SignInManager responsibilities configured once in
IdentityConfig.cs:
// App_Start/IdentityConfig.cs
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public static ApplicationUserManager Create(
IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
manager.PasswordValidator = new PasswordValidator { RequiredLength = 8, RequireNonLetterOrDigit = true };
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(15);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
return manager;
}
}
External logins
Google, Facebook, Microsoft, and Twitter providers register via app.Use*Authentication(…) in
Startup.Auth.cs (see the Google example above); the callback flow lands on Account/ExternalLoginCallback,
which either signs in an existing linked account or prompts the user to complete registration:
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null) return RedirectToAction("Login");
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
return result switch
{
SignInStatus.Success => RedirectToLocal(returnUrl),
SignInStatus.LockedOut => View("Lockout"),
_ => View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email })
};
}
[Authorize(Roles = …)]
[Authorize(Roles = "Administrator,Support")]
public class AdminController : Controller { ... }
Role membership is stored via IdentityUserRole/AspNetUserRoles, and checked the same way any
System.Web.Mvc.AuthorizeAttribute checks roles — see Filters for how
[Authorize] fits as an authorization filter.
Migrating from Membership to Identity
The legacy ASP.NET Membership provider (Membership.CreateUser, aspnet_Membership/aspnet_Users SQL
tables, Roles.AddUserToRole) predates OWIN entirely and is not compatible with Identity’s schema or password
hash format. A migration typically: creates the Identity schema (AspNetUsers, etc.) alongside the old
Membership tables, writes a one-time script mapping aspnet_Users/aspnet_Membership rows into AspNetUsers
(re-hashing passwords is not possible without the plaintext, so migrated users are commonly forced through a
password-reset flow on first login), and migrates aspnet_Roles/aspnet_UsersInRoles into AspNetRoles/
AspNetUserRoles. See
Migrating
an Existing Website from SQL Membership to ASP.NET Identity.
Next: Security Hardening builds on this authentication foundation with CSRF, XSS, and the rest of the OWASP Top 10.