capacitor-biometric-authentication
v2.3.3
Published
Biometric and WebAuthn authentication for web, iOS and Android — one API, no providers.
Downloads
1,049
Maintainers
Readme
Docs · npm · AI Guide · Support
[!IMPORTANT] A successful ceremony is not proof of identity on its own. On web, send
result.webAuthnResponseto your server and verify it against the challenge your server issued. On iOS and Android the assertion does not reach your server at all in this version — treat native success as a local unlock, never as the basis for issuing a session. See Limitations.
One call — BiometricAuth.authenticate() — runs whatever ceremony the platform provides: WebAuthn in
the browser, LocalAuthentication and the Secure Enclave on iOS, BiometricPrompt and the Android
Keystore on Android, Touch ID or Windows Hello under Electron. There is no provider to mount and no
React context to wire, so the same import works in React, Vue, Angular or plain JavaScript. Capacitor
is an optional peer: install it for native, omit it and the web path still works.
| | |
|---|---|
| Version | 2.3.3 |
| License | MIT |
| Node | >=18.0.0 |
| Platforms | Web · iOS · Android · Electron (macOS/Windows) |
| Install size | ≈13 kB min+gzip (dist/web.js, measured) |
| Types | Bundled .d.ts · ESM + CJS entry points |
| Status | Stable · actively maintained |
🧭 Table of Contents #
- 💡 Why capacitor-biometric-authentication
- ✨ Features
- 📱 Platform Support
- 📋 Requirements
- 📦 Installation
- 🚀 Quick Start
- 🛠️ Usage
- ⚙️ Configuration
- 🔧 API Reference
- 🧩 Types
- 🎛️ Advanced Features
- 🚑 Recovery & Troubleshooting
- 🚧 Limitations
- ❓ FAQ
- 📚 Documentation
- 🔄 Changelog
- 🗺️ Roadmap
- 🤝 Contributing
- 💬 Support
- 📄 License
- 👤 Author
- 🔗 Links
- 🏷️ Keywords
💡 Why capacitor-biometric-authentication #
Biometric sign-in usually means writing the same flow three times: WebAuthn for the browser, a Capacitor plugin call for native, and a third path for desktop — each with its own option shape, its own error vocabulary, and its own idea of what "failed" means. This package collapses that into one method with one result type, and picks the adapter from the detected platform at runtime.
| | capacitor-biometric-authentication | A native-only biometric plugin |
|---|---|---|
| Web support | WebAuthn, server-verifiable | none — native only |
| Capacitor required | optional peer | required |
| Framework binding | none — direct calls | none, but usually paired with a wrapper |
| Failure model | resolves with result.error | typically throws |
| Server verification | web today; native planned for 2.4.0 | not provided |
Not the right tool when you need a server-verified assertion from iOS or Android — that path is not wired in this version, and no amount of client code works around it. It is also not the right tool for Cordova, React Native, or Electron on Linux, none of which have an adapter. If your app is native-only and you need a hardware-backed assertion reaching your backend, wait for 2.4.0 or use a native SDK directly.
✨ Features #
- One API across four platforms — the adapter is chosen at runtime from the detected platform.
- Provider-less — a direct import and direct calls; no context, no HOC, no plugin registration.
- Server-verifiable on web —
authenticate()hands back the standard WebAuthn JSON for your relying party to check. - Hardware-backed where the platform allows — Android Keystore, iOS Secure Enclave and Keychain, platform authenticators on web.
- Never throws —
authenticate()always resolves; you branch onresult.successand a canonical error code instead of writingtry/catch. - Capacitor optional — declared as an optional peer and reached through a guarded dynamic import, so a pure web app never loads it.
- TypeScript first — every option, result and error code is typed, and
.d.tsfiles ship with the package. - No runtime dependencies.
- Subscribable session state — a client-side session with a configurable duration and a
subscribe()callback for UI binding.
📱 Platform Support #
| Platform | Supported | Notes |
|---|---|---|
| Web | ✅ | WebAuthn. Chrome 67+, Firefox 60+, Safari 14+, Edge 79+. HTTPS or localhost required. Server-verifiable. |
| iOS | ⚠️ | iOS 13+. Touch ID / Face ID via LocalAuthentication + Secure Enclave. Local unlock only — the assertion does not reach your server. |
| Android | ⚠️ | API 23+. BiometricPrompt + Android Keystore. Local unlock only — the assertion does not reach your server. |
| Electron (macOS) | ✅ | Touch ID through the WebAuthn path. |
| Electron (Windows) | ✅ | Windows Hello through the WebAuthn path, Windows 10+. |
| Electron (Linux) | ❌ | No platform authenticator; isAvailable() returns false. A USB security key may still work. |
| Cordova | ❌ | Detected, but no adapter ships. Loading one throws. |
| React Native | ❌ | Detected, no adapter, no supported path. |
Full matrix with capability flags: Platform support.
📋 Requirements #
| Requirement | Version | Why |
|---|---|---|
| Node | >=18.0.0 | install and build tooling only — the library itself runs in a browser or WebView |
| @capacitor/core | ^8.0.1 | optional peer — needed only for iOS and Android; the host app owns it |
| Android minSdkVersion | 23 | BiometricPrompt arrived in Android 6.0 |
| Android compileSdkVersion | 35 | matches the Capacitor 8 toolchain |
| Java | 17 | required by the Android Gradle plugin used here |
| Xcode | 15+ | to build the iOS plugin |
| iOS deployment target | 13.0 | the LocalAuthentication APIs the plugin calls |
| Secure context (web) | HTTPS or localhost | WebAuthn refuses to run on an insecure origin |
📦 Installation #
yarn add capacitor-biometric-authenticationWeb only? That is the whole installation — Capacitor is not required.
Targeting iOS or Android? Sync the native projects, or the plugin is never registered and every native call quietly falls back to the web adapter:
npx cap syncThen add the platform prerequisites.
iOS — add the Face ID usage string to ios/App/App/Info.plist. Without it the app crashes the
first time Face ID is used:
<key>NSFaceIDUsageDescription</key>
<string>We use Face ID to confirm it is you before unlocking your account.</string>Android — confirm android/app/build.gradle meets the levels in Requirements. The
plugin declares its own USE_BIOMETRIC permission, so nothing needs adding to your manifest:
android {
compileSdkVersion 35
defaultConfig {
minSdkVersion 23
targetSdkVersion 35
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}🚀 Quick Start #
import BiometricAuth from 'capacitor-biometric-authentication';
async function signIn(): Promise<boolean> {
// 1. Can we offer the button at all?
if (!(await BiometricAuth.isAvailable())) {
return signInWithPassword(); // always keep a non-biometric path
}
// 2. Run the ceremony. This resolves — it never throws.
const result = await BiometricAuth.authenticate({
reason: 'Sign in to your account',
title: 'Biometric sign-in',
fallbackTitle: 'Use passcode',
});
// 3. Branch on the failure, not on an exception.
if (!result.success) {
return signInWithPassword();
}
// 4. On web the ceremony is only a claim until your server verifies it.
if (result.webAuthnResponse) {
return verifyOnServer(result.webAuthnResponse);
}
return true;
}🛠️ Usage #
Choosing the button label
getSupportedBiometrics() resolves to an array of strings describing what the device has.
const types = await BiometricAuth.getSupportedBiometrics();
let label: string;
if (types.includes('faceId')) label = 'Sign in with Face ID';
else if (types.includes('touchId')) label = 'Sign in with Touch ID';
else if (types.includes('fingerprint')) label = 'Sign in with your fingerprint';
else label = 'Sign in with biometrics';Compare against the string values. BiometryType is exported from the package root as a type only,
so BiometryType.FACE_ID does not exist at runtime — see Types.
Handling failure
import BiometricAuth, {
BiometricErrorCode,
} from 'capacitor-biometric-authentication';
const result = await BiometricAuth.authenticate({ reason: 'Sign in' });
if (!result.success) {
switch (result.error?.code) {
case BiometricErrorCode.USER_CANCELLED:
break; // the user chose not to — show nothing
case BiometricErrorCode.NOT_ENROLLED:
showMessage('Set up a fingerprint or face in your device settings first.');
break;
case BiometricErrorCode.LOCKED_OUT:
showMessage('Too many attempts. Unlock your device, then try again.');
break;
case BiometricErrorCode.INSECURE_CONTEXT:
showMessage('Biometric sign-in needs a secure (HTTPS) connection.');
break;
default:
offerPasswordSignIn();
}
}BiometricErrorCode is a runtime value export, so this switch works as written.
Verifying on your server (web)
const result = await BiometricAuth.authenticate({
reason: 'Sign in',
webAuthnOptions: {
get: {
challenge: serverChallenge, // issued by YOUR server, for THIS attempt
userVerification: 'required',
},
},
});
if (result.success && result.webAuthnResponse) {
// result.webAuthnCeremony is 'registration' or 'authentication' —
// it tells the server which endpoint should verify this payload.
await fetch('/webauthn/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ response: result.webAuthnResponse }),
});
}The matching server-side verifier is
webauthn-server-buildkit.
Tracking session state
const unsubscribe = BiometricAuth.subscribe((state) => {
setSignedIn(state.isAuthenticated);
});
// later
unsubscribe();Clearing credentials
BiometricAuth.logout(); // end the local session; stored credentials survive
await BiometricAuth.deleteCredentials(); // remove credentials — next attempt re-registers
const registered = await BiometricAuth.hasCredentials();⚙️ Configuration #
BiometricAuth.configure({ sessionDuration: 3600, debug: false });configure() returns void — it is not async.
| Option | Type | Default | What it does |
|---|---|---|---|
| adapter | 'auto' \| 'web' \| 'capacitor' \| 'electron' \| string | 'auto' | Forces a platform adapter instead of detecting one |
| sessionDuration | number | 3600 | Local session lifetime in seconds |
| debug | boolean | false | Verbose logging; warnings and errors are always emitted |
| customAdapters | Record<string, BiometricAuthAdapter> | — | Adapters registered at configure time |
| encryptionKey | string | — | Key material for the platform's secure storage |
| requireAuthenticationForEveryAccess | boolean | false | Re-prompts on every sensitive operation |
| uiConfig | BiometricUIConfig | — | Colours and logo where the platform's prompt is customisable |
| fallbackMethods | FallbackMethod[] | — | Which device-credential fallbacks are permitted |
Per-call options — reason, title, subtitle, fallbackTitle, cancelTitle, maxAttempts — plus
the platform-scoped platform.web, platform.android and platform.ios blocks are documented at
Options.
🔧 API Reference #
Every method below is a property of the default export, BiometricAuth.
| Export | Signature | Docs |
|---|---|---|
| configure | (config: Partial<BiometricAuthConfiguration>) => void | → |
| isAvailable | () => Promise<boolean> | → |
| getSupportedBiometrics | () => Promise<BiometryType[]> | → |
| authenticate | (options?: BiometricAuthOptions) => Promise<BiometricAuthResult> | → |
| deleteCredentials | () => Promise<void> | → |
| hasCredentials | () => Promise<boolean> | → |
| logout | () => void | → |
| getState | () => BiometricAuthState | → |
| isAuthenticated | () => boolean | → |
| subscribe | (cb: (state: BiometricAuthState) => void) => () => void | → |
| requireAuthentication | (cb: () => void \| Promise<void>, options?: BiometricAuthOptions) => Promise<void> | → |
| withAuthentication | <T>(cb: () => T \| Promise<T>, options?: BiometricAuthOptions) => Promise<T> | → |
| registerAdapter | (name: string, adapter: BiometricAuthAdapter) => void | → |
| BiometricErrorCode | enum — a runtime value, named export | → |
BiometricAuthCore, PlatformDetector, WebAdapter and CapacitorAdapter are also named exports —
see Advanced Features.
🧩 Types #
import type {
BiometricAuthOptions,
BiometricAuthResult,
BiometricAuthConfiguration,
BiometricAuthAdapter,
BiometricAuthState,
BiometryType,
BiometricError,
} from 'capacitor-biometric-authentication';
interface BiometricAuthResult {
success: boolean;
token?: string;
sessionId?: string;
error?: BiometricError;
biometryType?: BiometryType;
platform?: string;
webAuthnResponse?: WebAuthnResponseJSON; // web only — send this to your server
webAuthnCeremony?: 'registration' | 'authentication';
}[!WARNING]
BiometryTypeis re-exported from the package root withexport type, so it has no runtime value there. WritingBiometryType.FINGERPRINTfails to compile withTS1362: 'BiometryType' cannot be used as a value because it was exported using 'export type', and isundefinedin plain JavaScript. Import it for annotations and compare against its string values:'fingerprint','faceId','touchId','iris','faceAuthentication','multiple','passcode','pattern','pin','opticId','unknown'.BiometricErrorCodeis unaffected — it is a real value export.
🎛️ Advanced Features #
- Custom adapters — implement
BiometricAuthAdapterand register it withregisterAdapter()to reach a platform this package does not. → - Android Keystore crypto — sign or encrypt with a biometric-gated key through
androidOptions.cryptoType, returned asresult.androidCryptoResult. → - Session helpers —
requireAuthentication()andwithAuthentication()prompt only when the local session has lapsed. → - Forced adapter selection — pin
configure({ adapter: 'web' })to reproduce one platform's behaviour while developing on another. →
🚑 Recovery & Troubleshooting #
| Symptom | Cause | Fix |
|---|---|---|
| error.code === 'INSECURE_CONTEXT' on web | WebAuthn refuses a non-secure origin | Serve over HTTPS, or develop on localhost |
| App crashes on the first Face ID prompt | NSFaceIDUsageDescription is missing | Add the key to Info.plist — see Installation |
| A native build behaves like a web build | npx cap sync was not run, so the native plugin never registered | Run npx cap sync, then configure({ debug: true }) and read which adapter loaded |
| Cordova support not yet implemented | No Cordova adapter ships | Use Capacitor for native |
| TS1362: 'BiometryType' cannot be used as a value | BiometryType is a type-only export | Compare against its string values — see Types |
| isAvailable() returns false on Electron/Linux | Linux exposes no platform authenticator | Offer a USB security key or a password path |
| success: true but your server rejects the user | The client result is a claim, not proof | Verify webAuthnResponse server-side; the server wins |
🚧 Limitations #
- Native ceremonies are not server-verifiable in 2.3.1. The Capacitor bridge does not forward
webAuthnOptionsto the native layer, and the nativeregister()ceremony is not on the JavaScript surface — so an iOS or Android assertion never reaches your relying party. Treat native success as a local unlock. Planned for 2.4.0. isAuthenticated()is a UX gate, not a security boundary — a client-side flag with a timer. It decides whether to prompt again, never whether to release data.isAvailable()returns a plain boolean and does not say why something is unavailable.- You cannot require a specific modality.
getSupportedBiometrics()is a hint for UI copy; the OS decides which enrolled modality satisfies the prompt. - No Cordova, React Native, or Electron-on-Linux adapter.
BiometryTypehas no runtime value at the package root — see Types.- No automated test suite ships. Quality is gated by typecheck, lint, a clean multi-format build, and verified ESM, CommonJS and subpath imports of the packed tarball.
- On web only the credential id is stored locally. The private key never leaves the authenticator, which is correct — but it means this package cannot offer key export or backup.
❓ FAQ #
Do I need Capacitor?
No. @capacitor/core is an optional peer, reached only through a guarded dynamic import on native. A
pure web app never pulls it in.
Does authenticate() throw?
No. It always resolves. Check result.success and result.error?.code.
Why is BiometryType.FACE_ID undefined?
It is exported as a type only. Compare against the string 'faceId' — see Types.
Is success: true enough to sign a user in?
Not on its own. On web, verify result.webAuthnResponse on your server. On native there is nothing to
verify in this version — see Limitations.
Is sessionDuration in seconds or milliseconds?
Seconds. The default is 3600.
Does it work from a plain <script> tag?
Yes — the package ships an IIFE build for a classic script tag and an ES build for
<script type="module">.
📚 Documentation #
| Document | Read it when |
|---|---|
| Introduction | deciding whether this package fits |
| Installation | setting it up, including the native steps |
| Quick start | building your first working flow |
| API — methods | you need an exact signature |
| API — options | tuning per-call or per-platform behaviour |
| API — types | writing type annotations against the result |
| API — error codes | mapping a failure to user-facing copy |
| Security model | before you ship — what success: true proves |
| Server verification | wiring the relying-party side |
| Framework examples | using it from React, Vue, Angular or vanilla JavaScript |
| Platform support | checking what a target really provides |
| llms.txt | a coding agent is implementing against this package |
🔄 Changelog #
Latest release: 2.3.3 — documentation only: the at-a-glance table above reported the previous version, because it is a static duplicate of package.json. Full history in the changelog.
The full history ships inside the package at
node_modules/capacitor-biometric-authentication/CHANGELOG.md.
🗺️ Roadmap #
- 2.4.0 — server-verifiable native ceremonies: forward
webAuthnOptionsthrough the Capacitor bridge and expose the nativeregister()ceremony, so an iOS or Android assertion can reach your relying party. This is the one gap that changes what the package can be used for.
🤝 Contributing #
The source repository is private, so there is no public fork-and-pull-request path today. Bug reports, reproductions and feature requests are welcome by email at [email protected]. A minimal reproduction plus the platform, package version, and browser or OS version makes a report immediately actionable.
💬 Support #
Questions and bug reports: [email protected].
If this package saves you time, you can support its maintenance at aoneahsan.com/payment.
📄 License #
MIT © Ahsan Mahmood. The full text ships in the package as
LICENSE.
👤 Author #
Ahsan Mahmood — aoneahsan.com · GitHub · LinkedIn · [email protected]
🔗 Links #
| | | |---|---| | Documentation | https://capacitor-biometric-authentication-docs.aoneahsan.com | | npm | https://www.npmjs.com/package/capacitor-biometric-authentication | | AI agent guide | https://capacitor-biometric-authentication-docs.aoneahsan.com/llms.txt | | Server-side verifier | https://www.npmjs.com/package/webauthn-server-buildkit | | Support the project | https://aoneahsan.com/payment | | Author | https://aoneahsan.com |
🏷️ Keywords #
capacitor · capacitor-plugin · biometric · authentication · webauthn · passkey · fingerprint · face-id · touch-id · android · ios · framework-agnostic
