aspnetcore icon indicating copy to clipboard operation
aspnetcore copied to clipboard

InvalidToken when confirming email change

Open twojnarowski opened this issue 1 year ago • 5 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Describe the bug

In a .NET8 Blazor Server Side application I am managing my users - I want to have an option to change emails. I have a Blazor Component for editing a user and there after submitting this code is executed:

var user = await UserManager.FindByIdAsync(Id).ConfigureAwait(true);
var code = await UserManager.GenerateChangeEmailTokenAsync(user, newEmail).ConfigureAwait(true);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));

ConfirmEmailChange.cshtml page has this code OnGetAsync:

code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
var result = await this.userManager.ChangeEmailAsync(user, email, code);

And the result is not succeeded, and in result.Errors I get an error InvalidToken. I have added logging both raw code, encoded, received, and decoded. Raw and decoded codes are identical, and encoded and received codes are also identical. + are still +, they are not changed into spaces, all characters are identical. This happens when the Application is running on a Linux Server. But when I run it locally on my Windows computer, the email changing works. However when I try to change my email in the Identity Email.cshtml page, with the following code:

var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));

Everything works both on Linux and on Windows, it also directs to the ConfirmEmailChange.cshtml page.

So: why does the userManager.ChangeEmailAsync method work when receiving a:

  • windows razor page generated token
  • windows blazor component generated token
  • linux razor page generated token

but doesn't work on:

  • linux blazor component generated token

?

Expected Behavior

No response

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version

8.0.200

Anything else?

No response

twojnarowski avatar Feb 22 '24 12:02 twojnarowski

I have pushed the application in this form to a production server, which is also linux, and there everything works. I will try to find out what is the difference between test linux server and production linux server.

twojnarowski avatar Feb 22 '24 14:02 twojnarowski

Both servers have system Ubuntu 22.04.3 LTS

The difference that I can see is, that the working production server has some packages like aspnetcore-targeting-pack, dotnet-apphost-pack, dotnet-targeting-pack in version 8.0.2-0 while the not working staging server has them in 8.0.2-1. Another difference is the dotnet-sdk and the working server has 8.0.101-1, while to not working one has 8.0.200-1

Both apps are run from an identical systemdservice with the only difference being that a different Environment variable is set. This environment mostly just sets a correct connection string and different Serilog sinks. It does not change anything Identity-related apart from the connection string.

twojnarowski avatar Feb 23 '24 10:02 twojnarowski

The tokens must be generated and validated using the same data protection key. By default, this is stored in the users home directory on Linux. You should see this in ~/.aspnet/DataProtection-Keys/. Can you verify that you're always using the same key used to generate the token when confirming the email change, and that the application name of project generating and validating the token is the same?

Could the issue be that different applications with different names are generating and validating the token? Or that the same application is running on multiple different hosts or as different users which don't share a key?

halter73 avatar Feb 23 '24 21:02 halter73

Thank you for the answer @halter73 - I don't have what you are mentioning configured explicitly. This is my Identity configuration:

builder.Services.AddIdentity<CustomUser, CustomRole>(
    options =>
    {
        options.SignIn.RequireConfirmedAccount = true;
        // other pasword requirements configured here
    })
    .AddEntityFrameworkStores<CustomDbContext>()
    .AddDefaultTokenProviders()
    .AddUserConfirmation<UserConfirmation<CustomUser>>()
    .AddErrorDescriber<CustomIdentityErrorDescriber>()
    .AddDefaultUI();

On the working server in~/.aspnet/DataProtection-Keys I have to .xml files, first one was valid thru 8.02.2024, and the second one is currently valid. On the not working server there are 4 xml files, because it has been running for longer, and the previous key was valid until 18.12.2023 These servers are running only this one application, and they are both deployed from the same repository. The generating and validating are done within the same project, the only difference being that they are generating in a razor component, and validated on a .cshtml razor page. I will add explicit configuration of the Keys to ToFileSystem and will SetApplicationName. When I do that I will let you know if that changed the outcome.

twojnarowski avatar Feb 26 '24 09:02 twojnarowski

@halter73 I have added this configuration:

builder.Services
    .AddDataProtection()
    .SetApplicationName("NAME");

This is just one application on the server, on the same host, one instance and always running as root user. I deleted all old keys and restarted the application to make sure that there was just one key generated in /root/.aspnet/DataProtection-Keys. The key was generated there but the issue persists.

twojnarowski avatar Feb 27 '24 10:02 twojnarowski

If it's a single project, can you please publish it to GitHub so we can quickly try it ourselves on Linux?

halter73 avatar Feb 27 '24 17:02 halter73

@halter73 we cannot make this repository public.

twojnarowski avatar Mar 01 '24 09:03 twojnarowski

Can you create a simplified project that recreates the issue and publish that to GitHub?

halter73 avatar Mar 02 '24 00:03 halter73

Thank you for filing this issue. In order for us to investigate this issue, please provide a minimal repro project that illustrates the problem without unnecessary code. Please share with us in a public GitHub repo because we cannot open ZIP attachments, and don't include any confidential content.