mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
feat: native app links for passkeys (#12580)
# Which Problems Are Solved - Native iOS/Android passkeys need OS trust files on the ZITADEL domain; without them, Associated Domains / App Links verification fails. - Operators could not configure iOS Team ID + Bundle ID or Android package name + SHA-256 fingerprints on OIDC apps. - `/.well-known/apple-app-site-association` and `/.well-known/assetlinks.json` were not served from application config. # How the Problems Are Solved - Add iOS/Android app-link fields on OIDC app create/update (Application API v2 + Management), with validation. - Persist and project those fields; query active app-link configs instance-wide. - Serve AASA (`webcredentials`) and Digital Asset Links (`get_login_creds`) from well-known paths, with configurable `Cache-Control` and fingerprint normalization at serve time. - Console UI to edit the fields, with links to the well-known endpoints. - Operator docs for configuration, endpoints, caching, and verification. # Additional Changes - Document on API fields that well-known responses may be HTTP-cached and platform verifiers may delay propagation. - Runtime config: `WellKnown.AppLinksCacheControlMaxAge` (default `5m`; `0` → `no-store`). # Additional Context - Closes #12497 - Implemented and reviewed as stack: - #12531 API contract - #12532 storage wiring - #12536 well-known endpoints - #12537 console - #12547 docs --------- Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -15,6 +15,13 @@ This would give us `${CUSTOM_DOMAIN}/.well-known/openid-configuration`.
|
||||
|
||||
**Link to spec.** [OpenID Connect Discovery 1.0 incorporating errata set 1](https://openid.net/specs/openid-connect-discovery-1_0.html)
|
||||
|
||||
ZITADEL also serves instance-scoped association files for native passkeys (not part of OIDC discovery):
|
||||
|
||||
- `${CUSTOM_DOMAIN}/.well-known/apple-app-site-association`
|
||||
- `${CUSTOM_DOMAIN}/.well-known/assetlinks.json`
|
||||
|
||||
See [Native app links for passkeys](/guides/integrate/login/oidc/native-app-links).
|
||||
|
||||
## authorization_endpoint
|
||||
|
||||
`${CUSTOM_DOMAIN}/oauth/v2/authorize`
|
||||
|
||||
@@ -35,5 +35,6 @@ Imagine signing in without passwords! Passkeys, replacing traditional passwords,
|
||||
* Passkey support is still evolving in browsers and platforms. Check compatibility for your target audience.
|
||||
* ZITADEL actively develops its passkey features. Stay updated with documentation and releases.
|
||||
* Passkeys are bound to your domain, thus we recommend configuring a [Custom Domain](/concepts/features/custom-domain) before setting up passkeys.
|
||||
* Native iOS and Android apps that use platform passkey APIs also need domain association files. Configure them on the OIDC app and let ZITADEL serve the well-known documents — see [Native app links for passkeys](/guides/integrate/login/oidc/native-app-links).
|
||||
|
||||
Don't hesitate to ask if you have further questions about integrating passkeys in your ZITADEL application!
|
||||
|
||||
@@ -12,6 +12,11 @@ Passkeys are a replacement for passwords that provide faster, easier, and more s
|
||||
Unlike passwords, passkeys are phishing-resistant and can improve the user experience and security at the same time.
|
||||
Passkeys and there underlying protocols are a standard defined by the [FIDO Standard](https://fidoalliance.org/) .
|
||||
|
||||
<Callout type="info">
|
||||
This guide covers passkeys in a **custom browser login UI**.
|
||||
Native iOS/Android apps that use platform passkey APIs also need domain association files served from your issuer — see [Native app links for passkeys](/guides/integrate/login/oidc/native-app-links).
|
||||
</Callout>
|
||||
|
||||
## Register Passkey
|
||||
|
||||
### Flow
|
||||
|
||||
@@ -21,5 +21,6 @@ import { FileText, Folder, Link as LinkIcon } from 'lucide-react';
|
||||
<Card title="Grant Types" href="/apis/openidoauth/grant-types" icon={<FileText />} />
|
||||
<Card title="Authrequest" href="/apis/openidoauth/authrequest" icon={<FileText />} />
|
||||
<Card title="Webkeys" href="/guides/integrate/login/oidc/webkeys" icon={<FileText />} />
|
||||
<Card title="Native App Links" href="/guides/integrate/login/oidc/native-app-links" icon={<FileText />} />
|
||||
<Card title="Token Exchange" href="/guides/integrate/token-exchange" icon={<FileText />} />
|
||||
</Cards>
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: Native app links for passkeys
|
||||
description: "Configure iOS and Android association on OIDC apps so ZITADEL can serve apple-app-site-association and assetlinks.json for native passkeys"
|
||||
sidebar_label: Native App Links
|
||||
---
|
||||
|
||||
Native mobile apps that create or use passkeys with platform WebAuthn APIs need the issuer domain to publish OS trust association files.
|
||||
ZITADEL serves those files from your instance based on iOS and Android settings on OIDC applications.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- An OIDC **Native** application in your project
|
||||
- Passkeys enabled in your login policy
|
||||
- A stable issuer / [custom domain](/concepts/features/custom-domain) (passkeys are domain-bound)
|
||||
- Platform association configured in the mobile app project (Associated Domains on iOS; Digital Asset Links / App Links on Android)
|
||||
|
||||
## Configure the OIDC application
|
||||
|
||||
Set association fields on each Native OIDC app that should participate in passkey trust.
|
||||
|
||||
You can do this in the [Console](/guides/manage/console/applications-overview#native-app-links) under the app's **Native App Links** section, or via the Application API / Management API OIDC app create and update endpoints (`ios` / `android`).
|
||||
|
||||
### iOS
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| **Team ID** | Apple Developer Team ID (exactly 10 alphanumeric characters), for example `ABCDE12345` |
|
||||
| **Bundle ID** | App bundle identifier (`CFBundleIdentifier`), for example `com.example.app` — do not include the Team ID prefix |
|
||||
|
||||
ZITADEL publishes these as a single App ID in the association file: `{team_id}.{bundle_id}`.
|
||||
|
||||
### Android
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| **Package name** | Android `applicationId` / package name from the app manifest |
|
||||
| **SHA-256 certificate fingerprints** | Signing certificate fingerprints (64 hex characters, optionally colon-separated). Include debug and release fingerprints as needed. |
|
||||
|
||||
### Update semantics
|
||||
|
||||
On update:
|
||||
|
||||
- Omit a field group to leave it unchanged
|
||||
- Send an empty message / empty values to clear
|
||||
- Send populated values to replace
|
||||
|
||||
## Well-known endpoints
|
||||
|
||||
ZITADEL aggregates association data from **active** OIDC apps on the instance and serves:
|
||||
|
||||
| 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` |
|
||||
|
||||
If no apps are configured, both endpoints still return HTTP `200` with empty collections.
|
||||
|
||||
### Example: apple-app-site-association
|
||||
|
||||
```json
|
||||
{
|
||||
"webcredentials": {
|
||||
"apps": [
|
||||
"ABCDE12345.com.example.one",
|
||||
"FGHIJ67890.com.example.two"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Duplicate iOS App IDs across apps are deduplicated.
|
||||
|
||||
### Example: assetlinks.json
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"relation": ["delegate_permission/common.get_login_creds"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "com.example.one",
|
||||
"sha256_cert_fingerprints": [
|
||||
"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Fingerprints are normalized to colon-separated uppercase hex when served.
|
||||
|
||||
## 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:
|
||||
|
||||
```
|
||||
Cache-Control: public, max-age=300
|
||||
```
|
||||
|
||||
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.
|
||||
</Callout>
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
curl -i "${CUSTOM_DOMAIN}/.well-known/apple-app-site-association"
|
||||
curl -i "${CUSTOM_DOMAIN}/.well-known/assetlinks.json"
|
||||
```
|
||||
|
||||
Confirm:
|
||||
|
||||
- HTTP status `200`
|
||||
- `Content-Type: application/json`
|
||||
- Expected `Cache-Control` header
|
||||
- JSON contents match your configured apps
|
||||
|
||||
## Related
|
||||
|
||||
- [Passkeys concept](/concepts/features/passkeys)
|
||||
- [Console applications](/guides/manage/console/applications-overview)
|
||||
- [Custom domain](/concepts/features/custom-domain)
|
||||
- [Passkeys in a custom login UI](/guides/integrate/login-ui/passkey) (browser / WebAuthn APIs)
|
||||
@@ -63,6 +63,15 @@ Native Applications are installed on a thin client, such as a smartphone or desk
|
||||
|
||||
<AuthType components={props.components} appType="native" />
|
||||
|
||||
#### Native app links
|
||||
|
||||
For native passkeys, configure OS association on the OIDC app under **Native App Links**:
|
||||
|
||||
* **iOS:** Team ID and Bundle ID (published in `/.well-known/apple-app-site-association`)
|
||||
* **Android:** Package name and SHA-256 certificate fingerprints (published in `/.well-known/assetlinks.json`)
|
||||
|
||||
See [Native app links for passkeys](/guides/integrate/login/oidc/native-app-links) for endpoint details, caching, and verification.
|
||||
|
||||
### User Agent
|
||||
|
||||
User Agent Applications are executed entirely in the web browser (Client-Side).
|
||||
@@ -219,3 +228,4 @@ Ensure the management of application settings is limited to authorized users onl
|
||||
## References
|
||||
|
||||
- [Recommended OIDC Flows](../../integrate/login/oidc/oauth-recommended-flows)
|
||||
- [Native app links for passkeys](../../integrate/login/oidc/native-app-links)
|
||||
|
||||
@@ -269,6 +269,7 @@ export const guidesSidebar: readonly SidebarItem[] = [
|
||||
"apis/openidoauth/grant-types",
|
||||
"apis/openidoauth/authrequest",
|
||||
"guides/integrate/login/oidc/webkeys",
|
||||
"guides/integrate/login/oidc/native-app-links",
|
||||
"guides/integrate/token-exchange",
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user