goauthentik / goauthentik/authentik
Email stage: 'Pretend user exists' placeholder only handled in recovery flows – passwordless authentication flow crashes with ValueError for unknown identifiers (docs say it works generally)
- Dominant language
- Python
- Stars
- 25.6k
- Forks
- 2k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 644
Description
**Describe the bug**
The Email stage documentation says that with **Pretend user exists** enabled on the Identification stage, the Email stage "shows the same *Email sent.* challenge for this placeholder user without sending an email" (to avoid user enumeration). In practice this only works when the flow's **designation is `recovery`**. In an `authentication` flow (passwordless "magic link" login: Identification → Email → User Login) an unknown identifier crashes the flow with a system exception:
```
ValueError: save() prohibited to prevent data loss due to unsaved related object 'user'.
authentik/stages/email/stage.py:160 get
authentik/stages/email/stage.py:116 send_email
authentik/stages/email/stage.py:89 get_token -> FlowToken.objects.create(user=)
```
The user sees "Something went wrong. Please try again later." with a request ID.
Cause (`authentik/stages/email/stage.py`, `send_email`):
```python
if not pending_user.pk and self.executor.flow.designation == FlowDesignation.RECOVERY:
# pretend to send ...
```
The check is limited to `RECOVERY`; for every other designation the placeholder user reaches `get_token()`.
**To Reproduce**
1. Flow, designation `authentication`, stages in order: Identification (user_fields `[email]`, `pretend_user_exists: true`, no password stage) → Email (use global settings) → User Login.
2. Set it as the brand's authentication flow.
3. Enter an email address that does not exist → system exception as above.
4. Same flow with designation `recovery` → "Check your inbox" page, no email sent (expected behaviour).
5. Same flow with `pretend_user_exists: false` → "Failed to authenticate" (works, but enumerates users).
**Expected behavior**
Either the Email stage pretends to send for placeholder users in any flow designation (as the docs describe), or the docs state the `recovery`-only limitation and the Identification stage / Email stage reject the unsupported combination instead of raising a 500.
**Version and Deployment**
- authentik version: 2026.5.6 (docker-compose); same code path present in 2026.8.1
- Deployment: docker-compose
**Additional context**
Workaround in use: setting the login flow's designation to `recovery` while keeping it bound as `flow_authentication` on the brand. Works, but is obviously a misuse of the designation field.
Docs reference: Email stage page ("Pretend user exists" paragraph) and Identification stage page ("stages like the Password stage and Email stage are aware of this pretend user").
Contributor guide
Research direction
Start with authentik/stages/email/stage.py, especially send_email and get_token, and reproduce the authentication flow described in the issue with an unknown identifier. Compare this behavior with the recovery flow and review the Email and Identification stage documentation. Done means the unsupported combination no longer raises a system exception and the implementation or docs clearly match the supported behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- authentication, documentation
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 66/100