Authentication
ShipFlutter uses Firebae Authentication as a backend, offering a set of authentication methods that you can use to authenticate your users, in addition to a responsive login page that handles social signin, email-password, anonymous auth, as well as verification and recover flow.
Before starting, make sure you have completed the Firebase setup, including the Firebase Authentication steps.
Features
The authentication module provides the following features:
- Multiple sign-in methods (Email/Password, Anonymous, Social)
- Secure credential management
- Real-time authentication state
- Email verification
- Password recovery
- Account deletion
- Social authentication (Google, Apple, Facebook)
- Custom claims support
- Handles reauthorization dialog
Overview
The authentication module is structured in the following way:
Directoryaccount/access
- auth_view.dart // Responsive login page
- auth_dialog.dart // Utility dialogs (e.g recover password)
- auth_route.dart // Navigation logic
- auth_controller.dart // Business logic and state
- auth_service.dart // Connector to the Firebase Auth Backend
- credentials.dart // Model class with user auth data
- credentials_controller.dart // Observes changes in the credentials
Directoryfirebase/
Directoryauth/
- auth_service.dart // Connector to the Firebase Auth Backend
AuthService
The AuthService
class (in core/firebase/auth
) is the main interface for authentication operations. It provides methods for:
// Initialize the servicefinal authService = await authService.instance;
// Sign in methodsawait authService.signin(email, password);await authService.signinAnonymously();await authService.signinWithGoogle();await authService.signinWithApple();
// Account managementawait authService.signup(email, password);await authService.sendVerifyEmail();await authService.recoverPassword(email);await authService.delete(password: 'optional-password');
// Authentication statefinal isAuthenticated = authService.isAuthenticated();final credentials = await authService.getCurrent();authService.onCredentialsChange().listen((credentials) { // Handle auth state changes});
Credentials Model
The Credentials
class (in core/account/credentials
) represents the user’s authentication state:
class Credentials { final String id; // User's unique identifier final String? token; // Authentication token final CredentialsType type; // anonymous, unverified, or verified final DateTime createdAt; // Account creation time final String? name; // User's display name final String? email; // User's email final String? phone; // User's phone number final String? imageUrl; // User's profile image final Map<String, dynamic>? claims; // Custom claims}
Error Handling
The UI automatically handles common authentication errors:
void _onError(Object? error) { if (error is AuthError) { switch (error.type) { case AuthErrorType.network: showError('Check your connection'); case AuthErrorType.invalid: showError('Invalid credentials'); // ... other error types } }}
AuthView
ShipFlutter provides a responsive and customizable authentication UI that adapts to different screen sizes and orientations. The UI is implemented in the core/account/access
module.
Features
-
Responsive Design
- Portrait mode for mobile devices
- Landscape mode for tablets and desktops
- Adapts to different screen sizes
-
Multiple Authentication Methods
- Email/Password form with validation
- Social login buttons (Google, Apple, Facebook)
- Anonymous login option with “Skip for now” option
-
User-Friendly Experience
- Clear error messages
- Loading indicators
- Smooth animations
- Form validation
Customization
The UI is built with Material Design 3 and automatically adapts to your app’s theme. You can customize:
-
Colors and Typography
// In your theme.dartfinal theme = AppTheme(// Your theme configuration); -
Text and Labels
// In your i18n/en.json{"auth": {"login_title": "Welcome Back","signup_title": "Create Account","email_label": "Email","password_label": "Password",// ... other labels}} -
Social Login Providers
// In your auth_controller.dartfinal socials = [AuthEvent.google,AuthEvent.apple,// ... other providers];
How to use it
You can either use the UI components or the controller directly:
// 1. As a full screen page (recommended)context.go('/auth');
// 2. As a dialogshowDialog(context: context, builder: (context) => const AuthView());
// 1. Sign in usertry { final credentials = await authService.signin(email, password); // User is now signed in} on AuthError catch (e) { switch (e.type) { case AuthErrorType.network: // Handle network error case AuthErrorType.invalid: // Handle invalid credentials // ... handle other error types }}
// 2. Listen to auth state changesauthService.onCredentialsChange().listen((credentials) { if (credentials == null) { // User is signed out } else { // User is signed in print('Signed in as: ${credentials.email}'); }});
// Google Sign-intry { final credentials = await authService.signinWithGoogle(); // User is now signed in with Google} catch (e) { // Handle sign-in error}
// Apple Sign-in (iOS/macOS)try { final credentials = await authService.signinWithApple(); // User is now signed in with Apple} catch (e) { // Handle sign-in error}
// Send verification emailawait authService.sendVerifyEmail();
// Password recoveryawait authService.recoverPassword(email);
// Delete account (requires recent authentication)try { await authService.delete(password: 'current-password');} on FirebaseAuthException catch (e) { if (e.code == 'requires-recent-login') { await authService.reauthenticate(password: 'current-password'); await authService.delete(); }}
Security Best Practices
-
Always Handle Authentication Errors
- Use the
AuthError
type to handle specific error cases - Provide appropriate user feedback for each error type
- Use the
-
Verify Email When Required
- Check
credentials.type
to determine verification status - Use
sendVerifyEmail()
for unverified accounts
- Check
-
Secure Token Management
- Never store tokens in plain text
- Use the built-in token management system
-
Regular Reauthorization
- Use
reauthenticate()
for sensitive operations - Handle
requires-recent-login
errors appropriately
- Use
Error Handling
The authentication module uses the AuthError
class which provides specific error types:
enum AuthErrorType { unknown, // Unexpected errors network, // Network connectivity issues invalid, // Invalid credentials weakPassword, // Password doesn't meet requirements exists, // Email already in use}
Handle errors appropriately in your UI:
try { await authService.signin(email, password);} on AuthError catch (e) { switch (e.type) { case AuthErrorType.network: showError('Check your internet connection'); case AuthErrorType.invalid: showError('Invalid email or password'); case AuthErrorType.exists: showError('Account already exists'); // ... handle other cases }}