Migrating Asp.Net MemberShip to Asp.Net MVC Identity for authentification
Posted on: 2014-01-27
I was using WebMatrix with the Gym Workout project. But, it wasn't a clean way to handle authentification. It was in the middle of the old membership with a new flavor. However, since Microsoft is going with the new One Asp.Net Owin Identity framework which allow to use claims but also to be dissociate from Microsoft Sql Server make it more appealing. It's also on the frame of OWIN, so we have almost all positive features without the negative parts. Finally, Identity does have the clean way to have custom field to extend default username/password attribute. It's clean because it creates specific columns for each field. So, if you have an existing Asp.Net MVC, how do you upgrade to the Identity framework for authentification? This is what I will explain in this Identity tutorial.
First, some libraries are required.
- Microsoft.AspNet.Identity.EntityFramework
- Microsoft.AspNet.Identity.Core
- Microsoft.AspNet.Identity.OWIN
This can be found by using Nuget with the following command:
PM> Install-Package Microsoft.AspNet.Identity.Core
PM> Install-Package Microsoft.AspNet.Identity.Owin
PM> Install-Package Microsoft.AspNet.Identity.EntityFramework
PM> Install-Package Microsoft.Owin.Host.SystemWeb
The next step was to get rid of everything concerning the user which was in its own class called for the project "UserProfile". Instead, I created a new class called "ApplicationUser" which inherit from the default one of Identity framework. I removed the WebUserProvider because now, it will use Entity Framework to get user information. Providers are not required and everything will go with the service layer, to the data access layer to the database with Entity Framework (EF). The AccountModel (is in fact the view model) still exist which allow to have our custom fields like the Role, Language and Email in the view. Everything is bound to the model class ApplicationUser.
public class ApplicationUser : IdentityUser, ICurrentUser { public ApplicationUser() { Email = ""; Language = ""; } public string UserId { get { return base.Id; } set{} } public string Email { get; set; } public string Language { get; set; } }
Even if the ApplicationUser inherit from IdentityUser which as a Id property, the ICurrentUser that we had previously use the UserId property. This is why this one is defined in ApplicationUser and link to the IdentityUser one. The only thing that we will do later is to ignore this property to not have the data inside the database. We also need to create a ApplicationUserService that contact the Data Access Layer.
A major change concerns the ServiceFactory class which has every entities service classes. Since the Account come from the database and that we do not want to go to the database every time one of the entity require to have something related to the user logged, they need to share the same account. This require a change in the constructor that is going to take in its constructor the IUserProvider which has a property Account. This one is passed to every service constructor.
public class ServiceFactory : IServiceFactory { private IUserProvider_userProvider; #region Implementation of IServiceFactory
public IAccountService Account { get; private set; } public IMuscleService Muscle { get; private set; } public IWorkoutService Workout { get; private set; } public IWorkoutSessionService WorkoutSession { get; private set; } public IWorkoutSessionExerciseService WorkoutSessionExercise { get; private set; }
public IExerciseService Exercise { get; private set; }
#endregion
public ServiceFactory(IRepositoryFactory repositoryFactory, IMapperFactory mapperFactory, IUserProvider userProvider) {_userProvider = userProvider; var account =_userProvider.Account; Account = new ApplicationUserService(repositoryFactory, mapperFactory); Muscle = new MuscleService(repositoryFactory, mapperFactory, account); Workout = new WorkoutService(repositoryFactory, mapperFactory, account); WorkoutSession = new WorkoutSessionService(repositoryFactory, mapperFactory, account); WorkoutSessionExercise = new WorkoutSessionExerciseService(repositoryFactory, mapperFactory, account); Exercise = new ExerciseService(repositoryFactory, mapperFactory, Muscle, account); } }
Entities inherit of BaseService.
public abstract class BaseService { public BaseService(IRepositoryFactory repositoryFactory, IMapperFactory mapperFactory, ICurrentUser user) { Repository = repositoryFactory; Mapper = mapperFactory; Repository.SetUser(user); }
protected IRepositoryFactory Repository { get; private set; } protected IMapperFactory Mapper { get; private set; } }
When the service is initialized, it uses the injected Repository to set the user that is also injected. Since the ServiceFactory calls the Account once, one call is done to the database. After, it only set the object to the repository which could be used later.
The concept of IUserProvider remains. This is because we will use a WebUserProvider for the Asp.Net MVC and a WebServiceUserProvider.
The Global.asax.cs file changes because it does not use anymore the AuthConfig.RegisterAuth()
because the Identity with OWIN does have a Startup page in the App folder.
public partial class Startup {
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864 public void ConfigureAuth(IAppBuilder app) { // Enable the application to use a cookie to store information for the signed in user app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login") }); // Use a cookie to temporarily store information about a user logging in with a third party login provider app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); }
}
This also require to register the Startup class with another Startup class that is used as a bootstrapper for the OWIN container.
using Owin;
[assembly: OwinStartup(typeof(WorkoutPlanner.Startup))] namespace WorkoutPlanner { public partial class Startup { public void Configuration(IAppBuilder app) { ConfigureAuth(app); } } }
Notice the assembly instruction that tell which class is used for OwinStartup. It uses two classes but could use one. the only benefit of splitting the class in two is to have one statup entry and the code to configure somewhere else. This could later uncluttered the booting class.
The migration changes also. The Seed method does not use the WebSecurity to initialize default user. It uses the ApplicationUser class with the UserStore and UserManager class that use Entity Framework to work with the database.
To create the database with all Identity classes and all the business classes, the migration command in the package console is required. The
update-database -ConfigurationTypeName "Configuration"
This works because the Configuration class inherit of DbMigrationsConfigurations which allow the migration tool to be executed. It takes the database context to be used. In our case, it's the one that contains every entities of the application.
public class Configuration : DbMigrationsConfiguration<DatabaseContext>//<DatabaseContext> { public Configuration() { base.AutomaticMigrationsEnabled = true; }
protected override void Seed(DatabaseContext context) { var userStore = new UserStore<ApplicationUser>(context); var manager = new UserManager<ApplicationUser>(userStore);
var role = new IdentityUserRole { Role = new IdentityRole(Model.Roles.ADMINISTRATOR) }; var user = new ApplicationUser() { UserName = "123123", Email = "123123@123.com", Language = "en-US" }; user.Roles.Add(role); IdentityResult result = manager.Create(user, "123123");
var role2 = new IdentityUserRole { Role = new IdentityRole(Model.Roles.NORMAL) }; var user2 = new ApplicationUser() { UserName = "qweqwe", Email = "qweqwe@qweqwe.com", Language = "fr-CA" }; user.Roles.Add(role2); IdentityResult result2 = manager.Create(user2, "qweqwe");
var muscles = new[]{new Muscle { Id = 1, Name = new LocalizedString { French = "Cou", English = "Neck" } }, new Muscle { Id = 2, Name = new LocalizedString { French = "Épaule", English = "Shoulder" } } };
context.Set<Muscle>().AddOrUpdateRange(muscles); //... and so on... base.Seed(context); } }
When working with Asp.Net Identity one key is to have your database context class inherit from IdentityDbContext and not directly from DbContext. Otherwise, you will get that keys are not defined for IdentityUserLogin, IdentityUserRole, IdentityUserLogins and IdentityUserRoles.
DataAccessLayer.Database.IdentityUserLogin: : EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType. DataAccessLayer.Database.IdentityUserRole: : EntityType 'IdentityUserRole' has no key defined. Define the key for this EntityType. IdentityUserLogins: EntityType: EntitySet 'IdentityUserLogins' is based on type 'IdentityUserLogin' that has no keys defined. IdentityUserRoles: EntityType: EntitySet 'IdentityUserRoles' is based on type 'IdentityUserRole' that has no keys defined.
public class DatabaseContext :IdentityDbContext<ApplicationUser>, IDatabaseContext
Migrating from Asp.Net Membership to Identity is not something that can be done for the first time under few hours. In fact, it took me around 12 hours to figure out that I had to inherit from IdentityDbContext and to solve several seeding problem. Also, having to use Entity Framework changed a little but how to use the user information. Nevertheless, the time is not exponential since it concerns only users and roles entities. I believe that it should take less than a day for someone who have all the information.