Flutter Passkey Service
A comprehensive Flutter plugin that provides seamless integration with Passkeys (WebAuthn) for both iOS and Android platforms. Enable passwordless authentication in your Flutter applications using biometric authentication, device PINs, or security keys.
โจ Features
- ๐ Passwordless Authentication - Secure biometric and device-based authentication
- ๐ฑ Cross-Platform Support - Native implementation for iOS 16.0+ and Android API 28+
- ๐ก๏ธ WebAuthn Compliant - Full compliance with W3C WebAuthn standards
- ๐ Cross-Device Sync - Passkeys sync across user's devices via platform providers
- ๐ Type-Safe API - Generated with Pigeon for reliable Flutter-to-native communication
- ๐ฏ Easy Integration - Simple, developer-friendly API with comprehensive error handling
- ๐ Well Documented - Complete API documentation with examples
๐ Quick Start
Installation
Add flutter_passkey_service
to your pubspec.yaml
:
dependencies:
flutter_passkey_service: ^0.0.1
Run the following command:
flutter pub get
Platform Setup
iOS Setup
-
Minimum Requirements: iOS 16.0+
-
Add Capability: In Xcode, add "Associated Domains" capability
- Open your project in Xcode
- Select your target โ "Signing & Capabilities"
- Click "+" and add "Associated Domains"
-
Configure Domain: Add your domain with
webcredentials
prefix:webcredentials:yourdomain.com
-
Domain Verification: Create an
apple-app-site-association
file on your server:{ "webcredentials": { "apps": ["TEAMID.com.yourcompany.yourapp"] } }
- Host at:
https://yourdomain.com/.well-known/apple-app-site-association
- No file extension required
- Content-Type:
application/json
- Verification Tool: Apple App Site Association Validator
- Host at:
Android Setup
-
Minimum Requirements: Android API 28+ (Android 9.0)
-
Add Dependencies: The plugin automatically includes required dependencies
-
Configure Digital Asset Links: Create an
assetlinks.json
file:[{ "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.yourcompany.yourapp", "sha256_cert_fingerprints": ["SHA256_FINGERPRINT_OF_YOUR_APP"] } }]
-
Host Asset Links File:
- Upload to:
https://yourdomain.com/.well-known/assetlinks.json
- Content-Type:
application/json
- Must be accessible via HTTPS
- Upload to:
-
Get SHA256 Fingerprint:
# For debug keystore keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android # For release keystore keytool -list -v -keystore /path/to/your/keystore.jks -alias your_key_alias
-
Verification Tools:
Basic Usage
import 'package:flutter_passkey_service/flutter_passkey_service.dart';
class PasskeyAuth {
// Register a new passkey
Future<void> registerPasskey() async {
try {
final options = FlutterPasskeyService.createRegistrationOptions(
challenge: 'your-server-challenge',
rpName: 'Your App Name',
rpId: 'yourdomain.com',
userId: 'user-123',
username: 'user@example.com',
displayName: 'John Doe',
);
final response = await FlutterPasskeyService.register(options);
// Send response to your server for verification
print('Registration successful: ${response.id}');
} on PasskeyException catch (e) {
print('Registration failed: ${e.message}');
}
}
// Authenticate with existing passkey
Future<void> authenticateWithPasskey() async {
try {
final request = FlutterPasskeyService.createAuthenticationOptions(
challenge: 'your-server-challenge',
rpId: 'yourdomain.com',
);
final response = await FlutterPasskeyService.authenticate(request);
// Send response to your server for verification
print('Authentication successful: ${response.id}');
} on PasskeyException catch (e) {
print('Authentication failed: ${e.message}');
}
}
}
๐ Comprehensive Guide
Domain Verification Setup
Proper domain verification is essential for passkey functionality. Both platforms require your app to be associated with your web domain.
iOS Domain Verification (Apple App Site Association)
-
Create the Association File:
{ "webcredentials": { "apps": [ "TEAMID.com.yourcompany.yourapp", "TEAMID.com.yourcompany.yourapp.staging" ] } }
-
Host the File:
- URL:
https://yourdomain.com/.well-known/apple-app-site-association
- Important: No
.json
file extension! - Content-Type:
application/json
- Must be served over HTTPS
- Must return HTTP 200 status
- URL:
-
Find Your Team ID:
- Go to Apple Developer Account
- Navigate to "Membership" section
- Copy your Team ID (10-character string)
-
Verify Association:
# Test your association file curl -v https://yourdomain.com/.well-known/apple-app-site-association
Android Domain Verification (Digital Asset Links)
-
Get Your App's SHA256 Fingerprint:
# Debug keystore (for development) keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android | grep SHA256 # Release keystore (for production) keytool -list -v -keystore /path/to/release-key.keystore -alias release-key-alias | grep SHA256 # From APK file keytool -printcert -jarfile app-release.apk | grep SHA256
-
Create Asset Links File:
[ { "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.yourcompany.yourapp", "sha256_cert_fingerprints": [ "AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78" ] } } ]
-
Host the File:
- URL:
https://yourdomain.com/.well-known/assetlinks.json
- Content-Type:
application/json
- Must be served over HTTPS
- Must return HTTP 200 status
- URL:
-
Verify Asset Links:
# Test your asset links file curl -v https://yourdomain.com/.well-known/assetlinks.json
- Use Google's Tester
- Use Asset Links Generator
Common Domain Verification Issues
Issue | Solution |
---|---|
File not found (404) | Ensure files are in /.well-known/ directory |
HTTPS required | Both files must be served over HTTPS only |
Wrong content-type | Set Content-Type: application/json |
Invalid JSON | Validate JSON syntax |
Wrong package name | Must match your app's package identifier exactly |
Case sensitivity | Package names and fingerprints are case-sensitive |
Caching issues | Clear CDN/server cache after updating files |
Testing Domain Verification
// Test domain association in your Flutter app
void testDomainVerification() async {
try {
// This will fail if domain verification is not set up correctly
final request = FlutterPasskeyService.createAuthenticationOptions(
challenge: 'test-challenge',
rpId: 'yourdomain.com', // Must match your verified domain
);
print('Domain verification appears to be working');
} catch (e) {
print('Domain verification issue: $e');
}
}
Registration Flow
The passkey registration process involves creating a new credential for the user:
Future<CreatePasskeyResponseData> registerUser({
required String username,
required String userId,
required String challenge,
}) async {
// 1. Create registration options
final options = RegisterGenerateOptionData(
challenge: challenge, // Base64URL encoded challenge from server
rp: RegisterGenerateOptionRp(
name: 'Your App Name',
id: 'yourdomain.com',
),
user: RegisterGenerateOptionUser(
id: userId, // Unique user identifier
name: username,
displayName: 'Display Name',
),
pubKeyCredParams: [
RegisterGenerateOptionPublicKeyParams(alg: -7, type: 'public-key'), // ES256
RegisterGenerateOptionPublicKeyParams(alg: -257, type: 'public-key'), // RS256
],
timeout: 60000,
attestation: 'none',
excludeCredentials: [], // Previously registered credentials to exclude
authenticatorSelection: RegisterGenerateOptionAuthenticatorSelection(
residentKey: 'preferred',
userVerification: 'required',
requireResidentKey: false,
authenticatorAttachment: 'platform',
),
extensions: RegisterGenerateOptionExtension(credProps: true),
);
// 2. Perform registration
final response = await FlutterPasskeyService.register(options);
// 3. Send to server for verification and storage
// response contains: id, rawId, type, authenticatorAttachment,
// response (attestationObject, clientDataJSON), clientExtensionResults
return response;
}
Authentication Flow
Authenticate users with their existing passkeys:
Future<GetPasskeyAuthenticationResponseData> authenticateUser({
required String challenge,
List<String>? allowedCredentialIds,
}) async {
// 1. Create authentication request
final request = AuthGenerateOptionResponseData(
rpId: 'yourdomain.com',
challenge: challenge, // Base64URL encoded challenge from server
allowCredentials: allowedCredentialIds?.map((id) =>
AuthGenerateOptionAllowCredential(
id: id,
type: 'public-key',
transports: ['internal', 'hybrid'],
)
).toList() ?? [],
timeout: 60000,
userVerification: 'required',
);
// 2. Perform authentication
final response = await FlutterPasskeyService.authenticate(request);
// 3. Send to server for verification
// response contains: id, rawId, type, authenticatorAttachment,
// response (clientDataJSON, authenticatorData, signature, userHandle)
return response;
}
Error Handling
The plugin provides comprehensive error handling through PasskeyException
:
try {
await FlutterPasskeyService.register(options);
} on PasskeyException catch (e) {
switch (e.errorType) {
case PasskeyErrorType.userCancelled:
showMessage('User cancelled the operation');
break;
case PasskeyErrorType.noCredentialsAvailable:
showMessage('No passkeys available for this account');
break;
case PasskeyErrorType.invalidParameters:
showMessage('Invalid request parameters');
break;
case PasskeyErrorType.platformNotSupported:
showMessage('Passkeys not supported on this device');
break;
default:
showMessage('Authentication failed: ${e.message}');
}
}
Available Error Types
Error Type | Description |
---|---|
invalidParameters |
Invalid or missing parameters |
userCancelled |
User cancelled the operation |
userTimeout |
Operation timed out |
noCredentialsAvailable |
No credentials available for authentication |
credentialNotFound |
Specified credential not found |
platformNotSupported |
Platform doesn't support passkeys |
domainNotAssociated |
Domain not associated with app |
invalidResponse |
Invalid response received |
systemError |
System-level error occurred |
networkError |
Network-related error |
unknownError |
Unknown error occurred |
๐๏ธ Advanced Configuration
Custom Registration Options
final customOptions = RegisterGenerateOptionData(
challenge: challenge,
rp: RegisterGenerateOptionRp(name: 'App', id: 'domain.com'),
user: RegisterGenerateOptionUser(id: 'user', name: 'username', displayName: 'User'),
pubKeyCredParams: [
RegisterGenerateOptionPublicKeyParams(alg: -7, type: 'public-key'),
],
timeout: 120000, // 2 minutes
attestation: 'direct', // Request attestation
excludeCredentials: [
RegisterGenerateOptionExcludeCredential(
id: 'existing-credential-id',
type: 'public-key',
transports: ['internal'],
),
],
authenticatorSelection: RegisterGenerateOptionAuthenticatorSelection(
residentKey: 'required', // Force resident key
userVerification: 'preferred',
requireResidentKey: true,
authenticatorAttachment: 'cross-platform', // Allow external authenticators
),
extensions: RegisterGenerateOptionExtension(credProps: true),
);
Server Integration
Challenge Generation
// Generate a cryptographically secure challenge on your server
String generateChallenge() {
final bytes = List<int>.generate(32, (i) => Random.secure().nextInt(256));
return base64Url.encode(bytes);
}
Verification
// Verify registration response on server
bool verifyRegistration(CreatePasskeyResponseData response, String challenge) {
// 1. Decode and verify clientDataJSON
// 2. Verify challenge matches
// 3. Verify origin matches your domain
// 4. Parse and verify attestationObject
// 5. Store credential for future authentication
return true; // Simplified
}
// Verify authentication response on server
bool verifyAuthentication(GetPasskeyAuthenticationResponseData response, String challenge) {
// 1. Decode and verify clientDataJSON
// 2. Verify challenge matches
// 3. Verify origin matches your domain
// 4. Verify signature using stored public key
// 5. Update credential sign count
return true; // Simplified
}
๐ง Platform Requirements
iOS
- Minimum Version: iOS 16.0+
- Frameworks: AuthenticationServices
- Capabilities: Associated Domains
- Features: Touch ID, Face ID, Device Passcode support
Android
- Minimum Version: Android 9.0 (API 28)+
- Dependencies: Credential Manager API
- Features: Biometric authentication, Device PIN support
- Requirements: Google Play Services
๐งช Testing
Unit Testing
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_passkey_service/flutter_passkey_service.dart';
void main() {
group('FlutterPasskeyService', () {
test('createRegistrationOptions returns valid options', () {
final options = FlutterPasskeyService.createRegistrationOptions(
challenge: 'test-challenge',
rpName: 'Test App',
rpId: 'test.com',
userId: 'user-123',
username: 'test@example.com',
);
expect(options.challenge, 'test-challenge');
expect(options.rp.name, 'Test App');
expect(options.user.id, 'user-123');
});
});
}
Integration Testing
import 'package:integration_test/integration_test.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Passkey Integration Tests', () {
testWidgets('registration flow', (tester) async {
// Test complete registration flow
// Note: Requires physical device and user interaction
});
});
}
๐ Security Considerations
- Challenge Generation: Always generate challenges server-side using cryptographically secure methods
- Origin Verification: Verify the origin in clientDataJSON matches your domain
- Timeout Handling: Implement appropriate timeouts for user operations
- Error Messages: Avoid exposing sensitive information in error messages
- Credential Storage: Store public keys and metadata securely on your server
- Sign Count: Track and validate signature counter to prevent replay attacks
๐ค Contributing
We welcome contributions! Please read our Contributing Guidelines for details on:
- Code of Conduct
- Development setup
- Pull request process
- Issue reporting
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Support
- Documentation: API Reference
- Issues: GitHub Issues
- Discussions: GitHub Discussions
๐ Resources
WebAuthn & Passkeys
- WebAuthn Specification
- Passkeys Overview
- Passkeys.dev - Community resources and guides
Platform Documentation
- iOS AuthenticationServices
- Android Credential Manager
- iOS Passkeys Developer Guide
- Android Passkeys Implementation Guide
Domain Verification Tools
- Apple App Site Association Validator
- Google Digital Asset Links Tester
- Digital Asset Links Generator
- Apple Team ID Lookup
Testing & Debugging
- WebAuthn.io - Test WebAuthn implementations
- Passkeys Debugger - Debug WebAuthn flows
- Yubico WebAuthn Demo - Test various scenarios
Security Resources
- FIDO Alliance - Security specifications and guidelines
- WebAuthn Security Considerations
- OWASP Authentication Guide
Made with โค๏ธ for the Flutter community