goauthentik / goauthentik/authentik
CSRF Failed: "X-Authentik-Csrf HTTP header has incorrect length" recurring across versions and flows (password change, token creation, WebAuthn setup)
- Dominant language
- Python
- Stars
- 25.6k
- Forks
- 2k
- Avg merge
- 1d 4h
- Merged PRs (30d)
- 634
Description
### Describe the bug
This is a recurrence of the class of bug described in #8704 and #10479: the `X-Authentik-Csrf` header sent by the frontend does not match what Django's CSRF middleware expects, even though a valid `authentik_csrf` cookie is present. Unlike those closed issues, I've reproduced it consistently across three different flows on a current version (2026.2.7), and ruled out the most common cause (Origin/Host header misconfiguration) documented in the troubleshooting guide.
**Environment**
- authentik version: 2026.2.7 (also reproduced on 2026.2.2 before upgrading, specifically to test whether a newer release fixed it - it did not)
- Deployment: docker-compose (server + worker + postgresql + redis)
- Reverse proxy: nginx-based (BunkerWeb), terminating TLS and forwarding to the embedded outpost
**What I ruled out**
Per the [CSRF troubleshooting guide](https://docs.goauthentik.io/troubleshooting/csrf/), the usual cause is an incorrect `Origin` or `Host` header from the reverse proxy. I checked this via `GET /api/v3/admin/system/`, which reports:
```json
"http_headers": { "HTTP_HOST": "authentik.example.com", ... },
"http_host": "authentik.example.com"
```
No port, matches the configured domain exactly. The proxy config also does not rewrite `Origin` and correctly forwards `Host`/`X-Forwarded-*` headers. So this doesn't look like the Origin/Host mismatch the docs describe - the error text is also different in nature ("incorrect length" is a token-value check, not an origin check).
**Actual behavior**
Server log for each failing request shows:
```json
{"event": "PermissionDenied(\"CSRF Failed: CSRF token from the 'X-Authentik-Csrf' HTTP header has incorrect length.\")", "flow_slug": "default-password-change", "logger": "authentik.flows.views.executor", "level": "warning", "auth_via": "session"}
```
**Expected behavior**
The frontend should send a correctly-formatted `X-Authentik-Csrf` header (mirroring the `authentik_csrf` cookie), and these standard account-management actions should succeed.
**Workaround**
Using a Bearer-token-authenticated API request (not cookie/session-based) to the same underlying endpoints (e.g. `POST /api/v3/core/users/{id}/set_password/`) works fine and is unaffected, since token auth doesn't go through the CSRF-cookie code path. This is what we're using as a workaround, but it requires bypassing the web UI entirely for these actions.
**Related**
- #8704 - same symptom ("X-Authentik-Csrf header is empty"), 2023.10/2024.2, closed as bug
- #10479 - same exact error text, 2024.6.1, different flow (WebAuthn setup), closed as wontfix
Given this keeps recurring across ~3 years of releases and multiple unrelated flows, it seems like a persistent fragility in how the frontend reads/mirrors the CSRF cookie into that header, rather than an environment-specific misconfiguration. Happy to provide more diagnostics if useful (HAR capture, etc.).
### How to reproduce
Reproduced with a completely fresh browser session (ruling out stale cookies/session) on all of the following, each performed while authenticated via a normal cookie/session (not API token):
1. Self-service password change: /if/user/ -> Update your password -> Change password -> submit new password. Flow executor POST to /api/v3/flows/executor/default-password-change/ returns HTTP 200 with {"component": "ak-stage-flow-error", "request_id": "..."}.
2. Admin "Set password" on another user's detail page in /if/admin/.
3. Creating a new API token via /if/user/#/settings;{"page":"page-tokens"} -> New Token -> fill in identifier -> Create Token -> "There was an error submitting the form. Response returned an error code."
All three fail the same way, in the same session, for the same user.
### Expected behavior
The frontend should send a correctly-formatted X-Authentik-Csrf header (mirroring the authentik_csrf cookie), and these standard account-management actions (changing your own password, an admin setting another user's password, creating an API token) should succeed instead of failing with a CSRF error.
### Screenshots
_No response_
### Additional context
_No response_
### Deployment Method
Docker
### Version
2026.2.7 (also reproduced on 2026.2.2)
### Relevant log output
```shell
{"event": "PermissionDenied(\"CSRF Failed: CSRF token from the 'X-Authentik-Csrf' HTTP header has incorrect length.\")", "flow_slug": "default-password-change", "logger": "authentik.flows.views.executor", "level": "warning", "auth_via": "session"}
```
Contributor guide
Research direction
Start with the CSRF troubleshooting guide and the flow executor endpoint `/api/v3/flows/executor/default-password-change/`, then trace how the frontend mirrors the `authentik_csrf` cookie into `X-Authentik-Csrf`. Compare the session-authenticated requests for password changes, API token creation, and WebAuthn setup; done means these flows send a correctly sized token and succeed without the CSRF error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- django, python
- Domain
- authentication, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100