Open Closed

Invalid token in email confirmation #9569


User avatar
0
jaylin created

Exception message and full stack trace:

  • Exception message and full stack trace:

[18:21:05 INF] Request starting HTTP/2 GET https://localhost:44318/Account/EmailConfirmation?userId=0841cd40-1ff0-0017-27e2-3a1ae6b1bafd&__tenant=712d7070-8d81-3fd3-eaa8-3a18008b6246&confirmationToken=CfDJ8C8bts6Bgc9OvV5TVAdDQVYp77XSKp4N1RHrG1eCiaGOlYEIka3r8jjzhMbFhrqrlIiNjWgVEjrKz4B5R3nUodUnKBmvk%2B3j0%2BSTnbyh5AUx8E9wnaP6q3TFQeG4CJG44Xu5fa6z52Y8QkC1ovb6PGWl8BbSevC82hcKj94GmpJgYSTmnLsx%2BSpELpUFCCfkIoQVpzIMiPKz4vez2dxmcbTOkOHKhIK7GJNAtrZfLYQL%2BmvCjKbqkRgLXYoDodQRYw%3D%3D&returnUrl=%2fconnect%2fauthorize%3fresponse_type%3dcode%26client_id%3dBetterBilling_App%26state%3ddmVLd3Z4LWlqSlBodzBOSlpSbEp2bUxzdWsxcjJPdko4ZTh5UlZ6aEVlUUJW%26redirect_uri%3dhttp%253A%252F%252Flocalhost%253A4200%26scope%3dopenid%2520offline_access%2520BetterBilling%26code_challenge%3dZsE0yr8Kl625hSK2SOsW86wIL6q_54oBGfP8CIsqw7Q%26code_challenge_method%3dS256%26nonce%3ddmVLd3Z4LWlqSlBodzBOSlpSbEp2bUxzdWsxcjJPdko4ZTh5UlZ6aEVlUUJW%26culture%3den%26ui-culture%3den - null null [18:21:05 DBG] The event OpenIddict.Validation.OpenIddictValidationEvents+ProcessRequestContext was successfully processed by OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlers+ResolveRequestUri. [18:21:05 DBG] The event OpenIddict.Server.OpenIddictServerEvents+ProcessRequestContext was successfully processed by OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers+ResolveRequestUri. [18:21:05 DBG] The event OpenIddict.Server.OpenIddictServerEvents+ProcessRequestContext was successfully processed by OpenIddict.Server.OpenIddictServerHandlers+InferEndpointType. [18:21:05 DBG] The event OpenIddict.Server.OpenIddictServerEvents+ProcessRequestContext was successfully processed by Volo.Abp.Account.Web.Pages.Account.OpenIddictImpersonateInferEndpointType. [18:21:05 DBG] The event OpenIddict.Server.OpenIddictServerEvents+ProcessRequestContext was successfully processed by OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers+ValidateTransportSecurityRequirement. [18:21:05 DBG] The event OpenIddict.Server.OpenIddictServerEvents+ProcessRequestContext was successfully processed by OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers+ValidateHostHeader. [18:21:05 DBG] Get dynamic claims cache for user: 0841cd40-1ff0-0017-27e2-3a1ae6b1bafd [18:21:05 INF] Executing endpoint '/Account/EmailConfirmation' [18:21:05 INF] Route matched with {page = "/Account/EmailConfirmation", action = "", controller = "", area = ""}. Executing page /Account/EmailConfirmation [18:21:05 INF] Skipping the execution of current filter as its not the most effective filter implementing the policy Microsoft.AspNetCore.Mvc.ViewFeatures.IAntiforgeryPolicy [18:21:05 INF] Executing handler method Volo.Abp.Account.Public.Web.Pages.Account.EmailConfirmationModel.OnGetAsync - ModelState is Valid [18:21:05 DBG] VerifyUserTokenAsync() failed with purpose: EmailConfirmation for user. [18:21:05 INF] Executed handler method OnGetAsync, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. [18:21:05 DBG] Added 0 entity changes to the current audit log [18:21:05 DBG] Added bundle 'LeptonX.Global' to the page in 4.45 ms. [18:21:05 DBG] Added bundle 'LeptonX.Global' to the page in 3.16 ms. [18:21:05 INF] Executed page /Account/EmailConfirmation in 60.4712ms [18:21:05 INF] Executed endpoint '/Account/EmailConfirmation' [18:21:05 INF] Request finished HTTP/2 GET https://localhost:44318/Account/EmailConfirmation?userId=0841cd40-1ff0-0017-27e2-3a1ae6b1bafd&__tenant=712d7070-8d81-3fd3-eaa8-3a18008b6246&confirmationToken=CfDJ8C8bts6Bgc9OvV5TVAdDQVYp77XSKp4N1RHrG1eCiaGOlYEIka3r8jjzhMbFhrqrlIiNjWgVEjrKz4B5R3nUodUnKBmvk%2B3j0%2BSTnbyh5AUx8E9wnaP6q3TFQeG4CJG44Xu5fa6z52Y8QkC1ovb6PGWl8BbSevC82hcKj94GmpJgYSTmnLsx%2BSpELpUFCCfkIoQVpzIMiPKz4vez2dxmcbTOkOHKhIK7GJNAtrZfLYQL%2BmvCjKbqkRgLXYoDodQRYw%3D%3D&returnUrl=%2fconnect%2fauthorize%3fresponse_type%3dcode%26client_id%3dBetterBilling_App%26state%3ddmVLd3Z4LWlqSlBodzBOSlpSbEp2bUxzdWsxcjJPdko4ZTh5UlZ6aEVlUUJW%26redirect_uri%3dhttp%253A%252F%252Flocalhost%253A4200%26scope%3dopenid%2520offline_access%2520BetterBilling%26code_challenge%3dZsE0yr8Kl625hSK2SOsW86wIL6q_54oBGfP8CIsqw7Q%26code_challenge_method%3dS256%26nonce%3ddmVLd3Z4LWlqSlBodzBOSlpSbEp2bUxzdWsxcjJPdko4ZTh5UlZ6aEVlUUJW%26culture%3den%26ui-culture%3den - 200 null text/html; charset=utf-8 83.3383ms [18:21:05 INF] Request starting HTTP/2 GET https://localhost:44318/Abp/ApplicationLocalizationScript?cultureName=en - null null [18:21:05 INF] Request starting HTTP/2 GET https://localhost:44318/Abp/ApplicationConfigurationScript - null null [18:21:05 INF] Request starting HTTP/2 GET https://localhost:44318/Abp/ServiceProxyScript - null null **

  • Steps to reproduce the issue: This issue happened recently, it happened on both local dev and production. I have got the Confirmation Email token validation error:

As I don't have source code of /Account/EmailConfirmation, I suppose some of the keys are expired?

I have tried dataProtectionBuilder with both local aspnet and Redis, none of them are working.

        var dataProtectionBuilder = context
            .Services.AddDataProtection()
            .SetApplicationName("BetterBilling")
            // Keys expire after 1 year
            .SetDefaultKeyLifetime(TimeSpan.FromDays(365));

        if (!hostingEnvironment.IsDevelopment())
        {
            var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!);
            dataProtectionBuilder.PersistKeysToStackExchangeRedis(
                redis,
                "BetterBilling-Protection-Keys"
            );
        }
        

I am sure it was working before, or does any of the abp upgrade impact it?


3 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent

    The "Invalid token" error during email confirmation in ABP Framework is typically caused by one of the following issues:

    1. Token Expiry or Reuse: Email confirmation tokens are single-use and expire after a certain period. If the user tries to use the same token twice (e.g., clicking the link again after confirming), it will show as invalid. This is by design for security.

    2. Data Protection Key Issues: If your application uses distributed environments (multiple servers, containers, or environments), the data protection keys used to generate and validate tokens must be shared and consistent across all instances. If the keys change (e.g., after a redeploy, or if Redis/DB is cleared), previously generated tokens become invalid.

    3. Tenant Context: In a multi-tenant setup, ensure the correct tenant context is set when validating the token. The token is tenant-specific.

    4. ABP Upgrade Impact: Upgrading ABP or changing the data protection configuration can invalidate existing tokens if the key ring changes.

    Recommended Solutions

    • Ensure Data Protection Key Persistence:
      For distributed or production environments, persist your data protection keys using Redis or a shared file system. Your configuration looks correct, but ensure the Redis instance is stable and not cleared between deployments.

      var dataProtectionBuilder = context
          .Services.AddDataProtection()
          .SetApplicationName("BetterBilling")
          .SetDefaultKeyLifetime(TimeSpan.FromDays(365));
      
      if (!hostingEnvironment.IsDevelopment())
      {
          var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!);
          dataProtectionBuilder.PersistKeysToStackExchangeRedis(
              redis,
              "BetterBilling-Protection-Keys"
          );
      }
      
    • Check for Key Loss:
      If Redis or your key store was cleared, all previously issued tokens will be invalid. Users will need to request new confirmation emails.

    • Token Usage:
      Tokens are single-use. If a user tries to confirm an already confirmed email, you may want to customize the error message to indicate the email is already confirmed (see below).

    • Customizing the Error Message:
      As of ABP 7.3, the framework skips checking if EmailConfirmed is true before showing "invalid token." You can override the behavior in your custom page model to show a friendlier message if the email is already confirmed.

      Example:

      if (user.EmailConfirmed)
      {
          // Show "You already confirmed your email" message
      }
      else
      {
          // Show "Invalid token" message
      }
      

    References

    If you recently upgraded ABP or changed your data protection configuration, all previously generated tokens will be invalid. Instruct users to request a new confirmation email if they encounter this error.


    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

  • User Avatar
    0
    jaylin created

    Don't worry about my above question, I found others have similar problem - register but also update the user info which broke the security stamp.

    However, I have another question, as I need to create a Sign Up button in Angular. There is a navigateToLogin method in auth service, but there is no such navigateToRegister method. login() { this.authService.navigateToLogin(); }

    I customised my own one, but it didn't invoke the /token endpoint therefore, the user registered through my custom flow will not be authenticated straight away, until they actively click on "register" / "login" to refresh the page to get logged in.

    async navigateToRegisterEx2() {
    const authServerUrl = environment.oAuthConfig.issuer;
    const responseType = environment.oAuthConfig.responseType;
    const clientId = environment.oAuthConfig.clientId;
    const redirectUri = environment.oAuthConfig.redirectUri;
    const scope = environment.oAuthConfig.scope;
    
    // Generate PKCE parameters
    const codeVerifier = this.generateCodeVerifier();
    const codeChallenge = await this.generateCodeChallenge(codeVerifier);
    const state = this.generateState();
    const nonce = this.generateNonce();
    
    // Store PKCE parameters in session storage for later use
    sessionStorage.setItem('code_verifier', codeVerifier);
    sessionStorage.setItem('state', state);
    sessionStorage.setItem('nonce', nonce);
    
    const returnUrl = encodeURIComponent(
      `/connect/authorize?response_type=${responseType}&client_id=${clientId}&redirect_uri=${encodeURIComponent(
        redirectUri
      )}&scope=${encodeURIComponent(
        scope
      )}&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256&nonce=${nonce}&culture=en&ui-culture=en`
    );
    
    window.location.href = `${authServerUrl}Account/Login?register=true&returnUrl=${returnUrl}`;
    

    }

    I could not find anywhere to invoke the out of box Register OAuthFlow in angular. Please advise.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi jaylin

    I will refund your current question credits. Can you create a new question for your angular problem?

    Thanks.

Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.0.0-preview. Updated on July 11, 2025, 11:35