The Identity System used in ASP.Net MVC 5
ASP.NET has long supported two basic types of authentication: 1.Windows authentication and 2.forms authentication. Windows authentication is seldom practical for public Web sites because it’s based on Windows accounts and access control list (ACL) tokens. Thus, it requires users to have a Windows account in the application’s domain, and it also assumes clients are connecting from Windows-equipped machines. The other option is forms authentication, a widely adopted approach. Forms authentication is based on a simple idea. For each access to a protected resource, the application ensures the request includes a valid authentication cookie. If a valid cookie is found, then the request is served as usual; otherwise, the user is redirected to a login page and asked to provide credentials. If these credentials are recognized as valid, then the application issues an authentication cookie with a given expiration policy. It’s simple and it just works.
Implementation of any forms authentication module can’t happen without a distinct module that takes care of collecting user credentials and checking them against a database of known users. Writing this membership subsystem has been one of the key responsibilities of development teams—but also one of the most annoying things ever. Writing a membership system is not hard, per se. It mostly requires running a query against some sort of storage system and checking a user name and password. This code is boilerplate and can grow fairly big as you add new authentication features such as changing and recovering passwords, handling a changing number of online users and so on. In addition, it has to be rewritten nearly from scratch if you change the structure of the storage or add more information to the object that describes the user. Back in 2005, with the release of ASP.NET 2.0, Microsoft addressed this problem by introducing right into the framework a provider-based architecture and the membership provider. Instead of reinventing the wheel every time, you could just derive membership from the built-in system and override only the functions you intended to change.
The ASP.NET native membership provider is a standalone component that exposes a contracted interface. The ASP.NET runtime, which orchestrates the authentication process, is aware of the membership interface and can invoke whatever component is configured as the membership provider of the application. ASP.NET came with a default membership provider based on a new given database schema. However, you could easily write your own membership provider to basically target a different database—typically, an existing database of users.
Does that sound like a great chunk of architecture? In the beginning, nearly everybody thought so. In the long run, though, quite a few people who repeatedly tried to build a custom membership provider started complaining about the verbosity of the interface. Actually, the membership provider comes in the form of an inheritable base class, MembershipProvider, which includes more than 30 members marked as abstract. This means that for any new membership provider you wanted to create, there were at least 30 members to override. Worse yet, you didn’t really need many of them most of the time. A simpler membership architecture was needed.
Introducing the Simple Membership Provider
To save you from the burden of creating a custom membership layer completely from scratch, Microsoft introduced with Visual Studio 2010 SP1 another option: the simple membership API. Originally available in WebMatrix and Web Pages, the simple membership API has become quite a popular way of managing authentication, especially in ASP.NET MVC. In particular, the Internet application template in ASP.NET MVC 4 uses the simple membership API to support user management and authentication.
Looking under the hood of the API, it turns out that it’s just a wrapper on top of the classic ASP.NET membership API and its SQL Server-based data stores. Simple membership lets you work with any data store you have and requires only that you indicate which columns in the table serve as the user name and user ID.
The major difference from the classic membership API is a significantly shorter list of parameters for any methods. In addition, you get a lot more freedom as far as the schema of the membership storage is concerned. As an example of the simplified API, consider what it takes to create a new user:
- WebSecurity.CreateUserAndAccount(username, password,
- new { FirstName = fname, LastName = lname, Email = email });
You do most of the membership chores via the WebSecurity class. In ASP.NET MVC 4, however, the WebSecurity class expects to work with an extended membership provider, not a classic membership provider. The additional capabilities in an extended membership provider are related to dealing with OAuth accounts. As a result, in ASP.NET MVC 4, you have two parallel routes for membership implementation: classic membership API using the MembershipProvider class and simple membership API using the ExtendedMembershipProvider class. The two APIs are incompatible.
Before the arrival of Visual Studio 2013 and ASP.NET MVC 5, ASP.NET already offered quite a few ways to handle user authentication. With forms authentication, you could rely on classic membership, the simple membership API as defined in Web Pages and a variety of custom membership systems. Consider the common position among ASP.NET experts was that complex real-world applications require their own membership provider. More often than not, the main reason for having a custom membership system was to circumvent structural differences between the required database format and the format of the existing database of user credentials, which might have been in use for years.
Clearly, this wasn’t a situation that could last forever. The community of developers demanded with loud voices a unified system for membership that’s simple to use, narrowly focused and usable in the same way from within any flavor of ASP.NET. This idea weds together well with the One ASP.NET approach pushed by Visual Studio 2013.
One Identity Framework
The purpose of authentication is getting the identity associated with the current user. The identity is retrieved and the provided credentials are compared to records stored in a database. Subsequently, an identity system such as ASP.NET Identity is based on two primary blocks: the authentication manager and the store manager. In the ASP.NET Identity framework, the authentication manager takes the form of the UserManager class. This class basically provides a façade for signing users in and out. The store manager is an instance of the UserStore class. Figure 1 shows the skeleton of an ASP.NET MVC account controller class that’s based on ASP.NET Identity.
Figure 1 Foundation of a Controller Based on ASP.NET Identity
- public class AccountController : Controller
- {
- public UserManager
UserManager { get; private set; }
- public AccountController(UserManager
manager)
- {
- UserManager = manager;
- }
- public AccountController() :
- this(new UserManager
(
- new UserStore
(new ApplicationDbContext())))
- {
- }
- ...
- }
The controller holds a reference to the authentication identity manager, UserManager. This instance of UserManager is injected into the controller. You can use either an Inversion of Control (IoC) framework or the poor man’s alternative, the dependency injection (DI) pattern, which uses two controllers, one of which gets a default value (see Figure 1).
The identity store, in turn, is injected into the authentication identity manager, where it’s used to verify credentials. The identity store takes the form of the UserStore class. This class results from the composition of multiple types:
- public class UserStore
:
- IUserLoginStore
,
- IUserClaimStore
,
- IUserRoleStore
,
- IUserPasswordStore
,
- IUserSecurityStampStore
,
- IUserStore
,
- IDisposable where TUser : IdentityUser
- {
- }
All interfaces implemented by UserStore are basic repositories for optional user-related data such as passwords, roles, claims and, of course, user data. The identity store needs to know about the actual data source, though. As shown in Figure 1, the data source is injected in the UserStore class through the constructor.
Storage of users’ data is managed through the Entity Framework Code First approach. This means you don’t strictly need to create a physical database to store your users’ credentials; you can, instead, define a User class and have the underlying framework create the most appropriate database to store such records.
The ApplicationDbContext class wraps up the Entity Framework context to save users’ data. Here’s a possible definition for the ApplicationDbContext class:
- public class ApplicationDbContext : IdentityDbContext
- {
- }
Basically, the database context of ASP.NET Identity handles the persistence of a given user type. The user type must implement the IUser interface or just inherit from IdentityUser. Figure 2 presents the source code of the default IdentityUser class.
Figure 2 Definition of the Default User Class in ASP.NET Identity
- namespace Microsoft.AspNet.Identity.EntityFramework
- {
- public class IdentityUser : IUser
- {
- public string Id { get; }
- public string UserName { get; set; }
- public string PasswordHash { get; set; }
- public string SecurityStamp { get; set; }
- public ICollection
Roles { get; private set; }
- public ICollection
Claims { get; private set; }
- public ICollection
Logins { get; private set; }
- }
- }
Here’s an example of a realistic custom user class you might want to use in your applications:
- public class ApplicationUser : IdentityUser{
- public DateTime Birthdate { get; set; }
- }
The use of Entity Framework Code First is a great move here as it makes the structure of the database a secondary point. You still need one, but to create it, you can use code based on classes. In addition, you can use Entity Framework Code First migration tools to modify a previously created database as you make changes to the class behind it.
To Authenticate a User
ASP.NET Identity is based on the newest Open Web Interface for .NET (OWIN) authentication middleware. This means the typical steps of authentication (for example, creating and checking cookies) can be carried out through the abstract OWIN interfaces and not directly via ASP.NET/IIS interfaces. Support for OWIN requires the account controller to have another handy property, like this:
- private IAuthenticationManager AuthenticationManager
- {
- get {
- return HttpContext.GetOwinContext().Authentication;
- }
- }
The IAuthenticationManager interface is defined in the Microsoft.Owin.Security namespace. This property is important because it needs to be injected into any operation that involves authentication-related steps. Here’s a typical login method:
- private async Task SignInAsync(ApplicationUser user, bool isPersistent)
- {
- var identity = await UserManager.CreateIdentityAsync(user,
- DefaultAuthenticationTypes.ApplicationCookie);
- AuthenticationManager.SignIn(new AuthenticationProperties() {
- IsPersistent = isPersistent }, identity);
- }
The method SignInAsync checks the specified user name and password against the store associated with the authentication manager. To register a user and add the user to the membership database, you use code like this:
- var user = new ApplicationUser() { UserName = model.UserName };
- var result = await UserManager.CreateAsync(user, model.Password);
- if (result.Succeeded)
- {
- await SignInAsync(user, isPersistent: false);
- return RedirectToAction("Index", "Home");
- }
All in all, ASP.NET Identity provides a unified API for tasks related to authentication. For example, it unifies the code required to authenticate against a proprietary database or a social network OAuth-based endpoint. Figure 3 shows a fragment of the code you need to authenticate users against an external login engine. The code in Figure 3 gets called once the OAuth authentication (for example, against Facebook) has been completed successfully.
Figure 3 Finalizing the Authentication Process through an External Endpoint
- public async Task
ExternalLoginCallback(
- string loginProvider, string returnUrl)
- {
- ClaimsIdentity id = await UserManager
- .Authentication
- .GetExternalIdentityAsync(AuthenticationManager);
- var result = await UserManager
- .Authentication
- .SignInExternalIdentityAsync(
- AuthenticationManager, id);
- if (result.Success)
- return RedirectToLocal(returnUrl);
- else if (User.Identity.IsAuthenticated)
- {
- result = await UserManager
- .Authentication
- .LinkExternalIdentityAsync(
- id, User.Identity.GetUserId());
- if (result.Success)
- return RedirectToLocal(returnUrl);
- else
- return View("ExternalLoginFailure");
- }
- }
In this post reference is taken from the microsoft magazine.
No comments:
Post a Comment