Asp.net Core 2 Identity with IdentityServer4 and email confirmation: Correlation failed. cookie not found

Viewed 2471

I'm using IdetityServer with asp.net core identity. The issue is that i want a user to confirm his email after registration. To implement that i used guide from asp.net core website, so it's pretty much standard.

So user receives email with a link, which points to ItentityServer. After user clicks a link IdentityServer verifies token, finalizes registration and asks user to log in. And here, after the login, user is redirected to actual website, where he gets the error.

As i understand, website with oidc middleware expects special correlation cookies from IdentityServer which are obviously missed from the response since it initially came from confirmation email..

Maybe anyone faced with such case?

warn: Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler[15]

  '.AspNetCore.Correlation.OpenIdConnect.3jB4rPx9WvoggXG4jjvHMcvub3BxPBU_tQN
zGyIH9KM' cookie not found.
info: 
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler[4]
  Error from RemoteAuthentication: Correlation failed..
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[0]
  An unhandled exception has occurred while executing the request
System.Exception: Correlation failed.
at Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.
<HandleR
equestAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at 
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNot
ification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.
<Invoke>d__6.
MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at 
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNot
ification(Task task)
at Microsoft.AspNetCore.SpaServices.Webpack.ConditionalProxyMiddleware.
<Invoke>d__6.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at 
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNot
ification(Task task)
at Microsoft.AspNetCore.SpaServices.Webpack.ConditionalProxyMiddleware.
<Invoke>d__6.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at 
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNot
ification(Task task)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.
<Invoke>
d__7.MoveNext()
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
  Request finished in 265.0674ms 500 text/html; charset=utf-8
1 Answers

Rewritten to minimize client-app dependencies:

As noted below, OIDC requires that the client app initiates the login flow. Apart from defining your auth scheme in Startup.cs, the bare-minimum involvement with auth is going to be [Authorize] attributes on your controllers or handlers, but with a little extra data, you can leave the verification up to Identity Server.

I'm actually doing the same thing on a project I'm working on, to a point. First, I make the client app display a single link reading Register or Login. This way it's logical for users to start an OIDC flow to Identity Server regardless of whether they have an account yet.

The reason you want to register in the context of an OIDC login flow is that Identity Server is designed to service many clients. When your user starts a new session by clicking the validation link in email, that new session needs to know which client to contact upon successful validation. The OIDC context that Identity Server normally relies upon simply doesn't exist, so that's what you need to work around.

So, registering within the context of an OIDC flow from a known client, at the same time you generate the email-verification token, also store a redirect URL pointing back to an [Authorize]-protected URL on the client. Store the URL to the database alongside the new verification token. (For a completely "clean" implementation, you can add it to the Properties dictionary of the Identity Server Client in the ClientStore, and look it up at runtime by injecting that and the IdentityServerInteractionService. There are good examples of using that in the Identity Server Quickstart UI).

When the user clicks the link (new session) and Identity Server verifies the token, send the user to the redirect URL stored in that same record earlier. When the user's browser requests the page from your client app, your client's auth middleware will respond to the [Authorize] attribute by automatically starting a new OIDC login sequence, sending the user back to Identity Server for login.

Depending on how you set up the cookie expiration and whether you let the user's account remain logged in while verifying, this flow may result in transparent login (no UI presented). But if you're using the default session-expiration cookies, or if the login actually expired, or a few other edge-cases arise, the user will be presented with a regular login request. (With even more work, you could guarantee a transparent login for local accounts, but not for third-party auth.)

Related