Implementing User Confirmation and Password Reset with One ASP.NET Identity – Pain or Pleasure
Recently, I was trying to introduce GitHub and Twitter login for my TextUml, it was using the simple membership which has out of the box support for Twitter login, my attempt to create a client for GitHub OAuth2 failed, it seems the OAuthWebSecurity was receiving different query string name for provider from GitHub, anyway I did not want to invest too much time as Microsoft is coming up with a new Membership system called ASP.NET Identity (also the default in ASP.NET MVC 5) and I want to give it a try as the server side of TextUml is already running on all the edge versions of different packages.
Once installed, I quickly realized that it is not at all compatible with earlier versions of Membership, you have to create your own solution to migrate your existing data but the initial thing that surprised me that there is no built-in support for user confirmation and password reset. The user confirmation is when a user signs up, your application sends an email with a link, when the user visits the link the account is confirmed. And reset password is when user forgets password, s/he provides the email and the application sends an email (if matched) with a link, when the link is visited, it allows the user to set new password. Pretty standard stuff and these are available in the current simple membership and it worked pretty nicely in my projects. To implement these features it is obvious that I have to store few additional data like confirmation token, a flag whether it is confirmed, reset password token and its expiration timestamp. Though the above MSDN blog post has a bullet list of features but I find that the customization is more towards the additional attributes of user rather than injecting your own logic in the login process, you will find it shortly. So to store this data, I have created a new class called Token:
The Token has one-to-one relation with the User, I am using ActivatedAt as a confirmation flag, but it can be a boolean property as well, there are few other helper methods which we would see in action shortly. Now, whenever a user is going to be created I have to create the corresponding token as well. For creating a new user it has CreateLocalUser method of the IdentityStoreManager class. We can use it for creating the user then we can persist the corresponding token, but in that case we are not treating it as single unit of work (multiple SaveChanges of DbContext). So it is better if I create an overloaded version, but in that case I have to create an inherited class of IdentityStoreManager.
If you compare the above code with the original CreateLocalUser method, you would see I had to duplicate almost all of the code instead of reusing the existing building blocks.
Now, that we have created the user, lets move to the login part, as you can guess we also have to consider the confirmation flag along with the user name and password, unless the user is confirmed s/he is not allowed to login even the credential matches. The user name and password matching happens in the Validate method of UserSecretStore class and the confirmation checking should go there. The Validate method is not virtual, but the good news is the UserSecretStore class implements IUserSecretStore interface, which means we can create a new class with our custom logic that implements the interface and plugs it in.
In the above, we created an instance of the same UserSecretStore class internally and delegated all our method calls to it, the only exception is the Validate method where we are checking confirmation status before delegating. Now, to plug it in we have to create another inherited class from IdentityStoreContext:
Next, In order to keep my rest of the application code mixing with this new membership code I created a MembershipService that as a façade. With the above sign-up and sign-in it looks like the following:
The next three features are rather simple and easy as we have all the necessary things in place:
The next part is integrating the external login support which does not require any modification, for GitHub OAuth2 I have also created a Owin authentication module like the other built-in modules, you can check the GitHub implementation over here and the nuget package.
Now, everything seems to run smoothly until I found out that UserName of User needs to be unique and seems silly as I myself have same user name in Facebook and GitHub. It turns out that uniqueness checking is in the ValidateEntity method of IdentityDbContext. All I have to overcome this issue is override this method:
But it introduces another issue, now we are allowing to have same user name for internal users and to restrict it we need a little modification in our CreateLocalUser method:
And we are done, you will find the complete implementation in TextUml.
Few more observations:
Although there is an interface IUser but creating a custom User and implementing this interface does not work external user as the method GetUserIdentityClaims of IdentityAuthenticationManager needs an concrete class of User not IUser .
Both UserRoles and UserSecrets in the database stores the name instead of the Id which is quite unusual should be avoided.
You cannot have the same name entity class (e.g. User, Role etc.) that is already here, though it seems more of an EF issue.
Overall I think it is doing whole lot of things that it should not do and If your application logic varies from what it provides out of the box you are going to end up implementing the same.