Hello, we are having problems with the `csrf_token...
# talk-kratos
i
Hello, we are having problems with the 
csrf_token
  when working on the registration flow. We are trying to configure our frontend (
Vue
) with Kratos using the quickstart example with Kratos running in docker. We have replaced port 
4455
 in the Kratos config with our frontend port, and changed the registration routes to match our sign-up page. On our registration page we call 
initializeSelfServiceRegistrationFlowForBrowsers
 passing in the 
returnTo
 string and this returns with status 
200
 and gives us the 
csrf_token
 as part of the 
data.ui.nodes
 along with the other form nodes. We can also see the cookie in the Set-Cookie Response Header, however it appears they are different from the ones in the form attributes. Are the cookie and form tokens different because they are values in a pair? When testing the same part of the React example app, we can see that there is a 
Location Header
 in the browser call and the 
csrf_token
 cookie is being set as a cookie when clicking the Sign up button. However, we are not getting the same behaviour in our app. We cannot see a 
Location
 Header, and although we see a 
Set-Cookie
 header, the cookie does not seem to be being set in the browser. Also, when we call
submitSelfServiceRegistrationFlow
passing in the 
csrf_token
 that was initially included in the 
data.ui.nodes
 response from 
initializeSelfServiceRegistrationFlowForBrowsers
 we get a 
403
 error:
Copy code
"The HTTP Cookie Header was set and a CSRF token was sent but they do not match. We recommend deleting all cookies for this domain and retrying the flow."
How can we make sure that the tokens match? Our config:
Copy code
version: v0.7.1-alpha.1

dsn: memory

serve:
  public:
    base_url: <http://127.0.0.1:4433/>
    cors:
      enabled: true
  admin:
    base_url: <http://kratos:4434/>

selfservice:
  default_browser_return_url: <http://127.0.0.1:8084/>
  whitelisted_return_urls:
    - <http://127.0.0.1:8084>

  methods:
    password:
      enabled: true

  flows:
    error:
      ui_url: <http://127.0.0.1:8084/error>

    settings:
      ui_url: <http://127.0.0.1:8084/settings>
      privileged_session_max_age: 15m

    recovery:
      enabled: true
      ui_url: <http://127.0.0.1:8084/recovery>

    verification:
      enabled: true
      ui_url: <http://127.0.0.1:8084/verification>
      after:
        default_browser_return_url: <http://127.0.0.1:8084/>

    logout:
      after:
        default_browser_return_url: <http://127.0.0.1:8084/login>

    login:
      ui_url: <http://127.0.0.1:8084/login>
      lifespan: 10m

    registration:
      lifespan: 10m
      ui_url: <http://127.0.0.1:8084/sign-up>
      after:
        password:
          hooks:
            -
              hook: session

log:
  level: debug
  format: text
  leak_sensitive_values: true

secrets:
  cookie:
    - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
  cipher:
    - 32-LONG-SECRET-NOT-SECURE-AT-ALL

ciphers:
  algorithm: xchacha20-poly1305

hashers:
  argon2:
    parallelism: 1
    memory: 128MB
    iterations: 2
    salt_length: 16
    key_length: 16

identity:
  default_schema_url: file:///etc/config/kratos/identity.schema.json

courier:
  smtp:
    connection_uri: <smtps://test:test@mailslurper:1025/?skip_ssl_verify=true>
l
Hi @User We’ve been there and I can tell you it is quite annoying. If I understand you correctly you are calling API to initialize the flow through backend? This can lead to CSRF issues since it is quite easy to forget to add CSRF token when doing backend/frontend calls interchangeably. The easiest would be to redirect browser
/selfservice/registration/browser
and only add
return_to
query param to the URL. This way you will get
Set-Cookie
with CSRF tokens on the first redirect and you would need to add cookies to all your backend calls as well.
Theoretically you could do this via backend but it is easy to mess things up with redirects and store/pass wrong cookie to the browser causing hard to debug issues. Let the browser do it’s thing and user browser flows and you should be OK.
i
Thanks! We are calling
initializeSelfServiceRegistrationFlowForBrowsers
and
submitSelfServiceRegistrationFlow
in our Vue frontend. Similar to the React example here https://github.com/ory/kratos-selfservice-ui-react-nextjs/blob/master/pages/registration.tsx
l
Well I’m not exactly frontend expert 😉 I would check if you don’t have some requests that are being made and if they have cookies passed so Kratos will not re-generate CSRF tokens for the flow. I think there should be
/selfservice/registration/browser
call somewhere (this is what you are seeing in the sample React app?) which should set all the cookies you need.
so I suspect there might be some discrepancies with cookies passed in
submitSelfServiceRegistrationFlow
and
getSelfServiceRegistrationFlow
(which you are calling as well?)
j
You have to call getSelfServiceRegistrationFlow and use the output to render a form https://www.ory.sh/kratos/docs/concepts/ui-user-interface/
f
The call to
getSelfServiceRegistratinFlow
is the part that is failing for us. We initially check if a flow ID is set, if it is not set we redirect the browser to http://127.0.0.1:4433/self-service/registration/browser to get a flow ID and a cookie. Once we get redirected back to our sign-in page, the cookie is successfully being set in the browser and we now have a flow ID. If we pass this flow ID into
getSelfServiceRegistratinFlow
we get the following error: “The HTTP Cookie Header was set and a CSRF token was sent but they do not match. We recommend deleting all cookies for this domain and retrying the flow.” We’re simply passing in the flow ID to
getSelfServiceRegistratinFlow
. There are parameters to pass a cookies into this function, but we aren’t doing that yet as we don’t seem to have access to them in our Vue code using
Vue.$cookies
so we’re at a bit of a loss.
l
So I believe that is the problem since this method check for CSRF cookies that were sent in
/selfservice/registration/browser
.
e
Did you use the includeCredentials option in axios options? So the returning cookie is actual saved and send by the browser? Like
Copy code
const response = await this.sdk.initializeSelfServiceRegistrationFlowForBrowsers(undefined, {
        withCredentials: true,
      });
You have to add it for every sdk function. Or in the base options when you create a new instance of the SDK.
i
Ah ok, we've tried adding the
includeCredentials
option but keep getting CORS errors 😞 so without the the
includeCredentials
option it's not going to work at all with the cookie?
e
I had made a frontend app myself with Vue. Then I ran into the problem with includeCredentials. Because of this, I was always unauthenticated. I think I also ran into a CORS problem. I can share my settings for CORS in the config. Locally I run the Kratos api on port 8081. My Vue frontend runs on port 8080. With the following settings I don't get an error: (I don't think everything in this config is needed. But I'm also still in an orientation phase. So everything is not perfectly configured yet.)
Copy code
serve:
  public:
    base_url: <http://localhost:8081>
    cors:
      enabled: true
      allowed_origins:
        - <http://localhost>
        - <http://localhost:8080>
      allowed_methods:
        - POST
        - GET
        - PUT
        - PATCH
        - DELETE
      allowed_headers:
        - Authorization
        - Cookie
        - Content-Type
      exposed_headers:
        - Content-Type
        - Set-Cookie
To answer your question, withCredentials is an option of Axios (NPM module which is used for http requests in the SDK). This blocks cookies when the option is not set to true. Especially when it comes to cross-site Access-Control requests. https://axios-http.com/docs/req_config
i
@User thank you so much! Updating the config solved the CORS issue for us 🎉 We tried adding
allowed_origins
in the config but I think we had missed the first entry without the port.
e
👍 😀
f
We haven’t confirmed this yet, but for people searching through the Slack for answers, we believe it was specifically ensuring we have the URL without the port in our configs. Originally, we tried just
<http://127.0.0.1:8084>
which gave us CORS errors. Adding
<http://127.0.0.1>
as well seemed to have fixed our CORS errors, which meant we could use
includeCredentials
without getting CORS errors!
Thanks a lot Tom!