secure_key_manager 1.1.0
secure_key_manager: ^1.1.0 copied to clipboard
Fetch secret keys by name from Firebase Cloud Functions backed by Google Secret Manager. No secrets ever ship in your app binary, repo, or .env.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:secure_key_manager/secure_key_manager.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// No FlutterFire CLI, no firebase_options.dart, no google-services.json /
// GoogleService-Info.plist. Just paste the config from the Firebase Console
// (Project settings → Your apps → SDK setup and configuration) here.
await SecureKeyManager.initialize(
firebaseOptions: const FirebaseOptions(
apiKey: 'YOUR_API_KEY',
appId: 'YOUR_APP_ID',
messagingSenderId: 'YOUR_SENDER_ID',
projectId: 'YOUR_PROJECT_ID',
),
region: 'asia-south1', // your Cloud Functions region
cacheTtl: const Duration(minutes: 30),
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'secure_key_manager demo',
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
home: const KeyDemoPage(),
);
}
}
class KeyDemoPage extends StatefulWidget {
const KeyDemoPage({super.key});
@override
State<KeyDemoPage> createState() => _KeyDemoPageState();
}
class _KeyDemoPageState extends State<KeyDemoPage> {
static const _keys = [
'AGORA_APP_ID',
'GOOGLE_MAPS_KEY',
'OPENAI_API_KEY',
];
String _keyName = _keys.first;
String _status = 'Pick a key and tap Fetch.';
bool _loading = false;
Future<void> _fetch() async {
setState(() {
_loading = true;
_status = 'Fetching $_keyName…';
});
try {
final value = await SecureKeyManager.get(_keyName);
// Never print real secrets in production logs — masked here for the demo.
final masked = value.length <= 4
? '••••'
: '${value.substring(0, 2)}••••${value.substring(value.length - 2)}';
setState(() => _status = '$_keyName = $masked (len ${value.length})');
} catch (e) {
setState(() => _status = 'Error: $e');
} finally {
setState(() => _loading = false);
}
}
Future<void> _logout() async {
await FirebaseAuth.instance.signOut();
SecureKeyManager.clearCache();
setState(() => _status = 'Signed out and cache cleared.');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('SecureKeyManager')),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
DropdownButton<String>(
value: _keyName,
isExpanded: true,
items: _keys
.map((k) => DropdownMenuItem(value: k, child: Text(k)))
.toList(),
onChanged: _loading
? null
: (v) => setState(() => _keyName = v ?? _keyName),
),
const SizedBox(height: 16),
FilledButton(
onPressed: _loading ? null : _fetch,
child: _loading
? const SizedBox(
height: 18,
width: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Fetch key'),
),
const SizedBox(height: 8),
OutlinedButton(
onPressed: _loading ? null : () => SecureKeyManager.evict(_keyName),
child: Text('Evict $_keyName from cache'),
),
const SizedBox(height: 8),
OutlinedButton(
onPressed: _loading ? null : _logout,
child: const Text('Logout (clear cache)'),
),
const SizedBox(height: 24),
Text(_status, style: Theme.of(context).textTheme.bodyLarge),
],
),
),
);
}
}