CodeDigest.Com Logo
Featured:

Asp.Net Identity in Asp.Net MVC 5.0 - Customizing ApplicationUser Table and Using Single DbContext

Tagged as: Asp.Net MVC Posted By

Asp.Net Identity is new unified Identity System released by Microsoft for providing identity services that could work across many platforms. If you are new to Asp.Net Identity then I suggest you to read the below articles to have a better understanding on Asp.Net Identity System.

Introduction to Asp.Net Identity

Building our First Application using Asp.Net Identity

By default, when you select “Individual User Accounts” for Authentication when creating new projects the Visual Studio will add a default authentication mechanism using Asp.Net Identity System. As mentioned in the previous articles listed above, it is good place to start understanding how to build authentication mechanisms using Asp.Net Identity. The default project template code has user table (AspNetUsers) created with only username (as Email) and Password fields. For any real time application, we will need to store more information for users. The user (ApplicationUser) class can be extended to add more properties for adding more information about user. Similarly, the default implementation uses Entity Framework Code First for data access and it creates a DataContext class by default with name ApplicationDbContext. In this article, let’s see how to add multiple new properties to user table and merge the Identity DataContext class with our application DbContext class.

For better understanding, let’s create a new project with “No Authentication” as Authentication method so that we could add all the required Asp.Net Identity Nuget packages ourselves and build a basic authentication with registration and login modules. This walkthrough will help you to do that step by step and it uses some the standard codes used by the default project template for simplicity. Reading this article will also help you adding Asp.Net Identity to existing projects that need to have Asp.Net Identity integrated. Full source code is attached at the end of this article for reference.

The TLDR section will give a quick understanding of the detailed steps for integrating Asp.Net Indentity in Asp.Net MVC applications.

TL;DR;

  1. Include Asp.Net Identity Nuget packages.

  2. Configure Asp.Net Identity objects, IUser, IUserStore, SignInManager and UserManager

  3. Include Katana OWIN library and configure CookieAuthentication in OWIN Startup class.

  4. Build the AccountController class and use the Asp.Net Identity objects created in step 2 to register and login users.

Steps

  1. Open Visual Studio 2015 (or 2017).

  2. Click File > New > Project.. Select “Asp.Net Web Application” and click OK.

  1. Select MVC project template and change the Authentication to “No Authentication” as seen in the below image. Click OK.

  1. This will create a new Asp.Net MVC site with all required Nuget package to run MVC application.

  2. Let’s add the Asp.Net Identity Nuget package. Right click project in solution explorer and click “Manage Nuget Packages…”

We need the below Nuget package for Asp.Net Identity.

  1. Microsoft.AspNet.Identity.Owin

  2. Microsoft.AspNet.Identity.Core

  3. Microsoft.AspNet.Identity.EntityFramework

Search and Install the above 3 Nuget packages from Browse.. tab.

Please note installing Microsoft.AspNet.Identity.Owin will install Microsoft.AspNet.Identity.Core as dependency. Or install Microsoft.AspNet.Identity.EntityFramework first which will include the other 2 packages as dependency.

  1. Asp.Net Identity works only on OWIN enabled application. Include OWIN library provided by Project Katana. Since we intend to use IIS install only the below Katana Nuget package for IIS integration and for OWIN startup.

 

Microsoft.Owin.Host.SystemWeb

 

  1. Now, add an OWIN startup class into the project. Right click the project in solution explorer and click Add > Add New Item… Select OWIN Startup class and click Add as seen in the below image.

This will add an empty Startup class like below.

 

namespace AuthenticationDemo

{

    public class Startup

    {

        public void Configuration(IAppBuilder app)

        {

            // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888

        }

    }

}

 

  1. We need to add the below Web.Config settings to disable Forms Authentication module completely from the application. Refer the bolded settings below.

 

<system.web>

<authentication mode="None" />

<compilation debug="true" targetFramework="4.6.2" />

<httpRuntime targetFramework="4.6.2" />

</system.web>

<system.webServer>

<modules>

  <remove name="FormsAuthentication" />

</modules>

</system.webServer>

 

Please note FormsAuthentication is deprecated from Asp.Net MVC 5.0. Though Forms Authentication still works it is advisable to use Asp.Net Identity if you are creating new projects. A good read on this here.

  1. Under Models folder, let’s add ApplicationUser model by deriving from IdentityUser interface for User object. Add 2 additional new properties, FirstName and LastName as seen below.

 

public class ApplicationUser : IdentityUser

{

 

[Required]

[StringLength(50)]

public string FirstName { get; set; }

 

[Required]

[StringLength(50)]

public string LastName { get; set; }

 

 

public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)

{

// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType

var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

// Add custom user claims here

return userIdentity;

}

}

Please note Email, EmailConfirmed, UserName, etc. are already part of IdentityUser<TKey, TLogin, TRole, TClaim> interface the IdentityUser object inherits. The GenerateUserIdentityAsync() will help to regenerate cookie when a new request arrives and the cookie is invalidated. We will see about this when configuring Startup class.

  1. Let’s add the DataContext class for the project include Asp.Net Identity models also here.

public class DBModel :  IdentityDbContext<ApplicationUser>
{
    public DBModel()
        : base("DefaultConnection", throwIfV1Schema: false)
    {
    }

    public static DBModel Create()
    {
        return new DBModel();
    }
    
    //Add your application DbSet<T>
}
 

The create method will be used to add the DataContext object into OWIN context to make application to use a single instance per request. You can include your application specific models here. The IdentityDbContext<ApplicationUser> object already inherits DbContext object, so we need not derive it again here.

  1. Add 2 new classes, ApplicationUserManager & ApplicationSignInManager class and configure them. If we recollect the required Asp.Net Identity objects discussed in the article Building our First Application using Asp.Net Identity, we need the User(IUser), UserStore, UserManager, SignInManager class to be configured for using the Asp.Net Identity system. This why we need ApplicationUserManager & ApplicationSignInManager class in our project.

For ApplicationUserManager (or UserManager), I have included the configuration for password complexity and making username as email here. The default project template code(Individual User Accounts) has many configurations like account lockout, 2 factor authentication, etc. which I have removed for simplicity. Include those if required.

               

public class ApplicationUserManager : UserManager<ApplicationUser>
{
    public ApplicationUserManager(IUserStore<ApplicationUser> store)
        : base(store)
    {
    }

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
    {
        var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<DBModel>()));
        // Configure validation logic for usernames
        manager.UserValidator = new UserValidator<ApplicationUser>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };

        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6,
            RequireNonLetterOrDigit = true,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };

        return manager;
    }
}

 


Configure the SignInManager from ApplicationSignInManager class as seen below.

 

public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{
    public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
      : base(userManager, authenticationManager)
    {
    }

    public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
    {
        return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
    }
}
 

The default project code for ApplicationSignInManager has a overridden CreateUserIdentityAsync() method to add custom claims if you have. Add it to the above class if you require. This method will get called during user authentication.

public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
    return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
}
 

Note - The default project template code puts these classes into IdentityConfig.cs inside App_Start folder. You can also create there. I have put it under Models/IdentityModels folder.

  1. Let’s now configure the Startup class to use Asp.Net Identity. In App_Start folder, add a class file called Auth.cs and add a partial class for Startup class and include ConfugureAuth() method in the file. Here we will enable the Cookie authentication which is the OWIN middleware alternative for Forms Authentication IIS module. Configure the Cookie authentication middleware using the UseCookieAuthentication() extension method of IAppBuilder packed in Owin namespace(part of Microsoft.Owin.Security.Cookies package).

Let’s also include the DBModel, ApplicationUserManager and ApplicationSignInManager instance per owin context into the OWIN context.

Note – You can also directly add this code into Startup class instead of creating partial class. The below code follows the default project code standard from better organisation of code modules.

Code below.

public partial class Startup
{
    public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the db context, user manager and signin manager to use a single instance per request
        app.CreatePerOwinContext(DBModel.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
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  
            OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
            validateInterval: TimeSpan.FromMinutes(30),
            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
            }
        });

        
    }
}
 

All the cookie authentication settings need to configured when adding the cookie authentication middleware by passing CookieAuthenticationOptions object as seen in the above code. The OnValidateIdentity is configured for the cookie authentication to invalidate and regenerate the identity and auth cookie when the security timestamp is changed in the user table. Please note whenever there is user table changes like password change the identity and cookie is regenerated for making the existing sessions logged off for better security.

In this article, I have left the Id of user as string. If you change it to int then you need to register the callback getUserIdCallback as seen below when configuring CookieAuthenticationProvider.  

 

getUserIdCallback: (id) => (Int32.Parse(id.GetUserId())))

 

  1. Next, let’s call the ConfigureAuth() method from Startup class Configuration() method.

 

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        ConfigureAuth(app);
    }
}
 

That’s it! All the required components and configurations needed for Asp.Net Identity ends here.

  1. Let’s now add an AccountController and add Login and Registration action methods. I have used the default project template code here since it is just the same. Please note the additional properties I have added in Register view model(RegisterViewModel) for user below (bolded). On Register view, I have also included additional fields for FirstName and LastName.

For brevity, I have included only post action methods. Refer the source code attachment for full source code.

AccountController.cs

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser { FirstName = model.FirstName, LastName = model.LastName, UserName = model.Email, Email = model.Email };
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
            return RedirectToAction("Index", "Home");
        }
        AddErrors(result);
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
    switch (result)
    {
        case SignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case SignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Invalid login attempt.");
            return View(model);
    }
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
    return RedirectToAction("Index", "Home");
}


RegisterViewModel.cs


public class RegisterViewModel
{
    [Required]
    [MaxLength(50)]
    public string FirstName { get; set; }

    [Required]
    [MaxLength(50)]
    public string LastName { get; set; }


    [Required]
    [EmailAddress]
    [Display(Name = "Email")]
    public string Email { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}
 

Execute the application and see it in action. The register view now will have the additional fields.

 

Download the source and see it in action!



Feedback

Comments

Student
Thanks bro you save my life.....
Commented by Ali on 10/6/2020 10:31:12 AM

Extending Login with custom fields
Hi, I would like to ask you if is possible to extend the login functionality. The Login page will contain the username and password and the Company to which he owns. So when user SignIn his username, password and Company will be validated. Also validate, by example, if user is enabled or not. Course, those fields must be part of the Identity model. Thanks for you comments.
Commented by Ignacio Estrada on 7/7/2020 6:53:13 AM

outdated
Sorry to sound critical, but why are discussing OWIN, Katana and Web.config in mid-2017?
Commented by Bryan on 8/28/2017 2:40:22 PM