How to migrate ASP.NET Membership users to ServiceStack

By October 24, 2013Uncategorized

We love ServiceStack here at Falafel. If you’re already using it, you know why. If you haven’t used it or even heard of it, read the wiki intro to see what all the love is about.

When it comes time to create a real application with ServiceStack, you may be faced with a situation where you already have a lot of user logins already in existence in the former de-facto authentication system for Microsoft developers: ASP.NET Membership. The good news is that as long as you kept to the defaults when it came to how ASP.NET Membership stored user passwords, it is possible to authenticate users against the aspnet DB tables without needing to call any methods of the Membership class.

This is the game plan: create a custom ServiceStack CredentialsAuthProvider that will test for the existence of the user in the ServiceStack UserAuthRepository, and if it’s not found, import it from the aspnet DB tables. Once that step is complete, continue with attempting to authenticate the user against the UserAuthRepository as normal.

The simplest way to implement a custom credentials provider in ServiceStack is to subclass the one you want to customize and override the TryAuthenticate method. In this case, we want to add some extra logic smack in the middle of the normal flow of logic in TryAuthenticate, so it doesn’t flow well to call the base method and add extra logic before and after. My solution to this was to disassemble the TryAuthenticate method and copy the code into the overridden method, then modify as necessary.

public override bool TryAuthenticate(IServiceBase authService, string userName, string password) {
    // Disassembled code from the parent
    /* ... */
    // Migration condition test and migration call
    if (userAuthRepository.GetUserAuthByUserName(userName) == null)
        MigrateAspnetMember(userAuthRepository, userName, password);
    // Disassembled code from the parent
    /* ... */
}

The migration code is fairly straightforward if you’re familiar with the ASP.NET Membership DB tables. The queries are in strings here for simplicity, but you could easily implement them as stored procedures instead. In fact, with a stored procedure, you could return the roles in JSV format (e.g. “[Role1, Role2]”) and ServiceStack will automatically map them to a collection property such as string[] or List<string>, negating the need for a second query.

/// <summary>
/// This procedure migrates ASP.NET Membership Users and Roles
/// </summary>
private bool MigrateAspnetMember(IUserAuthRepository userAuthRepository, string userName, string password) {
    // The following line assumes that an IDbConnectionFactory has been registered with the IOC container
    using (var db = AppHostBase.Resolve<IDbConnectionFactory>().Open()) {
        // The following queries could also be implemented as stored procedures
        // They're implemented as SQL strings for simplicity
        var queryMember = @"
            select m.UserId, m.Email, m.Password, m.PasswordSalt
            from aspnet_Membership m
            join aspnet_Users u on u.UserId = m.UserId
            join aspnet_Applications a on a.ApplicationId = m.ApplicationId
            where u.UserName = @UserName
            and a.ApplicationName = @ApplicationName";
        var queryRoles = @"
            select r.RoleName
            from aspnet_Roles r
            join aspnet_UsersInRoles ur on ur.RoleId = r.RoleId
            join aspnet_Applications a on a.ApplicationId = r.ApplicationId
            where ur.UserId = @UserId
            and a.ApplicationName = @ApplicationName";
        var member = db.QuerySingle<AspnetMember>(
            queryMember, new { UserName = userName, ApplicationName = OLD_APPLICATION_NAME });
                
        if (member == null)
            return false;
        var hash = ComputeSaltedHash(password, member.PasswordSalt);
        if (hash != member.Password)
            return false;
        var roles = db.Query<string>(
            queryRoles, new { UserId = member.UserId, ApplicationName = OLD_APPLICATION_NAME });
        var userAuth = userAuthRepository.CreateUserAuth(new UserAuth() {
            UserName = userName,
            Email = member.Email,
            Roles = roles
        }, password);
    }
    return true;
}

The final piece is the actual algorithm to compute the salted hash. Credit for this part goes to Malcom Swaine.

private string ComputeSaltedHash(string clearPassword, string passwordSalt) {
    byte[] bIn = Encoding.Unicode.GetBytes(clearPassword);
    byte[] bSalt = Convert.FromBase64String(passwordSalt);
    byte[] bAll = new byte[bSalt.Length + bIn.Length];
    byte[] bRet = null;
    Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
    Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
    HashAlgorithm s = HashAlgorithm.Create("SHA1");
    bRet = s.ComputeHash(bAll);
    string newHash = Convert.ToBase64String(bRet);
    return newHash;
}

Then all that’s left to do is to plug it into ServiceStack during the AppHost initialization:

Plugins.Add(new AuthFeature(
    () => new CustomUserSession(),
    new[] { new CredentialsAuthProviderWithAspnetMigration() }
));

I’ve posted the complete file that includes the uninteresting bits as well as a GitHub Gist. I hope you find it useful!

The following two tabs change content below.
  • Adam Anderson

    You don’t. This is a temporary measure put in place until all legacy users have been migrated, then you decommission the legacy repository.