I’ve been a vocal critic of the Microsoft identity solutions. In their defense this is not a trivial subject, but I feel that they’ve not really spent the time or focus they need to to provide a fully robust solution given the putative standards for modern security. ASP.NET Identity v2 is the latest iteration in the history of their implementations, and while it’s the best so far I feel there are still major issues with the security of the implementation.
I’ve also been touting MembershipReboot as an alternative, but I have realized several issues related to its visibility and adoption. MembershipReboot is a fairly small open source project. It’s a completely different library than the one(s) from Microsoft. I’m the only main developer. I am not paid to work on it. I do not have a staff. I only have the barest of documentation (I know — shame on me). The only real support is via the github issue tracker. And perhaps most importantly it’s hard to gain notice amid the marketing behemoth that is Microsoft. Notice in this very recent video there is no mention of how passwords are stored or how their library actually provides any security; rather topics such as EF and Azure are discussed *rolls eyes*. So, I completely understand that many won’t consider or even notice MembershipReboot and will adopt ASP.NET Identity without knowing any better.
Despite this I’m still doing my best to make this space better. As such, I’m announcing IdentityReboot.
IdentityReboot is a set of extensions to ASP.NET Identity that implement the major missing features I’ve complained about (put up or shut up, right?). The code to do this is essentially the same code I have already implemented in MembershipReboot, but it’s been adapted to the ASP.NET Identity programming model. The master branch targets ASP.NET Identity v2, but there is a v1 branch for ASP.NET Identity v1.
The main features provided by IdentityReboot (targeting v2) are:
- Per-account adaptive password hashing (as discussed here)
- Login and two factor authentication code brute force prevention (as discussed here)
- Fix the issue with the time-based two factor authentication codes (as discussed here)
The various components provided are:
The password hashing algorithm is pluggable in ASP.NET Identity, so IdentityReboot provides an AdaptivePasswordHasher class that implements the password stretching I described here.
The AdaptivePasswordHasher constructor allows a developer to indicate the number of iterations to use. If the iterations are not provided then it uses the year-based adaptive algorithm. It can then be configured on the UserManager.PasswordHasher property and it will be used when password hashing is needed by the UserManager.
The iteration count is stored in the hashed password string itself so there’s no schema change needed. Also the AdaptivePasswordHasher is compatible with passwords already hashed and persisted with the built-in password hasher from ASP.NET Identity. This means you can add this and your existing users hashed passwords will still work.
To implement brute force prevention for passwords and two factor codes, the logic from the ASP.NET Identity UserManager had to be replaced. To do this, IdentityReboot provides a UserManager-derived class called IdentityRebootUserManager.
IdentityRebootUserManager overrides the methods that perform password and two factor authentication code validation. If too many invalid attempts have been made within a window of time, then these validation methods fail not allowing the user to proceed. These values are configurable, but default to 5 attempts within 5 minutes.
To implement this brute force prevention, some data needs to be stored in the database. As such, a new store interface was introduced to persist the necessary data: IBruteForcePreventionStore. This interface models storage for the last date/time of the failed login and the number of failed attempts. This store interface pattern is consistent with ASP.NET Identity’s pattern to allow any storage implementation.
Also, by default, the IdentityRebootUserManager automatically configures and uses the AdaptivePasswordHasher mentioned above.
To fix the issue with time-based two factor authentication codes, IdentityReboot provides a new token provider class called MobileStoredTwoFactorCodeProvider. This token provider is meant to replace those built-in with ASP.NET Identity and improves upon the time-based providers by generating a code that is valid for a window of time from the time it is generated (as opposed to fixed windows of time, as I describe here).
To achieve this, the MobileStoredTwoFactorCodeProvider creates a random code which is sent via SMS to the user. This code is also hashed and stored in the database along with the time the code was generated. The hash is necessary to thwart attackers that have access to the database. The time is used to ensure the user has a sufficient window in which to validate the token. The number of digits and the validity duration are both configurable, and default to 6 digits and a 5 minute window.
To store the pertinent data for the MobileStoredTwoFactorCodeProvider another new store interface was defined: ITwoFactorCodeStore. It models persisting the hashed two factor authentication code and the date/time it was issued.
Finally, there is also an IdentityReboot.Ef project that provides an EF-backed IdentityRebootUserStore that implements the two additional store interfaces and an IdentityRebootUser class which defines the properties needed for the new store interfaces.
To use IdentityReboot, you can either get started from the samples or you can create a new empty ASP.NET application and then add the “Microsoft ASP.NET Identity Samples” from the nightly builds (as documented here). There are also packages on NuGet here and here.
In your web project starting from the templates all that is needed is to reference the IdentityReboot assemblies and then make these changes:
- Change ApplicationUser to derive from IdentityRebootUser instead of IdentityUser (in ~/Models/IdentityModels.cs)
- Replace new UserStore with new IdentityRebootUserStore (2 locations in ~/App_Start/IdentityConfig.cs)
- Change ApplicationUserManager to derive from IdentityRebootUserManager instead of UserManager (in ~/App_Start/IdentityConfig.cs)
- Remove the two calls to RegisterTwoFactorProvider and replace them by registering the MobileStoredTwoFactorCodeProvider.
If you have an existing database then you will need to use EF migrations to update the database with the new schema for the new stored data.
The intent with IdentityReboot was to enhance the security of ASP.NET Identity with as few changes and disruptions as possible. Again, all of these are illustrated in the samples.