From 9c927f35b02482a1688a15642b1703cf86a4908a Mon Sep 17 00:00:00 2001 From: jhickmanit Date: Mon, 24 Nov 2025 20:28:20 -0800 Subject: [PATCH 1/9] feat: added a guide for the passkey integrations work needed for mobile passkeys. Also tackled a bit of the web passkeys as well. --- .../guides/passkey-integration-web-mobile.mdx | 874 ++++++++++++++++++ docs/kratos/passwordless/05_passkeys.mdx | 8 + 2 files changed, 882 insertions(+) create mode 100644 docs/kratos/guides/passkey-integration-web-mobile.mdx diff --git a/docs/kratos/guides/passkey-integration-web-mobile.mdx b/docs/kratos/guides/passkey-integration-web-mobile.mdx new file mode 100644 index 0000000000..1eb62023cb --- /dev/null +++ b/docs/kratos/guides/passkey-integration-web-mobile.mdx @@ -0,0 +1,874 @@ +--- +id: passkeys-implementation +title: Implement passkey authentication in web and mobile applications +sidebar_label: Passkeys implementation +slug: passkeys-mobile-web-implementation +--- + +## Implementing passkeys for web and mobile + +This guide covers how to implement passkey authentication in your applications using Ory Kratos or Ory Network. Passkeys provide a passwordless authentication experience using WebAuthn across web browsers and mobile platforms. + +:::info +This page assumes you have already configured the passkey method in your Ory configuration. See the [Passkeys overview](../passwordless/passkeys) for initial setup instructions. +::: + +:::note +Code examples in this guide are illustrative and likely need adjustments based on your specific configuration, identity schema, and application requirements. +::: + +## Overview + +Passkey implementation differs between platforms: + +- Web applications use browser-native WebAuthn APIs with JavaScript. +- Mobile applications use platform-specific credential management APIs (iOS AuthenticationServices, Android CredentialManager) with Ory's JSON API endpoints. + +This guide focuses on the integration patterns for each platform. + +## Web implementation + +For web applications, you can use the browser's native WebAuthn API to create and authenticate with passkeys. + +### Using Ory's webauthn.js + +Ory provides a `webauthn.js` helper script that simplifies WebAuthn integration in browser flows. When you initialize a registration or login flow through the browser, Ory automatically injects the necessary JavaScript to handle passkey operations. + +```html + + +``` + +The script automatically: + +- Detects passkey-related form fields. +- Calls `navigator.credentials.create()` for registration. +- Calls `navigator.credentials.get()` for authentication. +- Submits the WebAuthn response back to Ory. + +See [Custom UI Advanced Integration](../bring-your-own-ui/custom-ui-advanced-integration#passwordless-authentication) for details on using `webauthn.js` in custom UIs. + +### Manual WebAuthn integration + +For more control, you can manually integrate the [W3C WebAuthn API](https://www.w3.org/TR/webauthn-2/): + +1. Initialize a registration or login flow via Ory's API. +2. Parse the WebAuthn challenge from the flow response. +3. Call `navigator.credentials.create()` or `navigator.credentials.get()`. +4. Submit the credential response back to Ory. + +The WebAuthn API is well-documented by the W3C and MDN: + +- [W3C WebAuthn Specification](https://www.w3.org/TR/webauthn-2/) +- [MDN Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) + +## Mobile implementation + +Mobile passkey implementation requires using platform-specific APIs and Ory's JSON API endpoints. Unlike browser flows, mobile apps don't receive the `webauthn.js` script and must handle credential operations manually. + +### Platform requirements + +Both iOS and Android require configuration to associate your app with your authentication domain. + +### iOS Associated Domains + +iOS requires an Associated Domains entitlement that links your app to your authentication domain. + +#### Add the Associated Domains entitlement + +Add the entitlement to your Xcode project: + +```xml + + + + + + com.apple.developer.associated-domains + + webcredentials:$PROJECT_SLUG.projects.oryapis.com + + + +``` + +The domain in your entitlement must match the Relying Party ID in your Kratos passkey configuration. + +#### Serve the apple-app-site-association file + +You need to host an `apple-app-site-association` file at `https://{your_domain}/.well-known/apple-app-site-association` to allow the application to register and authenticate with credentials associated with the Relying Party ID. + +Example `apple-app-site-association` file: + +```json +{ + "webcredentials": { + "apps": ["ABCDE12345.com.example.app"] + } +} +``` + +The value uses the format `{Application_Identifier_Prefix}.{Bundle_Identifier}`. Find your Application Identifier Prefix (Team ID) in the [Apple Developer portal](https://developer.apple.com/account) under Membership. + +```mdx-code-block + + +``` + +:::warning +Ory Network doesn't currently host `apple-app-site-association` files automatically. This feature is planned for future releases. You must host this file on your own domain and configure your Kratos passkey settings to use that domain as the Relying Party ID. +::: + +```mdx-code-block + + +``` + +For self-hosted Kratos, serve the `apple-app-site-association` file at your authentication domain: + +`https://ory.your-custom-domain.com/.well-known/apple-app-site-association` + +```mdx-code-block + + +``` + +#### Important constraints + +The domain in your Associated Domains entitlement must exactly match the `rp.id` in your Kratos passkey configuration. The domain must be accessible via HTTPS with a valid TLS certificate. The `apple-app-site-association` file must be served with `Content-Type: application/json`. + +#### Apple documentation + +- [Supporting Associated Domains](https://developer.apple.com/documentation/xcode/supporting-associated-domains) +- [About the apple-app-site-association file](https://developer.apple.com/documentation/bundleresources/applinks/details/supporting_associated_domains) + +### Android Digital Asset Links + +Android requires an `assetlinks.json` file to verify your app's relationship with your authentication domain. + +#### Serve the assetlinks.json file + +You need to host an `assetlinks.json` file at `https://{your_domain}/.well-known/assetlinks.json` to verify your app's relationship with your authentication domain. + +Example `assetlinks.json` file: + +```json +[ + { + "relation": [ + "delegate_permission/common.handle_all_urls", + "delegate_permission/common.get_login_creds" + ], + "target": { + "namespace": "android_app", + "package_name": "com.example.yourapp", + "sha256_cert_fingerprints": [ + "14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5" + ] + } + } +] +``` + +Generate your SHA-256 certificate fingerprint: + +```shell +keytool -list -v -keystore your-keystore.jks +``` + +```mdx-code-block + + +``` + +:::warning +Ory Network doesn't currently host `assetlinks.json` files automatically. This feature is planned for future releases. You must host this file on your own domain and configure your Kratos passkey settings to use that domain as the Relying Party ID. +::: + +```mdx-code-block + + +``` + +For self-hosted Kratos, serve the `assetlinks.json` file at your authentication domain: + +`https://ory.your-custom-domain.com/.well-known/assetlinks.json` + +```mdx-code-block + + +``` + +#### Important constraints + +The domain in your `assetlinks.json` must exactly match the `rp.id` in your Kratos passkey configuration. The domain must be accessible via HTTPS with a valid TLS certificate. The file must be served with `Content-Type: application/json`. The package name must match your Android app's `applicationId`. The SHA-256 fingerprint must match your app's signing key. + +#### Android documentation + +- [Android Credential Manager](https://developer.android.com/training/sign-in/passkeys) +- [Digital Asset Links](https://developers.google.com/digital-asset-links/v1/getting-started) +- [Verify Android App Links](https://developer.android.com/training/app-links/verify-android-applinks) + +### iOS implementation + +iOS passkey support uses the [AuthenticationServices framework](https://developer.apple.com/documentation/authenticationservices). Here's how to integrate with Ory's API. + +#### Registration flow + +Initialize the registration flow: + +```swift +func initializeRegistrationFlow() async throws -> FlowResponse { + var request = URLRequest(url: URL(string: "\(oryBaseURL)/self-service/registration/api")!) + request.httpMethod = "GET" + request.setValue("application/json", forHTTPHeaderField: "Accept") + + let (data, response) = try await URLSession.shared.data(for: request) + // Handle response... + return try decodeFlowResponse(from: data) +} +``` + +Parse the WebAuthn challenge by extracting the challenge from the `passkey_create_data` node: + +```swift +func extractRegistrationChallenge(_ flow: FlowResponse) -> String? { + guard let ui = flow.raw["ui"] as? [String: Any], + let nodes = ui["nodes"] as? [[String: Any]] else { + return nil + } + + // Find the passkey_create_data node + for node in nodes { + guard let attributes = node["attributes"] as? [String: Any], + let name = attributes["name"] as? String, + name == "passkey_create_data" else { + continue + } + + // Parse the nested JSON value + if let valueStr = attributes["value"] as? String, + let valueData = valueStr.data(using: .utf8), + let json = try? JSONSerialization.jsonObject(with: valueData) as? [String: Any], + let credentialOptions = json["credentialOptions"] as? [String: Any], + let publicKey = credentialOptions["publicKey"] as? [String: Any], + let challenge = publicKey["challenge"] as? String { + return challenge + } + } + return nil +} +``` + +The `passkey_create_data` node contains a JSON string with this structure: + +```json +{ + "credentialOptions": { + "publicKey": { + "challenge": "base64url-encoded-challenge", + "rp": { "name": "Your App", "id": "ory.your-custom-domain.com" }, + "user": { "id": "base64url-user-id", "name": "", "displayName": "" }, + "pubKeyCredParams": [ + { "type": "public-key", "alg": -7 }, + { "type": "public-key", "alg": -257 } + ], + "authenticatorSelection": { + "userVerification": "required", + "residentKey": "required" + } + } + }, + "displayNameFieldName": "traits.email" +} +``` + +Create the passkey: + +```swift +func signUpWith(userName: String, challenge: Data, domain: String) { + let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider( + relyingPartyIdentifier: domain + ) + + let userID = Data(UUID().uuidString.utf8) + let registrationRequest = publicKeyCredentialProvider.createCredentialRegistrationRequest( + challenge: challenge, + name: userName, + userID: userID + ) + + let authController = ASAuthorizationController(authorizationRequests: [registrationRequest]) + authController.delegate = self + authController.presentationContextProvider = self + authController.performRequests() +} +``` + +When the user completes registration, format and submit the credential: + +```swift +func submitRegistration(credential: ASAuthorizationPlatformPublicKeyCredentialRegistration) async throws { + let credentialDict: [String: Any] = [ + "id": credential.credentialID.base64URLEncodedString(), + "rawId": credential.credentialID.base64URLEncodedString(), + "type": "public-key", + "response": [ + "clientDataJSON": credential.rawClientDataJSON.base64URLEncodedString(), + "attestationObject": credential.rawAttestationObject?.base64URLEncodedString() ?? "" + ] + ] + + let credentialJSON = try JSONSerialization.data(withJSONObject: credentialDict) + let credentialString = String(data: credentialJSON, encoding: .utf8)! + + var request = URLRequest(url: URL(string: "\(oryBaseURL)/self-service/registration?flow=\(flowId)")!) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + let payload: [String: Any] = [ + "method": "passkey", + "passkey_register": credentialString, + "traits": [ + "email": userName // Or other identity traits + ] + ] + + request.httpBody = try JSONSerialization.data(withJSONObject: payload) + let (data, response) = try await URLSession.shared.data(for: request) + // Handle response... +} +``` + +#### Login flow + +Initialize the login flow: + +```swift +func initializeLoginFlow() async throws -> FlowResponse { + var request = URLRequest(url: URL(string: "\(oryBaseURL)/self-service/login/api")!) + request.httpMethod = "GET" + request.setValue("application/json", forHTTPHeaderField: "Accept") + + let (data, response) = try await URLSession.shared.data(for: request) + return try decodeFlowResponse(from: data) +} +``` + +Extract the challenge from the `passkey_challenge` node: + +```swift +func extractLoginChallenge(_ flow: FlowResponse) -> String? { + guard let ui = flow.raw["ui"] as? [String: Any], + let nodes = ui["nodes"] as? [[String: Any]] else { + return nil + } + + // Find the passkey_challenge node + for node in nodes { + guard let attributes = node["attributes"] as? [String: Any], + let name = attributes["name"] as? String, + name == "passkey_challenge" else { + continue + } + + // Parse the JSON value + if let valueStr = attributes["value"] as? String, + let valueData = valueStr.data(using: .utf8), + let json = try? JSONSerialization.jsonObject(with: valueData) as? [String: Any], + let publicKey = json["publicKey"] as? [String: Any], + let challenge = publicKey["challenge"] as? String { + return challenge + } + } + return nil +} +``` + +The `passkey_challenge` node contains a JSON string with this structure: + +```json +{ + "publicKey": { + "challenge": "base64url-encoded-challenge", + "rpId": "ory.your-custom-domain.com", + "allowCredentials": [], + "userVerification": "required" + } +} +``` + +Authenticate with passkey: + +```swift +func signInWith(challenge: Data, domain: String) { + let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider( + relyingPartyIdentifier: domain + ) + + let assertionRequest = publicKeyCredentialProvider.createCredentialAssertionRequest( + challenge: challenge + ) + + let authController = ASAuthorizationController(authorizationRequests: [assertionRequest]) + authController.delegate = self + authController.presentationContextProvider = self + authController.performRequests() +} +``` + +Submit the assertion: + +```swift +func submitLogin(credential: ASAuthorizationPlatformPublicKeyCredentialAssertion) async throws { + let credentialDict: [String: Any] = [ + "id": credential.credentialID.base64URLEncodedString(), + "rawId": credential.credentialID.base64URLEncodedString(), + "type": "public-key", + "response": [ + "clientDataJSON": credential.rawClientDataJSON.base64URLEncodedString(), + "authenticatorData": credential.rawAuthenticatorData.base64URLEncodedString(), + "signature": credential.signature.base64URLEncodedString(), + "userHandle": credential.userID.base64URLEncodedString() + ] + ] + + let credentialJSON = try JSONSerialization.data(withJSONObject: credentialDict) + let credentialString = String(data: credentialJSON, encoding: .utf8)! + + var request = URLRequest(url: URL(string: "\(oryBaseURL)/self-service/login?flow=\(flowId)")!) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + let payload: [String: Any] = [ + "method": "passkey", + "passkey_login": credentialString + ] + + request.httpBody = try JSONSerialization.data(withJSONObject: payload) + let (data, response) = try await URLSession.shared.data(for: request) + // Handle response... +} +``` + +#### Base64URL encoding + +iOS requires Base64URL encoding (not standard Base64) for WebAuthn: + +```swift +extension Data { + func base64URLEncodedString() -> String { + return self.base64EncodedString() + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "=", with: "") + } + + init?(base64URLEncoded string: String) { + var base64 = string + .replacingOccurrences(of: "-", with: "+") + .replacingOccurrences(of: "_", with: "/") + let remainder = base64.count % 4 + if remainder > 0 { + base64 += String(repeating: "=", count: 4 - remainder) + } + self.init(base64Encoded: base64) + } +} +``` + +### Android implementation + +Android passkey support uses the [Credential Manager API](https://developer.android.com/training/sign-in/passkeys). The integration pattern is similar to iOS. + +#### Dependencies + +Add the Credential Manager dependency to your `build.gradle`: + +```gradle +dependencies { + implementation "androidx.credentials:credentials:1.2.0" + implementation "androidx.credentials:credentials-play-services-auth:1.2.0" +} +``` + +#### Registration flow + +Initialize the registration flow: + +```kotlin +suspend fun initializeRegistrationFlow(): FlowResponse { + val url = URL("$oryBaseURL/self-service/registration/api") + val connection = url.openConnection() as HttpURLConnection + connection.requestMethod = "GET" + connection.setRequestProperty("Accept", "application/json") + + val response = connection.inputStream.bufferedReader().readText() + return parseFlowResponse(response) +} +``` + +Parse the WebAuthn challenge: + +```kotlin +fun extractRegistrationChallenge(flow: FlowResponse): String? { + val ui = flow.raw["ui"] as? Map<*, *> ?: return null + val nodes = ui["nodes"] as? List<*> ?: return null + + for (node in nodes) { + val nodeMap = node as? Map<*, *> ?: continue + val attributes = nodeMap["attributes"] as? Map<*, *> ?: continue + val name = attributes["name"] as? String ?: continue + + if (name == "passkey_create_data") { + val valueStr = attributes["value"] as? String ?: continue + val json = JSONObject(valueStr) + val credentialOptions = json.getJSONObject("credentialOptions") + val publicKey = credentialOptions.getJSONObject("publicKey") + return publicKey.getString("challenge") + } + } + return null +} +``` + +Create the passkey: + +```kotlin +suspend fun signUpWith(userName: String, requestJson: String, context: Context) { + val credentialManager = CredentialManager.create(context) + + val request = CreatePublicKeyCredentialRequest(requestJson) + + try { + val result = credentialManager.createCredential( + request = request, + context = context as Activity + ) as CreatePublicKeyCredentialResponse + + submitRegistration(result.registrationResponseJson) + } catch (e: CreateCredentialException) { + // Handle error + } +} +``` + +Submit the credential: + +```kotlin +suspend fun submitRegistration(credentialJson: String) { + val url = URL("$oryBaseURL/self-service/registration?flow=$flowId") + val connection = url.openConnection() as HttpURLConnection + connection.requestMethod = "POST" + connection.setRequestProperty("Content-Type", "application/json") + connection.doOutput = true + + val payload = JSONObject().apply { + put("method", "passkey") + put("passkey_register", credentialJson) + put("traits", JSONObject().apply { + put("email", userName) + }) + } + + connection.outputStream.write(payload.toString().toByteArray()) + val response = connection.inputStream.bufferedReader().readText() + // Handle response... +} +``` + +#### Login flow + +Initialize the login flow: + +```kotlin +suspend fun initializeLoginFlow(): FlowResponse { + val url = URL("$oryBaseURL/self-service/login/api") + val connection = url.openConnection() as HttpURLConnection + connection.requestMethod = "GET" + connection.setRequestProperty("Accept", "application/json") + + val response = connection.inputStream.bufferedReader().readText() + return parseFlowResponse(response) +} +``` + +Parse the WebAuthn challenge: + +```kotlin +fun extractLoginChallenge(flow: FlowResponse): String? { + val ui = flow.raw["ui"] as? Map<*, *> ?: return null + val nodes = ui["nodes"] as? List<*> ?: return null + + for (node in nodes) { + val nodeMap = node as? Map<*, *> ?: continue + val attributes = nodeMap["attributes"] as? Map<*, *> ?: continue + val name = attributes["name"] as? String ?: continue + + if (name == "passkey_challenge") { + val valueStr = attributes["value"] as? String ?: continue + val json = JSONObject(valueStr) + val publicKey = json.getJSONObject("publicKey") + return publicKey.getString("challenge") + } + } + return null +} +``` + +Authenticate with passkey: + +```kotlin +suspend fun signInWith(requestJson: String, context: Context) { + val credentialManager = CredentialManager.create(context) + + val request = GetPublicKeyCredentialOption(requestJson) + val getCredRequest = GetCredentialRequest(listOf(request)) + + try { + val result = credentialManager.getCredential( + request = getCredRequest, + context = context as Activity + ) + + val credential = result.credential as PublicKeyCredential + submitLogin(credential.authenticationResponseJson) + } catch (e: GetCredentialException) { + // Handle error + } +} +``` + +Submit the assertion: + +```kotlin +suspend fun submitLogin(credentialJson: String) { + val url = URL("$oryBaseURL/self-service/login?flow=$flowId") + val connection = url.openConnection() as HttpURLConnection + connection.requestMethod = "POST" + connection.setRequestProperty("Content-Type", "application/json") + connection.doOutput = true + + val payload = JSONObject().apply { + put("method", "passkey") + put("passkey_login", credentialJson) + } + + connection.outputStream.write(payload.toString().toByteArray()) + val response = connection.inputStream.bufferedReader().readText() + // Handle response... +} +``` + +## API response handling + +### Flow response structure + +All registration and login flows return a similar structure: + +```json +{ + "id": "flow-id-uuid", + "type": "api", + "expires_at": "2025-11-23T12:00:00Z", + "issued_at": "2025-11-23T11:00:00Z", + "request_url": "https://example.com/self-service/registration/api", + "ui": { + "action": "https://example.com/self-service/registration?flow=flow-id-uuid", + "method": "POST", + "nodes": [...] + } +} +``` + +### Node types to parse + +Registration flows contain these key nodes: + +- `csrf_token` (group: default): CSRF protection token +- `traits.email` (group: default): User identity traits +- `passkey_create_data` (group: passkey): WebAuthn creation options +- `passkey_register` (group: passkey): Where to submit the credential + +Login flows contain: + +- `csrf_token` (group: default): CSRF protection token +- `identifier` (group: default): Optional username field +- `passkey_challenge` (group: passkey): WebAuthn assertion options +- `passkey_login` (group: passkey): Where to submit the assertion + +### Success response + +On successful authentication, Ory returns a session: + +```json +{ + "session": { + "id": "session-id-uuid", + "active": true, + "expires_at": "2025-11-23T13:00:00Z", + "authenticated_at": "2025-11-23T11:00:00Z", + "authenticator_assurance_level": "aal1", + "authentication_methods": [ + { + "method": "passkey", + "aal": "aal1", + "completed_at": "2025-11-23T11:00:00Z" + } + ], + "identity": { + "id": "identity-id-uuid", + "schema_id": "default", + "traits": { + "email": "user@example.com" + } + } + } +} +``` + +Store the session token for authenticated requests. On mobile, use secure storage (iOS Keychain, Android Keystore). + +## Error handling + +### Common errors + +#### Invalid WebAuthn response + +```json +{ + "error": { + "id": "browser_location_change_required", + "code": 422, + "status": "Unprocessable Entity", + "reason": "Unable to parse WebAuthn response: Parse error for Registration" + } +} +``` + +This error occurs when there is incorrect Base64URL encoding (using standard Base64 instead), missing required fields in the credential response, or malformed JSON in `passkey_register` or `passkey_login` fields. + +To resolve this, verify your Base64URL encoding and ensure all required WebAuthn response fields are included. + +#### Domain mismatch + +```json +{ + "error": { + "id": "security_identity_mismatch", + "code": 400, + "status": "Bad Request", + "reason": "The request was malformed or contained invalid parameters" + } +} +``` + +This error occurs when the Associated Domains or `assetlinks.json` domain doesn't match Kratos `rp.id`, the AASA or `assetlinks.json` file isn't properly served, or the application uses HTTP instead of HTTPS. + +To resolve this, verify your Associated Domains entitlement matches your Kratos configuration. Test AASA file accessibility using `curl https://ory.your-custom-domain.com/.well-known/apple-app-site-association`. Test `assetlinks.json` using `curl https://ory.your-custom-domain.com/.well-known/assetlinks.json`. Ensure HTTPS with valid certificate. + +#### Flow expired + +```json +{ + "error": { + "id": "self_service_flow_expired", + "code": 410, + "status": "Gone", + "reason": "The self-service flow has expired" + } +} +``` + +This error occurs when the user took too long to complete the flow (default: 1 hour). + +To resolve this, initialize a new flow and retry the operation. + +#### User canceled + +On mobile platforms, users can cancel the passkey prompt. Handle this gracefully. + +```mdx-code-block + + +``` + +```swift +func authorizationController(controller: ASAuthorizationController, + didCompleteWithError error: Error) { + if let authError = error as? ASAuthorizationError, + authError.code == .canceled { + // User canceled - show alternative login options + } +} +``` + +```mdx-code-block + + +``` + +```kotlin +catch (e: GetCredentialException) { + when (e) { + is GetCredentialCancellationException -> { + // User canceled - show alternative login options + } + } +} +``` + +```mdx-code-block + + +``` + +### Mobile-specific issues + +#### AASA file not found on iOS + +The passkey prompt doesn't appear or the user sees "No credentials available" errors. + +To troubleshoot this issue: + +1. Verify the AASA file is accessible via browser. +2. Check the file has correct `Content-Type: application/json`. +3. Verify the Team ID and Bundle ID are correct. +4. Try uninstalling and reinstalling the app. +5. Check the device isn't using a VPN that blocks the domain. + +#### assetlinks.json validation failed on Android + +The user sees "No credentials found" errors or the passkey dialog doesn't show. + +To troubleshoot this issue: + +1. Verify the `assetlinks.json` file is accessible via browser. +2. Use the [Statement List Generator and Tester](https://developers.google.com/digital-asset-links/tools/generator) to validate. +3. Verify the SHA-256 fingerprint matches your signing key. +4. Ensure the package name matches exactly. +5. Clear app data and retry. + +#### Domain not HTTPS + +Both iOS and Android require HTTPS for passkeys. HTTP domains fail silently or with cryptic errors. + +To resolve this, use HTTPS with a valid TLS certificate. For local development, use a tool like [ngrok](https://ngrok.com/) to create an HTTPS tunnel. + +## Best practices + +### Session management + +Store session tokens securely using iOS Keychain or Android Keystore. Handle session expiration by checking session validity before making authenticated requests. Implement token refresh using Ory's `/sessions/whoami` endpoint to verify sessions. + +### User experience + +Provide fallback options to allow users to sign in with other methods if passkeys fail. Handle errors gracefully by showing user-friendly error messages. Support AutoFill on iOS 17+ and Android 14+ to enable AutoFill-assisted passkey sign-in for better user experience. + +### Testing + +Test on physical devices as passkeys don't work reliably in simulators or emulators. Test with multiple accounts to verify credential isolation. Test cross-platform to ensure passkeys created on one platform work on others via cloud sync. + +## Next steps + +Review the [Passkeys overview](../passwordless/passkeys) for configuration options. See [Self-Service Flows](../self-service) for more flow details. Check out the [API documentation](../reference/api) for complete endpoint reference. \ No newline at end of file diff --git a/docs/kratos/passwordless/05_passkeys.mdx b/docs/kratos/passwordless/05_passkeys.mdx index 79c3c3674c..2dc740ccee 100644 --- a/docs/kratos/passwordless/05_passkeys.mdx +++ b/docs/kratos/passwordless/05_passkeys.mdx @@ -147,6 +147,14 @@ is defined, the passkey strategy will not work. }, } ``` +### Implementing passkeys in your application + +After configuring passkeys, you need to integrate them into your application. The implementation differs depending on your platform: + +- Web applications can use Ory's `webauthn.js` helper script or manually integrate the WebAuthn API. +- Mobile applications (iOS and Android) require platform-specific credential management APIs and direct integration with Ory's JSON API endpoints. + +For detailed implementation instructions, code examples, and best practices for both web and mobile platforms, see [Implementing passkeys for web and mobile](../guides/passkeys-implementation.mdx). ## Passkeys with the WebAuthN strategy From 4fb49a0f182834692a585b28cec689db5d337b55 Mon Sep 17 00:00:00 2001 From: jhickmanit Date: Mon, 24 Nov 2025 22:45:42 -0800 Subject: [PATCH 2/9] chore: format --- .../guides/passkey-integration-web-mobile.mdx | 89 ++- docs/kratos/passwordless/05_passkeys.mdx | 10 +- package-lock.json | 619 +++++++++++++++++- 3 files changed, 678 insertions(+), 40 deletions(-) diff --git a/docs/kratos/guides/passkey-integration-web-mobile.mdx b/docs/kratos/guides/passkey-integration-web-mobile.mdx index 1eb62023cb..f68070d284 100644 --- a/docs/kratos/guides/passkey-integration-web-mobile.mdx +++ b/docs/kratos/guides/passkey-integration-web-mobile.mdx @@ -7,22 +7,22 @@ slug: passkeys-mobile-web-implementation ## Implementing passkeys for web and mobile -This guide covers how to implement passkey authentication in your applications using Ory Kratos or Ory Network. Passkeys provide a passwordless authentication experience using WebAuthn across web browsers and mobile platforms. +This guide covers how to implement passkey authentication in your applications using Ory Kratos or Ory Network. Passkeys provide a +passwordless authentication experience using WebAuthn across web browsers and mobile platforms. -:::info -This page assumes you have already configured the passkey method in your Ory configuration. See the [Passkeys overview](../passwordless/passkeys) for initial setup instructions. -::: +:::info This page assumes you have already configured the passkey method in your Ory configuration. See the +[Passkeys overview](../passwordless/passkeys) for initial setup instructions. ::: -:::note -Code examples in this guide are illustrative and likely need adjustments based on your specific configuration, identity schema, and application requirements. -::: +:::note Code examples in this guide are illustrative and likely need adjustments based on your specific configuration, identity +schema, and application requirements. ::: ## Overview Passkey implementation differs between platforms: - Web applications use browser-native WebAuthn APIs with JavaScript. -- Mobile applications use platform-specific credential management APIs (iOS AuthenticationServices, Android CredentialManager) with Ory's JSON API endpoints. +- Mobile applications use platform-specific credential management APIs (iOS AuthenticationServices, Android CredentialManager) + with Ory's JSON API endpoints. This guide focuses on the integration patterns for each platform. @@ -32,7 +32,8 @@ For web applications, you can use the browser's native WebAuthn API to create an ### Using Ory's webauthn.js -Ory provides a `webauthn.js` helper script that simplifies WebAuthn integration in browser flows. When you initialize a registration or login flow through the browser, Ory automatically injects the necessary JavaScript to handle passkey operations. +Ory provides a `webauthn.js` helper script that simplifies WebAuthn integration in browser flows. When you initialize a +registration or login flow through the browser, Ory automatically injects the necessary JavaScript to handle passkey operations. ```html @@ -46,7 +47,8 @@ The script automatically: - Calls `navigator.credentials.get()` for authentication. - Submits the WebAuthn response back to Ory. -See [Custom UI Advanced Integration](../bring-your-own-ui/custom-ui-advanced-integration#passwordless-authentication) for details on using `webauthn.js` in custom UIs. +See [Custom UI Advanced Integration](../bring-your-own-ui/custom-ui-advanced-integration#passwordless-authentication) for details +on using `webauthn.js` in custom UIs. ### Manual WebAuthn integration @@ -64,7 +66,8 @@ The WebAuthn API is well-documented by the W3C and MDN: ## Mobile implementation -Mobile passkey implementation requires using platform-specific APIs and Ory's JSON API endpoints. Unlike browser flows, mobile apps don't receive the `webauthn.js` script and must handle credential operations manually. +Mobile passkey implementation requires using platform-specific APIs and Ory's JSON API endpoints. Unlike browser flows, mobile +apps don't receive the `webauthn.js` script and must handle credential operations manually. ### Platform requirements @@ -96,7 +99,8 @@ The domain in your entitlement must match the Relying Party ID in your Kratos pa #### Serve the apple-app-site-association file -You need to host an `apple-app-site-association` file at `https://{your_domain}/.well-known/apple-app-site-association` to allow the application to register and authenticate with credentials associated with the Relying Party ID. +You need to host an `apple-app-site-association` file at `https://{your_domain}/.well-known/apple-app-site-association` to allow +the application to register and authenticate with credentials associated with the Relying Party ID. Example `apple-app-site-association` file: @@ -108,16 +112,17 @@ Example `apple-app-site-association` file: } ``` -The value uses the format `{Application_Identifier_Prefix}.{Bundle_Identifier}`. Find your Application Identifier Prefix (Team ID) in the [Apple Developer portal](https://developer.apple.com/account) under Membership. +The value uses the format `{Application_Identifier_Prefix}.{Bundle_Identifier}`. Find your Application Identifier Prefix (Team ID) +in the [Apple Developer portal](https://developer.apple.com/account) under Membership. ```mdx-code-block ``` -:::warning -Ory Network doesn't currently host `apple-app-site-association` files automatically. This feature is planned for future releases. You must host this file on your own domain and configure your Kratos passkey settings to use that domain as the Relying Party ID. -::: +:::warning Ory Network doesn't currently host `apple-app-site-association` files automatically. This feature is planned for future +releases. You must host this file on your own domain and configure your Kratos passkey settings to use that domain as the Relying +Party ID. ::: ```mdx-code-block @@ -135,7 +140,9 @@ For self-hosted Kratos, serve the `apple-app-site-association` file at your auth #### Important constraints -The domain in your Associated Domains entitlement must exactly match the `rp.id` in your Kratos passkey configuration. The domain must be accessible via HTTPS with a valid TLS certificate. The `apple-app-site-association` file must be served with `Content-Type: application/json`. +The domain in your Associated Domains entitlement must exactly match the `rp.id` in your Kratos passkey configuration. The domain +must be accessible via HTTPS with a valid TLS certificate. The `apple-app-site-association` file must be served with +`Content-Type: application/json`. #### Apple documentation @@ -148,17 +155,15 @@ Android requires an `assetlinks.json` file to verify your app's relationship wit #### Serve the assetlinks.json file -You need to host an `assetlinks.json` file at `https://{your_domain}/.well-known/assetlinks.json` to verify your app's relationship with your authentication domain. +You need to host an `assetlinks.json` file at `https://{your_domain}/.well-known/assetlinks.json` to verify your app's +relationship with your authentication domain. Example `assetlinks.json` file: ```json [ { - "relation": [ - "delegate_permission/common.handle_all_urls", - "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.yourapp", @@ -181,8 +186,8 @@ keytool -list -v -keystore your-keystore.jks ``` -:::warning -Ory Network doesn't currently host `assetlinks.json` files automatically. This feature is planned for future releases. You must host this file on your own domain and configure your Kratos passkey settings to use that domain as the Relying Party ID. +:::warning Ory Network doesn't currently host `assetlinks.json` files automatically. This feature is planned for future releases. +You must host this file on your own domain and configure your Kratos passkey settings to use that domain as the Relying Party ID. ::: ```mdx-code-block @@ -201,7 +206,9 @@ For self-hosted Kratos, serve the `assetlinks.json` file at your authentication #### Important constraints -The domain in your `assetlinks.json` must exactly match the `rp.id` in your Kratos passkey configuration. The domain must be accessible via HTTPS with a valid TLS certificate. The file must be served with `Content-Type: application/json`. The package name must match your Android app's `applicationId`. The SHA-256 fingerprint must match your app's signing key. +The domain in your `assetlinks.json` must exactly match the `rp.id` in your Kratos passkey configuration. The domain must be +accessible via HTTPS with a valid TLS certificate. The file must be served with `Content-Type: application/json`. The package name +must match your Android app's `applicationId`. The SHA-256 fingerprint must match your app's signing key. #### Android documentation @@ -211,7 +218,8 @@ The domain in your `assetlinks.json` must exactly match the `rp.id` in your Krat ### iOS implementation -iOS passkey support uses the [AuthenticationServices framework](https://developer.apple.com/documentation/authenticationservices). Here's how to integrate with Ory's API. +iOS passkey support uses the [AuthenticationServices framework](https://developer.apple.com/documentation/authenticationservices). +Here's how to integrate with Ory's API. #### Registration flow @@ -479,7 +487,8 @@ extension Data { ### Android implementation -Android passkey support uses the [Credential Manager API](https://developer.android.com/training/sign-in/passkeys). The integration pattern is similar to iOS. +Android passkey support uses the [Credential Manager API](https://developer.android.com/training/sign-in/passkeys). The +integration pattern is similar to iOS. #### Dependencies @@ -746,7 +755,8 @@ Store the session token for authenticated requests. On mobile, use secure storag } ``` -This error occurs when there is incorrect Base64URL encoding (using standard Base64 instead), missing required fields in the credential response, or malformed JSON in `passkey_register` or `passkey_login` fields. +This error occurs when there is incorrect Base64URL encoding (using standard Base64 instead), missing required fields in the +credential response, or malformed JSON in `passkey_register` or `passkey_login` fields. To resolve this, verify your Base64URL encoding and ensure all required WebAuthn response fields are included. @@ -763,9 +773,12 @@ To resolve this, verify your Base64URL encoding and ensure all required WebAuthn } ``` -This error occurs when the Associated Domains or `assetlinks.json` domain doesn't match Kratos `rp.id`, the AASA or `assetlinks.json` file isn't properly served, or the application uses HTTP instead of HTTPS. +This error occurs when the Associated Domains or `assetlinks.json` domain doesn't match Kratos `rp.id`, the AASA or +`assetlinks.json` file isn't properly served, or the application uses HTTP instead of HTTPS. -To resolve this, verify your Associated Domains entitlement matches your Kratos configuration. Test AASA file accessibility using `curl https://ory.your-custom-domain.com/.well-known/apple-app-site-association`. Test `assetlinks.json` using `curl https://ory.your-custom-domain.com/.well-known/assetlinks.json`. Ensure HTTPS with valid certificate. +To resolve this, verify your Associated Domains entitlement matches your Kratos configuration. Test AASA file accessibility using +`curl https://ory.your-custom-domain.com/.well-known/apple-app-site-association`. Test `assetlinks.json` using +`curl https://ory.your-custom-domain.com/.well-known/assetlinks.json`. Ensure HTTPS with valid certificate. #### Flow expired @@ -853,22 +866,28 @@ To troubleshoot this issue: Both iOS and Android require HTTPS for passkeys. HTTP domains fail silently or with cryptic errors. -To resolve this, use HTTPS with a valid TLS certificate. For local development, use a tool like [ngrok](https://ngrok.com/) to create an HTTPS tunnel. +To resolve this, use HTTPS with a valid TLS certificate. For local development, use a tool like [ngrok](https://ngrok.com/) to +create an HTTPS tunnel. ## Best practices ### Session management -Store session tokens securely using iOS Keychain or Android Keystore. Handle session expiration by checking session validity before making authenticated requests. Implement token refresh using Ory's `/sessions/whoami` endpoint to verify sessions. +Store session tokens securely using iOS Keychain or Android Keystore. Handle session expiration by checking session validity +before making authenticated requests. Implement token refresh using Ory's `/sessions/whoami` endpoint to verify sessions. ### User experience -Provide fallback options to allow users to sign in with other methods if passkeys fail. Handle errors gracefully by showing user-friendly error messages. Support AutoFill on iOS 17+ and Android 14+ to enable AutoFill-assisted passkey sign-in for better user experience. +Provide fallback options to allow users to sign in with other methods if passkeys fail. Handle errors gracefully by showing +user-friendly error messages. Support AutoFill on iOS 17+ and Android 14+ to enable AutoFill-assisted passkey sign-in for better +user experience. ### Testing -Test on physical devices as passkeys don't work reliably in simulators or emulators. Test with multiple accounts to verify credential isolation. Test cross-platform to ensure passkeys created on one platform work on others via cloud sync. +Test on physical devices as passkeys don't work reliably in simulators or emulators. Test with multiple accounts to verify +credential isolation. Test cross-platform to ensure passkeys created on one platform work on others via cloud sync. ## Next steps -Review the [Passkeys overview](../passwordless/passkeys) for configuration options. See [Self-Service Flows](../self-service) for more flow details. Check out the [API documentation](../reference/api) for complete endpoint reference. \ No newline at end of file +Review the [Passkeys overview](../passwordless/passkeys) for configuration options. See [Self-Service Flows](../self-service) for +more flow details. Check out the [API documentation](../reference/api) for complete endpoint reference. diff --git a/docs/kratos/passwordless/05_passkeys.mdx b/docs/kratos/passwordless/05_passkeys.mdx index 2dc740ccee..645bf6dfef 100644 --- a/docs/kratos/passwordless/05_passkeys.mdx +++ b/docs/kratos/passwordless/05_passkeys.mdx @@ -147,14 +147,18 @@ is defined, the passkey strategy will not work. }, } ``` + ### Implementing passkeys in your application -After configuring passkeys, you need to integrate them into your application. The implementation differs depending on your platform: +After configuring passkeys, you need to integrate them into your application. The implementation differs depending on your +platform: - Web applications can use Ory's `webauthn.js` helper script or manually integrate the WebAuthn API. -- Mobile applications (iOS and Android) require platform-specific credential management APIs and direct integration with Ory's JSON API endpoints. +- Mobile applications (iOS and Android) require platform-specific credential management APIs and direct integration with Ory's + JSON API endpoints. -For detailed implementation instructions, code examples, and best practices for both web and mobile platforms, see [Implementing passkeys for web and mobile](../guides/passkeys-implementation.mdx). +For detailed implementation instructions, code examples, and best practices for both web and mobile platforms, see +[Implementing passkeys for web and mobile](../guides/passkeys-implementation.mdx). ## Passkeys with the WebAuthN strategy diff --git a/package-lock.json b/package-lock.json index e9da96630a..a0c7b88562 100644 --- a/package-lock.json +++ b/package-lock.json @@ -270,6 +270,7 @@ "version": "5.44.0", "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.44.0.tgz", "integrity": "sha512-/FRKUM1G4xn3vV8+9xH1WJ9XknU8rkBGlefruq9jDhYUAvYozKimhrmC2pRqw/RyHhPivmgZCRuC8jHP8piz4Q==", + "peer": true, "dependencies": { "@algolia/client-common": "5.44.0", "@algolia/requester-browser-xhr": "5.44.0", @@ -446,6 +447,7 @@ "node_modules/@babel/core": { "version": "7.26.0", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.0", @@ -2432,6 +2434,7 @@ "url": "https://opencollective.com/csstools" } ], + "peer": true, "engines": { "node": ">=18" }, @@ -2453,6 +2456,7 @@ "url": "https://opencollective.com/csstools" } ], + "peer": true, "engines": { "node": ">=18" } @@ -2557,6 +2561,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -2962,6 +2967,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -3900,6 +3906,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz", "integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==", + "peer": true, "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/logger": "3.9.2", @@ -4184,6 +4191,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.9.2.tgz", "integrity": "sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag==", + "peer": true, "dependencies": { "@docusaurus/mdx-loader": "3.9.2", "@docusaurus/module-type-aliases": "3.9.2", @@ -4287,6 +4295,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.9.2.tgz", "integrity": "sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ==", + "peer": true, "dependencies": { "@docusaurus/logger": "3.9.2", "@docusaurus/types": "3.9.2", @@ -4344,6 +4353,17 @@ "node": ">=20.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emotion/is-prop-valid": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", @@ -4457,6 +4477,33 @@ "@img/sharp-libvips-darwin-arm64": "1.0.2" } }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.3.tgz", + "integrity": "sha512-2QeSl7QDK9ru//YBT4sQkoq7L0EAJZA3rtV+v9p8xTKl4U1bUqTIaCnoC7Ctx2kCjQgwFXDasOtPTCT8eCTXvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.2" + } + }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz", @@ -4479,6 +4526,398 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz", + "integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=10.13", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz", + "integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz", + "integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz", + "integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz", + "integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz", + "integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz", + "integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.3.tgz", + "integrity": "sha512-Q7Ee3fFSC9P7vUSqVEF0zccJsZ8GiiCJYGWDdhEjdlOeS9/jdkyJ6sUSPj+bL8VuOYFSbofrW0t/86ceVhx32w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.3.tgz", + "integrity": "sha512-Zf+sF1jHZJKA6Gor9hoYG2ljr4wo9cY4twaxgFDvlG0Xz9V7sinsPp8pFd1XtlhTzYo0IhDbl3rK7P6MzHpnYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.3.tgz", + "integrity": "sha512-vFk441DKRFepjhTEH20oBlFrHcLjPfI8B0pMIxGm3+yilKyYeHEVvrZhYFdqIseSclIqbQ3SnZMwEMWonY5XFA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.3.tgz", + "integrity": "sha512-Q4I++herIJxJi+qmbySd072oDPRkCg/SClLEIDh5IL9h1zjhqjv82H0Seupd+q2m0yOfD+/fJnjSoDFtKiHu2g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.2" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.3.tgz", + "integrity": "sha512-qnDccehRDXadhM9PM5hLvcPRYqyFCBN31kq+ErBSZtZlsAc1U4Z85xf/RXv1qolkdu+ibw64fUDaRdktxTNP9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.2" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.3.tgz", + "integrity": "sha512-Jhchim8kHWIU/GZ+9poHMWRcefeaxFIs9EBqf9KtcC14Ojk6qua7ghKiPs0sbeLbLj/2IGBtDcxHyjCdYWkk2w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.2" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.3.tgz", + "integrity": "sha512-68zivsdJ0koE96stdUfM+gmyaK/NcoSZK5dV5CAjES0FUXS9lchYt8LAB5rTbM7nlWtxaU/2GON0HVN6/ZYJAQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.1.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.3.tgz", + "integrity": "sha512-CyimAduT2whQD8ER4Ux7exKrtfoaUiVr7HG0zZvO0XTFn2idUWljjxv58GxNTkFb8/J9Ub9AqITGkJD6ZginxQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.3.tgz", + "integrity": "sha512-viT4fUIDKnli3IfOephGnolMzhz5VaTvDRkYqtZxOMIoMQ4MrAziO7pT1nVnOt2FAm7qW5aa+CCc13aEY6Le0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -5039,6 +5478,7 @@ "node_modules/@mdx-js/react": { "version": "3.1.0", "license": "MIT", + "peer": true, "dependencies": { "@types/mdx": "^2.0.0" }, @@ -5098,6 +5538,7 @@ "node_modules/@octokit/core": { "version": "5.2.0", "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -5446,6 +5887,7 @@ "node_modules/@rjsf/core": { "version": "5.24.1", "license": "Apache-2.0", + "peer": true, "dependencies": { "lodash": "^4.17.21", "lodash-es": "^4.17.21", @@ -5466,6 +5908,7 @@ "resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.24.1.tgz", "integrity": "sha512-A25fFj/TNz5bKikCIs20DiedKAalLuAQ7vUX9VQkD2hps5C9YVr0dJgSlsPa5kzl6lQMaRsNouTx8E1ZdLV2fg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "json-schema-merge-allof": "^0.8.1", "jsonpointer": "^5.0.1", @@ -5755,6 +6198,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -5854,6 +6298,7 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.2", "@swc/types": "^0.1.5" @@ -5916,6 +6361,142 @@ "node": ">=10" } }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.8.tgz", + "integrity": "sha512-PP9JIJt19bUWhAGcQW6qMwTjZOcMyzkvZa0/LWSlDm0ORYVLmDXUoeQbGD3e0Zju9UiZxyulnpjEN0ZihJgPTA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.8.tgz", + "integrity": "sha512-HvEWnwKHkoVUr5iftWirTApFJ13hGzhAY2CMw4lz9lur2m+zhPviRRED0FCI6T95Knpv7+8eUOr98Z7ctrG6DQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.8.tgz", + "integrity": "sha512-kY8+qa7k/dEeBq9p0Hrta18QnJPpsiJvDQSLNaTIFpdM3aEM9zbkshWz8gaX5VVGUEALowCBUWqmzO4VaqM+2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.8.tgz", + "integrity": "sha512-0WWyIw432wpO/zeGblwq4f2YWam4pn8Z/Ig4KzHMgthR/KmiLU3f0Z7eo45eVmq5vcU7Os1zi/Zb65OOt09q/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.8.tgz", + "integrity": "sha512-p4yxvVS05rBNCrBaSTa20KK88vOwtg8ifTW7ec/yoab0bD5EwzzB8KbDmLLxE6uziFa0sdjF0dfRDwSZPex37Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.8.tgz", + "integrity": "sha512-jKuXihxAaqUnbFfvPxtmxjdJfs87F1GdBf33il+VUmSyWCP4BE6vW+/ReDAe8sRNsKyrZ3UH1vI5q1n64csBUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.8.tgz", + "integrity": "sha512-O0wT4AGHrX8aBeH6c2ADMHgagAJc5Kf6W48U5moyYDAkkVnKvtSc4kGhjWhe1Yl0sI0cpYh2In2FxvYsb44eWw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.8.tgz", + "integrity": "sha512-C2AYc3A2o+ECciqsJWRgIpp83Vk5EaRzHe7ed/xOWzVd0MsWR+fweEsyOjlmzHfpUxJSi46Ak3/BIZJlhZbXbg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "dev": true, @@ -6694,7 +7275,8 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/keyv": { "version": "3.1.4", @@ -6726,6 +7308,7 @@ "node_modules/@types/node": { "version": "22.8.4", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.19.8" } @@ -6768,6 +7351,7 @@ "node_modules/@types/react": { "version": "18.3.12", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -7090,6 +7674,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7170,6 +7755,7 @@ "node_modules/ajv": { "version": "8.17.1", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7211,6 +7797,7 @@ "version": "5.44.0", "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.44.0.tgz", "integrity": "sha512-f8IpsbdQjzTjr/4mJ/jv5UplrtyMnnciGax6/B0OnLCs2/GJTK13O4Y7Ff1AvJVAaztanH+m5nzPoUq6EAy+aA==", + "peer": true, "dependencies": { "@algolia/abtesting": "1.10.0", "@algolia/client-abtesting": "5.44.0", @@ -7503,6 +8090,7 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -7999,6 +8587,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -8360,6 +8949,7 @@ "version": "11.0.3", "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "peer": true, "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", @@ -9001,6 +9591,7 @@ "version": "3.38.1", "hasInstallScript": true, "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" @@ -9290,6 +9881,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -9600,6 +10192,7 @@ "node_modules/cytoscape": { "version": "3.31.0", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10" } @@ -9965,6 +10558,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "peer": true, "engines": { "node": ">=12" } @@ -11501,6 +12095,7 @@ "node_modules/file-loader/node_modules/ajv": { "version": "6.12.6", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -14162,6 +14757,7 @@ "node_modules/jsep": { "version": "1.4.0", "license": "MIT", + "peer": true, "engines": { "node": ">= 10.16.0" } @@ -17321,6 +17917,7 @@ "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.13.7.tgz", "integrity": "sha512-aChaVU/DO5aRPmk1GX8L+whocagUUpBQqoPtJk+cm7UOXUk87J4PeWCh6nNmTTIfEhiR9DI/+FnA8dln/hTK7g==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/mobx" @@ -17800,6 +18397,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -18780,6 +19378,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.1", @@ -19635,6 +20234,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -20203,6 +20803,7 @@ "version": "3.2.5", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -20592,6 +21193,7 @@ "version": "6.12.6", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -20656,6 +21258,7 @@ "node_modules/react": { "version": "19.0.0", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -20665,6 +21268,7 @@ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.6.8.tgz", "integrity": "sha512-yD6uN78XlFOkETQp6GRuVe0s5509x3XYx8PfPbirwFTYCj5/RfmSs9YZGCwkUrhZNFzj7tZPdpb+3k50mK1E4g==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.14.0", "@restart/context": "^2.1.4", @@ -20692,6 +21296,7 @@ "node_modules/react-dom": { "version": "19.0.0", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.25.0" }, @@ -20744,6 +21349,7 @@ "name": "@docusaurus/react-loadable", "version": "6.0.0", "license": "MIT", + "peer": true, "dependencies": { "@types/react": "*" }, @@ -20788,6 +21394,7 @@ "node_modules/react-router": { "version": "5.3.4", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.13", "history": "^4.9.0", @@ -23238,6 +23845,7 @@ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.18.tgz", "integrity": "sha512-Mvf3gJFzZCkhjY2Y/Fx9z1m3dxbza0uI9H1CbNZm/jSHCojzJhQ0R7bByrlFJINnMzz/gPulpoFFGymNwrsMcw==", "license": "MIT", + "peer": true, "dependencies": { "@emotion/is-prop-valid": "1.2.2", "@emotion/unitless": "0.8.1", @@ -23522,6 +24130,7 @@ "node_modules/terser-webpack-plugin/node_modules/ajv": { "version": "6.12.6", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -23950,6 +24559,7 @@ "version": "10.9.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -23995,7 +24605,8 @@ }, "node_modules/tslib": { "version": "2.8.0", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tty-browserify": { "version": "0.0.1", @@ -24476,6 +25087,7 @@ "node_modules/url-loader/node_modules/ajv": { "version": "6.12.6", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -24808,6 +25420,7 @@ "node_modules/webpack": { "version": "5.95.0", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -25088,6 +25701,7 @@ "node_modules/webpack/node_modules/ajv": { "version": "6.12.6", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -25611,6 +26225,7 @@ "version": "4.1.12", "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From ea720c61693d74462966efe1260f5f37f6061c7c Mon Sep 17 00:00:00 2001 From: jhickmanit Date: Mon, 24 Nov 2025 22:59:18 -0800 Subject: [PATCH 3/9] fix: broke out of mdx code block and didn't close tab items --- .../guides/passkey-integration-web-mobile.mdx | 96 ++++++++----------- docs/kratos/passwordless/05_passkeys.mdx | 2 +- 2 files changed, 43 insertions(+), 55 deletions(-) diff --git a/docs/kratos/guides/passkey-integration-web-mobile.mdx b/docs/kratos/guides/passkey-integration-web-mobile.mdx index f68070d284..180414379c 100644 --- a/docs/kratos/guides/passkey-integration-web-mobile.mdx +++ b/docs/kratos/guides/passkey-integration-web-mobile.mdx @@ -5,24 +5,27 @@ sidebar_label: Passkeys implementation slug: passkeys-mobile-web-implementation --- +import Tabs from "@theme/Tabs" +import TabItem from "@theme/TabItem" + ## Implementing passkeys for web and mobile -This guide covers how to implement passkey authentication in your applications using Ory Kratos or Ory Network. Passkeys provide a -passwordless authentication experience using WebAuthn across web browsers and mobile platforms. +This guide covers how to implement passkey authentication in your applications using Ory Kratos or Ory Network. Passkeys provide a passwordless authentication experience using WebAuthn across web browsers and mobile platforms. -:::info This page assumes you have already configured the passkey method in your Ory configuration. See the -[Passkeys overview](../passwordless/passkeys) for initial setup instructions. ::: +:::info +This page assumes you have already configured the passkey method in your Ory configuration. See the [Passkeys overview](../passwordless/passkeys) for initial setup instructions. +::: -:::note Code examples in this guide are illustrative and likely need adjustments based on your specific configuration, identity -schema, and application requirements. ::: +:::note +Code examples in this guide are illustrative and likely need adjustments based on your specific configuration, identity schema, and application requirements. +::: ## Overview Passkey implementation differs between platforms: - Web applications use browser-native WebAuthn APIs with JavaScript. -- Mobile applications use platform-specific credential management APIs (iOS AuthenticationServices, Android CredentialManager) - with Ory's JSON API endpoints. +- Mobile applications use platform-specific credential management APIs (iOS AuthenticationServices, Android CredentialManager) with Ory's JSON API endpoints. This guide focuses on the integration patterns for each platform. @@ -32,8 +35,7 @@ For web applications, you can use the browser's native WebAuthn API to create an ### Using Ory's webauthn.js -Ory provides a `webauthn.js` helper script that simplifies WebAuthn integration in browser flows. When you initialize a -registration or login flow through the browser, Ory automatically injects the necessary JavaScript to handle passkey operations. +Ory provides a `webauthn.js` helper script that simplifies WebAuthn integration in browser flows. When you initialize a registration or login flow through the browser, Ory automatically injects the necessary JavaScript to handle passkey operations. ```html @@ -47,8 +49,7 @@ The script automatically: - Calls `navigator.credentials.get()` for authentication. - Submits the WebAuthn response back to Ory. -See [Custom UI Advanced Integration](../bring-your-own-ui/custom-ui-advanced-integration#passwordless-authentication) for details -on using `webauthn.js` in custom UIs. +See [Custom UI Advanced Integration](../bring-your-own-ui/custom-ui-advanced-integration#passwordless-authentication) for details on using `webauthn.js` in custom UIs. ### Manual WebAuthn integration @@ -66,8 +67,7 @@ The WebAuthn API is well-documented by the W3C and MDN: ## Mobile implementation -Mobile passkey implementation requires using platform-specific APIs and Ory's JSON API endpoints. Unlike browser flows, mobile -apps don't receive the `webauthn.js` script and must handle credential operations manually. +Mobile passkey implementation requires using platform-specific APIs and Ory's JSON API endpoints. Unlike browser flows, mobile apps don't receive the `webauthn.js` script and must handle credential operations manually. ### Platform requirements @@ -99,8 +99,7 @@ The domain in your entitlement must match the Relying Party ID in your Kratos pa #### Serve the apple-app-site-association file -You need to host an `apple-app-site-association` file at `https://{your_domain}/.well-known/apple-app-site-association` to allow -the application to register and authenticate with credentials associated with the Relying Party ID. +You need to host an `apple-app-site-association` file at `https://{your_domain}/.well-known/apple-app-site-association` to allow the application to register and authenticate with credentials associated with the Relying Party ID. Example `apple-app-site-association` file: @@ -112,17 +111,18 @@ Example `apple-app-site-association` file: } ``` -The value uses the format `{Application_Identifier_Prefix}.{Bundle_Identifier}`. Find your Application Identifier Prefix (Team ID) -in the [Apple Developer portal](https://developer.apple.com/account) under Membership. +The value uses the format `{Application_Identifier_Prefix}.{Bundle_Identifier}`. Find your Application Identifier Prefix (Team ID) in the [Apple Developer portal](https://developer.apple.com/account) under Membership. + +:::warning +Ory Network doesn't currently host `apple-app-site-association` files automatically. This feature is planned for future releases. +::: ```mdx-code-block ``` -:::warning Ory Network doesn't currently host `apple-app-site-association` files automatically. This feature is planned for future -releases. You must host this file on your own domain and configure your Kratos passkey settings to use that domain as the Relying -Party ID. ::: +You must host this file on your own domain and configure your Kratos passkey settings to use that domain as the Relying Party ID. ```mdx-code-block @@ -140,9 +140,7 @@ For self-hosted Kratos, serve the `apple-app-site-association` file at your auth #### Important constraints -The domain in your Associated Domains entitlement must exactly match the `rp.id` in your Kratos passkey configuration. The domain -must be accessible via HTTPS with a valid TLS certificate. The `apple-app-site-association` file must be served with -`Content-Type: application/json`. +The domain in your Associated Domains entitlement must exactly match the `rp.id` in your Kratos passkey configuration. The domain must be accessible via HTTPS with a valid TLS certificate. The `apple-app-site-association` file must be served with `Content-Type: application/json`. #### Apple documentation @@ -155,15 +153,17 @@ Android requires an `assetlinks.json` file to verify your app's relationship wit #### Serve the assetlinks.json file -You need to host an `assetlinks.json` file at `https://{your_domain}/.well-known/assetlinks.json` to verify your app's -relationship with your authentication domain. +You need to host an `assetlinks.json` file at `https://{your_domain}/.well-known/assetlinks.json` to verify your app's relationship with your authentication domain. Example `assetlinks.json` file: ```json [ { - "relation": ["delegate_permission/common.handle_all_urls", "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.yourapp", @@ -181,14 +181,16 @@ Generate your SHA-256 certificate fingerprint: keytool -list -v -keystore your-keystore.jks ``` +:::warning +Ory Network doesn't currently host `assetlinks.json` files automatically. This feature is planned for future releases. +::: + ```mdx-code-block ``` -:::warning Ory Network doesn't currently host `assetlinks.json` files automatically. This feature is planned for future releases. You must host this file on your own domain and configure your Kratos passkey settings to use that domain as the Relying Party ID. -::: ```mdx-code-block @@ -206,9 +208,7 @@ For self-hosted Kratos, serve the `assetlinks.json` file at your authentication #### Important constraints -The domain in your `assetlinks.json` must exactly match the `rp.id` in your Kratos passkey configuration. The domain must be -accessible via HTTPS with a valid TLS certificate. The file must be served with `Content-Type: application/json`. The package name -must match your Android app's `applicationId`. The SHA-256 fingerprint must match your app's signing key. +The domain in your `assetlinks.json` must exactly match the `rp.id` in your Kratos passkey configuration. The domain must be accessible via HTTPS with a valid TLS certificate. The file must be served with `Content-Type: application/json`. The package name must match your Android app's `applicationId`. The SHA-256 fingerprint must match your app's signing key. #### Android documentation @@ -218,8 +218,7 @@ must match your Android app's `applicationId`. The SHA-256 fingerprint must matc ### iOS implementation -iOS passkey support uses the [AuthenticationServices framework](https://developer.apple.com/documentation/authenticationservices). -Here's how to integrate with Ory's API. +iOS passkey support uses the [AuthenticationServices framework](https://developer.apple.com/documentation/authenticationservices). Here's how to integrate with Ory's API. #### Registration flow @@ -487,8 +486,7 @@ extension Data { ### Android implementation -Android passkey support uses the [Credential Manager API](https://developer.android.com/training/sign-in/passkeys). The -integration pattern is similar to iOS. +Android passkey support uses the [Credential Manager API](https://developer.android.com/training/sign-in/passkeys). The integration pattern is similar to iOS. #### Dependencies @@ -755,8 +753,7 @@ Store the session token for authenticated requests. On mobile, use secure storag } ``` -This error occurs when there is incorrect Base64URL encoding (using standard Base64 instead), missing required fields in the -credential response, or malformed JSON in `passkey_register` or `passkey_login` fields. +This error occurs when there is incorrect Base64URL encoding (using standard Base64 instead), missing required fields in the credential response, or malformed JSON in `passkey_register` or `passkey_login` fields. To resolve this, verify your Base64URL encoding and ensure all required WebAuthn response fields are included. @@ -773,12 +770,9 @@ To resolve this, verify your Base64URL encoding and ensure all required WebAuthn } ``` -This error occurs when the Associated Domains or `assetlinks.json` domain doesn't match Kratos `rp.id`, the AASA or -`assetlinks.json` file isn't properly served, or the application uses HTTP instead of HTTPS. +This error occurs when the Associated Domains or `assetlinks.json` domain doesn't match Kratos `rp.id`, the AASA or `assetlinks.json` file isn't properly served, or the application uses HTTP instead of HTTPS. -To resolve this, verify your Associated Domains entitlement matches your Kratos configuration. Test AASA file accessibility using -`curl https://ory.your-custom-domain.com/.well-known/apple-app-site-association`. Test `assetlinks.json` using -`curl https://ory.your-custom-domain.com/.well-known/assetlinks.json`. Ensure HTTPS with valid certificate. +To resolve this, verify your Associated Domains entitlement matches your Kratos configuration. Test AASA file accessibility using `curl https://ory.your-custom-domain.com/.well-known/apple-app-site-association`. Test `assetlinks.json` using `curl https://ory.your-custom-domain.com/.well-known/assetlinks.json`. Ensure HTTPS with valid certificate. #### Flow expired @@ -866,28 +860,22 @@ To troubleshoot this issue: Both iOS and Android require HTTPS for passkeys. HTTP domains fail silently or with cryptic errors. -To resolve this, use HTTPS with a valid TLS certificate. For local development, use a tool like [ngrok](https://ngrok.com/) to -create an HTTPS tunnel. +To resolve this, use HTTPS with a valid TLS certificate. For local development, use a tool like [ngrok](https://ngrok.com/) to create an HTTPS tunnel. ## Best practices ### Session management -Store session tokens securely using iOS Keychain or Android Keystore. Handle session expiration by checking session validity -before making authenticated requests. Implement token refresh using Ory's `/sessions/whoami` endpoint to verify sessions. +Store session tokens securely using iOS Keychain or Android Keystore. Handle session expiration by checking session validity before making authenticated requests. Implement token refresh using Ory's `/sessions/whoami` endpoint to verify sessions. ### User experience -Provide fallback options to allow users to sign in with other methods if passkeys fail. Handle errors gracefully by showing -user-friendly error messages. Support AutoFill on iOS 17+ and Android 14+ to enable AutoFill-assisted passkey sign-in for better -user experience. +Provide fallback options to allow users to sign in with other methods if passkeys fail. Handle errors gracefully by showing user-friendly error messages. Support AutoFill on iOS 17+ and Android 14+ to enable AutoFill-assisted passkey sign-in for better user experience. ### Testing -Test on physical devices as passkeys don't work reliably in simulators or emulators. Test with multiple accounts to verify -credential isolation. Test cross-platform to ensure passkeys created on one platform work on others via cloud sync. +Test on physical devices as passkeys don't work reliably in simulators or emulators. Test with multiple accounts to verify credential isolation. Test cross-platform to ensure passkeys created on one platform work on others via cloud sync. ## Next steps -Review the [Passkeys overview](../passwordless/passkeys) for configuration options. See [Self-Service Flows](../self-service) for -more flow details. Check out the [API documentation](../reference/api) for complete endpoint reference. +Review the [Passkeys overview](../passwordless/passkeys) for configuration options. See [Self-Service Flows](../self-service) for more flow details. Check out the [API documentation](../reference/api) for complete endpoint reference. \ No newline at end of file diff --git a/docs/kratos/passwordless/05_passkeys.mdx b/docs/kratos/passwordless/05_passkeys.mdx index 645bf6dfef..cdaf6f4864 100644 --- a/docs/kratos/passwordless/05_passkeys.mdx +++ b/docs/kratos/passwordless/05_passkeys.mdx @@ -158,7 +158,7 @@ platform: JSON API endpoints. For detailed implementation instructions, code examples, and best practices for both web and mobile platforms, see -[Implementing passkeys for web and mobile](../guides/passkeys-implementation.mdx). +[Implementing passkeys for web and mobile](../guides/passkey-integration-web-mobile.mdx). ## Passkeys with the WebAuthN strategy From b7479d5783d704253dc17c043ec2dc9ef41cd04e Mon Sep 17 00:00:00 2001 From: jhickmanit Date: Mon, 24 Nov 2025 23:07:22 -0800 Subject: [PATCH 4/9] chore: format --- .../guides/passkey-integration-web-mobile.mdx | 87 +++++++++++-------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/docs/kratos/guides/passkey-integration-web-mobile.mdx b/docs/kratos/guides/passkey-integration-web-mobile.mdx index 180414379c..ab5b46c1b9 100644 --- a/docs/kratos/guides/passkey-integration-web-mobile.mdx +++ b/docs/kratos/guides/passkey-integration-web-mobile.mdx @@ -10,22 +10,22 @@ import TabItem from "@theme/TabItem" ## Implementing passkeys for web and mobile -This guide covers how to implement passkey authentication in your applications using Ory Kratos or Ory Network. Passkeys provide a passwordless authentication experience using WebAuthn across web browsers and mobile platforms. +This guide covers how to implement passkey authentication in your applications using Ory Kratos or Ory Network. Passkeys provide a +passwordless authentication experience using WebAuthn across web browsers and mobile platforms. -:::info -This page assumes you have already configured the passkey method in your Ory configuration. See the [Passkeys overview](../passwordless/passkeys) for initial setup instructions. -::: +:::info This page assumes you have already configured the passkey method in your Ory configuration. See the +[Passkeys overview](../passwordless/passkeys) for initial setup instructions. ::: -:::note -Code examples in this guide are illustrative and likely need adjustments based on your specific configuration, identity schema, and application requirements. -::: +:::note Code examples in this guide are illustrative and likely need adjustments based on your specific configuration, identity +schema, and application requirements. ::: ## Overview Passkey implementation differs between platforms: - Web applications use browser-native WebAuthn APIs with JavaScript. -- Mobile applications use platform-specific credential management APIs (iOS AuthenticationServices, Android CredentialManager) with Ory's JSON API endpoints. +- Mobile applications use platform-specific credential management APIs (iOS AuthenticationServices, Android CredentialManager) + with Ory's JSON API endpoints. This guide focuses on the integration patterns for each platform. @@ -35,7 +35,8 @@ For web applications, you can use the browser's native WebAuthn API to create an ### Using Ory's webauthn.js -Ory provides a `webauthn.js` helper script that simplifies WebAuthn integration in browser flows. When you initialize a registration or login flow through the browser, Ory automatically injects the necessary JavaScript to handle passkey operations. +Ory provides a `webauthn.js` helper script that simplifies WebAuthn integration in browser flows. When you initialize a +registration or login flow through the browser, Ory automatically injects the necessary JavaScript to handle passkey operations. ```html @@ -49,7 +50,8 @@ The script automatically: - Calls `navigator.credentials.get()` for authentication. - Submits the WebAuthn response back to Ory. -See [Custom UI Advanced Integration](../bring-your-own-ui/custom-ui-advanced-integration#passwordless-authentication) for details on using `webauthn.js` in custom UIs. +See [Custom UI Advanced Integration](../bring-your-own-ui/custom-ui-advanced-integration#passwordless-authentication) for details +on using `webauthn.js` in custom UIs. ### Manual WebAuthn integration @@ -67,7 +69,8 @@ The WebAuthn API is well-documented by the W3C and MDN: ## Mobile implementation -Mobile passkey implementation requires using platform-specific APIs and Ory's JSON API endpoints. Unlike browser flows, mobile apps don't receive the `webauthn.js` script and must handle credential operations manually. +Mobile passkey implementation requires using platform-specific APIs and Ory's JSON API endpoints. Unlike browser flows, mobile +apps don't receive the `webauthn.js` script and must handle credential operations manually. ### Platform requirements @@ -99,7 +102,8 @@ The domain in your entitlement must match the Relying Party ID in your Kratos pa #### Serve the apple-app-site-association file -You need to host an `apple-app-site-association` file at `https://{your_domain}/.well-known/apple-app-site-association` to allow the application to register and authenticate with credentials associated with the Relying Party ID. +You need to host an `apple-app-site-association` file at `https://{your_domain}/.well-known/apple-app-site-association` to allow +the application to register and authenticate with credentials associated with the Relying Party ID. Example `apple-app-site-association` file: @@ -111,11 +115,11 @@ Example `apple-app-site-association` file: } ``` -The value uses the format `{Application_Identifier_Prefix}.{Bundle_Identifier}`. Find your Application Identifier Prefix (Team ID) in the [Apple Developer portal](https://developer.apple.com/account) under Membership. +The value uses the format `{Application_Identifier_Prefix}.{Bundle_Identifier}`. Find your Application Identifier Prefix (Team ID) +in the [Apple Developer portal](https://developer.apple.com/account) under Membership. -:::warning -Ory Network doesn't currently host `apple-app-site-association` files automatically. This feature is planned for future releases. -::: +:::warning Ory Network doesn't currently host `apple-app-site-association` files automatically. This feature is planned for future +releases. ::: ```mdx-code-block @@ -140,7 +144,9 @@ For self-hosted Kratos, serve the `apple-app-site-association` file at your auth #### Important constraints -The domain in your Associated Domains entitlement must exactly match the `rp.id` in your Kratos passkey configuration. The domain must be accessible via HTTPS with a valid TLS certificate. The `apple-app-site-association` file must be served with `Content-Type: application/json`. +The domain in your Associated Domains entitlement must exactly match the `rp.id` in your Kratos passkey configuration. The domain +must be accessible via HTTPS with a valid TLS certificate. The `apple-app-site-association` file must be served with +`Content-Type: application/json`. #### Apple documentation @@ -153,17 +159,15 @@ Android requires an `assetlinks.json` file to verify your app's relationship wit #### Serve the assetlinks.json file -You need to host an `assetlinks.json` file at `https://{your_domain}/.well-known/assetlinks.json` to verify your app's relationship with your authentication domain. +You need to host an `assetlinks.json` file at `https://{your_domain}/.well-known/assetlinks.json` to verify your app's +relationship with your authentication domain. Example `assetlinks.json` file: ```json [ { - "relation": [ - "delegate_permission/common.handle_all_urls", - "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.yourapp", @@ -181,8 +185,7 @@ Generate your SHA-256 certificate fingerprint: keytool -list -v -keystore your-keystore.jks ``` -:::warning -Ory Network doesn't currently host `assetlinks.json` files automatically. This feature is planned for future releases. +:::warning Ory Network doesn't currently host `assetlinks.json` files automatically. This feature is planned for future releases. ::: ```mdx-code-block @@ -208,7 +211,9 @@ For self-hosted Kratos, serve the `assetlinks.json` file at your authentication #### Important constraints -The domain in your `assetlinks.json` must exactly match the `rp.id` in your Kratos passkey configuration. The domain must be accessible via HTTPS with a valid TLS certificate. The file must be served with `Content-Type: application/json`. The package name must match your Android app's `applicationId`. The SHA-256 fingerprint must match your app's signing key. +The domain in your `assetlinks.json` must exactly match the `rp.id` in your Kratos passkey configuration. The domain must be +accessible via HTTPS with a valid TLS certificate. The file must be served with `Content-Type: application/json`. The package name +must match your Android app's `applicationId`. The SHA-256 fingerprint must match your app's signing key. #### Android documentation @@ -218,7 +223,8 @@ The domain in your `assetlinks.json` must exactly match the `rp.id` in your Krat ### iOS implementation -iOS passkey support uses the [AuthenticationServices framework](https://developer.apple.com/documentation/authenticationservices). Here's how to integrate with Ory's API. +iOS passkey support uses the [AuthenticationServices framework](https://developer.apple.com/documentation/authenticationservices). +Here's how to integrate with Ory's API. #### Registration flow @@ -486,7 +492,8 @@ extension Data { ### Android implementation -Android passkey support uses the [Credential Manager API](https://developer.android.com/training/sign-in/passkeys). The integration pattern is similar to iOS. +Android passkey support uses the [Credential Manager API](https://developer.android.com/training/sign-in/passkeys). The +integration pattern is similar to iOS. #### Dependencies @@ -753,7 +760,8 @@ Store the session token for authenticated requests. On mobile, use secure storag } ``` -This error occurs when there is incorrect Base64URL encoding (using standard Base64 instead), missing required fields in the credential response, or malformed JSON in `passkey_register` or `passkey_login` fields. +This error occurs when there is incorrect Base64URL encoding (using standard Base64 instead), missing required fields in the +credential response, or malformed JSON in `passkey_register` or `passkey_login` fields. To resolve this, verify your Base64URL encoding and ensure all required WebAuthn response fields are included. @@ -770,9 +778,12 @@ To resolve this, verify your Base64URL encoding and ensure all required WebAuthn } ``` -This error occurs when the Associated Domains or `assetlinks.json` domain doesn't match Kratos `rp.id`, the AASA or `assetlinks.json` file isn't properly served, or the application uses HTTP instead of HTTPS. +This error occurs when the Associated Domains or `assetlinks.json` domain doesn't match Kratos `rp.id`, the AASA or +`assetlinks.json` file isn't properly served, or the application uses HTTP instead of HTTPS. -To resolve this, verify your Associated Domains entitlement matches your Kratos configuration. Test AASA file accessibility using `curl https://ory.your-custom-domain.com/.well-known/apple-app-site-association`. Test `assetlinks.json` using `curl https://ory.your-custom-domain.com/.well-known/assetlinks.json`. Ensure HTTPS with valid certificate. +To resolve this, verify your Associated Domains entitlement matches your Kratos configuration. Test AASA file accessibility using +`curl https://ory.your-custom-domain.com/.well-known/apple-app-site-association`. Test `assetlinks.json` using +`curl https://ory.your-custom-domain.com/.well-known/assetlinks.json`. Ensure HTTPS with valid certificate. #### Flow expired @@ -860,22 +871,28 @@ To troubleshoot this issue: Both iOS and Android require HTTPS for passkeys. HTTP domains fail silently or with cryptic errors. -To resolve this, use HTTPS with a valid TLS certificate. For local development, use a tool like [ngrok](https://ngrok.com/) to create an HTTPS tunnel. +To resolve this, use HTTPS with a valid TLS certificate. For local development, use a tool like [ngrok](https://ngrok.com/) to +create an HTTPS tunnel. ## Best practices ### Session management -Store session tokens securely using iOS Keychain or Android Keystore. Handle session expiration by checking session validity before making authenticated requests. Implement token refresh using Ory's `/sessions/whoami` endpoint to verify sessions. +Store session tokens securely using iOS Keychain or Android Keystore. Handle session expiration by checking session validity +before making authenticated requests. Implement token refresh using Ory's `/sessions/whoami` endpoint to verify sessions. ### User experience -Provide fallback options to allow users to sign in with other methods if passkeys fail. Handle errors gracefully by showing user-friendly error messages. Support AutoFill on iOS 17+ and Android 14+ to enable AutoFill-assisted passkey sign-in for better user experience. +Provide fallback options to allow users to sign in with other methods if passkeys fail. Handle errors gracefully by showing +user-friendly error messages. Support AutoFill on iOS 17+ and Android 14+ to enable AutoFill-assisted passkey sign-in for better +user experience. ### Testing -Test on physical devices as passkeys don't work reliably in simulators or emulators. Test with multiple accounts to verify credential isolation. Test cross-platform to ensure passkeys created on one platform work on others via cloud sync. +Test on physical devices as passkeys don't work reliably in simulators or emulators. Test with multiple accounts to verify +credential isolation. Test cross-platform to ensure passkeys created on one platform work on others via cloud sync. ## Next steps -Review the [Passkeys overview](../passwordless/passkeys) for configuration options. See [Self-Service Flows](../self-service) for more flow details. Check out the [API documentation](../reference/api) for complete endpoint reference. \ No newline at end of file +Review the [Passkeys overview](../passwordless/passkeys) for configuration options. See [Self-Service Flows](../self-service) for +more flow details. Check out the [API documentation](../reference/api) for complete endpoint reference. From 04207237cf1d1ee944f3fb9c0f8da0a1235f5cfe Mon Sep 17 00:00:00 2001 From: Jeff Hickman Date: Tue, 25 Nov 2025 12:02:26 -0800 Subject: [PATCH 5/9] fix: Apply suggestions from code review to fix format Co-authored-by: Jonas Hungershausen --- .../guides/passkey-integration-web-mobile.mdx | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/kratos/guides/passkey-integration-web-mobile.mdx b/docs/kratos/guides/passkey-integration-web-mobile.mdx index ab5b46c1b9..aae491d5d2 100644 --- a/docs/kratos/guides/passkey-integration-web-mobile.mdx +++ b/docs/kratos/guides/passkey-integration-web-mobile.mdx @@ -13,11 +13,19 @@ import TabItem from "@theme/TabItem" This guide covers how to implement passkey authentication in your applications using Ory Kratos or Ory Network. Passkeys provide a passwordless authentication experience using WebAuthn across web browsers and mobile platforms. -:::info This page assumes you have already configured the passkey method in your Ory configuration. See the -[Passkeys overview](../passwordless/passkeys) for initial setup instructions. ::: +:::info -:::note Code examples in this guide are illustrative and likely need adjustments based on your specific configuration, identity -schema, and application requirements. ::: +This page assumes you have already configured the passkey method in your Ory configuration. See the +[Passkeys overview](../passwordless/passkeys) for initial setup instructions. + +::: + +:::note + +Code examples in this guide are illustrative and likely need adjustments based on your specific configuration, identity +schema, and application requirements. + +::: ## Overview @@ -118,8 +126,11 @@ Example `apple-app-site-association` file: The value uses the format `{Application_Identifier_Prefix}.{Bundle_Identifier}`. Find your Application Identifier Prefix (Team ID) in the [Apple Developer portal](https://developer.apple.com/account) under Membership. -:::warning Ory Network doesn't currently host `apple-app-site-association` files automatically. This feature is planned for future -releases. ::: +:::warning + +Ory Network doesn't currently host `apple-app-site-association` files automatically. + +::: ```mdx-code-block From de2171595ef917a42041a228afb10b8f03c1ce2f Mon Sep 17 00:00:00 2001 From: jhickmanit Date: Tue, 25 Nov 2025 12:09:15 -0800 Subject: [PATCH 6/9] chore: format --- .../kratos/guides/passkey-integration-web-mobile.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/kratos/guides/passkey-integration-web-mobile.mdx b/docs/kratos/guides/passkey-integration-web-mobile.mdx index aae491d5d2..457a4bebc1 100644 --- a/docs/kratos/guides/passkey-integration-web-mobile.mdx +++ b/docs/kratos/guides/passkey-integration-web-mobile.mdx @@ -13,17 +13,17 @@ import TabItem from "@theme/TabItem" This guide covers how to implement passkey authentication in your applications using Ory Kratos or Ory Network. Passkeys provide a passwordless authentication experience using WebAuthn across web browsers and mobile platforms. -:::info +:::info This page assumes you have already configured the passkey method in your Ory configuration. See the -[Passkeys overview](../passwordless/passkeys) for initial setup instructions. +[Passkeys overview](../passwordless/passkeys) for initial setup instructions. ::: -:::note +:::note -Code examples in this guide are illustrative and likely need adjustments based on your specific configuration, identity -schema, and application requirements. +Code examples in this guide are illustrative and likely need adjustments based on your specific configuration, identity schema, +and application requirements. ::: @@ -126,7 +126,7 @@ Example `apple-app-site-association` file: The value uses the format `{Application_Identifier_Prefix}.{Bundle_Identifier}`. Find your Application Identifier Prefix (Team ID) in the [Apple Developer portal](https://developer.apple.com/account) under Membership. -:::warning +:::warning Ory Network doesn't currently host `apple-app-site-association` files automatically. From 8db53516c84c1d82b6cd75a483c95f3f91d38494 Mon Sep 17 00:00:00 2001 From: Jeff Hickman Date: Wed, 26 Nov 2025 09:10:48 -0800 Subject: [PATCH 7/9] chore: Apply suggestions from code review for style Co-authored-by: Vincent --- docs/kratos/guides/passkey-integration-web-mobile.mdx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/kratos/guides/passkey-integration-web-mobile.mdx b/docs/kratos/guides/passkey-integration-web-mobile.mdx index 457a4bebc1..06ea6df483 100644 --- a/docs/kratos/guides/passkey-integration-web-mobile.mdx +++ b/docs/kratos/guides/passkey-integration-web-mobile.mdx @@ -8,18 +8,13 @@ slug: passkeys-mobile-web-implementation import Tabs from "@theme/Tabs" import TabItem from "@theme/TabItem" -## Implementing passkeys for web and mobile This guide covers how to implement passkey authentication in your applications using Ory Kratos or Ory Network. Passkeys provide a passwordless authentication experience using WebAuthn across web browsers and mobile platforms. -:::info - This page assumes you have already configured the passkey method in your Ory configuration. See the [Passkeys overview](../passwordless/passkeys) for initial setup instructions. -::: - :::note Code examples in this guide are illustrative and likely need adjustments based on your specific configuration, identity schema, From b5a76d45692288a4c4fd7ab5ab909a35a4d89dab Mon Sep 17 00:00:00 2001 From: jhickmanit Date: Wed, 26 Nov 2025 09:20:54 -0800 Subject: [PATCH 8/9] chore: format --- docs/kratos/guides/passkey-integration-web-mobile.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/kratos/guides/passkey-integration-web-mobile.mdx b/docs/kratos/guides/passkey-integration-web-mobile.mdx index 06ea6df483..5a160dfb72 100644 --- a/docs/kratos/guides/passkey-integration-web-mobile.mdx +++ b/docs/kratos/guides/passkey-integration-web-mobile.mdx @@ -8,7 +8,6 @@ slug: passkeys-mobile-web-implementation import Tabs from "@theme/Tabs" import TabItem from "@theme/TabItem" - This guide covers how to implement passkey authentication in your applications using Ory Kratos or Ory Network. Passkeys provide a passwordless authentication experience using WebAuthn across web browsers and mobile platforms. From e45a255c0d312dbe27587eabc78b9656826eec08 Mon Sep 17 00:00:00 2001 From: vinckr Date: Wed, 26 Nov 2025 15:55:36 -0300 Subject: [PATCH 9/9] fix: add to sidebar --- docs/kratos/passwordless/05_passkeys.mdx | 2 +- .../06_passkeys-mobile.mdx} | 14 +++++++------- .../passwordless/{06_code.mdx => 07_code.mdx} | 0 src/sidebar.ts | 1 + 4 files changed, 9 insertions(+), 8 deletions(-) rename docs/kratos/{guides/passkey-integration-web-mobile.mdx => passwordless/06_passkeys-mobile.mdx} (98%) rename docs/kratos/passwordless/{06_code.mdx => 07_code.mdx} (100%) diff --git a/docs/kratos/passwordless/05_passkeys.mdx b/docs/kratos/passwordless/05_passkeys.mdx index cdaf6f4864..6d2dcad5c1 100644 --- a/docs/kratos/passwordless/05_passkeys.mdx +++ b/docs/kratos/passwordless/05_passkeys.mdx @@ -158,7 +158,7 @@ platform: JSON API endpoints. For detailed implementation instructions, code examples, and best practices for both web and mobile platforms, see -[Implementing passkeys for web and mobile](../guides/passkey-integration-web-mobile.mdx). +[Implementing passkeys for web and mobile](./06_passkeys-mobile.mdx). ## Passkeys with the WebAuthN strategy diff --git a/docs/kratos/guides/passkey-integration-web-mobile.mdx b/docs/kratos/passwordless/06_passkeys-mobile.mdx similarity index 98% rename from docs/kratos/guides/passkey-integration-web-mobile.mdx rename to docs/kratos/passwordless/06_passkeys-mobile.mdx index 5a160dfb72..2f5533939a 100644 --- a/docs/kratos/guides/passkey-integration-web-mobile.mdx +++ b/docs/kratos/passwordless/06_passkeys-mobile.mdx @@ -1,7 +1,7 @@ --- -id: passkeys-implementation +id: passkeys-mobile title: Implement passkey authentication in web and mobile applications -sidebar_label: Passkeys implementation +sidebar_label: Passkeys for mobile slug: passkeys-mobile-web-implementation --- @@ -12,7 +12,7 @@ This guide covers how to implement passkey authentication in your applications u passwordless authentication experience using WebAuthn across web browsers and mobile platforms. This page assumes you have already configured the passkey method in your Ory configuration. See the -[Passkeys overview](../passwordless/passkeys) for initial setup instructions. +[Passkeys overview](./05_passkeys.mdx) for initial setup instructions. :::note @@ -52,8 +52,8 @@ The script automatically: - Calls `navigator.credentials.get()` for authentication. - Submits the WebAuthn response back to Ory. -See [Custom UI Advanced Integration](../bring-your-own-ui/custom-ui-advanced-integration#passwordless-authentication) for details -on using `webauthn.js` in custom UIs. +See [Custom UI Advanced Integration](../bring-your-own-ui/custom-ui-advanced-integration#passwordless-authentication.mdx) for +details on using `webauthn.js` in custom UIs. ### Manual WebAuthn integration @@ -899,5 +899,5 @@ credential isolation. Test cross-platform to ensure passkeys created on one plat ## Next steps -Review the [Passkeys overview](../passwordless/passkeys) for configuration options. See [Self-Service Flows](../self-service) for -more flow details. Check out the [API documentation](../reference/api) for complete endpoint reference. +Review the [Passkeys overview](./05_passkeys.mdx) for configuration options. See [Self-Service Flows](../self-service.mdx) for +more flow details. Check out the [API documentation](../reference/api.mdx) for complete endpoint reference. diff --git a/docs/kratos/passwordless/06_code.mdx b/docs/kratos/passwordless/07_code.mdx similarity index 100% rename from docs/kratos/passwordless/06_code.mdx rename to docs/kratos/passwordless/07_code.mdx diff --git a/src/sidebar.ts b/src/sidebar.ts index 8dd49c751c..35d0a3509c 100644 --- a/src/sidebar.ts +++ b/src/sidebar.ts @@ -358,6 +358,7 @@ const kratos: SidebarItemsConfig = [ "kratos/passwordless/passwordless", "kratos/passwordless/one-time-code", "kratos/passwordless/passkeys", + "kratos/passwordless/passkeys-mobile", "kratos/organizations/organizations", "kratos/emails-sms/custom-email-templates", ],