Open Closed

Email Confirmation Token is always Invalid #1400


0
[email protected] created
  • ABP Framework version: v4.2.2
  • UI type: Angular / MVC / Blazor
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): yes
  • Exception message and stack trace: AbpIdentityResultException: Invalid token.
An unhandled exception has occurred while executing the request.
Volo.Abp.Identity.AbpIdentityResultException: Invalid token.
   at Microsoft.AspNetCore.Identity.AbpIdentityResultExtensions.CheckErrors(IdentityResult identityResult)
   at Volo.Abp.Account.AccountAppService.ConfirmEmailAsync(ConfirmEmailInput input) in /home/ruben/FrogSlayer/Projects/LaborLoop/laborloop-api/modules/Volo.Account.Pro/src/Volo.Abp.Account.Pro.Public.Application/Volo/Abp/Account/AccountAppService.cs:line 173
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous(IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapter.ProceedAsync()
   at Volo.Abp.Validation.ValidationInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous(IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapter.ProceedAsync()
   at Volo.Abp.Authorization.AuthorizationInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous(IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapter.ProceedAsync()
   at Volo.Abp.Auditing.AuditingInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous(IInvocation invocation, IInvocationProceedInfo proceedInfo)
   at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapter.ProceedAsync()
   at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation)
   at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed)
   at Volo.Abp.Account.Public.Web.Pages.Account.EmailConfirmationModel.OnGetAsync() in /home/ruben/FrogSlayer/Projects/LaborLoop/laborloop-api/modules/Volo.Account.Pro/src/Volo.Abp.Account.Pro.Public.Web/Pages/Account/EmailConfirmation.cshtml.cs:line 41
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.NonGenericTaskHandlerMethod.Execute(Object receiver, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync()
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync()
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ExceptionContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Volo.Abp.AspNetCore.Serilog.AbpSerilogMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
   at Volo.Abp.AspNetCore.Auditing.AbpAuditingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Volo.Abp.AspNetCore.Auditing.AbpAuditingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
   at IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events, IBackChannelLogoutService backChannelLogoutService)
   at IdentityServer4.Hosting.MutualTlsEndpointMiddleware.Invoke(HttpContext context, IAuthenticationSchemeProvider schemes)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Volo.Abp.AspNetCore.Uow.AbpUnitOfWorkMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
   at Volo.Abp.AspNetCore.ExceptionHandling.AbpExceptionHandlingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Volo.Abp.AspNetCore.ExceptionHandling.AbpExceptionHandlingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Volo.Abp.AspNetCore.Tracing.AbpCorrelationIdMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.RequestLocalization.AbpRequestLocalizationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
  • Steps to reproduce the issue:

    • Go to Landing page of Angular application
    • Click on register new user
    • Fill out information
    • Click register
    • Recieve email with confirmation link
    • Click on the link
  • Additional Info:

    • We are using a postgres DB and redis cache
    • I verified that the token being generated is the same that is being recieved by the identity server
    • The identity server and api are running on different instances
    • Looking at the redis cache I see what should be the shared key between the two applicatons

For some reason the confirmation token in the email is always invalid. I double checked to make sure that the token being generated when the user is being created is the same as what is in the email and then checked again that the string matches before it tries to validate it on the identity server. Any help/insight would be appreciated.


31 Answer(s)
  • 0
    maliming created
    Support Team

    I think everything is ok locally, right?

    Can you add below code to identity server and api?

    public void ConfigureServices(IServiceCollection services)
    {
         var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]);
            context.Services.AddDataProtection()
                .PersistKeysToStackExchangeRedis(redis, "Your-DataProtection-Keys")
                .SetApplicationName("YourAppName");
    }
    
  • 0
    [email protected] created

    It is not working locally either.

    I have added that and it still gives me the same error.

    I have also tried using that along with DisableAutomaticKeyGeneration() with no luck also.

  • 0
    maliming created
    Support Team

    Will it work if identity server and api are run on one instance?

  • 0
    [email protected] created

    Locally they run on the same instance but are started on separate processes and it still does not work

  • 0
    maliming created
    Support Team

    hi

    Can i check it remotely? https://zoom.us/j/92883778530?pwd=SHRCbEd0UndjYzc5R2szVlJiLzJzQT09

  • 0
    maliming created
    Support Team

    hi

    I checked, the problem should DataProtectorTokenProvider.

    You can inject the UserManager in your identity server project, then call GenerateEmailConfirmationTokenAsync and ConfirmEmailAsync to check if the function is works.

    public IdentityUserManager UserManager { get; set; }
    
    public virtual Task<string> GenerateEmailConfirmationTokenAsync(TUser user);
    public virtual async Task<IdentityResult> ConfirmEmailAsync(TUser user, string token)
    
  • 0
    [email protected] created

    I have tried that on both the api and identity server and they work when they are called together on each individual application.

    The issue with that is that the GenerateEmailConfirmationTokenAsync is being called on the api while ConfirmEmailAsync is on the idenentity server so having the same UserManager is not possible accross both.

  • 0
    maliming created
    Support Team

    OK, I will investigate this.

  • 0
    maliming created
    Support Team

    hi

    If two instances use the same key and same ApplicationName, it is possible to call each other.

    https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-5.0

  • 0
    [email protected] created

    Is that not what we did when you said to do this?

    I think everything is ok locally, right?

    Can you add below code to identity server and api?

    public void ConfigureServices(IServiceCollection services) 
    { 
         var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); 
            context.Services.AddDataProtection() 
                .PersistKeysToStackExchangeRedis(redis, "Your-DataProtection-Keys") 
                .SetApplicationName("YourAppName"); 
    } 
    
  • 0
    maliming created
    Support Team

    Is that not what we did when you said to do this?

    Yes, Can you try on another computer? Let us rule out environment.

  • 0
    [email protected] created

    I have tried it on two different computers with the same result

  • 0
    maliming created
    Support Team

    hi

    I will check again.

  • 0
    maliming created
    Support Team

    hi ruben.vazquez-chapa

    I checked. The identity server sends and check the token by default.

    abp new qa -t app -u angular --separate-identity-server
    

  • 0
    maliming created
    Support Team

    The issue with that is that the GenerateEmailConfirmationTokenAsync is being called on the api while ConfirmEmailAsync is on the idenentity server so having the same UserManager is not possible accross both.

    I'm curious how this happened.

  • 0
    [email protected] created

    Yes, we implemented a custom registration process and put the registration API in the API project. We are still using the the built in Registration method that is in the Applications project

  • 0
    maliming created
    Support Team

    hi

    I add some test code and it works between two application.

    https://github.com/abpframework/abp/commit/0f13c80056b4159f5a83b50753a7ff7b66ae6e74

  • 0
    [email protected] created

    Looking at your example, we are doing almost the same thing but with two differences

    • We are using the built in AccountAppService class from ABP and calling SendingEmailConfirmationTokenAsync from the API and ConfirmEmailAsync from the Identity Server which calls UserManager.ConfirmEmailAsync
    • Since both instances are separate in our production environment we cannot persist the keys on the local file system

    Is the built in functionality in the AccountAppService class not suitable for this? Do we need to just use our own implementation instead?

  • 0
    maliming created
    Support Team

    Since both instances are separate in our production environment we cannot persist the keys on the local file system

    You can use other provider to save your keys.

    https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-5.0

  • 0
    [email protected] created

    Yes, and as stated in previous messages and seen in our zoom call we are using Redis to save the keys

  • 0
    maliming created
    Support Team

    hi

    Can you check your redis to see if the keys are created?

    I think your Redis may have problem, Because we've confirmed the Data Protection can works between two application.

  • 0
    [email protected] created

    I checked and Redis is not the problem. What is happening is that in ConfirmEmailAsync the line that calls UserManager.ConfirmEmailAsync is not using Redis for the key. When I change it to call UserManager.VerifyUserTokenAsync directly it does. Can you check as to why this is happening? Since this is the IdentityUserManager for the ABP library and the code in question is also from the ABP library I would have expected the using the code given to us would work out of the box.

    This is all in the AccountAppService class from Volo.Abp.Account.Pro.Public.Application project using the Volo.Abp.Account namespace

  • 0
    maliming created
    Support Team

    hi

    Can you check the tokenProvider and purpose?

    https://github.com/dotnet/aspnetcore/blob/52eff90fbcfca39b7eb58baad597df6a99a542b0/src/Security/samples/Identity.ExternalClaims/Pages/Account/ConfirmEmail.cshtml.cs#L34

    https://github.com/dotnet/aspnetcore/blob/af9bb41d623b17eec946029815db5554b73be156/src/Identity/Extensions.Core/src/UserManager.cs#L1495

  • 0
    [email protected] created

    We are using the IdentityUserManager class from the Volo.Abp.Identity identity package so im not sure if I am following you. We have not modified or changed that class so im not sure how to check those unless it is in a setting somewhere.

  • 0
    maliming created
    Support Team

    hi

    Can you share a demo project that can simple reproduce the problem?

    [email protected]

  • 0
    [email protected] created

    I just sent you a demo project to that address

  • 0
    maliming created
    Support Team

    hi I will check asap.

  • 0
    maliming created
    Support Team

    It works for me after SetApplicationName

  • 0
    [email protected] created

    Yes on the demo project it started to work after setting that. On our actual project it still does not work even when adding that in, Im currently trying to see what changes could be causing this.

  • 0
    [email protected] created

    I found our issue, we were modifying a value on the user after sending the confirmation email that was causing the token to be invalid

  • 0
    maliming created
    Support Team

    I think it is because the user's SecurityStamp has changed.

    SecurityStamp: A random value that must change whenever a users credentials change (password changed, login removed)