diff --git a/docs/kratos/passwordless/05_passkeys.mdx b/docs/kratos/passwordless/05_passkeys.mdx index 79c3c3674..6d2dcad5c 100644 --- a/docs/kratos/passwordless/05_passkeys.mdx +++ b/docs/kratos/passwordless/05_passkeys.mdx @@ -148,6 +148,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: + +- 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](./06_passkeys-mobile.mdx). + ## Passkeys with the WebAuthN strategy ### Configuration diff --git a/docs/kratos/passwordless/06_passkeys-mobile.mdx b/docs/kratos/passwordless/06_passkeys-mobile.mdx new file mode 100644 index 000000000..2f5533939 --- /dev/null +++ b/docs/kratos/passwordless/06_passkeys-mobile.mdx @@ -0,0 +1,903 @@ +--- +id: passkeys-mobile +title: Implement passkey authentication in web and mobile applications +sidebar_label: Passkeys for mobile +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. + +This page assumes you have already configured the passkey method in your Ory configuration. See the +[Passkeys overview](./05_passkeys.mdx) 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.mdx) 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. + +:::warning + +Ory Network doesn't currently host `apple-app-site-association` files automatically. + +::: + +```mdx-code-block + + +``` + +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 +``` + +:::warning Ory Network doesn't currently host `assetlinks.json` files automatically. This feature is planned for future releases. +::: + +```mdx-code-block + + +``` + +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](./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/package-lock.json b/package-lock.json index e9da96630..a0c7b8856 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" } diff --git a/src/sidebar.ts b/src/sidebar.ts index 8dd49c751..35d0a3509 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", ],