← Retour au blog
Capacitornative-pluginsmobile-developmentbiometric-authpush-notifications

Capacitor Native Plugins: Integrating Camera, Biometrics, and Push Notifications with Confidence

Capacitor has reshaped how web developers build mobile applications. Unlike Cordova, which relies on aging WebView plugins, Capacitor presents a modern, maintainable architecture with excellent documentation and an active community. Its core strength is accessing native APIs without sacrificing web standards flexibility. The three integrations we cover—camera, biometrics, and push notifications—account for roughly 80% of native integration requests in production mobile apps.

Camera: From Capture to Secure Storage

Capacitor's Camera plugin provides a unified interface across iOS and Android while preserving the ability to drop to native code when needed. The Camera.getPhoto() method accepts a rich configuration object: format (JPEG, PNG), quality, dimensions, and source (camera or gallery). Complexity emerges during storage. Capacitor returns either a web URI ( file:// ) or base64 data, depending on platform and configuration. On Android 10+, direct filesystem access is restricted, requiring FilesystemDirectory.Documents or FilesystemDirectory.Cache from the Filesystem plugin, plus permission queries via Permissions.query() . A safe, common pattern is to capture as base64, validate client-side (dimensions, size), then send to server or store locally with Filesystem. Here's a concrete example:

import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';

import { Filesystem, Directory } from '@capacitor/filesystem';

async captureAndStore() {

const photo = await Camera.getPhoto({

quality: 85,

allowEditing: false,

resultType: CameraResultType.Base64,

source: CameraSource.Camera

});

const fileName = photo_${Date.now()}.jpg ;

await Filesystem.writeFile({

directory: Directory.Cache,

path: fileName,

data: photo.base64String || ''

});

return fileName;

}

Keep in mind: base64 encoding inflates file size by roughly 33%. For photo-heavy applications (galleries, social), prefer CameraResultType.Uri and handle the file directly. On iOS, cache is system-managed; on Android, use Directory.ExternalStorage with appropriate permissions if you need persistent user-visible storage.

Biometric Authentication: Fingerprint and Face Recognition

Capacitor BiometricAuth unifies iOS LocalAuthentication and Android BiometricPrompt. The API is simple, but the security context is critical. Always check availability with BiometricAuth.isAvailable() before prompting, and verify permissions ( BIOMETRIC_READ on Android 28+). Then BiometricAuth.authenticate() shows a native dialog—user scans fingerprint or face. Success returns an opaque token; failure or cancellation throws. A classic pitfall: developers store sensitive data (JWT tokens, passwords) in memory after biometric success without server-side revalidation. Biometrics is only a local authentication factor; it never replaces backend verification. The correct flow is: (1) user logs in with email/password, (2) server issues a long-lived refresh token, (3) app stores it locally in Keychain/Keystore (via Capacitor Storage with useEncryption: true or SecureStorageNative), (4) on next launch, biometrics unlocks locally and the refresh token is used to fetch a short-lived access token.

import { BiometricAuth } from '@capacitor-community/biometric-auth';

async authenticateWithBiometric() {

try {

const available = await BiometricAuth.isAvailable();

if (!available.isAvailable) {

throw new Error('Biometrics unavailable');

}

const result = await BiometricAuth.authenticate({

reason: 'Secure authentication',

title: 'Verify your identity'

});

if (result.success) {

// Retrieve refresh token from local keystore

const token = await this.secureStorage.get('refreshToken');

// Call API to obtain short-lived access token

await this.refreshAccessToken(token);

}

} catch (e) {

console.error('Authentication failed', e);

}

}

Never store credentials in localStorage or sessionStorage after biometric authentication. iOS and Android provide system-level encrypted storage; Capacitor Storage with encryption and native keystore plugins are the standard.

Push Notifications: Orchestration and Reliable Delivery

Push notifications are the most complex native integration. Capacitor PushNotifications handles registration with notification services (Firebase Cloud Messaging on Android, APNs on iOS), but does not handle server-side sending. Your backend must communicate with FCM and APNs directly or via a third party (SendGrid, Braze, OneSignal). Client-side flow: (1) on startup, call PushNotifications.requestPermissions() , (2) register with PushNotifications.register() , (3) capture the token via the registration event, (4) send it to your backend for user association. For events, listen to pushNotificationReceived (app in foreground) and pushNotificationActionPerformed (user tap). A frequent pitfall: failing to handle notifications when the app is closed. Android displays them automatically; iOS requires correct capability configuration or they vanish. Another trap: conflating push and local notifications. Capacitor LocalNotifications is separate and useful for locally-triggered reminders (alarms, timers). Here's a minimal, reliable setup:

import { PushNotifications } from '@capacitor/push-notifications';

async setupPushNotifications() {

let permStatus = await PushNotifications.requestPermissions();

if (permStatus.receive === 'granted') {

await PushNotifications.register();

}

PushNotifications.addListener('registration', (token) => {

console.log('Token received:', token.value);

// Send token.value to backend

this.sendTokenToBackend(token.value);

});

PushNotifications.addListener('pushNotificationReceived', (notification) => {

console.log('Notification received:', notification);

// Process or display notification

});

PushNotifications.addListener('pushNotificationActionPerformed', (action) => {

console.log('Action:', action);

// Navigate or trigger action

});

}

Always test on real devices. iOS simulators and Android emulators do not receive push notifications. Finally, manage token expiration: FCM tokens can be regenerated, and you must update your backend proactively to avoid lost notifications.

Common Pitfalls and Best Practices

Several antipatterns emerge repeatedly. First: testing only in the browser. Capacitor provides mocks in development, but real device behavior—permissions, storage, camera—differs. Second: ignoring platform variance. iOS and Android have different APIs, permissions, and lifecycles. Read the plugin documentation for each platform. Third: blocking the main thread with image processing. High-resolution photos can freeze the UI; use Web Workers or delegate to backend. Fourth: forgetting runtime permissions. Android 6+ and iOS 10+ require runtime permission requests, not install-time. Capacitor handles this, but verify the native dialog appears and users understand why.

Conclusion: A Pragmatic Approach

Capacitor makes native integration accessible without Cordova's nightmares. The three plugins covered—Camera, BiometricAuth, PushNotifications—address most needs. The key is understanding Capacitor is not magic abstraction; it is a gateway, and each platform has quirks. Test on real devices, read the official docs (they are excellent), and never sacrifice security for convenience. If a Capacitor plugin falls short, you can write custom native plugins or drop to native WebView code—Capacitor gives you the choice.

Développeur Angular & Mobile freelance — Strasbourg.

© 2026 Emilien Pons — Tous droits réservés.Conçu avec Angular, PrimeNG et ❤️