authflow 1.1.0 copy "authflow: ^1.1.0" to clipboard
authflow: ^1.1.0 copied to clipboard

A flexible, provider-based authentication toolkit for Flutter with stream-based auth state, customizable storage, and composable UI widgets.

example/lib/main.dart

// This example shows how to plug in your own AuthProvider and user model with Authflow.
import 'dart:convert';

import 'package:authflow/authflow.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() => runApp(const MyApp());

/// HTTP-based authentication provider that fetches user data from a remote endpoint
class HttpEmailPasswordAuthProvider extends EmailPasswordAuthProvider {
  final String baseUrl;

  @override
  // The providerId is a unique identifier for this provider.
  // It's used in AuthManager to find the right provider for login operations.
  // This ID can be referenced in loginWithProvider() calls and is used to match
  // the defaultProviderId in AuthConfig.
  String get providerId => 'my_auth_provider';

  HttpEmailPasswordAuthProvider({required this.baseUrl});

  @override
  Future<AuthResult> authenticate(EmailPasswordCredentials credentials) async {
    try {
      final response = await http.get(Uri.parse("$baseUrl/${credentials.email}.json"));

      if (response.statusCode != 200) {
        throw AuthException.provider(
          providerId,
          'HTTP Error ${response.statusCode}',
          'Server returned ${response.statusCode}',
        );
      }

      final data = jsonDecode(response.body);
      final user = data['user'];

      return AuthResult(
        user: DefaultAuthUser(id: user['id'], email: user['email'], displayName: user['displayName']),
        token: AuthToken(accessToken: data['token']['accessToken'], refreshToken: data['token']['refreshToken']),
      );
    } catch (e) {
      throw AuthException.from(e);
    }
  }
}

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

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

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    _configureAuth();
  }

  Future<void> _configureAuth() async {
    final anonymousProvider = AnonymousAuthProvider();
    final emailProvider = HttpEmailPasswordAuthProvider(
      baseUrl: 'https://raw.githubusercontent.com/vfiruz97/authflow/refs/heads/main/example/assets',
    );

    await AuthManager().configure(
      AuthConfig(
        providers: [anonymousProvider, emailProvider],
        // The defaultProviderId tells AuthManager which provider to use by default
        // when login() is called without specifying a provider ID.
        // This matches the ID from HttpEmailPasswordAuthProvider's providerId getter.
        defaultProviderId: 'my_auth_provider',
        // SecureAuthStorage persists user and token data between app restarts.
        // withDefaultUser() configures it to use the DefaultAuthUser implementation.
        // You could also implement a custom AuthStorage for different persistence needs.
        storage: SecureAuthStorage.withDefaultUser(),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Authflow Demo',
      theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true),
      home: const AuthScreen(),
    );
  }
}

class AuthScreen extends StatelessWidget {
  const AuthScreen({super.key});

  @override
  Widget build(BuildContext context) {
    // Using AuthBuilder to conditionally render based on auth state
    return AuthBuilder(
      // The authenticated callback provides both user and token objects
      authenticated: (context, user, token) => HomeScreen(user: user, token: token),
      // The unauthenticated callback shows the login screen
      unauthenticated: (context) => const LoginScreen(),
      // This buildWhen parameter solves the problem of the login screen disappearing during login attempts.
      // Without it, AuthBuilder would rebuild and show a loading state when logging in,
      // which would cause the login form to vanish temporarily.
      buildWhen: (previous, current) {
        // Don't rebuild during login attempts (when going from authenticated/unauthenticated to loading)
        if (current.status == AuthStatus.loading && previous.status != AuthStatus.loading) {
          return false;
        }
        // Otherwise, rebuild on state changes
        return true;
      },
    );
  }
}

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

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _emailController = TextEditingController(text: 'user@example.com');
  final _passwordController = TextEditingController(text: 'password123');
  String? _errorMessage;
  bool _isLoading = false;

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  // Helper method to handle login attempts
  Future<void> _performLogin(Future<void> Function() loginFunction) async {
    // Set loading state and clear previous errors
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });

    try {
      // Perform the login operation
      await loginFunction();
    } catch (e) {
      // Handle errors if the widget is still mounted
      if (mounted) {
        setState(() {
          _errorMessage = switch (e) {
            AuthException ae => 'Auth error (${ae.type}): ${ae.message}',
            _ => 'Error: ${e.toString()}',
          };
        });
      }
    } finally {
      // Reset loading state if the widget is still mounted
      if (mounted) {
        setState(() {
          _isLoading = false;
        });
      }
    }
  }

  // Login with email/password using the default provider
  Future<void> _loginWithEmailPassword() async {
    await _performLogin(() {
      // Uses the default provider (configured as 'my_auth_provider')
      // AuthManager.login() automatically uses the defaultProviderId from configuration
      return AuthManager().login({'email': _emailController.text, 'password': _passwordController.text});
    });
  }

  // Login anonymously using the anonymous provider
  Future<void> _loginAnonymously() async {
    await _performLogin(() {
      // Uses a specific provider by ID rather than the default
      // This approach allows you to use any registered provider explicitly
      return AuthManager().loginWithProvider('anonymous', {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Authflow Demo')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child:
            _isLoading
                ? const Center(child: CircularProgressIndicator())
                : Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    if (_errorMessage != null)
                      Container(
                        margin: const EdgeInsets.only(bottom: 16),
                        padding: const EdgeInsets.all(8.0),
                        color: Colors.red.shade100,
                        child: Text(_errorMessage!, style: TextStyle(color: Colors.red.shade900)),
                      ),

                    TextField(
                      controller: _emailController,
                      decoration: const InputDecoration(labelText: 'Email', border: OutlineInputBorder()),
                      keyboardType: TextInputType.emailAddress,
                    ),
                    const SizedBox(height: 16),
                    TextField(
                      controller: _passwordController,
                      decoration: const InputDecoration(labelText: 'Password', border: OutlineInputBorder()),
                      obscureText: true,
                    ),
                    const SizedBox(height: 16),
                    ElevatedButton(onPressed: _loginWithEmailPassword, child: const Text('Login')),
                    const SizedBox(height: 8),
                    OutlinedButton(onPressed: _loginAnonymously, child: const Text('Continue as Guest')),
                  ],
                ),
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  final AuthUser user;
  final AuthToken token;

  const HomeScreen({super.key, required this.user, required this.token});

  // Helper to create info cards
  Widget _buildInfoCard(String title, List<Widget> children) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            ...children,
          ],
        ),
      ),
    );
  }

  // Format token for display (show first and last 4 chars)
  String _formatToken(String token) {
    if (token.length <= 8) return token;
    return '${token.substring(0, 4)}...${token.substring(token.length - 4)}';
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Authflow Demo'),
        actions: [IconButton(icon: const Icon(Icons.logout), onPressed: () => AuthManager().logout())],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Welcome, ${user.displayName ?? user.email ?? 'User ${user.id}'}!',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            const SizedBox(height: 16),

            // User information card
            _buildInfoCard('User Information', [
              Text('ID: ${user.id}'),
              if (user.email != null) Text('Email: ${user.email}'),
              if (user.displayName != null) Text('Name: ${user.displayName}'),
              Text('Anonymous: ${user.isAnonymous}'),
            ]),

            const SizedBox(height: 16),

            // Token information card
            _buildInfoCard('Token Information', [
              Text('Access Token: ${_formatToken(token.accessToken)}'),
              if (token.refreshToken != null) Text('Refresh Token: ${_formatToken(token.refreshToken!)}'),
              if (token.expiresAt != null) Text('Expires: ${token.expiresAt!.toString()}'),
            ]),
          ],
        ),
      ),
    );
  }
}
1
likes
160
points
39
downloads

Publisher

unverified uploader

Weekly Downloads

A flexible, provider-based authentication toolkit for Flutter with stream-based auth state, customizable storage, and composable UI widgets.

Repository (GitHub)
View/report issues

Topics

#authentication #login

Documentation

API reference

License

MIT (license)

Dependencies

flutter, rxdart, shared_preferences

More

Packages that depend on authflow