Skip to Content
AuthenticationUsers

InstantDB provides comprehensive user authentication with email/password, magic links, magic codes, and session management. All authentication methods integrate seamlessly with real-time sync and presence features.

Getting Started with Auth

Initialize with Authentication

final db = await InstantDB.init( appId: 'your-app-id', config: const InstantConfig( syncEnabled: true, ), ); // Listen to authentication state changes db.auth.onAuthStateChange.listen((user) { if (user != null) { print('User signed in: ${user.email}'); } else { print('User signed out'); } });

Check Authentication Status

// One-time check final currentUser = db.getAuth(); // Reactive updates final authSignal = db.subscribeAuth(); // Use in widgets Watch((context) { final user = db.subscribeAuth().value; return user != null ? WelcomeScreen(user: user) : LoginScreen(); });

Authentication Methods

Magic Codes

One-time password (OTP) authentication:

class MagicCodeAuth extends StatefulWidget { @override State<MagicCodeAuth> createState() => _MagicCodeAuthState(); } class _MagicCodeAuthState extends State<MagicCodeAuth> { final _emailController = TextEditingController(); final _codeController = TextEditingController(); bool _isLoading = false; bool _codeSent = false; String? _errorMessage; Future<void> _sendMagicCode() async { setState(() { _isLoading = true; _errorMessage = null; }); try { final db = InstantProvider.of(context); await db.auth.sendMagicCode(_emailController.text.trim()); setState(() { _codeSent = true; }); } on InstantException catch (e) { setState(() { _errorMessage = e.message; }); } finally { setState(() { _isLoading = false; }); } } Future<void> _verifyCode() async { setState(() { _isLoading = true; _errorMessage = null; }); try { final db = InstantProvider.of(context); final user = await db.auth.verifyMagicCode( email: _emailController.text.trim(), code: _codeController.text.trim(), ); print('User authenticated: ${user.email}'); // Navigation handled by AuthBuilder } on InstantException catch (e) { setState(() { _errorMessage = e.message; }); } finally { setState(() { _isLoading = false; }); } } @override Widget build(BuildContext context) { return Column( children: [ TextField( controller: _emailController, decoration: const InputDecoration( labelText: 'Email Address', keyboardType: TextInputType.emailAddress, ), enabled: !_codeSent, ), const SizedBox(height: 16), if (_codeSent) ...[ TextField( controller: _codeController, decoration: const InputDecoration( labelText: 'Verification Code', hintText: 'Enter 6-digit code', ), keyboardType: TextInputType.number, maxLength: 6, ), const SizedBox(height: 16), ], if (_errorMessage != null) Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.red.shade100, borderRadius: BorderRadius.circular(8), ), child: Text( _errorMessage!, style: TextStyle(color: Colors.red.shade700), ), ), const SizedBox(height: 16), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _isLoading ? null : (_codeSent ? _verifyCode : _sendMagicCode), child: _isLoading ? const CircularProgressIndicator() : Text(_codeSent ? 'Verify Code' : 'Send Code'), ), ), if (_codeSent) ...[ const SizedBox(height: 16), OutlinedButton( onPressed: () { setState(() { _codeSent = false; _codeController.clear(); }); }, child: const Text('Use Different Email'), ), ], ], ); } }

Complete Authentication Flow

Combine all authentication methods in a unified experience:

class AuthFlow extends StatefulWidget { @override State<AuthFlow> createState() => _AuthFlowState(); } class _AuthFlowState extends State<AuthFlow> { AuthMethod _currentMethod = AuthMethod.emailPassword; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sign In'), ), body: Padding( padding: const EdgeInsets.all(24), child: Column( children: [ // Method selector SegmentedButton<AuthMethod>( segments: const [ ButtonSegment( value: AuthMethod.emailPassword, label: Text('Email/Password'), icon: Icon(Icons.password), ), ButtonSegment( value: AuthMethod.magicLink, label: Text('Magic Link'), icon: Icon(Icons.link), ), ButtonSegment( value: AuthMethod.magicCode, label: Text('Magic Code'), icon: Icon(Icons.pin), ), ], selected: {_currentMethod}, onSelectionChanged: (selection) { setState(() { _currentMethod = selection.first; }); }, ), const SizedBox(height: 32), // Authentication method Expanded( child: switch (_currentMethod) { AuthMethod.emailPassword => EmailPasswordAuth(), AuthMethod.magicLink => MagicLinkAuth(), AuthMethod.magicCode => MagicCodeAuth(), }, ), ], ), ), ); } } enum AuthMethod { emailPassword, magicLink, magicCode, }

Reactive Authentication Widget

Use the AuthBuilder widget for reactive authentication UI:

class App extends StatelessWidget { @override Widget build(BuildContext context) { return InstantProvider( db: db, child: AuthBuilder( builder: (context, user) { if (user != null) { // User is authenticated return MainApp(user: user); } else { // User needs to sign in return AuthFlow(); } }, loadingBuilder: (context) { return const Scaffold( body: Center( child: CircularProgressIndicator(), ), ); }, ), ); } } class MainApp extends StatelessWidget { final AuthUser user; const MainApp({super.key, required this.user}); @override Widget build(BuildContext context) { final db = InstantProvider.of(context); return Scaffold( appBar: AppBar( title: Text('Welcome, ${user.email}'), actions: [ PopupMenuButton( itemBuilder: (context) => [ PopupMenuItem( child: const Text('Profile'), onTap: () => _showProfile(context), ), PopupMenuItem( child: const Text('Sign Out'), onTap: () => db.auth.signOut(), ), ], ), ], ), body: YourAppContent(), ); } void _showProfile(BuildContext context) { showDialog( context: context, builder: (context) => Dialog( child: Padding( padding: const EdgeInsets.all(24), child: UserProfile(user: user), ), ), ); } }

Best Practices

1. Handle Authentication States

Always provide loading and error states:

AuthBuilder( builder: (context, user) => user != null ? MainApp() : LoginScreen(), loadingBuilder: (context) => LoadingScreen(), errorBuilder: (context, error) => ErrorScreen(error: error), )

2. Validate Input

Validate email and password formats:

String? validateEmail(String email) { if (email.isEmpty) return 'Email is required'; if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(email)) { return 'Please enter a valid email'; } return null; } String? validatePassword(String password) { if (password.isEmpty) return 'Password is required'; if (password.length < 8) return 'Password must be at least 8 characters'; return null; }

3. Secure Token Storage

InstantDB automatically handles secure token storage, but you can access tokens if needed:

final authToken = db.auth.authToken; if (authToken != null) { // Token is available for API calls }

4. Handle Authentication Errors

Provide clear error messages:

void handleAuthError(InstantException error) { String userMessage; switch (error.code) { case 'invalid_email': userMessage = 'Please enter a valid email address'; break; case 'weak_password': userMessage = 'Password is too weak. Please use a stronger password'; break; case 'auth_error': userMessage = 'Authentication failed. Please try again'; break; default: userMessage = 'An unexpected error occurred'; } showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Authentication Error'), content: Text(userMessage), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('OK'), ), ], ), ); }

Next Steps

Learn more about authentication features: