Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/docs/src/config/llmsCustomSets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/**
Expand Down
5 changes: 5 additions & 0 deletions apps/docs/src/config/sidebar.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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')],
Comment thread
riderx marked this conversation as resolved.
],
['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'],
Expand Down
59 changes: 59 additions & 0 deletions apps/docs/src/content/docs/docs/plugins/passkey/android.mdx
Original file line number Diff line number Diff line change
@@ -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.
57 changes: 57 additions & 0 deletions apps/docs/src/content/docs/docs/plugins/passkey/backend.mdx
Original file line number Diff line number Diff line change
@@ -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,
});
```
Original file line number Diff line number Diff line change
@@ -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'

<Steps>
1. **Install the package**
<PackageManagers pkg="@capgo/capacitor-passkey" pkgManagers={['bun']} />

2. **Sync native projects**
<PackageManagers type="exec" pkg="cap" args="sync" pkgManagers={['bun']} />

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,
});
```
</Steps>

## 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

<CardGrid>
<a href="/docs/plugins/passkey/ios/">
<Card title="iOS setup" icon="open-book">
Associated Domains and `apple-app-site-association`.
</Card>
</a>
<a href="/docs/plugins/passkey/android/">
<Card title="Android setup" icon="open-book">
Digital Asset Links and `assetlinks.json`.
</Card>
</a>
<a href="/docs/plugins/passkey/backend/">
<Card title="Backend notes" icon="open-book">
Origin validation and Android caveats.
</Card>
</a>
</CardGrid>
38 changes: 38 additions & 0 deletions apps/docs/src/content/docs/docs/plugins/passkey/index.mdx
Original file line number Diff line number Diff line change
@@ -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';

<CardGrid stagger>
<Card title="Browser-style API" icon="rocket">
Keep `navigator.credentials.create()` and `navigator.credentials.get()` in your app instead of rewriting your passkey flow around a custom API.
</Card>
<Card title="Minimal app changes" icon="pencil">
Add plugin config once, import `@capgo/capacitor-passkey/auto`, and keep the rest of your WebAuthn code close to the browser implementation.
</Card>
<Card title="Build-time native wiring" icon="setting">
The plugin patches the generated iOS and Android host projects during sync so you do not need to keep hand-editing those files.
</Card>
<Card title="Platform notes included" icon="open-book">
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/).
</Card>
</CardGrid>
47 changes: 47 additions & 0 deletions apps/docs/src/content/docs/docs/plugins/passkey/ios.mdx
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions apps/web/src/config/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
riderx marked this conversation as resolved.
@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
Expand Down Expand Up @@ -168,6 +169,7 @@ const pluginIconsByName: Record<string, string> = {
'@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',
Expand Down
Loading