awesome_node_auth_flutter 1.9.2
awesome_node_auth_flutter: ^1.9.2 copied to clipboard
A Flutter/Dart authentication client for the awesome-node-auth backend. Supports web (including WASM) via cookie + CSRF and native platforms via Bearer token.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:awesome_node_auth_flutter/awesome_node_auth_flutter.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Awesome Node Auth - Flutter Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const AuthScreen(),
);
}
}
class AuthScreen extends StatefulWidget {
const AuthScreen({super.key});
@override
State<AuthScreen> createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
// Initialize auth client pointing to your Node.js server
late final AuthClient authClient = AuthClient(
AuthOptions(
apiPrefix: 'http://localhost:3000/auth', // Change to your server URL
headless: true, // Don't auto-redirect on session expiry
),
);
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;
String? _errorMessage;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> _handleLogin() async {
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
final result = await authClient.login(
_emailController.text.trim(),
_passwordController.text,
);
if (!result.success) {
setState(() => _errorMessage = result.error ?? 'Login failed');
}
// state.userStream will emit the new user, triggering rebuild via listener
} catch (e) {
setState(() => _errorMessage = 'Login failed: $e');
} finally {
setState(() => _isLoading = false);
}
}
Future<void> _handleRegister() async {
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
final result = await authClient.register(
_emailController.text.trim(),
_passwordController.text,
'User', // firstName
'Demo', // lastName
);
if (!result.success) {
setState(() => _errorMessage = result.error ?? 'Registration failed');
}
// Automatically logged in after registration
} catch (e) {
setState(() => _errorMessage = 'Registration failed: $e');
} finally {
setState(() => _isLoading = false);
}
}
Future<void> _handleLogout() async {
try {
await authClient.logout();
if (mounted) {
_emailController.clear();
_passwordController.clear();
setState(() => _errorMessage = null);
}
} catch (e) {
if (mounted) {
setState(() => _errorMessage = 'Logout failed: $e');
}
}
}
@override
void initState() {
super.initState();
// Listen to auth state changes to trigger rebuilds
authClient.state.userStream.listen((_) {
if (mounted) setState(() {});
});
}
@override
Widget build(BuildContext context) {
final user = authClient.state.currentUser;
// Show profile screen if logged in
if (user != null) {
return Scaffold(
appBar: AppBar(title: const Text('User Profile'), centerTitle: true),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const CircleAvatar(
radius: 50,
child: Icon(Icons.person, size: 50),
),
const SizedBox(height: 24),
Text(
'Welcome!',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 16),
Text(user.email, style: Theme.of(context).textTheme.bodyLarge),
const SizedBox(height: 8),
if (user.id != null)
Text(
'ID: ${user.id}',
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 32),
ElevatedButton.icon(
onPressed: _isLoading ? null : _handleLogout,
icon: const Icon(Icons.logout),
label: const Text('Logout'),
),
],
),
),
);
}
// Show login/register screen if not logged in
return Scaffold(
appBar: AppBar(title: const Text('Awesome Node Auth'), centerTitle: true),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 60),
Icon(
Icons.lock_outline,
size: 80,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(height: 32),
Text(
'Authentication Example',
style: Theme.of(context).textTheme.headlineSmall,
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
if (_errorMessage != null)
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.shade100,
border: Border.all(color: Colors.red),
borderRadius: BorderRadius.circular(4),
),
child: Text(
_errorMessage!,
style: TextStyle(color: Colors.red.shade900),
),
),
if (_errorMessage != null) const SizedBox(height: 16),
TextField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
border: OutlineInputBorder(),
),
keyboardType: TextInputType.emailAddress,
enabled: !_isLoading,
),
const SizedBox(height: 16),
TextField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
border: OutlineInputBorder(),
),
obscureText: true,
enabled: !_isLoading,
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
onPressed: _isLoading ? null : _handleLogin,
child: _isLoading
? const SizedBox(
height: 24,
width: 24,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Login'),
),
),
const SizedBox(height: 12),
SizedBox(
width: double.infinity,
height: 48,
child: OutlinedButton(
onPressed: _isLoading ? null : _handleRegister,
child: const Text('Register'),
),
),
const SizedBox(height: 60),
Text(
'Make sure the Node.js server is running on http://localhost:3000',
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
),
],
),
),
),
),
),
);
}
}