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

A simple, production-ready Flutter package that completely abstracts Firebase App Check for Android, iOS, and Web.

example/lib/main.dart

import 'dart:async';

import 'package:app_check_secure/app_check_secure.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  String? startupError;

  try {
    // 1) Firebase initialization (required before App Check).
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    // Replace these with your own values from Firebase Console.
    // - recaptchaSiteKey: App Check → your Web app → reCAPTCHA site key (production)
    // - *DebugToken: App Check → Manage debug tokens (debug / CI only)
    const recaptchaSiteKey = 'YOUR_RECAPTCHA_SITE_KEY';
    const webDebugToken = 'YOUR_WEB_APP_CHECK_DEBUG_TOKEN';
    const androidDebugToken = 'YOUR_ANDROID_APP_CHECK_DEBUG_TOKEN';
    const iosDebugToken = 'YOUR_IOS_APP_CHECK_DEBUG_TOKEN';

    // 2) App Check — platforms default to false; enable only what you need.
    // debug: true  → uses *DebugToken (debug provider)
    // debug: false → uses recaptchaSiteKey (reCAPTCHA)
    await AppCheckSecure.initialize(
      config: AppCheckConfig(
        debug: kDebugMode,
        enableWeb: true,
        webSiteKey: recaptchaSiteKey,
        webDebugToken: webDebugToken,
        // Uncomment when testing Android / iOS:
        // enableAndroid: true,
        // enableIos: true,
        // androidDebugToken: androidDebugToken,
        // iosDebugToken: iosDebugToken,
      ),
    );
  } catch (error) {
    startupError = error.toString();
  }

  runApp(AppCheckExampleApp(startupError: startupError));
}

/// Example application demonstrating [AppCheckSecure].
class AppCheckExampleApp extends StatelessWidget {
  /// Creates the example app.
  const AppCheckExampleApp({super.key, this.startupError});

  /// Non-null when Firebase or App Check failed during startup.
  final String? startupError;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'app_check_secure example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF0B6E4F)),
        useMaterial3: true,
      ),
      home: AppCheckDemoPage(startupError: startupError),
    );
  }
}

/// Demo page for token generation, refresh, clear, and listening.
class AppCheckDemoPage extends StatefulWidget {
  /// Creates the demo page.
  const AppCheckDemoPage({super.key, this.startupError});

  /// Startup error message, if any.
  final String? startupError;

  @override
  State<AppCheckDemoPage> createState() => _AppCheckDemoPageState();
}

class _AppCheckDemoPageState extends State<AppCheckDemoPage> {
  StreamSubscription<String?>? _tokenSubscription;
  String? _token;
  String? _lastEvent;
  String? _error;
  bool _busy = false;

  @override
  void initState() {
    super.initState();
    _token = AppCheckSecure.currentToken;
    if (AppCheckSecure.isInitialized) {
      _tokenSubscription = AppCheckSecure.onTokenChanged.listen((token) {
        setState(() {
          _token = token;
          _lastEvent =
              'onTokenChanged → ${token == null ? 'null' : _short(token)}';
        });
      });
    }
  }

  @override
  void dispose() {
    _tokenSubscription?.cancel();
    super.dispose();
  }

  String _platformLabel() {
    if (kIsWeb) {
      return 'Web';
    }
    return switch (defaultTargetPlatform) {
      TargetPlatform.android => 'Android',
      TargetPlatform.iOS => 'iOS',
      _ => defaultTargetPlatform.name,
    };
  }

  String _short(String value) {
    if (value.length <= 48) {
      return value;
    }
    return '${value.substring(0, 24)}…${value.substring(value.length - 12)}';
  }

  Future<void> _run(Future<void> Function() action) async {
    setState(() {
      _busy = true;
      _error = null;
    });
    try {
      await action();
    } on AppCheckException catch (error) {
      setState(() => _error = error.toString());
    } catch (error) {
      setState(() => _error = error.toString());
    } finally {
      if (mounted) {
        setState(() => _busy = false);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    final startupError = widget.startupError;

    return Scaffold(
      appBar: AppBar(
        title: const Text('app_check_secure'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Text(
            'Platform: ${_platformLabel()}',
            style: Theme.of(context).textTheme.titleMedium,
          ),
          const SizedBox(height: 8),
          Text('Initialized: ${AppCheckSecure.isInitialized}'),
          if (startupError != null) ...[
            const SizedBox(height: 12),
            Card(
              color: Theme.of(context).colorScheme.errorContainer,
              child: Padding(
                padding: const EdgeInsets.all(12),
                child: Text(
                  'Startup error:\n$startupError\n\n'
                  'Configure Firebase with `flutterfire configure` in the '
                  'example/ directory, then rebuild. For web, also pass '
                  '--dart-define=RECAPTCHA_SITE_KEY=...',
                ),
              ),
            ),
          ],
          const SizedBox(height: 16),
          Text('Current token', style: Theme.of(context).textTheme.titleMedium),
          const SizedBox(height: 8),
          SelectableText(_token == null ? 'null' : _token!),
          if (_lastEvent != null) ...[
            const SizedBox(height: 12),
            Text('Last stream event: $_lastEvent'),
          ],
          if (_error != null) ...[
            const SizedBox(height: 12),
            Text(
              _error!,
              style: TextStyle(color: Theme.of(context).colorScheme.error),
            ),
          ],
          const SizedBox(height: 24),
          Wrap(
            spacing: 8,
            runSpacing: 8,
            children: [
              FilledButton(
                onPressed: !AppCheckSecure.isInitialized || _busy
                    ? null
                    : () => _run(() async {
                          final token = await AppCheckSecure.getToken();
                          setState(() {
                            _token = token;
                            _lastEvent =
                                'getToken → ${token == null ? 'null' : _short(token)}';
                          });
                        }),
                child: const Text('Get token'),
              ),
              FilledButton.tonal(
                onPressed: !AppCheckSecure.isInitialized || _busy
                    ? null
                    : () => _run(() async {
                          final token = await AppCheckSecure.refreshToken();
                          setState(() {
                            _token = token;
                            _lastEvent =
                                'refreshToken → ${token == null ? 'null' : _short(token)}';
                          });
                        }),
                child: const Text('Refresh token'),
              ),
              OutlinedButton(
                onPressed: !AppCheckSecure.isInitialized || _busy
                    ? null
                    : () => _run(() async {
                          await AppCheckSecure.clearToken();
                          setState(() {
                            _token = AppCheckSecure.currentToken;
                            _lastEvent = 'clearToken → null';
                          });
                        }),
                child: const Text('Clear token'),
              ),
            ],
          ),
          const SizedBox(height: 32),
          Text('Notes', style: Theme.of(context).textTheme.titleMedium),
          const SizedBox(height: 8),
          const Text(
            '• Platforms default to false — set enableAndroid / enableIos / enableWeb.\n'
            '• Set debug: true/false — package picks providers.\n'
            '• Optional androidDebugToken / iosDebugToken / webDebugToken (or setters).\n'
            '• Web production: reCAPTCHA — requires webSiteKey.\n'
            '• Register debug tokens in Firebase Console → App Check.',
          ),
        ],
      ),
    );
  }
}
0
likes
160
points
38
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A simple, production-ready Flutter package that completely abstracts Firebase App Check for Android, iOS, and Web.

Repository (GitHub)
View/report issues

Topics

#firebase #app-check #security #flutter

License

MIT (license)

Dependencies

firebase_app_check, flutter

More

Packages that depend on app_check_secure