Click here to Skip to main content
15,868,164 members
Articles / Security / Identity
Tip/Trick

ASP.NET MVC5 Authentication Using Custom UserStore

Rate me:
Please Sign up or sign in to vote.
4.88/5 (11 votes)
22 Dec 2014CPOL4 min read 55.7K   3K   27   3
Implementing a custom UserStore for ASP.NET MVC5 authentication

Introduction

Code generated by Visual Studio 2013 for an ASP.NET MVC5 website is great to get one started off quickly. Its now easy to build a great looking and highly maintainable website because of the excellent implementation of Model-View-Controller pattern. Now let's see how we can replace default the Entity Framework based user management and authentication model with a custom one.

Background

I have been spending a couple of days trying to migrate my existing PHP Yii based personal website to ASP.NET. Since the Yii framework is an MVC framework, I decided to use MVC with ASP.NET too. Coming to the point, as far as authentication is concerned, my website needs very little of it. In fact, this whole exploration into the ASP.NET MVC5 authentication system was because of a single requirement. I needed a way to execute the following in the 'Administration' section of the site.

If (username != "admin" or password != "mypassword") go to login page. That's it. I just need a single hard-coded user and password to protect a part of the site and nothing more.

However, because of the intrinsically rich functionality offered around authentication / user maintenance by ASP.NET MVC, implementing my simple requirement required me to learn a bit more than what I had originally imagined.

Using the Code

For starters, I have not found much material on this, so I started by understanding which classes and methods are really needed for what I had in mind. Below is a diagram which lists the main classes which we need to work with.

Class Diagram

The out-of-the-box authentication involves the classes ApplicationUser, ApplicationUserManager, ApplicationSignInManager and the UserStore class. The pattern is that the UserStore class is responsible to storing / retrieving instances of ApplicationUser. The ApplicationUserManager & the ApplicationSignInManager makes use of UserStore class to verify passwords, check account lockedout status and others. As part of our requirement, we will be replacing the ApplicationUser and UserStore classes with very basic user and userstore classes.

So, the classes which are implemented from scratch are:

  • SimpleUser - This replaces the ApplicationUser class which is generated by the IDE.
  • XmlUserStore - This replaces the built in UserStore class which is used by the generated code.

Just to add a little bit more sophistication than checking user != admin || password != password, XmlUserStore will be storing user ids and passwords in an XML file. Maintenance of this XML file is not part of the requirement, XmlUserStore is only responsible for reading and handing off user ids and passwords. If we want additional functions like add-new, modify, delete, etc., we need to implement those methods. We will encounter some of them but these only throw NotImplementedException for now.

The important thing is that we implement the correct interfaces and provide full implementation for only those methods we are interested in.

  • SimpleUser implements the basic IUser interface and has a GenerateUserIdentityAsync method.
  • The XmlUserStore class implements IUserStore<SimpleUser>, IUserPasswordStore<SimpleUser> and the IUserLockoutStore<SimpleUser, object> interfaces.
  • The important methods of XmlUserStore are:
    • IUserStore's FindByIdAsync(string userId)
    • IUserStore's FindByNameAsync(string userName)
    • IUserPasswordStore's GetPasswordHashAsync(SimpleUser user)
    • IUserLockoutStore's GetLockoutEnabledAsync(SimpleUser user)

These are all that is needed to achieve what we want to do. Once these two classes are in place, we need to modify the IDE generated code to use these classes instead of the built in User and UserStore class. The App_Start/IdentityConfig.cs file is where we start the modifications. The ApplicationUserManager is at the heart of the authentication work. We first change its derivation to:

C#
public class ApplicationUserManager : UserManager<SimpleUser>

For the purpose of understanding, the following methods have been implemented freshly in the ApplicationUserManager to log the call but ultimately call the base class' implementation:

C#
public override Task<SimpleUser> FindAsync(string userName, string password)
public override Task<SimpleUser> FindByEmailAsync(string email)
public override Task<SimpleUser> FindByNameAsync(string userName)
protected override Task<bool> VerifyPasswordAsync(IUserPasswordStore<SimpleUser, string> store, 
    SimpleUser user, string password)
public override Task<bool> IsLockedOutAsync(string userId)
public override Task<bool> GetTwoFactorEnabledAsync(string userId)
public override Task<ClaimsIdentity> CreateIdentityAsync(SimpleUser user, string authenticationType)

The static method Create() creates the ApplicationUserManager class and also initializes the XmlUserStore which needs the credentials-xml-file to be specified in the constructor. The password is hashed using ASP.NET's PasswordHasher class offline and stored in the file. The file content looks like this:

XML
<?xml version="1.0" encoding="utf-8" ?>
<users>
    <user id=pop@pop.com 
    password="ACWUFWmyVcWeLugKyLOrZJzDUpqmmUzeDZ+w0/Dbs2YAy7Xj5FfmdS65GRXX8lLTDw==" />
</users>

To test the sample WebApplication, use the login: pop@pop.com and password pop.

The next important class to modify is the ApplicationSignInManager. We change its derivation to:

C#
public class ApplicationSignInManager : SignInManager<SimpleUser, string>

For the purpose of understanding, the following methods have been implemented freshly in the ApplicationSignInManager to log the call but ultimately call the base class' implementation:

C#
public override Task<ClaimsIdentity> CreateUserIdentityAsync(SimpleUser user)
public override Task<SignInStatus> PasswordSignInAsync
    (string userName, string password, bool isPersistent, bool shouldLockout)
public static ApplicationSignInManager Create
    (IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)

Note, I have removed EmailService, SmsService classes from IdentityConfig.cs as they are not needed for this discussion.

The next file on our list is the App_Start/Startup.Auth.cs. We modify the public void ConfigureAuth(IAppBuilder app) method and use the SimpleUser class here:

C#
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, SimpleUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => 
            user.GenerateUserIdentityAsync(manager))<applicationusermanager, simpleuser="">

Since we are not supporting adding, editing, and other management tasks related to users, all related methods have been deleted from the AccountController.cs file. The only important methods are Login() and LogOff().

We now have a custom authentication working with ASP.NET MVC's authentication framework.

Points of Interest

It's not that we absolutely must implement a UserStore class since the ApplicationUserManager methods just delegate calls to corresponding methods in the UserStore class but from the separation of concerns point of view, it's probably better we stick to the design the MVC guys choose for us.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
My personal website is at http://sbytestream.pythonanywhere.com

Comments and Discussions

 
QuestionMissing diagram Pin
CSharpner.com23-Dec-14 5:31
CSharpner.com23-Dec-14 5:31 
AnswerRe: Missing diagram Pin
Siddharth R Barman25-Dec-14 3:33
Siddharth R Barman25-Dec-14 3:33 
GeneralRe: Missing diagram Pin
CSharpner.com26-Dec-14 4:01
CSharpner.com26-Dec-14 4:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.