raphrbnt
raphrbnt

Reputation: 31

ASP.NET Core Identity x Docker - Confirmation link invalid on other instances

I am currently developing a web API with ASP.NET Core, using Microsoft Identity Core as for the identity management. When a user registers, it is sent an email with a confirmation link - pretty basic so far.

The problem comes when publishing my API to Azure using a containerized Azure App Service, and when setting the number of instances to 2 or more. The confirmation link seems to be working only half the time; tests on my dev machine with multiple Docker containers running seemed to confirm that fact, as the confirmation link could be validated only on the instance the user had registered on (hence the instance where the confirmation link was created).

Having dug a bit on the subject by reading this article by Steve Gordon, and explored the public GitHub code for Identity Core, I still don't understand why different container instances would return different results when validating the token, as the validation should mainly be based on the user SecurityStamp (that remains unchanged between the instances becauses they all link to the same database).

Also, enabling 'debug' logging for the Microsoft.AspNetCore.Identity only logged

ValidateAsync failed: unhandled exception was thrown.

during token validation from the DataProtectorTokenProvider.ValidateAsync() method from AspNetCore.Identity, so it is not very helpful as I can't see precisely where the error happens...

May this be linked to the token DataProtector not being the same on different instances? Am I searching in the wrong direction? Any guess, solution or track for this?

Help would be immensely appreciated 🙏


Here is some simplified code context from my app for the record.

UserManager<User> _manager; // Set from DI
// ...

// Creating the user and sending the email confirmation link
[HttpGet(ApiRoutes.Users.Create)]
public async Task<IActionResult> RegisterUser(UserForRegistrationDto userDto)
{
    var user = userDto.ToUser();
    await _manager.CreateAsync(user, userDto.Password);

    // Create the confirmation token
    var token = await _manager.CreateEmailConfirmationTokenAsync(user);

    // Generate the confirmation link pointing to the below 'ConfirmEmail' endpoint
    var confirmationLink = Url.Action("ConfirmEmail", "Users",
        new { user.Email, token }, Request.Scheme);

    await SendConfirmationEmailAsync(user, confirmationLink); // Some email logic elsewhere

    return Ok();
}

// Confirms the email using the passed token
[HttpGet(ApiRoutes.Users.ValidateEmail)]
public async Task<IActionResult> ConfirmEmail(string email, string token)
{
    var user = await _userManager.FindByEmailAsync(email);

    if (user == null)
    {
        return NotFound();
    }

    var result = await _userManager.ConfirmEmailAsync(user, token);
    
    if (!result.Succeeded)
    {
        return BadRequest();
    }

    return Ok();
}

Upvotes: 3

Views: 283

Answers (1)

RKornu
RKornu

Reputation: 1

Token generated based on security stamp but Identity uses DataProtector to protect the token content. By default the data protection keys stored at location %LOCALAPPDATA%\ASP.NET\

If the application runs on single machine it is perfectly fine as there is no scope for key mismatch. But deployed on multiple instances the tokens will not work sometimes as the Keys are different on different machines and there is no guarantee the generation of token and validation of token will come to same instance.

To solve user redis or azurekeyvault https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-6.0#persisting-keys-with-redis

Upvotes: -1

Related Questions