diff --git a/apps/docs/src/config/llmsCustomSets.ts b/apps/docs/src/config/llmsCustomSets.ts index 54b4bd2ed..4d5b57625 100644 --- a/apps/docs/src/config/llmsCustomSets.ts +++ b/apps/docs/src/config/llmsCustomSets.ts @@ -66,6 +66,7 @@ Plugin Native Purchases|in-app purchases and subscriptions plugin|docs/plugins/n Plugin Navigation Bar|Android navigation bar customization plugin|docs/plugins/navigation-bar/** Plugin NFC|NFC reading and writing plugin|docs/plugins/nfc/** Plugin Pay|Apple Pay and Google Pay integration plugin|docs/plugins/pay/** +Plugin Passkey|browser-style WebAuthn passkey plugin with native shims and generated platform configuration|docs/plugins/passkey/** Plugin Privacy Screen|privacy screen plugin for hiding app content in system previews and screenshots|docs/plugins/privacy-screen/** Plugin PDF Generator|PDF generation plugin|docs/plugins/pdf-generator/** Plugin Pedometer|step counting pedometer plugin|docs/plugins/pedometer/** diff --git a/apps/docs/src/config/sidebar.mjs b/apps/docs/src/config/sidebar.mjs index 1f5267835..be825aeb5 100644 --- a/apps/docs/src/config/sidebar.mjs +++ b/apps/docs/src/config/sidebar.mjs @@ -133,6 +133,11 @@ const pluginEntries = [ ['Navigation Bar', 'navigation-bar'], ['NFC', 'nfc'], ['Pay', 'pay'], + [ + 'Passkey', + 'passkey', + [linkItem('iOS setup', '/docs/plugins/passkey/ios'), linkItem('Android setup', '/docs/plugins/passkey/android'), linkItem('Backend notes', '/docs/plugins/passkey/backend')], + ], ['Privacy Screen', 'privacy-screen', [linkItem('iOS behavior', '/docs/plugins/privacy-screen/ios'), linkItem('Android behavior', '/docs/plugins/privacy-screen/android')]], ['PDF Generator', 'pdf-generator'], ['Pedometer', 'pedometer'], diff --git a/apps/docs/src/content/docs/docs/plugins/passkey/android.mdx b/apps/docs/src/content/docs/docs/plugins/passkey/android.mdx new file mode 100644 index 000000000..8c5abbb80 --- /dev/null +++ b/apps/docs/src/content/docs/docs/plugins/passkey/android.mdx @@ -0,0 +1,59 @@ +--- +title: Android Setup +description: Configure passkeys on Android for @capgo/capacitor-passkey with Digital Asset Links and assetlinks.json. +sidebar: + order: 4 +--- + +On Android, passkeys work with your website when the app and the relying-party domain are connected through Digital Asset Links. + +## What the plugin handles + +After you add the plugin config and run `bunx cap sync`, the plugin patches the generated Android host project: + +- injects the `asset_statements` manifest metadata +- writes the generated string resource referenced by that metadata + +## What you still need to host + +You must publish `assetlinks.json` on the relying-party domain: + +```text +https://signin.example.com/.well-known/assetlinks.json +``` + +Example: + +```json +[ + { + "relation": [ + "delegate_permission/common.handle_all_urls", + "delegate_permission/common.get_login_creds" + ], + "target": { + "namespace": "android_app", + "package_name": "app.capgo.passkey.example", + "sha256_cert_fingerprints": [ + "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99" + ] + } + } +] +``` + +## Checklist + +1. Set `origin` and `domains` in `plugins.CapacitorPasskey` in `capacitor.config.*`. +2. Run `bunx cap sync`. +3. Use your real Android package name in `assetlinks.json`. +4. Add every signing certificate fingerprint you need, including debug or internal signing keys if you test those builds. +5. Host the file on the same domain you use as the relying-party ID. + +## Important behavior difference from a browser + +With Digital Asset Links configured, Android can use the same relying party and passkeys as your website. The remaining difference is the literal origin reported in native `clientDataJSON`. + +- A normal Android app does not behave like a privileged browser. +- The assertion origin can be tied to the Android app signature instead of your website origin. +- If your backend strictly validates `clientDataJSON.origin`, accept the Android app origin alongside the website origin. diff --git a/apps/docs/src/content/docs/docs/plugins/passkey/backend.mdx b/apps/docs/src/content/docs/docs/plugins/passkey/backend.mdx new file mode 100644 index 000000000..5f6151ca9 --- /dev/null +++ b/apps/docs/src/content/docs/docs/plugins/passkey/backend.mdx @@ -0,0 +1,57 @@ +--- +title: Backend Notes +description: Understand the backend contract for @capgo/capacitor-passkey, including WebAuthn challenge handling and Android origin validation. +sidebar: + order: 5 +--- + +Your backend still owns the normal WebAuthn ceremony: + +- generate registration and authentication challenges +- verify attestation and assertion responses +- enforce relying-party ID and challenge validation +- store credentials and counters the same way you would for a browser flow + +## What stays the same + +The plugin is designed to preserve the front-end shape of your existing WebAuthn code. + +- On the web, it forwards to the real browser WebAuthn API. +- On native Capacitor, it returns browser-like credential objects backed by native passkey APIs. +- Your backend can keep the same challenge and verification pipeline. + +## What changes on Android + +Android native passkeys are not identical to a browser trust model. + +- Digital Asset Links let Android share the same relying party and credential ecosystem as your website. +- The literal `clientDataJSON.origin` value can still differ from the website origin. +- If your server rejects anything except `https://your-domain`, Android native assertions can fail even when the passkey is otherwise valid. + +## Recommended backend rule + +Allow the expected browser origin and the expected Android app origin for the same relying party when you support native Android passkeys. + +That gives you: + +- browser support for the website +- native passkey support in the Capacitor app +- one passkey ecosystem for the same relying-party domain + +## If you need direct JSON-safe calls + +If your backend already returns `PublicKeyCredentialCreationOptionsJSON` and `PublicKeyCredentialRequestOptionsJSON`, you can also use the direct plugin API instead of the browser-style shim: + +```ts +import { CapacitorPasskey } from '@capgo/capacitor-passkey'; + +const registration = await CapacitorPasskey.createCredential({ + origin: 'https://signin.example.com', + publicKey: registrationOptionsFromBackend, +}); + +const authentication = await CapacitorPasskey.getCredential({ + origin: 'https://signin.example.com', + publicKey: requestOptionsFromBackend, +}); +``` diff --git a/apps/docs/src/content/docs/docs/plugins/passkey/getting-started.mdx b/apps/docs/src/content/docs/docs/plugins/passkey/getting-started.mdx new file mode 100644 index 000000000..a61fa19ab --- /dev/null +++ b/apps/docs/src/content/docs/docs/plugins/passkey/getting-started.mdx @@ -0,0 +1,96 @@ +--- +title: Getting Started +description: Install @capgo/capacitor-passkey, configure the plugin once, and keep your browser-style WebAuthn code in a Capacitor app. +sidebar: + order: 2 +--- + +import { Steps, Card, CardGrid } from '@astrojs/starlight/components'; +import { PackageManagers } from 'starlight-package-managers' + + +1. **Install the package** + + +2. **Sync native projects** + + +3. **Add the plugin config** + + ```ts + import type { CapacitorConfig } from '@capacitor/cli'; + + const config: CapacitorConfig = { + appId: 'app.capgo.passkey.example', + appName: 'My App', + webDir: 'dist', + plugins: { + CapacitorPasskey: { + origin: 'https://signin.example.com', + autoShim: true, + domains: ['signin.example.com'], + }, + }, + }; + + export default config; + ``` + +4. **Import the shim once** + + ```ts + import '@capgo/capacitor-passkey/auto'; + ``` + +5. **Keep your normal WebAuthn flow** + + ```ts + const registration = await navigator.credentials.create({ + publicKey: registrationOptions, + }); + + const authentication = await navigator.credentials.get({ + publicKey: requestOptions, + }); + ``` + + +## What the plugin config does + +The config is read from `plugins.CapacitorPasskey` in `capacitor.config.*`. + +- `origin`: primary HTTPS relying-party origin used by the shim and direct API +- `domains`: extra relying-party hostnames to patch into native config during sync +- `autoShim`: defaults to `true` when you use `@capgo/capacitor-passkey/auto` + +## What sync patches for you + +When you run `bunx cap sync`, the plugin updates the generated native host project: + +- iOS: associated domains entitlements and Xcode entitlements wiring when needed +- Android: `asset_statements` metadata and the generated resource used by the manifest + +The hook does not publish your website trust files for you. You still need to host: + +- `https://your-domain/.well-known/apple-app-site-association` +- `https://your-domain/.well-known/assetlinks.json` + +## Platform guides + + + + + Associated Domains and `apple-app-site-association`. + + + + + Digital Asset Links and `assetlinks.json`. + + + + + Origin validation and Android caveats. + + + diff --git a/apps/docs/src/content/docs/docs/plugins/passkey/index.mdx b/apps/docs/src/content/docs/docs/plugins/passkey/index.mdx new file mode 100644 index 000000000..6b9a54574 --- /dev/null +++ b/apps/docs/src/content/docs/docs/plugins/passkey/index.mdx @@ -0,0 +1,38 @@ +--- +title: "@capgo/capacitor-passkey" +description: Passkeys for Capacitor with a browser-style WebAuthn shim, minimal JavaScript changes, and native setup generated during sync. +tableOfContents: false +next: false +prev: false +sidebar: + order: 1 + label: "Introduction" +hero: + tagline: Keep your browser-style WebAuthn code in a Capacitor app while the plugin handles native passkey calls and native host patching. + actions: + - text: Get started + link: /docs/plugins/passkey/getting-started/ + icon: right-arrow + variant: primary + - text: GitHub + link: https://github.com/Cap-go/capacitor-passkey/ + icon: external + variant: minimal +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + + + + Keep `navigator.credentials.create()` and `navigator.credentials.get()` in your app instead of rewriting your passkey flow around a custom API. + + + Add plugin config once, import `@capgo/capacitor-passkey/auto`, and keep the rest of your WebAuthn code close to the browser implementation. + + + The plugin patches the generated iOS and Android host projects during sync so you do not need to keep hand-editing those files. + + + Follow the [Getting Started](/docs/plugins/passkey/getting-started/), [iOS setup](/docs/plugins/passkey/ios/), [Android setup](/docs/plugins/passkey/android/), and [backend notes](/docs/plugins/passkey/backend/). + + diff --git a/apps/docs/src/content/docs/docs/plugins/passkey/ios.mdx b/apps/docs/src/content/docs/docs/plugins/passkey/ios.mdx new file mode 100644 index 000000000..a91ee0ea3 --- /dev/null +++ b/apps/docs/src/content/docs/docs/plugins/passkey/ios.mdx @@ -0,0 +1,47 @@ +--- +title: iOS Setup +description: Configure passkeys on iOS for @capgo/capacitor-passkey with Associated Domains and the apple-app-site-association file. +sidebar: + order: 3 +--- + +On iOS, passkeys only work when the app is associated with the same relying-party domain as the website. + +## What the plugin handles + +After you add the plugin config and run `bunx cap sync`, the plugin patches the generated iOS host project so you do not need to keep editing it manually: + +- adds the `webcredentials:` associated domains entries for the configured domains +- wires `CODE_SIGN_ENTITLEMENTS` when the generated app target does not already point at an entitlements file + +## What you still need to host + +You must publish `apple-app-site-association` on the relying-party domain: + +```text +https://signin.example.com/.well-known/apple-app-site-association +``` + +Example: + +```json +{ + "webcredentials": { + "apps": ["ABCDE12345.app.capgo.passkey.example"] + } +} +``` + +## Checklist + +1. Set `origin` and `domains` in `plugins.CapacitorPasskey` in `capacitor.config.*`. +2. Run `bunx cap sync`. +3. Confirm your Apple Team ID and app bundle ID, then build the `TEAMID.bundleId` value for the association file. +4. Host `apple-app-site-association` with HTTP `200` and no `.json` extension. +5. Make sure the relying-party ID used by your backend matches the associated domain. + +## Notes + +- The website file must be served from the exact passkey domain you use as the relying-party ID. +- On iOS 17.4 and newer, the plugin uses the browser-style client-data API so the configured HTTPS origin is reflected in `clientDataJSON`. +- The plugin can patch native project files during sync, but it cannot create or host the website association file on your domain. diff --git a/apps/web/src/config/plugins.ts b/apps/web/src/config/plugins.ts index afd6b5ef6..fd50c63d6 100644 --- a/apps/web/src/config/plugins.ts +++ b/apps/web/src/config/plugins.ts @@ -47,6 +47,7 @@ const actionDefinitionRows = @capgo/capacitor-streamcall|github.com/Cap-go|Integrate video calling and live streaming with Stream SDK for real-time communication|https://github.com/Cap-go/capacitor-streamcall/|Streamcall @capgo/capacitor-autofill-save-password|github.com/Cap-go|Prompt users to save passwords to device autofill for seamless login experience|https://github.com/Cap-go/capacitor-autofill-save-password/|Autofill Save Password @capgo/capacitor-social-login|github.com/Cap-go|Authenticate users with Google, Facebook, and Apple Sign-In for easy social login|https://github.com/Cap-go/capacitor-social-login/|Social Login +@capgo/capacitor-passkey|github.com/Cap-go|Use browser-style WebAuthn passkeys in Capacitor with a native shim and generated platform configuration|https://github.com/Cap-go/capacitor-passkey/|Passkey @capgo/capacitor-jw-player|github.com/Cap-go|Embed JW Player for professional video streaming with ads and analytics support|https://github.com/Cap-go/capacitor-jw-player/|JW Player @capgo/capacitor-ricoh360-camera-plugin|github.com/Cap-go|Control Ricoh Theta 360-degree cameras for immersive panoramic photography|https://github.com/Cap-go/capacitor-ricoh360-camera-plugin/|Ricoh360 Camera @capgo/capacitor-admob|github.com/Cap-go|Monetize your app with Google AdMob banner, interstitial, and rewarded ads|https://github.com/Cap-go/capacitor-admob/|AdMob @@ -168,6 +169,7 @@ const pluginIconsByName: Record = { '@capgo/capacitor-streamcall': 'VideoCamera', '@capgo/capacitor-autofill-save-password': 'UserCircle', '@capgo/capacitor-social-login': 'UserCircle', + '@capgo/capacitor-passkey': 'Key', '@capgo/capacitor-jw-player': 'PlayCircle', '@capgo/capacitor-ricoh360-camera-plugin': 'Camera', '@capgo/capacitor-admob': 'Megaphone', diff --git a/apps/web/src/content/plugins-tutorials/en/capacitor-passkey.md b/apps/web/src/content/plugins-tutorials/en/capacitor-passkey.md new file mode 100644 index 000000000..10b2b8f15 --- /dev/null +++ b/apps/web/src/content/plugins-tutorials/en/capacitor-passkey.md @@ -0,0 +1,95 @@ +--- +locale: en +published: true +--- +# Using @capgo/capacitor-passkey + +`@capgo/capacitor-passkey` lets a Capacitor app keep the same WebAuthn flow you already use on the web: + +```ts +await navigator.credentials.create({ publicKey: registrationOptions }); +await navigator.credentials.get({ publicKey: requestOptions }); +``` + +On native builds, the plugin installs a shim for `navigator.credentials.create()` and `navigator.credentials.get()`, forwards the request to iOS and Android passkey APIs, and returns browser-like credential objects to your app. + +## Install the plugin + +```bash +bun add @capgo/capacitor-passkey +bunx cap sync +``` + +## Configure the host app once + +Add the plugin config in `capacitor.config.ts` or `capacitor.config.json`: + +```ts +import type { CapacitorConfig } from '@capacitor/cli'; + +const config: CapacitorConfig = { + appId: 'app.capgo.passkey.example', + appName: 'My App', + webDir: 'dist', + plugins: { + CapacitorPasskey: { + origin: 'https://signin.example.com', + autoShim: true, + domains: ['signin.example.com'], + }, + }, +}; + +export default config; +``` + +Run sync again after changing the config: + +```bash +bunx cap sync +``` + +During sync, the plugin patches the generated native host projects: + +- iOS: associated domains entitlements +- Android: `asset_statements` metadata for Digital Asset Links + +## Import the shim once + +Import the auto entrypoint in your app bootstrap: + +```ts +import '@capgo/capacitor-passkey/auto'; +``` + +After that, your existing browser-style passkey code can stay the same. + +## Keep your normal WebAuthn flow + +```ts +const credential = await navigator.credentials.create({ + publicKey: registrationOptions, +}); + +const assertion = await navigator.credentials.get({ + publicKey: requestOptions, +}); +``` + +## Native setup still needs website trust files + +The plugin reduces app-side work, but passkeys still depend on the website trust files for your relying-party domain: + +- iOS needs `/.well-known/apple-app-site-association` +- Android needs `/.well-known/assetlinks.json` + +The detailed setup is documented here: + +- [Getting started](/docs/plugins/passkey/getting-started/) +- [iOS setup](/docs/plugins/passkey/ios/) +- [Android setup](/docs/plugins/passkey/android/) +- [Backend notes](/docs/plugins/passkey/backend/) + +## Important Android caveat + +Android Credential Manager can share the same relying party and passkeys as your website when Digital Asset Links are configured, but the native assertion origin is not identical to a browser origin. If your backend strictly validates `clientDataJSON.origin`, make sure it accepts the Android app origin alongside your website origin. diff --git a/apps/web/src/data/github-stars.json b/apps/web/src/data/github-stars.json index 36316fd42..0099bbb01 100644 --- a/apps/web/src/data/github-stars.json +++ b/apps/web/src/data/github-stars.json @@ -47,6 +47,7 @@ "https://github.com/Cap-go/capacitor-media-session/": 4, "https://github.com/Cap-go/capacitor-mux-player/": 4, "https://github.com/Cap-go/capacitor-pay/": 6, + "https://github.com/Cap-go/capacitor-passkey/": 1, "https://github.com/Cap-go/capacitor-pdf-generator/": 3, "https://github.com/Cap-go/capacitor-persistent-account/": 5, "https://github.com/Cap-go/capacitor-photo-library/": 5, @@ -102,4 +103,4 @@ "https://github.com/Cap-go/capacitor-light-sensor/": 4, "https://github.com/Cap-go/capacitor-video-thumbnails/": 4, "https://github.com/Cap-go/capacitor-intent-launcher/": 6 -} \ No newline at end of file +} diff --git a/apps/web/src/data/npm-downloads.json b/apps/web/src/data/npm-downloads.json index b081fa284..3afb256a7 100644 --- a/apps/web/src/data/npm-downloads.json +++ b/apps/web/src/data/npm-downloads.json @@ -52,6 +52,7 @@ "@capgo/capacitor-media-session": 10546, "@capgo/capacitor-mux-player": 1041, "@capgo/capacitor-pay": 7144, + "@capgo/capacitor-passkey": 40, "@capgo/capacitor-privacy-screen": 85, "@capgo/capacitor-pdf-generator": 4011, "@capgo/capacitor-persistent-account": 13383,