Passkeys for Capacitor with a browser-style WebAuthn shim.
Most Capacitor apps already have browser-oriented passkey code based on:
await navigator.credentials.create({ publicKey: registrationOptions });
await navigator.credentials.get({ publicKey: requestOptions });This plugin keeps that shape in a Capacitor app:
- It can auto-patch
navigator.credentials.create/getforpublicKeyrequests. - It forwards the call to native passkey APIs on iOS and Android.
- It returns browser-like credential objects so existing code can keep working.
- It can auto-configure the generated iOS and Android host projects during
cap sync/update. - It also exposes direct JSON-safe methods when your backend already returns WebAuthn JSON.
What it does not do:
- It does not generate backend challenges for you.
- It does not remove the need for your website to host
apple-app-site-associationandassetlinks.json. - It does not make Android native passkeys report your website's HTTPS origin to the server. On Android, normal apps use the app origin (
android:apk-key-hash:...).
The most complete doc is available here: https://capgo.app/docs/plugins/passkey/
| Plugin version | Capacitor compatibility | Maintained |
|---|---|---|
| v8.*.* | v8.*.* | ✅ |
| v7.*.* | v7.*.* | On demand |
| v6.*.* | v6.*.* | ❌ |
| v5.*.* | v5.*.* | ❌ |
Note: The major version of this plugin follows the major version of Capacitor. Use the version that matches your Capacitor installation.
bun add @capgo/capacitor-passkey- Configure the plugin once in
capacitor.config.tsorcapacitor.config.json. - Import the
/autoentrypoint once during app bootstrap. - Keep your existing browser
navigator.credentials.create/getcode unchanged.
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;Config keys:
origin: the primary HTTPS relying-party origin for the app.domains: optional extra relying-party hostnames to wire natively duringcap sync/update.autoShim: defaults totrue. The/autoentrypoint uses this flag.
Then sync native projects:
bunx cap syncDuring cap sync / cap update, the plugin automatically patches the generated host app:
- iOS: updates the entitlements file and wires
CODE_SIGN_ENTITLEMENTSif needed. - Android: injects
asset_statementsmetadata and writes a generated string resource.
You do not need to manually edit the host project files every time.
import '@capgo/capacitor-passkey/auto';That import reads plugins.CapacitorPasskey from the native Capacitor config and installs the WebAuthn shim automatically.
After the bootstrap import, your browser-style code can stay the same:
const registration = await navigator.credentials.create({
publicKey: {
challenge: crypto.getRandomValues(new Uint8Array(32)),
rp: {
id: 'signin.example.com',
name: 'Example Inc',
},
user: {
id: crypto.getRandomValues(new Uint8Array(32)),
name: 'ada@example.com',
displayName: 'Ada Lovelace',
},
pubKeyCredParams: [
{ type: 'public-key', alg: -7 },
{ type: 'public-key', alg: -257 },
],
},
});
const authentication = await navigator.credentials.get({
publicKey: {
challenge: crypto.getRandomValues(new Uint8Array(32)),
rpId: 'signin.example.com',
},
});If you do not want the side-effect import, call the config-driven installer manually once:
import { CapacitorPasskey } from '@capgo/capacitor-passkey';
await CapacitorPasskey.autoShimWebAuthn();The plugin hook only patches the native host app files that Capacitor generates.
- It does not touch your app source code.
- It does not require you to permanently hand-edit Xcode or Android project files.
- It does not publish or generate the website association files for you.
If your backend already returns PublicKeyCredentialCreationOptionsJSON or PublicKeyCredentialRequestOptionsJSON, you can call the plugin directly:
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,
});Passkeys only work when your app is associated with the same relying-party domain as your website.
Host an apple-app-site-association file on your relying-party domain:
https://signin.example.com/.well-known/apple-app-site-association
Example:
{
"webcredentials": {
"apps": [
"ABCDE12345.app.capgo.passkey.example"
]
}
}Notes:
- The file must be served with HTTP
200. - Do not add a
.jsonextension. - The
webcredentialsdomain must match the relying-party id you use for passkeys. - The plugin hook writes the associated domains entitlement for you during
cap sync/update. - On iOS 17.4 and newer, the plugin uses the browser-style client-data API so the configured HTTPS origin is reflected in
clientDataJSON.
Host a Digital Asset Links file at:
https://signin.example.com/.well-known/assetlinks.json
Example:
[
{
"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"
]
}
}
]- Use the same domain as your relying-party id.
- Include every signing certificate fingerprint you need, including debug builds if you test them.
- The plugin hook writes the
asset_statementsmetadata and generated string resource for you duringcap sync/update. - Your website still needs to serve the actual
assetlinks.jsonfile.
This plugin preserves the front-end WebAuthn API shape, but native platforms are not identical to a browser backend contract.
- iOS 17.4+ can encode the HTTPS origin you configure for the shim or direct API.
- Android Credential Manager does not act like a privileged browser app. The native response origin is tied to the Android app signature (
android:apk-key-hash:...), not automatically to your website. - With Digital Asset Links configured, Android can still use the same relying party and passkeys as your website. The part that differs is the literal
clientDataJSON.originstring. - If your server strictly validates
clientDataJSON.origin, allow the Android app origin alongside your web origin. - Your backend still needs the normal WebAuthn challenge lifecycle and signature verification.
- On the web, the plugin forwards to the real browser WebAuthn API.
- On native Capacitor, the shim returns browser-like credential objects backed by native APIs.
- The
/autoentrypoint is designed for “import once, keep browser code” setups. - Conditional mediation currently returns
false.
The example-app/ folder demonstrates the auto-shim flow:
example-app/capacitor.config.jsonconfigures the plugin.example-app/src/main.jsimports@capgo/capacitor-passkey/auto.- The actual registration and authentication still go through
navigator.credentials.create/get.
shimWebAuthn(...)getConfiguration()autoShimWebAuthn(...)createCredential(...)getCredential(...)isSupported()getPluginVersion()- Interfaces
- Type Aliases
Capacitor Passkey plugin.
Use autoShimWebAuthn() or import @capgo/capacitor-passkey/auto
to keep existing navigator.credentials.create/get code working
inside a Capacitor app.
shimWebAuthn(options?: ShimWebAuthnOptions | undefined) => voidInstall a browser-style WebAuthn shim on top of the native plugin.
The shim patches navigator.credentials.create/get for publicKey
requests and returns browser-like credential objects.
Use this when you want to override the auto-loaded config manually.
| Param | Type |
|---|---|
options |
ShimWebAuthnOptions |
Since: 1.0.0
getConfiguration() => Promise<PasskeyRuntimeConfiguration>Load plugin configuration from the host Capacitor app.
This reads plugins.CapacitorPasskey from capacitor.config.*.
Returns: Promise<PasskeyRuntimeConfiguration>
Since: 1.1.0
autoShimWebAuthn(options?: ShimWebAuthnOptions | undefined) => Promise<PasskeyRuntimeConfiguration>Install the browser-style shim using host app configuration.
This is the easiest way to keep existing browser WebAuthn code working:
configure the plugin in capacitor.config.*, then call this once during
app bootstrap or use the /auto entrypoint.
| Param | Type |
|---|---|
options |
ShimWebAuthnOptions |
Returns: Promise<PasskeyRuntimeConfiguration>
Since: 1.1.0
createCredential(options: CreateCredentialOptions) => Promise<PasskeyRegistrationCredential>Register a passkey from a JSON-safe WebAuthn request.
This method is useful when your backend already returns the
PublicKeyCredentialCreationOptionsJSON form.
| Param | Type |
|---|---|
options |
CreateCredentialOptions |
Returns: Promise<PasskeyRegistrationCredential>
Since: 1.0.0
getCredential(options: GetCredentialOptions) => Promise<PasskeyAuthenticationCredential>Authenticate with an existing passkey from a JSON-safe WebAuthn request.
This method is useful when your backend already returns the
PublicKeyCredentialRequestOptionsJSON form.
| Param | Type |
|---|---|
options |
GetCredentialOptions |
Returns: Promise<PasskeyAuthenticationCredential>
Since: 1.0.0
isSupported() => Promise<PasskeySupportResult>Report whether passkeys are available in the current runtime.
Returns: Promise<PasskeySupportResult>
Since: 1.0.0
getPluginVersion() => Promise<PluginVersionResult>Returns the current platform implementation version marker.
Returns: Promise<PluginVersionResult>
Since: 1.0.0
Options used when installing the browser-style shim.
| Prop | Type | Description |
|---|---|---|
origin |
string |
Optional HTTPS origin to encode into iOS 17.4+ clientDataJSON. Use this when your Capacitor app runs from capacitor://localhost but your relying party expects https://signin.example.com. |
force |
boolean |
Force the shim even if the runtime already exposes navigator.credentials. Defaults to false. |
Runtime configuration loaded from the host app's Capacitor config.
This is the shape returned by getConfiguration().
| Prop | Type | Description |
|---|---|---|
autoShim |
boolean |
Whether the /auto entrypoint should install the shim automatically. Defaults to true. |
origin |
string |
Optional HTTPS origin used by the auto-shim. On iOS 17.4+ this origin is encoded into clientDataJSON. |
domains |
string[] |
Domains associated with the app for passkey usage. These come from domains in Capacitor config plus the hostname derived from origin when available. |
platform |
'ios' | 'android' | 'web' |
Current runtime platform. |
JSON-safe registration credential returned by the plugin.
| Prop | Type | Description |
|---|---|---|
id |
string |
Base64url-encoded credential identifier. |
rawId |
string |
Base64url-encoded raw credential identifier. |
type |
PasskeyCredentialType |
Credential type. Always public-key. |
authenticatorAttachment |
PasskeyAuthenticatorAttachment |
Optional authenticator attachment reported by the platform. |
clientExtensionResults |
Record<string, unknown> |
Client extension results returned by the platform. |
response |
PasskeyAuthenticatorAttestationResponseJSON |
Registration response payload. |
JSON-safe attestation response payload.
All binary fields are base64url encoded.
| Prop | Type | Description |
|---|---|---|
clientDataJSON |
string |
Base64url-encoded clientDataJSON. |
attestationObject |
string |
Base64url-encoded attestation object. |
publicKey |
string |
Optional base64url-encoded public key when provided by the platform. |
publicKeyAlgorithm |
number |
Optional public key algorithm when provided by the platform. |
transports |
string[] |
Optional transport list when provided by the platform. |
Direct registration request for the plugin transport.
| Prop | Type | Description |
|---|---|---|
publicKey |
PasskeyPublicKeyCredentialCreationOptionsJSON |
JSON-safe registration request. |
origin |
string |
Optional HTTPS origin to use for iOS 17.4+ browser-style clientDataJSON. |
JSON-safe registration request options.
All binary fields must be base64url encoded.
| Prop | Type | Description |
|---|---|---|
challenge |
string |
Base64url-encoded challenge. |
rp |
PasskeyRelyingParty |
Relying party information. |
user |
PasskeyUserEntity |
User information. |
pubKeyCredParams |
PasskeyCredentialParameter[] |
Supported credential algorithms. |
timeout |
number |
Optional timeout hint in milliseconds. |
excludeCredentials |
PasskeyCredentialDescriptor[] |
Optional credentials that must be excluded during registration. |
authenticatorSelection |
PasskeyAuthenticatorSelection |
Optional authenticator preferences. |
attestation |
PasskeyAttestation |
Optional attestation preference. |
hints |
string[] |
Optional hints copied from the JSON form used by modern WebAuthn toolkits. |
extensions |
Record<string, unknown> |
Optional extensions copied as-is into the request JSON. |
JSON-safe relying party information.
| Prop | Type | Description |
|---|---|---|
id |
string |
Relying party identifier. If omitted, the shim derives it from the configured origin when possible. |
name |
string |
Human-readable relying party name. |
JSON-safe WebAuthn user entity.
Binary identifiers must be base64url encoded.
| Prop | Type | Description |
|---|---|---|
id |
string |
Base64url-encoded user handle. |
name |
string |
User name shown by the authenticator. |
displayName |
string |
Optional display name shown by the authenticator. |
JSON-safe credential parameter entry.
| Prop | Type | Description |
|---|---|---|
type |
PasskeyCredentialType |
Credential type. Only public-key is supported. |
alg |
number |
COSE algorithm identifier. |
JSON-safe representation of a public key credential descriptor.
Binary identifiers must be base64url encoded.
| Prop | Type | Description |
|---|---|---|
id |
string |
Base64url-encoded credential identifier. |
type |
PasskeyCredentialType |
Credential type. Only public-key is supported. |
transports |
string[] |
Optional transport hints copied from WebAuthn. |
JSON-safe authenticator selection options.
| Prop | Type | Description |
|---|---|---|
authenticatorAttachment |
PasskeyAuthenticatorAttachment |
Optional authenticator attachment hint. |
residentKey |
PasskeyResidentKey |
Optional resident key preference. |
requireResidentKey |
boolean |
Legacy resident key requirement flag. |
userVerification |
PasskeyUserVerification |
Optional user verification preference. |
JSON-safe authentication credential returned by the plugin.
| Prop | Type | Description |
|---|---|---|
id |
string |
Base64url-encoded credential identifier. |
rawId |
string |
Base64url-encoded raw credential identifier. |
type |
PasskeyCredentialType |
Credential type. Always public-key. |
authenticatorAttachment |
PasskeyAuthenticatorAttachment |
Optional authenticator attachment reported by the platform. |
clientExtensionResults |
Record<string, unknown> |
Client extension results returned by the platform. |
response |
PasskeyAuthenticatorAssertionResponseJSON |
Assertion response payload. |
JSON-safe assertion response payload.
All binary fields are base64url encoded.
| Prop | Type | Description |
|---|---|---|
clientDataJSON |
string |
Base64url-encoded clientDataJSON. |
authenticatorData |
string |
Base64url-encoded authenticator data. |
signature |
string |
Base64url-encoded signature. |
userHandle |
string | null |
Optional base64url-encoded user handle. |
Direct authentication request for the plugin transport.
| Prop | Type | Description |
|---|---|---|
publicKey |
PasskeyPublicKeyCredentialRequestOptionsJSON |
JSON-safe authentication request. |
mediation |
string |
Optional mediation hint. conditional currently falls back to an explicit prompt. |
origin |
string |
Optional HTTPS origin to use for iOS 17.4+ browser-style clientDataJSON. |
JSON-safe authentication request options.
All binary fields must be base64url encoded.
| Prop | Type | Description |
|---|---|---|
challenge |
string |
Base64url-encoded challenge. |
timeout |
number |
Optional timeout hint in milliseconds. |
rpId |
string |
Optional relying party identifier. If omitted, the shim derives it from the configured origin when possible. |
allowCredentials |
PasskeyCredentialDescriptor[] |
Optional allow list copied from WebAuthn. |
userVerification |
PasskeyUserVerification |
Optional user verification preference. |
hints |
string[] |
Optional hints copied from the JSON form used by modern WebAuthn toolkits. |
extensions |
Record<string, unknown> |
Optional extensions copied as-is into the request JSON. |
Passkey support status for the current runtime.
| Prop | Type | Description |
|---|---|---|
available |
boolean |
Whether passkeys are available on the current runtime. |
conditionalMediation |
boolean |
Whether conditional mediation is available. |
platform |
'ios' | 'android' | 'web' |
Current platform identifier. |
Plugin version payload.
| Prop | Type | Description |
|---|---|---|
version |
string |
Version identifier reported by the current platform implementation. |
Supported WebAuthn credential type.
'public-key'
Supported authenticator attachment values.
'platform' | 'cross-platform'
Construct a type with a set of properties K of type T
{
[P in K]: T;
}
Supported resident key preferences.
'discouraged' | 'preferred' | 'required'
Supported user verification preferences.
'discouraged' | 'preferred' | 'required'
Supported attestation preferences.
'none' | 'indirect' | 'direct' | 'enterprise'
