goauthentik / goauthentik/authentik

Support WebAuthn Related Origin Requests: one RP ID shared across brands

Open
#25,541 0 comments 0 reactions 0 assignees View on GitHub
enhancement/confirmed pr_wanted
Dominant language
Python
Stars
25.6k
Forks
2k
Avg merge
1d 2h
Merged PRs (30d)
651

Description

### Is your feature request related to a problem?

A single authentik instance with brands on different registrable domains (`auth.example.com`, `auth.example.net`) cannot offer passkeys that work across brands: authentik derives the WebAuthn RP ID unconditionally from the request host (`get_rp_id()` in `authentik/stages/authenticator_webauthn/utils.py`), so every brand is its own credential silo and there is no configuration to change that.

WebAuthn Level 3 solves this with Related Origin Requests: one RP ID serves several origins, authorized via `https:///.well-known/webauthn`. Supported in Chrome/Edge 128+, Safari 18+, Firefox 152+. authentik implements no part of it.

Overlaps with #24791 (configurable expected origins for native apps — the origins list below would give those a home).

### Describe the solution you'd like

A `WebAuthnRPConfig` object, referenced by brands via dropdown. Brand-level because the RP ID is a domain property and brands already own the domain; explicit origins because `Brand.domain` is a suffix matcher, not an origin, and the default brand matches every host.

```text
WebAuthnRPConfig
name = "shared-passkeys"
rp_id = "auth.example.com" # unique across configs, immutable after creation
origins = ["https://auth.example.com", "https://auth.example.net", "android:apk-key-hash:..."]

Brand A/B -> rp_config = shared-passkeys
Brand C -> rp_config = null # today's behavior, feature is opt-in
```

- Both stages send the config's `rp_id` instead of the request host; verification passes it as `expected_rp_id` and the origins list as `expected_origin` (py_webauthn already accepts a list).
- authentik serves `GET /.well-known/webauthn` when the request host equals a config's `rp_id`, returning its HTTPS origins. This works only when the `rp_id` host routes to authentik; an RP ID on a domain authentik does not serve requires hosting the document externally (the format is public).
- Origin entries are format-validated on save; every listed origin can run ceremonies for the shared RP ID, so the list is exact — no wildcards.
- Brands without an RP config keep today's behavior exactly (RP ID and expected origin from the request) — the feature is fully opt-in, no change for existing deployments.

**The one sharp edge: attaching an existing brand to a shared config strands that brand's existing passkeys** (they are bound to the old request-host RP ID — WebAuthn, not implementation). Passkey-only users are locked out with no self-service recovery. Adoption therefore needs a pre-flight that blocks while users exist whose only factor would become unusable, and docs that require a fallback factor — also because non-ROR clients fail hard (`SecurityError`) and some passkey providers (e.g. 1Password) don't support ROR yet.

### Describe alternatives that you've considered

- Consolidating logins onto one domain: defeats the purpose of brands.
- Serving the well-known externally via ingress: insufficient — authentik still sends the request-host RP ID, and verification rejects foreign origins (the authenticator signs over the RP ID hash).
- Deriving origins from brand domains: unsound — suffix matchers, no scheme/port, default brand matches everything.

### Additional context

Spec: https://www.w3.org/TR/webauthn-3/#sctn-related-origins · Guide: https://passkeys.dev/docs/advanced/related-origins/

Happy to implement this and open a PR if the direction is acceptable.

Supersedes #25395 (opened via API, which drops the template labels; resubmitted through the form so it lands in triage).

Contributor guide

Open the contributing guide

Research direction

Start with get_rp_id() in authentik/stages/authenticator_webauthn/utils.py, then read the WebAuthn Related Origin Requests specification and the passkeys.dev guide. The requested result includes opt-in brand-linked RP configuration, RP ID and origin validation, the /.well-known/webauthn response, ceremony verification changes, and safeguards for existing passkeys and unsupported clients.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
authentication, backend-api-design, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.