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

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

Authflow #

Authflow is a Flutter-first authentication toolkit that provides flexible, stream-based authentication with multiple providers, token handling, and composable UI widgets.

🔥 Features #

  • ✅ Modular provider-based authentication
  • 🔐 Token + user result on login
  • 📦 Pluggable secure/local storage system
  • 🔄 Reactive streams for auth status, user, and token
  • 📡 Global login/logout event support
  • 🧱 Customizable user model and providers
  • 🧩 UI widgets for seamless auth-based rendering
  • 🛡️ Standardized exception handling

🛠️ Configuration #

Set up your providers and storage with AuthConfig, then initialize the system:

// Create providers
final anonymousProvider = AnonymousAuthProvider();
final emailProvider = MockEmailPasswordAuthProvider();

// Configure auth manager
await AuthManager().configure(AuthConfig(
  providers: [anonymousProvider, emailProvider],
  defaultProviderId: 'email_password', // Set the default provider
  storage: SecureAuthStorage.withDefaultUser(),
));

Note: Setting defaultProviderId is important when using AuthManager().login(). Without it, the system will try to use the current provider (if authenticated) or fall back to the first registered provider.


🚀 Usage #

Login #

// Login with default provider (as configured in AuthConfig)
final result = await AuthManager().login({
  'email': 'user@example.com',
  'password': 'secret',
});

// Login with specific provider
final result = await AuthManager().loginWithProvider(
  'anonymous',
  {},
);

// Access user and token from result
final user = result.user;
final token = result.token;

Manual Session #

// Inject a session directly
await AuthManager().setSession(
  user,
  token,
  providerId: 'custom',
);

Logout #

await AuthManager().logout();

Auth State #

// Get current state
final isLoggedIn = AuthManager().isAuthenticated;
final user = AuthManager().currentUser;
final token = AuthManager().currentToken;

// Listen to auth state changes
AuthManager().statusStream.listen((status) {
  print("Auth status: $status");
});

// Listen to user changes
AuthManager().userStream.listen((user) {
  if (user != null) {
    print("User: ${user.id}");
  }
});

// Listen to token changes
AuthManager().tokenStream.listen((token) {
  if (token != null) {
    print("Token: ${token.accessToken}");
  }
});

Global Events #

// Listen to all login events
AuthEventBus().onLogin((event) {
  print('User logged in: ${event.user.id} via ${event.providerId}');
});

// Listen to all logout events
AuthEventBus().onLogout((event) {
  print('User logged out: ${event.user?.id}');
});

🧩 Flutter UI Integration #

Use AuthBuilder to rebuild UI based on authentication state:

AuthBuilder(
  authenticated: (context, user, token) {
    return HomeScreen(user: user);
  },
  unauthenticated: (context) {
    return LoginScreen();
  },
  loading: (context) {
    return LoadingScreen();
  },
)

🔧 Custom Providers #

Implement your own authentication providers by extending AuthProvider:

class MyCustomAuthProvider extends AuthProvider {
  @override
  String get providerId => 'custom_provider';

  @override
  Future<AuthResult> login(Map<String, dynamic> credentials) async {
    // Implement your authentication logic here

    // Create user and token
    final user = DefaultAuthUser(
      id: 'user123',
      email: 'user@example.com',
    );

    final token = AuthToken(
      accessToken: 'my-access-token',
      refreshToken: 'my-refresh-token',
      expiresAt: DateTime.now().add(Duration(hours: 1)),
    );

    return AuthResult(user: user, token: token);
  }
}

🔐 Custom User Model #

Extend AuthUser to create your own user model:

class MyUser extends AuthUser {
  @override
  final String id;

  @override
  final String? email;

  @override
  final String? displayName;

  final String? photoUrl;
  final List<String> roles;

  MyUser({
    required this.id,
    this.email,
    this.displayName,
    this.photoUrl,
    this.roles = const [],
  });

  @override
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'email': email,
      'displayName': displayName,
      'photoUrl': photoUrl,
      'roles': roles,
    };
  }

  factory MyUser.fromJson(Map<String, dynamic> json) {
    return MyUser(
      id: json['id'],
      email: json['email'],
      displayName: json['displayName'],
      photoUrl: json['photoUrl'],
      roles: List<String>.from(json['roles'] ?? []),
    );
  }

  factory MyUser.deserialize(String data) {
    return MyUser.fromJson(jsonDecode(data));
  }
}

// Use with custom storage:
final storage = SecureAuthStorage(
  userDeserializer: (data) => MyUser.deserialize(data),
);

🛡️ Error Handling #

Authflow provides a standardized exception handling system with AuthException:

try {
  final result = await AuthManager().login({
    'email': 'user@example.com',
    // missing password
  });
} on AuthException catch (e) {
  // Access type and message
  print('Error type: ${e.type}');
  print('Error message: ${e.message}');

  // Handle specific error types
  switch (e.type) {
    case AuthExceptionType.credentials:
      showCredentialsError(e.message);
      break;
    case AuthExceptionType.provider:
      showProviderError(e.message);
      break;
    // Handle other error types...
    default:
      showGenericError(e.message);
  }
}

Available Exception Types #

The AuthExceptionType enum provides these core error categories:

  • credentials: Issues with credentials (missing, invalid, etc.)
  • provider: Provider-specific errors (provider not found, implementation errors)
  • custom: For user-defined authentication errors
  • unknown: Unclassified errors

Creating Custom Exceptions #

// Using a factory constructor
final exception = AuthException.credentials('Password is too weak');

// For provider-specific errors
final providerException = AuthException.provider(
  'google_signin',
  originalError,
  'Failed to authenticate with Google'
);

// For your own custom authentication errors
final myException = AuthException.custom(
  'Account requires verification',
  error: originalError,
  details: {'verificationType': 'email'},
);

🧱 Extending #

Custom Provider #

Implement AuthProvider, return an AuthResult, and add it to your AuthConfig:

class MyProvider extends AuthProvider {
  @override
  String get providerId => 'my_provider';

  @override
  Future<AuthResult> login(Map<String, dynamic> credentials) async {
    // Implement your authentication logic here
    // ...

    // Return AuthResult with user and token
    return AuthResult(user: customUser, token: customToken);
  }

  // ...logout(), isAuthenticated(), currentUser()
}

// Configure AuthManager with your custom provider
await AuthManager().configure(AuthConfig(
  providers: [
    MyProvider(),
    AnonymousAuthProvider(),
  ],
  // Optionally set your custom provider as default
  defaultProviderId: 'my_provider',
  storage: SecureAuthStorage.withDefaultUser(),
));

Custom User or Storage #

  • Implement your own AuthUser to match your API
  • Implement AuthStorage for custom persistence

📄 License #

This project is licensed under the MIT License.


👤 Author #

Firuz Vorisov
github.com/vfiruz97

Feel free to open issues or contribute via PR!

1
likes
0
points
64
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

License

unknown (license)

Dependencies

flutter, rxdart, shared_preferences

More

Packages that depend on authflow