flutter_passkey_service 0.1.0 copy "flutter_passkey_service: ^0.1.0" to clipboard
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 #

pub package Pub Points License: MIT

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 #

  • 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 #

  1. 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 userId string you passed. Their assertions still return the same userHandle as before. New registrations use the base64url-decoded bytes, matching Android. If your server compares userHandle to its stored user.id, accept both forms during the transition or re-register iOS users. iOS/macOS now decode user.id as base64url bytes exactly as Android does. A string that happens to be valid base64 but is not your intended handle (for example user-123) decodes to unintended bytes rather than failing, so base64url-encode your handle before sending it. invalidFormat is returned only when the string cannot be decoded at all or decodes to zero bytes.
  2. JSON defaults. If you relied on the plugin adding userVerification: required or authenticatorAttachment: platform to server JSON, send them from the server.
  3. CocoaPods apps: run pod install in ios/ and macos/ 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) #

  1. Add Capability: In Xcode, go to your target's Signing & Capabilities, add Associated Domains, and enter webcredentials:yourdomain.com.

  2. Host Association File: Create an apple-app-site-association file (no .json extension) and host it at https://yourdomain.com/.well-known/apple-app-site-association.

    {
      "webcredentials": {
        "apps": ["TEAMID.com.yourcompany.yourapp"]
      }
    }
    

    (Ensure Response Content-Type is application/json)

  1. Get SHA256 Fingerprint: Obtain the SHA256 signature of your release and debug keystores.

  2. Host Asset Links File: Create an assetlinks.json file and host it at https://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, and signature to securely verify the passkey login or credential registration.

🀝 Contributing & Support #

🌟 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.

2
likes
160
points
292
downloads

Documentation

API reference

Publisher

verified publishertritoone.com

Weekly Downloads

A comprehensive Flutter plugin for seamless Passkey (WebAuthn) integration on iOS, macOS, and Android. Enable passwordless authentication with biometric security.

Repository (GitHub)
View/report issues
Contributing

License

MIT (license)

Dependencies

flutter, meta, plugin_platform_interface

More

Packages that depend on flutter_passkey_service

Packages that implement flutter_passkey_service