mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-15 18:25:13 +00:00
fix(api): prevent cross-instance caching of well-known app link files (#12644)
# Which Problems Are Solved The `/.well-known/apple-app-site-association` and `/.well-known/assetlinks.json` responses are instance specific: the body depends on the requested host. The responses did not declare this, so a shared cache whose cache key does not include the host could store one instance's file and serve it on another instance's domain. This could result in a wrong or empty file being returned intermittently, breaking native passkey verification. Dropping the `public` directive (#12634) was not sufficient: depending on its cache mode, a shared cache may store any response carrying a `max-age`. The docs page for native app links also still documented the old `Cache-Control: public` header and only the single `get_login_creds` relation (missing `handle_all_urls`, added in #12634). # How the Problems Are Solved - Send **`Vary: Host`** on both well-known responses, declaring that the body depends on the host: a spec-compliant shared cache must store per-host variants or skip caching. Clients addressing a single host (the Apple and Google verifiers, browsers) are unaffected and keep honoring the configured `max-age`. - Correct the outdated caching reasoning in the code comments. - Guard the header with unit and integration tests. - Update the native app links docs page: both Digital Asset Links relations and the current response headers. # Additional Changes None. `WellKnown.AppLinksCacheControlMaxAge` behavior is unchanged (`0` still results in `no-store`). # Additional Context - Follow-up to #12634; relates to #12497. - Verified with interleaved requests against two instances: responses are no longer reused across domains, and each domain consistently serves its own configuration. - To be cherry-picked to `v4.x` from `main` once merged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
6fdb74593c
commit
c9d747d407
@@ -51,7 +51,7 @@ ZITADEL aggregates association data from **active** OIDC apps on the instance an
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `${CUSTOM_DOMAIN}/.well-known/apple-app-site-association` | iOS Associated Domains / `webcredentials` |
|
||||
| `${CUSTOM_DOMAIN}/.well-known/assetlinks.json` | Android Digital Asset Links for `delegate_permission/common.get_login_creds` |
|
||||
| `${CUSTOM_DOMAIN}/.well-known/assetlinks.json` | Android Digital Asset Links for `delegate_permission/common.handle_all_urls` and `delegate_permission/common.get_login_creds` |
|
||||
|
||||
If no apps are configured, both endpoints still return HTTP `200` with empty collections.
|
||||
|
||||
@@ -75,7 +75,10 @@ Duplicate iOS App IDs across apps are deduplicated.
|
||||
```json
|
||||
[
|
||||
{
|
||||
"relation": ["delegate_permission/common.get_login_creds"],
|
||||
"relation": [
|
||||
"delegate_permission/common.handle_all_urls",
|
||||
"delegate_permission/common.get_login_creds"
|
||||
],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "com.example.one",
|
||||
@@ -89,28 +92,20 @@ Duplicate iOS App IDs across apps are deduplicated.
|
||||
|
||||
Fingerprints are normalized to colon-separated uppercase hex when served.
|
||||
|
||||
Both relations from [Google's Credential Manager prerequisites](https://developer.android.com/identity/credential-manager/prerequisites) are included, as some devices reject passkey creation when `handle_all_urls` is missing.
|
||||
The additional relation only takes effect if your app declares matching intent filters.
|
||||
|
||||
## Caching
|
||||
|
||||
Successful responses include a `Cache-Control` header so intermediate caches may reuse the documents for a short time.
|
||||
|
||||
By default (and in ZITADEL Cloud) caching is allowed for 5 minutes:
|
||||
The contents are specific to your instance domain, so HTTP caching is disabled and the files are always served directly by ZITADEL:
|
||||
|
||||
```
|
||||
Cache-Control: public, max-age=300
|
||||
Cache-Control: no-store
|
||||
```
|
||||
|
||||
Self-hosters can change this with the `ZITADEL_WELLKNOWN_APPLINKSCACHECONTROLMAXAGE` environment variable or in the settings YAML:
|
||||
|
||||
```yaml
|
||||
WellKnown:
|
||||
AppLinksCacheControlMaxAge: 5m
|
||||
```
|
||||
|
||||
Setting the value to `0` results in `Cache-Control: no-store`.
|
||||
|
||||
<Callout type="info">
|
||||
Platform verifiers may cache association files on their side as well.
|
||||
After you change app link settings, allow time for HTTP caches and OS verification to refresh before expecting passkeys to work with the new values.
|
||||
Platform verifiers cache association files on their side.
|
||||
After you change app link settings, allow time for OS verification to refresh before expecting passkeys to work with the new values.
|
||||
</Callout>
|
||||
|
||||
## Verify
|
||||
@@ -124,7 +119,7 @@ Confirm:
|
||||
|
||||
- HTTP status `200`
|
||||
- `Content-Type: application/json`
|
||||
- Expected `Cache-Control` header
|
||||
- `Cache-Control: no-store` header
|
||||
- JSON contents match your configured apps
|
||||
|
||||
## Related
|
||||
|
||||
Reference in New Issue
Block a user