web_authn_web 0.0.1 copy "web_authn_web: ^0.0.1" to clipboard
web_authn_web: ^0.0.1 copied to clipboard

Flutter Web plugin for WebAuthn (passkeys) registration and authentication.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:web_authn_web/web_authn_web.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  final _webAuthnWebPlugin = WebAuthnWeb();
  String _registerRpName = 'ACME Corp';
  String _registerRpId = 'localhost';
  String _registerUserName = '[email protected]';
  String _registerUserId = 'CAMW';
  String _registerDisplayName = 'User Name';
  String _registerChallenge = 'Y2hhbGxlbmdl';
  String _registerAuthenticatorAttachment = 'platform';
  String _registerResidentKey = 'required';
  String _registerAttestation = 'direct';
  int _registerTimeoutMs = 60000;
  String _signChallenge = 'Y2hhbGxlbmdl';
  String _signRpId = 'localhost';
  String _signUserVerification = 'preferred';
  int _signTimeoutMs = 60000;

  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String platformVersion;
    // Platform messages may fail, so we use a try/catch PlatformException.
    // We also handle the message potentially returning null.
    try {
      platformVersion =
          await _webAuthnWebPlugin.getPlatformVersion() ??
          'Unknown platform version';
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Plugin example app')),
        body: Builder(
          builder: (context) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text('Running on: $_platformVersion\n'),
                  ElevatedButton(
                    onPressed: () async {
                      await _showRegisterDialog(context);
                    },
                    child: const Text('Register Passkey'),
                  ),
                  const SizedBox(height: 20),
                  ElevatedButton(
                    onPressed: () async {
                      await _showSignDialog(context);
                    },
                    child: const Text('Sign (Login)'),
                  ),
                ],
              ),
            );
          },
        ),
      ),
    );
  }

  int _parseInt(String value, int fallback) {
    final parsed = int.tryParse(value.trim());
    return parsed ?? fallback;
  }

  Future<void> _showRegisterDialog(BuildContext context) async {
    final rpNameController = TextEditingController(text: _registerRpName);
    final rpIdController = TextEditingController(text: _registerRpId);
    final userNameController = TextEditingController(text: _registerUserName);
    final userIdController = TextEditingController(text: _registerUserId);
    final displayNameController =
        TextEditingController(text: _registerDisplayName);
    final challengeController =
        TextEditingController(text: _registerChallenge);
    final attachmentController =
        TextEditingController(text: _registerAuthenticatorAttachment);
    final residentKeyController =
        TextEditingController(text: _registerResidentKey);
    final attestationController =
        TextEditingController(text: _registerAttestation);
    final timeoutController =
        TextEditingController(text: _registerTimeoutMs.toString());

    final confirmed = await showDialog<bool>(
      context: context,
      builder: (dialogContext) {
        return AlertDialog(
          title: const Text('Register Passkey'),
          content: SingleChildScrollView(
            child: Column(
              children: [
                TextField(
                  controller: rpNameController,
                  decoration: const InputDecoration(labelText: 'RP Name'),
                ),
                TextField(
                  controller: rpIdController,
                  decoration: const InputDecoration(labelText: 'RP ID'),
                ),
                TextField(
                  controller: userNameController,
                  decoration: const InputDecoration(labelText: 'User Name'),
                ),
                TextField(
                  controller: userIdController,
                  decoration: const InputDecoration(labelText: 'User ID'),
                ),
                TextField(
                  controller: displayNameController,
                  decoration: const InputDecoration(labelText: 'Display Name'),
                ),
                TextField(
                  controller: challengeController,
                  decoration: const InputDecoration(labelText: 'Challenge'),
                ),
                TextField(
                  controller: attachmentController,
                  decoration: const InputDecoration(
                    labelText: 'Authenticator Attachment',
                  ),
                ),
                TextField(
                  controller: residentKeyController,
                  decoration: const InputDecoration(labelText: 'Resident Key'),
                ),
                TextField(
                  controller: attestationController,
                  decoration: const InputDecoration(labelText: 'Attestation'),
                ),
                TextField(
                  controller: timeoutController,
                  decoration: const InputDecoration(labelText: 'Timeout (ms)'),
                  keyboardType: TextInputType.number,
                ),
              ],
            ),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(dialogContext).pop(false),
              child: const Text('Cancel'),
            ),
            ElevatedButton(
              onPressed: () => Navigator.of(dialogContext).pop(true),
              child: const Text('Register'),
            ),
          ],
        );
      },
    );

    if (confirmed != true || !mounted) return;

    setState(() {
      _registerRpName = rpNameController.text.trim();
      _registerRpId = rpIdController.text.trim();
      _registerUserName = userNameController.text.trim();
      _registerUserId = userIdController.text.trim();
      _registerDisplayName = displayNameController.text.trim();
      _registerChallenge = challengeController.text.trim();
      _registerAuthenticatorAttachment = attachmentController.text.trim();
      _registerResidentKey = residentKeyController.text.trim();
      _registerAttestation = attestationController.text.trim();
      _registerTimeoutMs =
          _parseInt(timeoutController.text, _registerTimeoutMs);
    });

    try {
      final options = PublicKeyCredentialCreationOptions(
        rp: RpEntity(name: _registerRpName, id: _registerRpId),
        user: UserEntity(
          name: _registerUserName,
          id: _registerUserId,
          displayName: _registerDisplayName,
        ),
        challenge: _registerChallenge,
        // base64url encoded challenge
        pubKeyCredParams: [
          PubKeyCredParam(type: 'public-key', alg: -7), // ES256
          PubKeyCredParam(type: 'public-key', alg: -257), // RS256
        ],
        authenticatorSelection: AuthenticatorSelectionCriteria(
          authenticatorAttachment:
              _registerAuthenticatorAttachment.isEmpty
                  ? null
                  : _registerAuthenticatorAttachment,
          residentKey:
              _registerResidentKey.isEmpty ? null : _registerResidentKey,
        ),
        timeout: _registerTimeoutMs,
        attestation: _registerAttestation.isEmpty ? null : _registerAttestation,
      );

      final result = await _webAuthnWebPlugin.register(options);
      print('Registration result: $result');
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Registration successful')),
      );
    } catch (e) {
      print('Registration error: $e');
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Registration failed: $e')),
      );
    }
  }

  Future<void> _showSignDialog(BuildContext context) async {
    final challengeController = TextEditingController(text: _signChallenge);
    final rpIdController = TextEditingController(text: _signRpId);
    final userVerificationController =
        TextEditingController(text: _signUserVerification);
    final timeoutController =
        TextEditingController(text: _signTimeoutMs.toString());

    final confirmed = await showDialog<bool>(
      context: context,
      builder: (dialogContext) {
        return AlertDialog(
          title: const Text('Sign (Login)'),
          content: SingleChildScrollView(
            child: Column(
              children: [
                TextField(
                  controller: challengeController,
                  decoration: const InputDecoration(labelText: 'Challenge'),
                ),
                TextField(
                  controller: rpIdController,
                  decoration: const InputDecoration(labelText: 'RP ID'),
                ),
                TextField(
                  controller: userVerificationController,
                  decoration:
                      const InputDecoration(labelText: 'User Verification'),
                ),
                TextField(
                  controller: timeoutController,
                  decoration: const InputDecoration(labelText: 'Timeout (ms)'),
                  keyboardType: TextInputType.number,
                ),
              ],
            ),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(dialogContext).pop(false),
              child: const Text('Cancel'),
            ),
            ElevatedButton(
              onPressed: () => Navigator.of(dialogContext).pop(true),
              child: const Text('Sign'),
            ),
          ],
        );
      },
    );

    if (confirmed != true || !mounted) return;

    setState(() {
      _signChallenge = challengeController.text.trim();
      _signRpId = rpIdController.text.trim();
      _signUserVerification = userVerificationController.text.trim();
      _signTimeoutMs = _parseInt(timeoutController.text, _signTimeoutMs);
    });

    try {
      final options = PublicKeyCredentialRequestOptions(
        challenge: _signChallenge, // base64url encoded challenge
        timeout: _signTimeoutMs,
        rpId: _signRpId.isEmpty ? null : _signRpId,
        userVerification:
            _signUserVerification.isEmpty ? null : _signUserVerification,
      );

      final result = await _webAuthnWebPlugin.sign(options);
      print('Sign result: $result');
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Sign successful')),
      );
    } catch (e) {
      print('Sign error: $e');
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Sign failed: $e')),
      );
    }
  }
}
2
likes
0
points
122
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter Web plugin for WebAuthn (passkeys) registration and authentication.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, flutter_web_plugins, plugin_platform_interface, web

More

Packages that depend on web_authn_web

Packages that implement web_authn_web