Search code examples
c#asp.netasp.net-identityasp.net-identity-2

Asp.Net Identity default name for DpapiDataProtectionProvider for Owin


Background is that our current infrastructure uses two web applications. One web application administers users and on the other users can login and reset their passwords. From the administration area we need to be able to initiate a password reset, preferably without calling an API action on the other domain.

Since both the name for DpapiDataProtectionProvider and the purpose for DataProtectorTokenProvider.Create needs to match in order for the generated password reset token to work this has proven to be an issue. We wan't to use Owin on the client domain and therefore create a new DpapiDataProtectionProvider and DataProtectorTokenProvider that match these criteria on the administration web application.

We got it working by creating the same ApplicationUserManager with the same UserTokenProvider but we would like to use to Owin instance instead on the client side.

Works:

var db = new ApplicationDbContext();
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(db));
var provider = new DpapiDataProtectionProvider("ASP.NET Identity");
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(
    provider.Create("ASP.NET Identity"));

Looking at App_Start -> IdentityConfig.cs -> public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) the following code exists:

var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
    manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}

However I cant find where the name for DataProtectionProvider that is of type IDataProtectionProvider is set. The interface only has one method and that is IDataProtector Create(params string[] purposes); How can I get this name? Could this affect security somehow? I think the only thing missing from below is what name the new DpapiDataProtectionProvider("<MISSING>"); should have.

[HttpGet]
[AllowAnonymous]
[Route("testReset")]
public IHttpActionResult TestResetAdminDomain()
{
    var db = new ApplicationDbContext();
    var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(db));
    var provider = new DpapiDataProtectionProvider("ASP.NET Identity");
    manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(
        provider.Create("ASP.NET Identity"));

    var email = "[email protected]";

    var user = new ApplicationUser() { UserName = email, Email = email };

    var identityUser = manager.FindByEmail(email);

    if (identityUser == null)
    {
        manager.Create(user);
        identityUser = manager.FindByEmail(email);
    }

    var token = manager.GeneratePasswordResetToken(identityUser.Id);
    return Ok(HttpUtility.UrlEncode(token));
}

[HttpGet]
[AllowAnonymous]
[Route("testResetWithOwin")]
public IHttpActionResult TestResetWithOwinClientDomain(string token)
{
    var manager = Request.GetOwinContext().GetUserManager<ApplicationUserManager>();

    var email = "[email protected]";
    var identityUser = manager.FindByEmail(email);
    var valid = Task.Run(() => manager.UserTokenProvider.ValidateAsync("ResetPassword", token, manager, identityUser)).Result;
    var result = manager.ResetPassword(identityUser.Id, token, "TestingTest1!");
    return Ok(result);
}

Solution

  • Update:

    According to https://github.com/aspnet/Identity/blob/master/src/Identity/DataProtectionTokenProvider.cs it should be DataProtectorTokenProvider given the code Protector = dataProtectionProvider.CreateProtector(Name ?? "DataProtectorTokenProvider")

    If a token is created it needs to be consumed by the same Application Pool! When the code was tested on different Application Pool Users the code did not pass, when the Application Pool User was the same it worked normally.

    Original:

    I did not find the name for Owin but I tested the security concern that I had. Created two web applications with the methods below. When the user was created in WebApplication1 I copied the User Id from that value too the user created for WebApplication2 so they had the same guid Id for the method manager.GeneratePasswordResetToken(identityUser.Id). When testing the token only worked for WebApplication1 and not WebApplication2 even though the users had the same Id and DpapiDataProtectionProvider and DataProtectorTokenProvider was created exactly the same.

    [HttpGet]
    [AllowAnonymous]
    [Route("testReset")]
    public IHttpActionResult TestResetAdminDomain()
    {
        var db = new ApplicationDbContext();
        var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(db));
        var provider = new DpapiDataProtectionProvider("ASP.NET Identity");
        manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(
            provider.Create("ASP.NET Identity"));
    
        var email = "[email protected]";
    
        var user = new ApplicationUser() { UserName = email, Email = email };
    
        var identityUser = manager.FindByEmail(email);
    
        if (identityUser == null)
        {
            manager.Create(user);
            identityUser = manager.FindByEmail(email);
        }
    
        var token = manager.GeneratePasswordResetToken(identityUser.Id);
        return Ok(HttpUtility.UrlEncode(token));
    }
    
    [HttpGet]
    [AllowAnonymous]
    [Route("testReset")]
    public IHttpActionResult TestResetClientDomain(string token)
    {
        var db = new ApplicationDbContext();
        var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(db));
        var provider = new DpapiDataProtectionProvider("ASP.NET Identity");
        manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(
            provider.Create("ASP.NET Identity"));
        var email = "[email protected]";
        var identityUser = manager.FindByEmail(email);
        var valid = Task.Run(() => manager.UserTokenProvider.ValidateAsync("ResetPassword", token, manager, identityUser)).Result;
        var result = manager.ResetPassword(identityUser.Id, token, "TestingTest1!");
        return Ok(result);
    }