Open Closed

Email Confirmation Token is always Invalid #1400


User avatar
0
  • 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.


32 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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");
    }
    
  • User Avatar
    0

    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.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

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

  • User Avatar
    0

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

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

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

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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)
    
  • User Avatar
    0

    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.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    OK, I will investigate this.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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

  • User Avatar
    0

    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"); 
    } 
    
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

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

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

  • User Avatar
    0

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

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    I will check again.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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
    

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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.

  • User Avatar
    0

    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

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

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

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

  • User Avatar
    0

    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?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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

  • User Avatar
    0

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

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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.

  • User Avatar
    0

    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

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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

  • User Avatar
    0

    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.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

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

    liming.ma@volosoft.com

Made with ❤️ on ABP v9.1.0-preview. Updated on November 11, 2024, 11:11