Trevor Ward
Trevor Ward

Reputation: 612

Invalid Token when confirming email address - Asp.Net Core

I'm occasionally getting an "Invalid Token" error from my call to userManager.ConfirmEmailAsync(user, token) . I've narrowed down the problem to the fact that my 2 web servers are sitting behind a load balancer, and the web server that generated the token isn't always the web server that is attempting to confirm the token. I had a similar problem with anti-forgery tokens in a different web site, which I fixed by persisting the data protection key to disk and sharing it between the web servers, so I tried a similar approach here.

services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp\API"));

I then copied the key to the same folder in my other web server but still wasn't successful. Debugging through the AspNetCore.Identity code I can see an exception thrown on the call to

var unprotectedData = Protector.Unprotect(Convert.FromBase64String(token))

in the DataProtectorTokenProvider class. the catch block for the exception in Microsoft's code is simply

 catch
        {
            // Do not leak exception
        }

so I decided to inject an IDataProtector into my own Controller and try making that call myself.

   public UserController(Microsoft.AspNetCore.Identity.UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager, Microsoft.AspNetCore.Identity.RoleManager<IdentityRole> roleManager,
        ILoggerFactory loggerFactory,
         IDataProtectionProvider dataProtectionProvider) 
    {

        Protector = dataProtectionProvider.CreateProtector("DataProtectorTokenProvider");
    }

try
        {
            var unconverted = Convert.FromBase64String(request.EmailConfirmationToken);
            var unprotectedData = Protector.Unprotect(unconverted);
        }
        catch (Exception e)
        {

        }

I can now catch the exception thrown on the Unprotect call and it's:

The payload was invalid

Microsoft.AspNetCore.DataProtection.Cng.CbcAuthenticatedEncryptor.DecryptImpl(Byte* pbCiphertext, UInt32 cbCiphertext, Byte* pbAdditionalAuthenticatedData, UInt32 cbAdditionalAuthenticatedData)\r\n at Microsoft.AspNetCore.DataProtection.Cng.Internal.CngAuthenticatedEncryptorBase.Decrypt(ArraySegment1 ciphertext, ArraySegment1 additionalAuthenticatedData)\r\n at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(Byte[] protectedData, Boolean allowOperationsOnRevokedKeys, UnprotectStatus& status)\r\n at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.DangerousUnprotect(Byte[] protectedData, Boolean ignoreRevocationErrors, Boolean& requiresMigration, Boolean& wasRevoked)\r\n at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect(Byte[] protectedData)\r\n at VTR.API.Controllers.UserController.d__16.MoveNext() in C:\Projects\Brewster.Travel\src\cres\trunk\VTR.API\src\VTR.API\Controllers\UserController.cs:line 409

If I make that call with a token generated on the same server then it gets unprotected successfully. I obviously have some problem with how I'm attempting to share my data protection keys, if anyone could shed some light on my problem I would appreciate it.

Upvotes: 2

Views: 1294

Answers (1)

Trevor Ward
Trevor Ward

Reputation: 612

I managed to get this working thanks to the documentation here: https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview

I needed to add a call to SetApplicationName() in ConfigureServices:

services.AddDataProtection()
          .PersistKeysToFileSystem(new DirectoryInfo(@"c:\someDirectory"))
          .SetApplicationName("myApplicationName");

Upvotes: 2

Related Questions