hng_firebase_auth 1.0.1
hng_firebase_auth: ^1.0.1 copied to clipboard
A simple and modular Firebase authentication wrapper for Flutter, supporting email/password, Google, and Apple sign-in.
๐ HNG Firebase Auth SDK #
A comprehensive, production-ready Firebase Authentication SDK for Flutter with pre-built UI and headless mode support.
โจ Features #
๐ Authentication Providers #
- โ Email/Password - Traditional authentication
- โ Google Sign-In - One-tap Google authentication
- โ Apple Sign-In - Native Apple authentication (iOS)
- ๐ง Expandable - Easy to add other Firebase providers
๐ State Management #
Automatically tracks and exposes three core authentication states:
Authenticated- User is signed inUnauthenticated- User is signed outTokenExpired- Session expired, re-authentication neededLoading- Authentication operation in progress
โ๏ธ Configuration System #
Flexible configuration object to enable/disable specific login methods:
AuthConfig(
providers: {
'email': true,
'google': true,
'apple': true,
},
autoRefreshToken: true,
tokenRefreshInterval: 3000000,
)
๐ก๏ธ Error Handling & Logging #
Unified error handling layer with custom exception types:
InvalidCredentialsException- Wrong password/email combinationUserNotFoundException- Account does not existEmailAlreadyInUseException- Sign-up conflictWeakPasswordException- Password doesn't meet requirementsTokenExpiredException- Session expiredNetworkException- Network connectivity issues
All Firebase errors are automatically mapped to these custom types.
๐จ UI Flexibility #
Default Mode (Plug-and-Play)
Pre-built widget that renders a complete login form based on configuration:
AuthWidget(
onSuccess: () => print('Login successful!'),
onError: (error) => print('Error: ${error.message}'),
)
Headless Mode (Custom UI)
Expose methods and streams for complete UI control:
final authProvider = Provider.of<AuthProvider>(context);
// Methods
await authProvider.signInWithEmail(email, password);
await authProvider.signInWithGoogle();
await authProvider.signInWithApple();
await authProvider.signOut();
// State access
final user = authProvider.user;
final isAuthenticated = authProvider.isAuthenticated;
final state = authProvider.state;
// Stream access (via SDK)
final authStream = firebaseAuthSDK.authStatusStream;
๐ฆ Installation #
1. Add Dependencies #
Add to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
firebase_core: ^2.32.0
firebase_auth: ^4.20.0
google_sign_in: ^6.1.5
sign_in_with_apple: ^5.0.0
provider: ^6.1.1
Run:
flutter pub get
2. Configure Firebase #
Generate Firebase Options
# Install FlutterFire CLI
dart pub global activate flutterfire_cli
# Configure Firebase
flutterfire configure
Initialize Firebase in main.dart
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
3. Platform-Specific Setup #
iOS Configuration
- Add
GoogleService-Info.plisttoios/Runner/ - Configure URL schemes in
Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>YOUR_REVERSED_CLIENT_ID</string>
</array>
</dict>
</array>
- For Sign In with Apple, ensure
Runner.entitlementsincludes:
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
Android Configuration
- Add
google-services.jsontoandroid/app/ - Update
android/build.gradle:
dependencies {
classpath 'com.google.gms:google-services:4.4.0'
}
- Update
android/app/build.gradle:
apply plugin: 'com.google.gms.google-services'
4. Secrets Configuration #
This project uses a secrets file to keep sensitive keys out of source control.
- Create a new file
lib/firebase_secrets.dart - Add your Firebase keys (found in your
GoogleService-Info.plistor Firebase Console):
class FirebaseSecrets {
static const String iosApiKey = 'YOUR_IOS_API_KEY';
static const String iosAppId = 'YOUR_IOS_APP_ID';
static const String iosMessagingSenderId = 'YOUR_IOS_SENDER_ID';
static const String iosProjectId = 'YOUR_PROJECT_ID';
static const String iosStorageBucket = 'YOUR_STORAGE_BUCKET';
static const String iosBundleId = 'YOUR_BUNDLE_ID';
}
Note: This file is ignored by git to protect your credentials.
๐ Quick Start #
Using Default Mode (Pre-built UI) #
import 'package:provider/provider.dart';
import 'package:hng_firebase_auth/src/providers/auth_provider.dart';
import 'package:hng_firebase_auth/src/ui/auth_widget.dart';
import 'package:hng_firebase_auth/src/core/auth_config.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AuthProvider(
config: AuthConfig(
providers: {
'email': true,
'google': true,
'apple': true,
},
),
),
child: MaterialApp(
home: LoginScreen(),
),
);
}
}
class LoginScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AuthWidget(
onSuccess: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => HomeScreen()),
);
},
onError: (error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error.message)),
);
},
),
),
);
}
}
Using Headless Mode (Custom UI) #
import 'package:provider/provider.dart';
import 'package:hng_firebase_auth/src/providers/auth_provider.dart';
class CustomLoginScreen extends StatefulWidget {
@override
_CustomLoginScreenState createState() => _CustomLoginScreenState();
}
class _CustomLoginScreenState extends State<CustomLoginScreen> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
final authProvider = Provider.of<AuthProvider>(context);
return Scaffold(
appBar: AppBar(title: Text('Custom Login')),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
// Display current user
if (authProvider.isAuthenticated)
Text('Logged in as: ${authProvider.user?.email}'),
// Email field
TextField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
),
// Password field
TextField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
),
SizedBox(height: 16),
// Sign in button
ElevatedButton(
onPressed: authProvider.isLoading ? null : () async {
try {
await authProvider.signInWithEmail(
_emailController.text,
_passwordController.text,
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
},
child: authProvider.isLoading
? CircularProgressIndicator()
: Text('Sign In'),
),
// Google Sign In
ElevatedButton.icon(
onPressed: () => authProvider.signInWithGoogle(),
icon: Icon(Icons.g_mobiledata),
label: Text('Sign in with Google'),
),
// Apple Sign In (iOS only)
if (Platform.isIOS)
ElevatedButton.icon(
onPressed: () => authProvider.signInWithApple(),
icon: Icon(Icons.apple),
label: Text('Sign in with Apple'),
),
// Sign out button
if (authProvider.isAuthenticated)
ElevatedButton(
onPressed: () => authProvider.signOut(),
child: Text('Sign Out'),
),
],
),
),
);
}
}
๐ API Reference #
AuthProvider #
The main provider class for managing authentication state.
Properties
AuthStatus status // Current authentication status
AuthState state // Current state (authenticated/unauthenticated/etc)
AuthUser? user // Current user object (null if not authenticated)
Exception? error // Last error that occurred
bool isAuthenticated // True if user is signed in
bool isLoading // True if operation in progress
Methods
Future<void> signInWithEmail(String email, String password)
Future<void> signUpWithEmail(String email, String password)
Future<void> signInWithGoogle()
Future<void> signInWithApple()
Future<void> signOut()
AuthUser? getCurrentUser()
AuthConfig #
Configuration object for the SDK.
AuthConfig({
Map<String, bool> providers = const {
'email': true,
'google': true,
'apple': true,
},
bool autoRefreshToken = true,
int tokenRefreshInterval = 3000000, // milliseconds
})
AuthState Enum #
enum AuthState {
authenticated, // User is signed in
unauthenticated, // User is signed out
tokenexpired, // Session expired
loading, // Operation in progress
}
AuthUser #
User information object.
class AuthUser {
final String uid;
final String? email;
final String? displayName;
final String? photoUrl;
final String provider; // 'email', 'google', or 'apple'
}
๐จ Error Code Documentation #
All authentication errors are mapped to custom exception types:
| Error Code | Exception Class | Message | Common Cause |
|---|---|---|---|
INVALID_CREDENTIALS |
InvalidCredentialsException |
Wrong email or password | Incorrect login credentials |
USER_NOT_FOUND |
UserNotFoundException |
Account does not exist | Email not registered |
EMAIL_IN_USE |
EmailAlreadyInUseException |
Email already registered | Sign-up with existing email |
WEAK_PASSWORD |
WeakPasswordException |
Password must be 6+ characters | Password too short |
TOKEN_EXPIRED |
TokenExpiredException |
Session expired, please login again | Auth token expired |
NETWORK_ERROR |
NetworkException |
Check your internet connection | No network connectivity |
UNKNOWN_ERROR |
AuthException |
Custom error message | Other Firebase errors |
Error Handling Example #
try {
await authProvider.signInWithEmail(email, password);
} on InvalidCredentialsException catch (e) {
print('Wrong password: ${e.message}');
} on UserNotFoundException catch (e) {
print('User not found: ${e.message}');
} on NetworkException catch (e) {
print('Network error: ${e.message}');
} on AuthException catch (e) {
print('Auth error (${e.code}): ${e.message}');
}
๐ Project Structure #
lib/
โโโ src/
โ โโโ core/
โ โ โโโ auth_config.dart # Configuration class
โ โ โโโ auth_sdk.dart # Core SDK implementation
โ โ โโโ auth_state.dart # State & user models
โ โโโ exceptions/
โ โ โโโ auth_exceptions.dart # Custom exception types
โ โโโ providers/
โ โ โโโ auth_provider.dart # ChangeNotifier provider
โ โโโ ui/
โ โโโ auth_widget.dart # Pre-built UI component
โโโ firebase_options.dart # Firebase configuration
โโโ main.dart # Example app
โ ๏ธ Important Notes #
Sign In with Apple - iOS Simulator Limitation #
Sign In with Apple DOES NOT work on iOS Simulators! This is an Apple limitation.
- โ Simulator: Will fail (expected behavior)
- โ Real Device: Works correctly
To test Sign In with Apple, you must use a physical iOS device.
See APPLE_SIGNIN_NOTES.md for detailed information.
Firebase Console Setup #
Ensure you've enabled authentication providers in Firebase Console:
- Go to Firebase Console
- Select your project
- Navigate to Authentication โ Sign-in method
- Enable Email/Password, Google, and Apple providers
๐งช Testing #
Run the example app:
flutter run
The example demonstrates both Default (pre-built UI) and Headless modes in a tabbed interface.
๐ Example App #
The example app (lib/main.dart) demonstrates:
- โ
Default mode with
AuthWidget - โ Headless mode with custom UI
- โ Stream-based state management
- โ Error handling
- โ All three authentication providers
๐ค Contributing #
This SDK is designed to be expandable. To add new Firebase providers:
- Add the provider package to
pubspec.yaml - Add sign-in method to
FirebaseAuthSDKclass - Add provider method to
AuthProvider - Update
AuthConfigto include the new provider - Update UI components as needed
๐ License #
This project is part of the HNG internship task.
๐ Resources #
โ Implementation Checklist #
- โ Email/Password authentication
- โ Google Sign-In integration
- โ Apple Sign-In integration
- โ State management (Authenticated, Unauthenticated, TokenExpired)
- โ Configuration system for providers
- โ Custom error handling with all required exception types
- โ Default mode (plug-and-play UI)
- โ Headless mode (exposed methods and streams)
- โ Example app with both modes
- โ Complete README with API reference
- โ Error code documentation
- โ Platform-specific setup instructions
- โ Firebase integration
- โ Automatic token refresh
- โ Stream-based state updates
Made with โค๏ธ for HNG Internship