Guide
Diagnosing Ory Kratos Self‑Service Flow Errors: CSRF, Expiry, and Redirect Loops
Guide to diagnosing CSRF, expiry, and redirect loop errors in Ory Kratos self‑service browser flows.
Published by Tasadduq Burney
29 Oct 2025, 12:26 UTC
4 min64.5K views0

Recognizable condition
When integrating a browser‑based frontend with Ory Kratos self‑service flows you may see one of the following HTTP responses while trying to load or submit a flow:
- 403 Forbidden with body 'CSRF token missing or incorrect'
- 410 Gone when fetching a flow URL
- Endless redirect loop between your app and Kratos
- 401 Unauthorized from /sessions/whoami after a seemingly successful login
Cause / diagnostic table
| Symptom | Likely cause | Quick check |
|---|---|---|
| 403 CSRF missing/incorrect | Browser did not send Kratos anti‑CSRF cookie (often cross‑origin request without credentials, SameSite mismatch, or calling API endpoint from browser) | Open DevTools → Application → Cookies for the Kratos domain; verify that a cookie named ory_kratos_csrf_token_* is present and sent with the request. |
| 410 Gone on flow fetch | Flow ID has expired or already been used (default lifespan is a few minutes) | Note the timestamp when you first requested the flow; wait > flow lifespan (check kratos config) and retry the same URL – you should see 410. |
| Redirect loop | Mismatched return URLs in kratos.yaml (selfservice.default_browser_return_url, flows..ui_url) pointing at each other or at a route that re‑starts the flow unconditionally | Compare the URLs you see in the browser address bar with the values in kratos.yaml; look for a pattern where the final URL equals the initial flow initiation URL. |
| 401 from /sessions/whoami | No valid session cookie or X‑Session‑Token after login; often caused by using browser flow endpoints from a non‑browser client or losing the session cookie on redirect | Make a request to /sessions/whoami with the cookie you received after login; check response code. |
Ordered checks
- Confirm the exact Kratos version (
kratos versionor inspect/health/readyJSON). Use the version‑specific API reference for endpoint paths. - Initiate a flow via browser:
GET https://kratos.example.com/self-service/login/browser. Observe the Set‑Cookie header for the anti‑CSRF cookie and the redirect location. - Follow the redirect (preserve cookies). If you are using fetch/XHR, set
credentials: 'include'(orwithCredentials: truefor XMLHttpRequest). - On the redirected page, inspect the network request that fetches the flow UI (usually a GET to the same URL you were redirected to). Verify that the cookie from step 2 is sent in the Cookie header.
- If you receive 403, go to step 2 and ensure the cookie is present and sent; check SameSite and Secure attributes match the scheme (e.g., Secure only on HTTPS).
- If you receive 410, note the flow ID from the URL; discard it and start a new flow (repeat step 2). Do not retry the same ID.
- If you see a redirect loop, capture the sequence of URLs. Compare each to
selfservice.default_browser_return_urland the specific flow’sui_urlin kratos.yaml. Adjust them so they point to your frontend routes that do not re‑initiate the flow. - After a successful login, call
GET https://kratos.example.com/sessions/whoamiwith the session cookie. Expect 200 and a JSON payload containing the identity. A 401 indicates the session was not created; check that you used the browser endpoint (/self-service/login/browser) and not the API endpoint.
Fixes tied to findings
- CSRF missing – Enable credentials on cross‑origin requests, ensure the cookie’s SameSite is set to
LaxorNone(with Secure) matching your frontend’s scheme, and never call/self-service//apifrom a browser. - Flow expired (410) – Treat any 410 as a signal to discard the flow ID and request a fresh flow. Optionally increase
selfservice.flow.lifespanin kratos.yaml if your UI needs more time, but keep it short for security. - Redirect loop – Set
selfservice.default_browser_return_urlto a route in your app that shows the logged‑in state (e.g.,https://app.example.com/dashboard) and ensure each flow’sui_urlpoints to a page that only renders the flow UI, not a page that immediately calls/self-service//browseragain. - Missing session (401) – Verify you are using the browser flow endpoints; after login, preserve the session cookie (named
ory_kratos_sessionby default) and send it on subsequent requests. If you are building a mobile or CLI client, switch to the/self-service//apiendpoints and use bearer tokens.
Escalation criteria
Proceed to deeper investigation or contact Ory support when:
- You have verified the cookie is set and sent correctly but still receive 403 after confirming SameSite/Secure match and credentials are included.
- Adjusting return URLs does not break the loop and the Kratos logs show repeated
flow: expiredorflow: usedmessages with timestamps that do not align with your UI timing. - The /sessions/whoami endpoint returns 401 even when the session cookie is present and unaltered, suggesting a secret‑key mismatch or token‑validation failure.
- You observe non‑standard error bodies (e.g., HTML error pages) that indicate a reverse proxy or ingress is interfering with Kratos responses.
In these cases, collect:
- Kratos version and full kratos.yaml (redact secrets).
- Browser HAR file or DevTools network log showing cookie headers and redirect chain.
- Relevant lines from Kratos stdout/stderr around the time of the failure.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.