ASP.NET Identity and CryptographicException when running your site on Microsoft Azure Web-Sites

UPD: Here is another good solution to this problem if you are running your own IIS.

Last week I was updating one of applications I work on to ASP.NET Identity. For a specific reasons I could not deploy to Azure for a while. But I did run all the tests locally and everything worked just fine.

When I mostly finished with Identity conversion, I finally managed to deploy the application to Azure Web-Sites. And it worked fine.. until I tried registering a user.

At that point I had an exception exploding in my face:

System.Security.Cryptography.CryptographicException: The data protection operation was unsuccessful. This may have been caused by not having the user profile loaded for the current thread's user context, which may be the case when the thread is impersonating.

A bit of digging online did not give me any results. Everything single solution in google was talking about Windows Identity Foundation, but I was not using it. I only have ASP.NET Identity.

After a bit of digging turned out that my application could not generate a token for email confirmation. And that was handled like this:

public class UserManager : UserManager<ApplicationUser>
{
    public UserManager() : base(new UserStore<ApplicationUser>(new MyDbContext()))
    {
        // this does not work on azure!!!
        var provider = new Microsoft.Owin.Security.DataProtection.DpapiDataProtectionProvider("ASP.NET IDENTITY");
        this.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("EmailConfirmation"))
        {
            TokenLifespan = TimeSpan.FromHours(24),
        };
    }
}

I have found this snippet somewhere online, but mostly had no idea what it does – cargo culting. My bad! And this very piece was causing issues when run on Azure Web-Sites.

After a lot of messing about with different available options, I came to conclusion there is a good way to do DI injection in UserManager and not take dependency on IdentityFactoryOptions<ApplicationUserManager> in constructor. For that you’ll need to save the reference to IDataProtector as a static where it is available – Startup.Auth.cs:

public partial class Startup
{
    internal static IDataProtectionProvider DataProtectionProvider { get; private set; }

    public void ConfigureAuth(IAppBuilder app)
    {
        DataProtectionProvider = app.GetDataProtectionProvider();
        // other stuff.
    }
}

Then you can reach this reference within UserManager

public class UserManager : UserManager<ApplicationUser>
{
    public UserManager() : base(new UserStore<ApplicationUser>(new MyDbContext()))
    {
        var dataProtectionProvider = Startup.DataProtectionProvider;
        this.UserTokenProvider = 
                new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));

        // do other configuration
    }
}

I have tried this and it works locally, hosted in IIS and IIS Express. Also just deployed this to Azure and it works in Azure as well, giving nice long user tokens.

After some discussion in the comments, I’ve been shown a better way. Thanks for that, by the way!
The better way is to implement your own IDataProtector, shown in this SO answer

You’ll need to have <machineKey> in your web.config. Usually it looks like this:

<system.web>
    <machineKey validationKey="2EEA416CE..............99E0C" decryptionKey="87..............41A592" validation="SHA1" />
</system.web>

Google for it if you don’t know what it is, there are sites that can generate one for you.

And you’ll need to use MachineKey class in a wrapper:

using System.Web.Security;
using Microsoft.Owin.Security.DataProtection;

public class MachineKeyDataProtector : IDataProtector
{
    private readonly string[] purposes;

    public MachineKeyDataProtector(params string[] purposes)
    {
        this.purposes = purposes;
    }

    public byte[] Protect(byte[] userData)
    {
        return MachineKey.Protect(userData, purposes);
    }

    public byte[] Unprotect(byte[] protectedData)
    {
        return MachineKey.Unprotect(protectedData, purposes);
    }
}

And then in your UserManager specify as following:

public class UserManager : UserManager<ApplicationUser>
{
    public UserManager() : base(new UserStore<ApplicationUser>(new MyDbContext()))
    {
        var machineKeyDataProtector = new MachineKeyDataProtector("ResetPasswordPurpose");
        this.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser, Guid>(machineKeyDataProtector)
        {
            TokenLifespan = TimeSpan.FromHours(24),
        };
    }
}

I’ve just tried it and works just fine in Azure Web-Site. With a long and scary confirmation code.