Going way back to, I think, .NET v3, ASP.NET had this new thing called Membership. Maybe it was a version earlier. I dunno. "Neat," I thought, I can write a provider adhering to this interface and use my existing user and auth structure to plug into this system. Then I saw that the membership and role providers each had about a bazillion (maybe quadbazillion) members to implement, and reality set in that what I already had was working just fine. Some years later, ASP.NET offered Identity, this newer thing that did sort of the same thing. It even made its way into Core.
You don't need it. For real. I'm not saying that it isn't a useful piece of the framework, but you need to stop making it the default for user management. It's not hard or time consuming to build out your own system of user entities and permissions (roles, claims, etc.) as you see fit. The problem, as I see it, is that developers are confusing the act of
persisting user information with
authentication. I get why that may be, as Identity uses one line of code to both verify a user and sign them in (
Core docs show how). But under the covers, there is code that first verifies the user/password against the database, then sets the auth cookie to indicate who the user is for future requests. You can in fact do one without the other.
Why would you do that? Part of it may just be an issue of control, but for me, it's because I want to be very specific about how I structure my user data. I also don't really want to use Entity Framework in many cases (read: most things I port from older apps), and EF is part of the magic of Identity. What I've seen in a number of projects is the use of Identity mixed with a home-grown set of user domain objects and a totally separate database or persistence mechanism. If you're doing all of that plumbing anyway, you definitely don't need the additional overhead of Identity.
Let's use ASP.NET Core as an example, first. In Startup, we use the Configure method to use cookie-based authentication:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme,
AutomaticAuthenticate = true
});
In some kind of login method, from our MVC controller, we look up the user in the code that we wrote, with whatever backing store we made, and then sign in. Let's pretend that myUser is some construct we've made up:
var myUser = _myUserLookerUpperService(email, password);
var claims = new List
{
new Claim(ClaimTypes.Name, myUser.Name)
};
var props = new AuthenticationProperties
{
IsPersistent = persistCookie,
ExpiresUtc = DateTime.UtcNow.AddYears(1)
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity), props);
The code should be pretty straightforward. Whatever our domain-specific user thingy is, it's something built for us, instead of the generic thing that the Identity framework has created. We use that to construct a set of claims and authentication properties, and then use the built-in Authentication system to sign in with our newly constructed principal. This is what creates the encrypted cookie on the user's browser. It's not as magic as the Identity service, but remember that you're welcome to use any kind of schema that you want to persist user data, and that means you can query it or normalize it (if you must) against any other bits of data you have.
Naturally, you may want to set up some other context, or simply verify that they're still a known-good user on each request. To do that, you can wireup middleware in the Startup's Config method (app.UseMiddleware();
). Middleware doesn't use an interface (and I don't know why they chose convention over an interface), but it does expect an Invoke method to do stuff. It's here that you would look up the user based on the identity:
public class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var name = context.User.Identity.Name;
if (!string.IsNullOrWhiteSpace(name))
{
var userService = context.RequestServices.GetService();
var user = userService.GetUserByName(name);
if (user != null)
{
// do stuff here
}
else
{
// do something about your bad user
}
}
await _next(context);
}
}
If you're still using ASP.NET 4.5 and MVC on top of it (or even WebForms), you don't need to use Identity here either. In your MVC action, or your event handler in WebForms, you can use Forms Authentication to do the same work, without any setup (though you can change the cookie name and some other things via web.config):
var user = _myUserLookerUpperService(email, password);
var ticket = new FormsAuthenticationTicket(1, user.Name, DateTime.Now, DateTime.Now.AddDays(30), createPersistentCookie, "");
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
cookie.Expires = DateTime.Now.AddDays(30);
context.Response.Cookies.Add(cookie);
Neat, right? The static FormsAuthentication class also has a SignOut method. Instead of middleware, we can use an IHttpModule or an IActionFilter to act on user data as appropriate.
To circle back, the point here is that Identity is great to spin up some user account persistence and authentication quickly, but if you want to do your own thing, or don't want EF involved, or you're a control freak, understand that you don't need Identity to auth your users.