Authentication in ASP.Net MVC 5 using Identity Libraries – Part 3
My earlier post listed the steps to set up cookie-based authentication in ASP.Net MVC 5 project using Identity libraries.
There was, however, a hardcoded username and password used for the authentication logic.
I will replace the same with the new membership features in ASP.Net Identity, by validating the credentials against information stored in the SQL database.
Install a new Nuget package To store the user information in the database we need to install another nuget package.
Microsoft.AspNet.Identity.EntityFramework
This can be installed from the Package explorer using
Install-Package Microsoft.AspNet.Identity.EntityFramework
This library uses Entity Framework to persist user data to SQL Server. Update the connection string in the
web.config
file accordingly.
I'm using the localdb database and hence myweb.config
file has the following settings...
<connectionStrings>
<add name="DefaultConnection"
connectionString="Data Source=(LocalDb)\mssqllocaldb;AttachDbFilename=|DataDirectory|\ASPNetMVC5Identity.mdf;Initial Catalog=ASPNetMVC5Identity;Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="mssqllocaldb" />
</parameters>
</defaultConnectionFactory>
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
- Next, we need to create a class to represent our users.
ASP.Net provides a classIdentityUser
which is a default implementation for theIUser
interface.
We can subclass theIdentityUser
class and add any additional properties that we plan to have for the user.
For this, I have created a new class fileIdentityConfig.cs
underApp_Start
folder.
Add the following code to this class. I have added the propertyCountry
to the user class.
public class AppUser : IdentityUser
{
public string Country { get; set; }
}
- ASP.Net provides an inbuilt
IdentityDbContext<TUser>
to interface with Entity Framework.
However, it is recommended that you create your Entity FrameworkDbContext
.
To do this, create a newIdentityModel.cs
class file under theModels
folder.
Add the following code to it...
public class AppDbContext : IdentityDbContext<AppUser>
{
public AppDbContext()
: base("DefaultConnection")
{
}
public static AppDbContext Create()
{
return new AppDbContext();
}
}
Note:
The Create
method has been added as an alternative to Dependency Injection(DI).
If you are using DI libraries like Ninject, you may appropriately handle this instantiation.
The ASP.NET Identity
UserManager
class is used to manage users.
Example:
Registering new users, validating credentials and loading user information.
It is not concerned with how user information is stored.
For this, it relies on aUserStore
(which in our case uses Entity Framework).
There are also implementations available for Azure Table Storage, RavenDB and MongoDB to name a few.We'll add our own
UserManager
class by subclassing theUserManager<TUser>
as follows...
public class AppUserManager : UserManager<AppUser>
{
public AppUserManager(IUserStore<AppUser> store)
: base(store)
{
}
public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> options, IOwinContext context)
{
var userManager = new AppUserManager(new UserStore<AppUser>(context.Get<AppDbContext>()));
// Configure validation logic for usernames
userManager.UserValidator = new UserValidator<AppUser>(userManager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
userManager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
//RequireNonLetterOrDigit = true,
//RequireDigit = true,
//RequireLowercase = true,
//RequireUppercase = true,
};
// Configure user lockout defaults
userManager.UserLockoutEnabledByDefault = true;
userManager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
userManager.MaxFailedAccessAttemptsBeforeLockout = 5;
return userManager;
}
}
- We'll now make the
UserManager<AppUser>
instance accessible fromAuthController
. To do this add the following to theAuthController
class...
public class AuthController : Controller
{
private AppUserManager _userManager;
public AuthController()
{
}
public AuthController(AppUserManager userManager)
{
UserManager = userManager;
}
public AppUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<AppUserManager>();
}
private set
{
_userManager = value;
}
}
// Rest of the code
}
- We also want to make sure that we dispose of the underlying Entity Framework
DbContext
at the end of the request.
To do this we override theDispose
method in theAuthController
as...
protected override void Dispose(bool disposing)
{
if (disposing && UserManager != null)
{
UserManager.Dispose();
}
base.Dispose(disposing);
}
- We can now replace the hardcoded authentication logic in the
Login
action of theAuthController
is as follows...
[HttpPost]
public async Task<ActionResult> Login(LoginModel model)
{
if (!ModelState.IsValid)
{
return View();
}
var user = await UserManager.FindAsync(model.Email, model.Password);
if (user != null)
{
await SignIn(user);
return Redirect(GetRedirectUrl(model.ReturnUrl));
}
// In case user authentication fails.
ModelState.AddModelError("", "Invalid email or password");
return View();
}
private async Task SignIn(AppUser user)
{
var identity = await UserManager.CreateIdentityAsync(
user, DefaultAuthenticationTypes.ApplicationCookie);
GetAuthenticationManager().SignIn(identity);
}
private IAuthenticationManager GetAuthenticationManager()
{
var ctx = Request.GetOwinContext();
var authManager = ctx.Authentication;
return authManager;
}
private string GetRedirectUrl(string returnUrl)
{
if (string.IsNullOrEmpty(returnUrl) || !Url.IsLocalUrl(returnUrl))
{
return Url.Action("index", "home");
}
return returnUrl;
}
We try to achieve the following here...
First, we attempt to find a user with the provided credentials using
UserManager.FindAsync
.If the user exists we create a claims identity for the user that can be passed to
AuthenticationManager
. This will include any custom claims that you've stored.Finally, we sign in the user using the cookie authentication middleware
SignIn(identity)
.
- With this the logic to log in the user is complete.
We now need a way to register the user.
We'll first create a view model to register the user, sayRegisterModel
.
public class RegisterModel
{
[Required]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Required]
public string Country { get; set; }
}
- We can now add Register actions to the
AuthController
as...
public ActionResult Register()
{
return View();
}
[HttpPost]
public async Task<ActionResult> Register(RegisterModel model)
{
if (!ModelState.IsValid)
{
return View();
}
var user = new AppUser
{
UserName = model.Email,
Email = model.Email,
Country = model.Country
};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignIn(user);
return RedirectToAction("index", "home");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error);
}
return View();
}
To create the user we call
UserManager.CreateAsync
passing ourAppUser
instance and the user password.
The ASP.NET Identity library will take care of hashing and storing this securely.Finally, we create a Register view as...
@model ASPNetMVC5Identity.Models.RegisterModel
@{
ViewBag.Title = "Register";
}
<h2>Register</h2>
@Html.ValidationSummary(false)
@using (Html.BeginForm())
{
@Html.EditorForModel()
<p>
<button type="submit">Register</button>
</p>
}
With this, we can now run the application and verify the end-to-end flow.
For handling the claims-related information we can use our own
ClaimsIdentityFactory
as follows...
This can be added in theIdentityConfig.cs
file.
public class AppUserClaimsIdentityFactory : ClaimsIdentityFactory<AppUser>
{
public override async Task<ClaimsIdentity> CreateAsync(UserManager<AppUser, string> manager, AppUser user, string authenticationType)
{
var identity = await base.CreateAsync(manager, user, authenticationType);
identity.AddClaim(new Claim(ClaimTypes.Country, user.Country));
return identity;
}
}
- Now we need to add a reference to an instance of the above
ClaimsIdentityFactory
in ourUserManager
class is as follows...
public class AppUserManager : UserManager<AppUser>
{
public AppUserManager(IUserStore<AppUser> store)
: base(store)
{
}
public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> options, IOwinContext context)
{
//Earlier Code
userManager.ClaimsIdentityFactory = new AppUserClaimsIdentityFactory();
return userManager;
}
}
With this we come to the end of this 3 part series.
Hope this benefits everyone.
Subscribe to my newsletter
Read articles from Sundeep Kamath directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Sundeep Kamath
Sundeep Kamath
My name is Sundeep Kamath. I’m a programmer based in Hyderabad, India. I work at Microsoft, but this blog, its content and opinions are my own. I blog about technology, code and the web.