flutter_passkey_service 0.1.0
flutter_passkey_service: ^0.1.0 copied to clipboard
A comprehensive Flutter plugin for seamless Passkey (WebAuthn) integration on iOS, macOS, and Android. Enable passwordless authentication with biometric security.
Flutter Passkey Service - WebAuthn FIDO2 Passwordless Authentication #
A robust, production-ready Flutter plugin for integrating Passkeys (WebAuthn/FIDO2) passwordless authentication on iOS, macOS, and Android. Transform user authentication with biometric security and eliminate passwords.
π Table of Contents #
- Features
- Platform Support
- Installation
- Migration to 0.1.0
- Domain Verification Setup
- Usage Guide
- Advanced Usage
- Security Considerations
- Contributing & Support
- License
β¨ Features #
- Passwordless Authentication: Secure biometric and device-based authentication.
- Cross-Platform: Unifies iOS AuthenticationServices and Android Credential Manager APIs.
- Cross-Device Sync: Auto-sync across devices via iCloud Keychain and Google Password Manager.
- WebAuthn Compliant: Full compliance with W3C WebAuthn standards.
- Advanced Extensions: Native support for PRF (derive symmetric Key Encryption Keys) and Large Blob (store data directly on the passkey).
- Type-Safe API: Reliable Flutter-to-native communication generated with Pigeon.
- JSON Serialization: Easy conversion to and from server JSON responses.
π Platform Support #
| Platform | Minimum Version | Notes |
|---|---|---|
| iOS | 16.0+ | Platform passkeys (iCloud Keychain). excludeCredentials 17.4+, Large Blob 17.0+, PRF 18.0+. pubKeyCredParams, timeout, hints, attestationFormats, residentKey, requireResidentKey and the appid extension have no platform API and are ignored; clientExtensionResults.credProps is always null. |
| macOS | 13.0+ | Same as iOS. excludeCredentials 13.5+, Large Blob 14.0+, PRF 15.0+. |
| Android | API 28+ (9.0) | Credential Manager (androidx.credentials 1.6.0). WebAuthn JSON fields are forwarded to the provider as sent; credProps.rk is reported as false when the provider omits it. Library minSdk is 23; passkeys require Google Play services on Android 9+. |
Both Swift Package Manager and CocoaPods are supported on iOS and macOS via the shared darwin/ package.
π Installation #
Add flutter_passkey_service to your pubspec.yaml:
dependencies:
flutter_passkey_service: ^0.0.3
Run:
flutter pub get
Migration to 0.1.0 #
- iOS/macOS user handle. Passkeys registered on iOS/macOS with 0.0.x carry a user handle equal to the UTF-8 bytes of the
userIdstring you passed. Their assertions still return the sameuserHandleas before. New registrations use the base64url-decoded bytes, matching Android. If your server comparesuserHandleto its storeduser.id, accept both forms during the transition or re-register iOS users. iOS/macOS now decodeuser.idas base64url bytes exactly as Android does. A string that happens to be valid base64 but is not your intended handle (for exampleuser-123) decodes to unintended bytes rather than failing, so base64url-encode your handle before sending it.invalidFormatis returned only when the string cannot be decoded at all or decodes to zero bytes. - JSON defaults. If you relied on the plugin adding
userVerification: requiredorauthenticatorAttachment: platformto server JSON, send them from the server. - CocoaPods apps: run
pod installinios/andmacos/after upgrading.
π§ Domain Verification Setup #
β οΈ Important: Passkeys require cryptographic proof that your app is tied to a specific web domain. Domain verification is mandatory.
iOS Setup (Apple App Site Association) #
-
Add Capability: In Xcode, go to your target's Signing & Capabilities, add Associated Domains, and enter
webcredentials:yourdomain.com. -
Host Association File: Create an
apple-app-site-associationfile (no.jsonextension) and host it athttps://yourdomain.com/.well-known/apple-app-site-association.{ "webcredentials": { "apps": ["TEAMID.com.yourcompany.yourapp"] } }(Ensure Response Content-Type is
application/json)
Android Setup (Digital Asset Links) #
-
Get SHA256 Fingerprint: Obtain the SHA256 signature of your release and debug keystores.
-
Host Asset Links File: Create an
assetlinks.jsonfile and host it athttps://yourdomain.com/.well-known/assetlinks.json.[{ "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.yourcompany.yourapp", "sha256_cert_fingerprints": ["YOUR_SHA256_FINGERPRINT"] } }](Ensure Response Content-Type is
application/json)
π» Usage Guide #
1. Registration Flow #
Create a new Passkey credential for the user. Usually, you request creation options from your backend.
import 'package:flutter_passkey_service/flutter_passkey_service.dart';
Future<void> registerPasskey() async {
try {
final options = FlutterPasskeyService.createRegistrationOptions(
challenge: 'base64url-encoded-challenge-from-server',
rpName: 'Your App Name',
rpId: 'yourdomain.com', // Must match verified domain
userId: 'dXNlci11bmlxdWUtaWQ', // base64url of your user handle bytes
username: 'user@example.com',
displayName: 'John Doe',
);
// Perform biometric authentication to create the Passkey
final response = await FlutterPasskeyService.register(options);
// Send `response` back to your server to store the public key
print('Registration successful: ${response.id}');
} on PasskeyException catch (e) {
print('Registration failed: ${e.message}');
}
}
2. Authentication Flow #
Authenticate a user with an existing Passkey.
Future<void> authenticate() async {
try {
final request = FlutterPasskeyService.createAuthenticationOptions(
challenge: 'base64url-encoded-challenge-from-server',
rpId: 'yourdomain.com', // Must match verified domain
);
// Prompt biometric authentication
final response = await FlutterPasskeyService.authenticate(request);
// Send `response` back to your server to verify the signature
print('Authentication successful: ${response.id}');
} on PasskeyException catch (e) {
print('Authentication failed: ${e.message}');
}
}
3. Working with Server JSON #
Often, your server will generate the WebAuthn options directly as JSON. The plugin natively supports parsing these.
// Register
final serverRegistrationJson = await backend.getRegistrationOptions();
final registerOptions = FlutterPasskeyService.createRegistrationOptionsFromJson(serverRegistrationJson);
final regResponse = await FlutterPasskeyService.register(registerOptions);
// Authenticate
final serverAuthJson = await backend.getAuthenticationOptions();
final authOptions = FlutterPasskeyService.createAuthenticationOptionsFromJson(serverAuthJson);
final authResponse = await FlutterPasskeyService.authenticate(authOptions);
You can also export options back to JSON for debugging:
print(registerOptions.toJsonString());
4. Error Handling #
The plugin provides a unified PasskeyException with typed errors.
try {
await FlutterPasskeyService.authenticate(request);
} on PasskeyException catch (e) {
switch (e.errorType) {
case PasskeyErrorType.userCancelled:
print('User cancelled the biometric prompt');
break;
case PasskeyErrorType.noCredentialsAvailable:
print('No passkeys found for this site.');
break;
case PasskeyErrorType.platformNotSupported:
print('Passkeys are not supported on this OS version.');
break;
case PasskeyErrorType.domainNotAssociated:
print('Domain verification failed. Check assetlinks.json / apple-app-site-association.');
break;
default:
print('Unhandled passkey error: ${e.message}');
}
}
5. WebAuthn Extensions (PRF & Large Blob) #
PRF (Key Encryption Key) The PRF extension allows you to derive a symmetric key (KEK) during authentication, tied strictly to the passkey. This is perfect for encrypting local offline game saves or profiles.
// 1. Enable PRF during Registration
final regOptions = FlutterPasskeyService.createRegistrationOptions(
/* ... */
enablePrf: true,
);
// 2. Derive Key during Authentication
final authOptions = FlutterPasskeyService.createAuthenticationOptionsFromJson(serverAuthJson);
// Send your salt to derive the KEK
authOptions.extensions = AuthGenerateOptionExtension(
prf: PrfExtensionInput(eval: {'first': 'base64url-encoded-salt-here'})
);
final response = await FlutterPasskeyService.authenticate(authOptions);
final derivedKey = response.clientExtensionResults?.prf?.results?['first'];
Large Blob Storage The Large Blob extension lets you store up to 1KB of arbitrary data directly within the passkey hardware.
// 1. Enable Large Blob Support during Registration
final regOptions = FlutterPasskeyService.createRegistrationOptions(
/* ... */
enableLargeBlob: true,
);
// 2. Write Data during Authentication
final authOptionsWrite = FlutterPasskeyService.createAuthenticationOptions(
/* ... */
largeBlobWrite: Uint8List.fromList('Hello World'.codeUnits),
);
await FlutterPasskeyService.authenticate(authOptionsWrite);
// 3. Read Data during Authentication
final authOptionsRead = FlutterPasskeyService.createAuthenticationOptions(
/* ... */
largeBlobRead: true,
);
final response = await FlutterPasskeyService.authenticate(authOptionsRead);
final blobData = response.clientExtensionResults?.largeBlob?.blob;
π Tutorials & Articles #
To get an in-depth understanding of the transition to passwordless logins and see a complete conceptual walkthrough of this plugin, check out this comprehensive guide:
ποΈ Advanced Usage #
For granular control, you can define custom options using the typed model classes directly:
final customOptions = RegisterGenerateOptionData(
challenge: '...',
rp: RegisterGenerateOptionRp(name: 'App', id: 'domain.com'),
user: RegisterGenerateOptionUser(id: 'user', name: 'user', displayName: 'User'),
pubKeyCredParams: [
RegisterGenerateOptionPublicKeyParams(alg: -7, type: 'public-key'), // ES256
RegisterGenerateOptionPublicKeyParams(alg: -257, type: 'public-key'), // RS256
],
timeout: 60000,
attestation: 'direct',
authenticatorSelection: RegisterGenerateOptionAuthenticatorSelection(
residentKey: 'required',
userVerification: 'required',
authenticatorAttachment: 'platform',
),
);
π Security Considerations #
- Server-Side Verification: This plugin only handles the client-side component of WebAuthn. You MUST securely verify the cryptographic signatures on your backend.
- Challenge Generation: Challenges must be generated server-side using cryptographically secure random number generators to prevent replay attacks.
- HTTPS: Apple and Google require your associated domain to be served over secure HTTPS.
- Verification Result: Always use
clientDataJSON,authenticatorData, andsignatureto securely verify the passkey login or credential registration.
π€ Contributing & Support #
- Repository: GitHub
- Issue Tracker: Report a bug or request a feature
- Contributions are welcome! Read the
CONTRIBUTING.mdfor guidelines.
π Contributors #
Thanks to these wonderful people who have contributed to the project:
| Contributor | Contribution |
|---|---|
| @minhtri1401 | Project creator & maintainer β iOS & Android implementation, PRF and Large Blob extensions |
| @hhanh00 | macOS platform support (#2) |
Want your name here? Check out CONTRIBUTING.md and open a pull request!
π License #
This project is licensed under the MIT License - see the LICENSE file for details.