ory / kratos

The most scalable and customizable identity server on the market. Replace your Homegrown, Auth0, Okta, Firebase with better UX and DX. Has all the tablestakes: Passkeys, Social Sign In, Multi-Factor Auth, SMS, SAML, TOTP, and more. Written in Go, cloud native, headless, API-first. Available as a service on Ory Network and for self-hosters.
https://www.ory.sh/?utm_source=github&utm_medium=banner&utm_campaign=kratos
Apache License 2.0
11.24k stars 963 forks source link

CSRF cookie is invalid after IODC callback if provider does not provide all traits #2561

Closed akkie closed 2 years ago

akkie commented 2 years ago

Preflight checklist

Describe the bug

If an OIDC provider doesn't provide all traits, then the CSRF token is invalid after the redirect to the registration UI.

I currently use the Google OIDC provider which is configured to return only the email address. My identity is configured to use the email and also a username. Based on the documentation, the callback endpoint of Kratos should redirect to the registration endpoint where the user can enter the missing information. The problem is that after the redirect, the error message "Please retry the flow and optionally clear your cookies. The request was rejected to protect you from Cross-Site-Request-Forgery (CSRF) which could cause account takeover, leaking personal information, and other serious security issues." will be shown if I try to load the flow for the passed flow ID.

Detailed description

Google OIDC endpoint

The google OIDC endpoint redirects the user to the Kratos callback endpoint:

https://project.app/kratos/self-service/methods/oidc/callback/google?state=...&code=...&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&authuser=0&prompt=none

Kratos callback endpoint

The Kratos callback endpoint redirect to the registration UI.

Request

Request URL: https://project.app/kratos/self-service/methods/oidc/callback/google?state=...&code=...&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&authuser=0&prompt=none

Cookies: cookie: csrf_token_3646c96d01ef8d75739130370498e7e1cfac60d1a5e56eff1ac3333af6c84d59=BcjYv6RjV%2FbQF%2F%2BGRSlPTiHFJgkQbas%2BWfvg4Pivrrg%3D; ory_kratos_continuity=MTY1Njg3OTE4NHxEdi1CQkFFQ180SUFBUkFCRUFBQVhfLUNBQUVHYzNSeWFXNW5EQ01BSVc5eWVWOXJjbUYwYjNOZmIybGtZMTloZFhSb1gyTnZaR1ZmYzJWemMybHZiZ1p6ZEhKcGJtY01KZ0FrTW1aa05EWTFOemt0WkdabU1TMDBabVl3TFRrM1l6UXRaalZoTVRBeFpXTTFaVGhqfGht_cKbbs7gD4joS3ShPqvd6tH0Cl69UorEz1ib5yIa

Response

location: https://project.app/auth/signup/email?flow=bfe844a0-d1ed-4fc4-b302-e1a0e1824474

set-cookie: csrf_token_3646c96d01ef8d75739130370498e7e1cfac60d1a5e56eff1ac3333af6c84d59=PPtnH6F51kqubGQknPXML3mVHZg1k+nV/nkJk7njYHo=; Path=/; Domain=project.app; Max-Age=31536000; HttpOnly; Secure; SameSite=Lax set-cookie: ory_kratos_continuity=MTY1Njg3OTE4NXxEdi1CQkFFQ180SUFBUkFCRUFBQUJQLUNBQUE9fIboBpPkmxNpDQvBUiyh8QNB50nEjMco7HbyjDSbRDA-; Path=/; Expires=Tue, 02 Aug 2022 20:13:05 GMT; Max-Age=2592000; HttpOnly; Secure; SameSite=Lax

Registration UI endpoint

After the redirect it's not possible to load the flow for the given flow ID, because it's ends up with the CSRF error message. If I change the actual CSRF cookie which was send in the response from the callback endpoint to the cookie that was send to the callback endpoint, then all works as expected and I can load the flow. It looks like that the callback endpoint will override my correct CSRF cookie with a new one this isn't valid for the flow.

Request

cookie: csrf_token_3646c96d01ef8d75739130370498e7e1cfac60d1a5e56eff1ac3333af6c84d59=PPtnH6F51kqubGQknPXML3mVHZg1k+nV/nkJk7njYHo=; ory_kratos_continuity=MTY1Njg3OTE4NXxEdi1CQkFFQ180SUFBUkFCRUFBQUJQLUNBQUE9fIboBpPkmxNpDQvBUiyh8QNB50nEjMco7HbyjDSbRDA-

Please let me know if you need additional information

Reproducing the bug

Please see description above

Relevant log output

No response

Relevant configuration

No response

Version

0.10.1

On which operating system are you observing this issue?

macOS

In which environment are you deploying?

Docker Compose

Additional Context

No response

aeneasr commented 2 years ago

Thank you for the report! We do have e2e tests for this flow so it is strange that it fails for you. How did you implement your registratino screen?

akkie commented 2 years ago

Thanks for your answer. I do not use the UI parts that Kratos provides. I use the SDK and provide my own endpoints to abstract the Kratos API. The flow is as follow:

I have two registration screens:

On the Index screen I start a new registration flow. If a user clicks on the button I call submitSelfServiceRegistrationFlow. I check for status code 422 and return the redirect URL to my UI which then redirects to the OIDC provider. Then after the OIDC flow is finished, Kratos will redirect to the Email screen which then fails, because I try to get the flow for the given flow ID.

The cookies are all correctly send in both directions from the API to the UI and vice versa. It also works if I remove the username claim as example. Then the user is correctly logged in over the OIDC provider.

Do you see anything I am doing wrong that could be causing the current issue?

aeneasr commented 2 years ago

Hm, it's possible that there is a race condition in the network requests which causes the problem. Please check your network tab to ensure that there are no concurrent requests ending at Ory Kratos :) Also make sure to use the credentials: include option in the SDK (see https://www.ory.sh/docs/guides/local-development)

akkie commented 2 years ago

OK, I will check that in detail. To understand it correctly, when will the CSRF token exactly be set in this flow? Is it set only once or is it set multiple times with different values?

aeneasr commented 2 years ago

The CSRF cookie is set if the request does not have a CSRF cookie. What can happen is that you have two requests, both without cookie, that execute at the same time. Then, you end up with two CSRF cookie values but only one value is actually valid.

akkie commented 2 years ago

I found the issue. My API has URL encoded the CSRF cookie. As a consequence, the callback endpoint couldn't read it and created a new one.