DuendeSoftware / Support

Support for Duende Software products
20 stars 0 forks source link

How can I end an authentication session in Identity Server if PKCE fails after returning from Identity Server? #1302

Closed norr-carr closed 3 weeks ago

norr-carr commented 3 weeks ago

Which version of Duende IdentityServer are you using? 7.0.4

Which version of .NET are you using? .NET 8

We are the only consumer of our own Identity Server, using OpenID Connect to authenticate. We use External Cookie Authentication for Identity Server. Our Relying Party uses an intermediate cookie scheme for persisting logins. The RP implements PKCE. In some edge cases the PKCE exchange can fail after authentication due to our RP forgetting the Code Verifier or Nonce values. This puts us in a state we'd like to avoid: Identity Server has issued an ID token to the RP and saved external cookies for itself, but the RP has not been able to validate the ID tokens. It seems to me that there are only two options for what to do in this situation:

  1. Go to Identity Server's End Session endpoint and try to log out the user. Unfortunately since the PKCE exchange failed, the RP does not have an ID token to complete the Logout process.
  2. Have the RP challenge the Identity Server again. It seems like this would defeat the point of PKCE validation, since the validation failed for legitimate reasons.

Is there any third option, where we could let Identity Server know that the authentication failed and the ID token should be revoked?

norr-carr commented 3 weeks ago

I have been able to reproduce the issue using an un-edited copy of the Samples/various/clients/Owin example.

  1. I started both the IdentityServerHost and MVC projects.
  2. In Browser 1, I go to localhost:44301 and am redirected to localhost:5001/account/login?[...].
  3. I enter alice/alice and am signed in as Alice, as indicated by the "sub: 1" claim on the localhost:44301/ home page.
  4. In Browser 2, I go to localhost:44301 and am redirected to localhost:5001/account/login?[...].
  5. I copy the entire account/login URL from Browser 2 into Browser 1.
  6. In Browser 1 I enter bob/bob and click "login".
  7. I get an exception on localhost:44301 saying "IDX21323: RequireNonce is '[PII is hidden]'. OpenIdConnectProtocolValidationContext.Nonce was null, OpenIdConnectProtocol.ValidatedIdToken.Payload.Nonce was not null. The nonce cannot be validated."
  8. If I navigate to localhost:5001 I can see that I'm logged in as "bob" in the system bar.
  9. If I navigate to localhost:44301 I can see that I'm still logged in as "alice" because "sub" claim is still "1"

So what can I do to recover from this scenario?

josephdecock commented 3 weeks ago
2. Have the RP challenge the Identity Server again.  It seems like this would defeat the point of PKCE validation, since the validation failed for legitimate reasons.

I think this is actually what you should do. Issuing a new challenge is safe, and doesn't undermine PKCE, because the goal of PKCE is to bind the front and back-channel requests that the client makes. If intermittently, the verifier or nonce are lost, the correct thing to do usually is to try again, with new verifier and nonce values.

Often the nonce and code verifier are stored in cookies with finite lifetimes (ASP.NET uses 10 minutes by default), so a user might cause this error by starting the login process but then not finishing quickly enough.

In that case, a new challenge will often get the user logged in quickly. If they have a session cookie at IdentityServer, this will behave like a single sign on experience (the user won't have to re-enter credentials, there will just be some redirecting).

I would however prompt the user with something like, "An error occurred during login - would you like to try again?" If for some reason, the pkce and or nonce cookies are unavailable, challenging blindly could cause an infinite loop of redirects.

It's also possible to cause a similar error if the user makes bookmarks or uses their browser history to go back to the login page. It's possible that the user is logged in at the client application, but then (via bookmark) goes to the login page at identity server. They then enter their credentials and get sent back to the signin callback, which won't have a nonce or code verifier. This is a confusing situation for the user - they might have a session at the client app for alice, but have intended to switch accounts and have a session at the identity provider for bob. So, when you're handling this error at the client application, you should verify that the current request into the client application actually was annonymous. If it is, then just prompting the user to try again is sensible. If the user has a session in the client app though, your "try again" page should highlight this situation to the user - "An error occurred during the login process, but you are signed in as Joe. Would you like to sign out and try again?". So we're pointing out very specifically that the current session exists, and now the try again button will also sign out of the client application and IdentityServer sessions.

norr-carr commented 3 weeks ago

Thanks Joe, I think that solves our problem!