From 92357f51b29f37fc5edd677f992b50f1cc37b44c Mon Sep 17 00:00:00 2001 From: Nabeel Parkar Date: Thu, 15 Jan 2026 15:06:26 +0530 Subject: [PATCH 1/2] docs: Add Firebase IDP docs --- .../04-providers/05-firebase/01-setup.md | 258 ++++++++++++++++++ .../05-firebase/02-configuration.md | 125 +++++++++ .../05-firebase/03-customizing-the-ui.md | 118 ++++++++ .../05-firebase/04-admin-operations.md | 114 ++++++++ .../04-providers/05-firebase/_category_.json | 4 + 5 files changed, 619 insertions(+) create mode 100644 docs/06-concepts/11-authentication/04-providers/05-firebase/01-setup.md create mode 100644 docs/06-concepts/11-authentication/04-providers/05-firebase/02-configuration.md create mode 100644 docs/06-concepts/11-authentication/04-providers/05-firebase/03-customizing-the-ui.md create mode 100644 docs/06-concepts/11-authentication/04-providers/05-firebase/04-admin-operations.md create mode 100644 docs/06-concepts/11-authentication/04-providers/05-firebase/_category_.json diff --git a/docs/06-concepts/11-authentication/04-providers/05-firebase/01-setup.md b/docs/06-concepts/11-authentication/04-providers/05-firebase/01-setup.md new file mode 100644 index 00000000..0038834e --- /dev/null +++ b/docs/06-concepts/11-authentication/04-providers/05-firebase/01-setup.md @@ -0,0 +1,258 @@ +# Setup + +Firebase authentication works differently from other identity providers in Serverpod. Instead of handling authentication directly, Serverpod's Firebase integration acts as a bridge between Firebase Authentication and your Serverpod backend. Firebase handles the actual sign-in process through its own SDKs and UI components, while Serverpod syncs the authenticated user and manages the server-side session. + +This approach allows you to use any authentication method supported by Firebase (email/password, phone, Google, Apple, Facebook, etc.) while maintaining a unified user system in your Serverpod backend. + +:::caution +You need to install the auth module before you continue, see [Setup](../../setup). +::: + +## Prerequisites + +Before setting up Firebase authentication, ensure you have: + +- A Firebase project (create one at [Firebase Console](https://console.firebase.google.com/)) +- Firebase CLI installed (`npm install -g firebase-tools`) +- FlutterFire CLI installed (`dart pub global activate flutterfire_cli`) +- At least one authentication method enabled in your Firebase project + +## Create your credentials + +### Generate Service Account Key + +The server needs service account credentials to verify Firebase ID tokens. To create a new key: + +1. Go to the [Firebase Console](https://console.firebase.google.com/) +2. Select your project +3. Navigate to **Project settings** > **Service accounts** +4. Click **Generate new private key**, then **Generate key** + +![Service account](/img/authentication/providers/firebase/1-server-key.png) + +This downloads a JSON file containing your service account credentials. + +### Enable Authentication Methods + +In the Firebase Console, enable the authentication methods you want to support: + +1. Go to **Authentication** > **Sign-in method** +2. Enable your desired providers (Email/Password, Phone, Google, Apple, etc.) +3. Configure each provider according to Firebase's documentation + +![Auth provider](/img/authentication/providers/firebase/2-auth-provider.png) + +## Server-side configuration + +### Store the Service Account Key + +This can be done by pasting the contents of the JSON file into the `firebaseServiceAccountKey` key in the `config/passwords.yaml` file or setting as value of the `SERVERPOD_PASSWORD_firebaseServiceAccountKey` environment variable. Alternatively, you can read the file contents directly using the `FirebaseServiceAccountCredentials.fromJsonFile()` method. + +```yaml +development: + firebaseServiceAccountKey: | + { + "type": "service_account", + "project_id": "your-project-id", + "private_key_id": "...", + "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n", + "client_email": "firebase-adminsdk-xxxxx@your-project-id.iam.gserviceaccount.com", + "client_id": "...", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token" + } +``` + +:::warning +The service account key gives admin access to your Firebase project and should not be version controlled. Store it securely using environment variables or secret management. +::: + +### Configure the Firebase Identity Provider + +In your main `server.dart` file, configure the Firebase identity provider: + +```dart +import 'package:serverpod/serverpod.dart'; +import 'package:serverpod_auth_idp_server/core.dart'; +import 'package:serverpod_auth_idp_server/providers/firebase.dart'; + +void run(List args) async { + final pod = Serverpod( + args, + Protocol(), + Endpoints(), + ); + + pod.initializeAuthServices( + tokenManagerBuilders: [ + JwtConfigFromPasswords(), + ], + identityProviderBuilders: [ + FirebaseIdpConfig( + credentials: FirebaseServiceAccountCredentials.fromJsonString( + pod.getPassword('firebaseServiceAccountKey')!, + ), + ), + ], + ); + + await pod.start(); +} +``` + +:::tip +You can use `FirebaseIdpConfigFromPasswords()` to automatically load credentials from `config/passwords.yaml` or the `SERVERPOD_PASSWORD_firebaseServiceAccountKey` environment variable: + +```dart +identityProviderBuilders: [ + FirebaseIdpConfigFromPasswords(), +], +``` + +::: + +### Expose the Endpoint + +Create an endpoint that extends `FirebaseIdpBaseEndpoint` to expose the Firebase authentication API: + +```dart +import 'package:serverpod_auth_idp_server/providers/firebase.dart'; + +class FirebaseIdpEndpoint extends FirebaseIdpBaseEndpoint {} +``` + +### Generate and Migrate + +Run the following commands to generate client code and create the database migration: + +```bash +serverpod generate +``` + +Then create and apply the migration: + +```bash +serverpod create-migration +docker compose up --build --detach +dart run bin/main.dart --role maintenance --apply-migrations +``` + +More detailed instructions can be found in the general [identity providers setup section](../../setup#identity-providers-configuration). + +### Basic configuration options + +- `credentials`: Required. Firebase service account credentials for verifying ID tokens. See the [configuration section](./configuration) for different ways to load credentials. +- `firebaseAccountDetailsValidation`: Optional. Validation function for Firebase account details. By default, this validates that the email is verified when present (phone-only authentication is allowed). See the [configuration section](./configuration#custom-account-validation) for customization options. + +## Client-side configuration + +### Install Required Packages + +Add the Firebase and Serverpod authentication packages to your Flutter project: + +```bash +flutter pub add firebase_core firebase_auth serverpod_auth_idp_flutter_firebase +``` + +If you want to use Firebase's pre-built UI components, also add: + +```bash +flutter pub add firebase_ui_auth +``` + +### Configure FlutterFire + +Run the FlutterFire CLI to configure Firebase for your Flutter project: + +```bash +flutterfire configure +``` + +This generates a `firebase_options.dart` file with your platform-specific Firebase configuration. + +### Initialize Firebase and Serverpod + +In your `main.dart`, initialize both Firebase and the Serverpod client: + +```dart +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; +import 'package:serverpod_flutter/serverpod_flutter.dart'; +import 'package:serverpod_auth_idp_flutter_firebase/serverpod_auth_idp_flutter_firebase.dart'; +import 'package:your_client/your_client.dart'; +import 'firebase_options.dart'; + +late Client client; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Initialize Firebase + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + + // Create the Serverpod client + client = Client('http://localhost:8080/') + ..connectivityMonitor = FlutterConnectivityMonitor() + ..authSessionManager = FlutterAuthSessionManager(); + + // Initialize Serverpod auth + await client.auth.initialize(); + + // Initialize Firebase sign-in service (enables automatic sign-out sync) + client.auth.initializeFirebaseSignIn(); + + runApp(const MyApp()); +} +``` + +## The authentication flow + +Understanding the Firebase authentication flow helps when building custom integrations: + +1. **User initiates sign-in** with Firebase using `firebase_auth` or `firebase_ui_auth` +2. **Firebase authenticates** the user and returns a `firebase_auth.User` object +3. **Your app calls** `FirebaseAuthController.login(user)` with the Firebase user +4. **The controller extracts** the Firebase ID token from the user +5. **Token is sent** to your server's `firebaseIdp.login()` endpoint +6. **Server validates** the JWT using the service account credentials +7. **Server creates or updates** the user account and issues a Serverpod session token +8. **Client session is updated** and the user is authenticated with Serverpod + +:::info +The `initializeFirebaseSignIn()` call in the client setup automatically signs out from Firebase when the user signs out from Serverpod, keeping both systems in sync. +::: + +## Present the authentication UI + +### Using FirebaseAuthController + +The `FirebaseAuthController` handles syncing Firebase authentication state with your Serverpod backend. After a user signs in with Firebase, pass the Firebase user to the controller: + +```dart +import 'package:firebase_auth/firebase_auth.dart' as firebase_auth; +import 'package:serverpod_auth_idp_flutter_firebase/serverpod_auth_idp_flutter_firebase.dart'; + +// Create the controller +final controller = FirebaseAuthController( + client: client, + onAuthenticated: () { + // User successfully synced with Serverpod + // NOTE: Do not navigate here - use client.auth.authInfoListenable instead + }, + onError: (error) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $error')), + ); + }, +); + +// After user signs in with Firebase +final firebaseUser = firebase_auth.FirebaseAuth.instance.currentUser; +if (firebaseUser != null) { + await controller.login(firebaseUser); +} +``` + +For details on building custom authentication UIs and integrating with `firebase_ui_auth`, see the [customizing the UI section](./customizing-the-ui). diff --git a/docs/06-concepts/11-authentication/04-providers/05-firebase/02-configuration.md b/docs/06-concepts/11-authentication/04-providers/05-firebase/02-configuration.md new file mode 100644 index 00000000..2816bb3c --- /dev/null +++ b/docs/06-concepts/11-authentication/04-providers/05-firebase/02-configuration.md @@ -0,0 +1,125 @@ +# Configuration + +This page covers configuration options for the Firebase identity provider beyond the basic setup. + +## Configuration options + +Below is a non-exhaustive list of some of the most common configuration options. For more details on all options, check the `FirebaseIdpConfig` in-code documentation. + +### Loading Firebase Credentials + +You can load Firebase service account credentials in several ways: + +**From JSON string (recommended for production):** + +```dart +final firebaseIdpConfig = FirebaseIdpConfig( + credentials: FirebaseServiceAccountCredentials.fromJsonString( + pod.getPassword('firebaseServiceAccountKey')!, + ), +); +``` + +**From JSON file:** + +```dart +import 'dart:io'; + +final firebaseIdpConfig = FirebaseIdpConfig( + credentials: FirebaseServiceAccountCredentials.fromJsonFile( + File('config/firebase_service_account_key.json'), + ), +); +``` + +**From JSON map:** + +```dart +final firebaseIdpConfig = FirebaseIdpConfig( + credentials: FirebaseServiceAccountCredentials.fromJson({ + 'type': 'service_account', + 'project_id': 'your-project-id', + 'private_key_id': '...', + 'private_key': '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n', + 'client_email': 'firebase-adminsdk-xxxxx@your-project-id.iam.gserviceaccount.com', + 'client_id': '...', + 'auth_uri': 'https://accounts.google.com/o/oauth2/auth', + 'token_uri': 'https://oauth2.googleapis.com/token', + }), +); +``` + +### Custom Account Validation + +You can customize the validation for Firebase account details before allowing sign-in. By default, the validation requires the email to be verified when present (phone-only authentication is allowed). + +The default validation logic: + +```dart +static void validateFirebaseAccountDetails( + final FirebaseAccountDetails accountDetails, +) { + // Firebase accounts may not have email if using phone auth + // Only validate verifiedEmail if email is present + if (accountDetails.email != null && accountDetails.verifiedEmail != true) { + throw FirebaseUserInfoMissingDataException(); + } +} +``` + +To customize validation, provide your own `firebaseAccountDetailsValidation` function: + +```dart +final firebaseIdpConfig = FirebaseIdpConfig( + credentials: FirebaseServiceAccountCredentials.fromJsonString( + pod.getPassword('firebaseServiceAccountKey')!, + ), + firebaseAccountDetailsValidation: (accountDetails) { + // Require verified email (even for phone auth) + if (accountDetails.verifiedEmail != true) { + throw Exception('Email must be verified'); + } + + // Restrict to specific email domain + if (accountDetails.email != null && + !accountDetails.email!.endsWith('@example.com')) { + throw Exception('Only @example.com emails allowed'); + } + }, +); +``` + +### FirebaseAccountDetails + +The `firebaseAccountDetailsValidation` callback receives a `FirebaseAccountDetails` record with the following properties: + +| Property | Type | Description | +|----------|------|-------------| +| `userIdentifier` | `String` | The Firebase user's unique identifier (UID) | +| `email` | `String?` | The user's email address (null for phone-only auth) | +| `fullName` | `String?` | The user's display name from Firebase | +| `image` | `Uri?` | URL to the user's profile image | +| `verifiedEmail` | `bool?` | Whether the email is verified | +| `phone` | `String?` | The user's phone number (for phone auth) | + +Example of accessing these properties: + +```dart +firebaseAccountDetailsValidation: (accountDetails) { + print('Firebase UID: ${accountDetails.userIdentifier}'); + print('Email: ${accountDetails.email}'); + print('Email verified: ${accountDetails.verifiedEmail}'); + print('Display name: ${accountDetails.fullName}'); + print('Profile image: ${accountDetails.image}'); + print('Phone: ${accountDetails.phone}'); + + // Custom validation logic + if (accountDetails.email == null && accountDetails.phone == null) { + throw Exception('Either email or phone is required'); + } +}, +``` + +:::info +The properties available depend on the Firebase authentication method used. For example, `phone` is only populated for phone authentication, and `email` may be null if the user signed in with phone only. +::: diff --git a/docs/06-concepts/11-authentication/04-providers/05-firebase/03-customizing-the-ui.md b/docs/06-concepts/11-authentication/04-providers/05-firebase/03-customizing-the-ui.md new file mode 100644 index 00000000..7b21ded1 --- /dev/null +++ b/docs/06-concepts/11-authentication/04-providers/05-firebase/03-customizing-the-ui.md @@ -0,0 +1,118 @@ +# Customizing the UI + +When using the Firebase identity provider, you build your authentication UI using Firebase's own packages (`firebase_auth` or `firebase_ui_auth`). The `FirebaseAuthController` handles syncing the authenticated Firebase user with your Serverpod backend. + +:::info +Unlike other Serverpod identity providers, Firebase does not provide built-in sign-in widgets. You use Firebase's official packages for the UI, then sync the result with Serverpod using `FirebaseAuthController`. +::: + +## Using the FirebaseAuthController + +The `FirebaseAuthController` manages the synchronization between Firebase authentication state and Serverpod sessions. + +```dart +import 'package:firebase_auth/firebase_auth.dart' as firebase_auth; +import 'package:serverpod_auth_idp_flutter_firebase/serverpod_auth_idp_flutter_firebase.dart'; + +final controller = FirebaseAuthController( + client: client, + onAuthenticated: () { + // User successfully synced with Serverpod + // NOTE: Do not navigate here - use client.auth.authInfoListenable instead + }, + onError: (error) { + // Handle errors + }, +); +``` + +### FirebaseAuthController State Management + +Your widget should render the appropriate UI based on the controller's state: + +```dart +// Check current state +final state = controller.state; // FirebaseAuthState enum + +// Check if loading +final isLoading = controller.isLoading; + +// Check if authenticated +final isAuthenticated = controller.isAuthenticated; + +// Get error message +final errorMessage = controller.errorMessage; + +// Get error object +final error = controller.error; + +// Listen to state changes +controller.addListener(() { + setState(() { + // Rebuild UI when controller state changes + }); +}); +``` + +### FirebaseAuthController States + +- `FirebaseAuthState.idle` - Ready for user interaction +- `FirebaseAuthState.loading` - Processing authentication with Serverpod +- `FirebaseAuthState.error` - An error occurred +- `FirebaseAuthState.authenticated` - Successfully authenticated with Serverpod + +### The login method + +The `login` method accepts a `firebase_auth.User` object and syncs it with Serverpod: + +```dart +// Get the current Firebase user after sign-in +final firebaseUser = firebase_auth.FirebaseAuth.instance.currentUser; + +if (firebaseUser != null) { + await controller.login(firebaseUser); +} +``` + +## Integration patterns + +### Using firebase_auth directly + +For full control over the authentication UI, use the `firebase_auth` package directly. The basic flow is: + +1. Build your own sign-in UI with text fields and buttons +2. Call Firebase authentication methods (e.g., `signInWithEmailAndPassword`) +3. On success, pass the `firebase_auth.User` to `controller.login()` +4. Handle errors from both Firebase and the Serverpod sync + +Refer to the [firebase_auth documentation](https://pub.dev/packages/firebase_auth) for available authentication methods. + +### Using firebase_ui_auth + +For a pre-built UI experience, use the `firebase_ui_auth` package. This provides ready-made screens for various authentication methods. + +1. Add the package: `flutter pub add firebase_ui_auth` +2. Use `firebase_ui.SignInScreen` with your desired providers +3. Add `AuthStateChangeAction` handlers for `SignedIn` and `UserCreated` events +4. In each handler, call `controller.login()` with the Firebase user + +```dart +actions: [ + firebase_ui.AuthStateChangeAction((context, state) async { + final user = firebase_auth.FirebaseAuth.instance.currentUser; + if (user != null) { + await controller.login(user); + } + }), +], +``` + +Refer to the [firebase_ui_auth documentation](https://pub.dev/packages/firebase_ui_auth) for configuration details and available providers. + +### Listening to Firebase auth state changes + +For apps that need to react to Firebase auth state changes automatically, listen to `FirebaseAuth.instance.authStateChanges()` and call `controller.login()` when a user signs in. + +:::note +When using the auth state listener pattern, check the Serverpod auth state before calling `login()` to prevent re-syncing an already authenticated user. +::: diff --git a/docs/06-concepts/11-authentication/04-providers/05-firebase/04-admin-operations.md b/docs/06-concepts/11-authentication/04-providers/05-firebase/04-admin-operations.md new file mode 100644 index 00000000..f24df302 --- /dev/null +++ b/docs/06-concepts/11-authentication/04-providers/05-firebase/04-admin-operations.md @@ -0,0 +1,114 @@ +# Admin Operations + +The Firebase identity provider provides admin operations through `FirebaseIdpAdmin` for managing Firebase-authenticated accounts. These operations are useful for administrative tasks and account management. + +## Accessing the FirebaseIdpAdmin + +You can access the admin operations through the `AuthServices.instance.firebaseIdp` property: + +```dart +import 'package:serverpod_auth_idp_server/providers/firebase.dart'; +import 'package:serverpod_auth_idp_server/core.dart'; + +// Get the FirebaseIdp instance +final firebaseIdp = AuthServices.instance.firebaseIdp; + +// Access admin operations +final admin = firebaseIdp.admin; +``` + +## Account Management + +The admin API provides methods for managing Firebase-authenticated accounts. + +### Finding Accounts + +```dart +// Find an account by email +final account = await admin.findAccountByEmail( + session, + email: 'user@example.com', +); + +// Find an account by Serverpod auth user ID +final account = await admin.findAccountByAuthUserId( + session, + authUserId: authUserId, +); + +// Find the Serverpod user ID by Firebase UID +final userId = await admin.findUserByFirebaseUserId( + session, + userIdentifier: 'firebase-uid', +); +``` + +### Linking Firebase Authentication + +Link an existing Serverpod user to a Firebase account: + +```dart +// Link a Firebase account to an existing user +final firebaseAccount = await admin.linkFirebaseAuthentication( + session, + authUserId: authUserId, + accountDetails: accountDetails, +); +``` + +The `accountDetails` parameter is a `FirebaseAccountDetails` record containing the Firebase user information. You can obtain this from a Firebase ID token using the `fetchAccountDetails` method: + +```dart +// Fetch account details from a Firebase ID token +final accountDetails = await admin.fetchAccountDetails( + session, + idToken: firebaseIdToken, +); + +// Then link the account +await admin.linkFirebaseAuthentication( + session, + authUserId: existingUserId, + accountDetails: accountDetails, +); +``` + +### Deleting Accounts + +```dart +// Delete a Firebase account by Firebase UID +final deletedAccount = await admin.deleteFirebaseAccount( + session, + userIdentifier: 'firebase-uid', +); + +// Delete all Firebase accounts for a Serverpod user +final deletedAccount = await admin.deleteFirebaseAccountByAuthUserId( + session, + authUserId: authUserId, +); +``` + +:::info +Deleting a Firebase account only removes the link between the Firebase authentication and the Serverpod user. It does not delete the user from your Serverpod database or from Firebase itself. +::: + +## FirebaseIdpUtils + +The `FirebaseIdpUtils` class provides utility functions for working with Firebase authentication: + +```dart +final utils = firebaseIdp.utils; + +// Authenticate a user with a Firebase ID token +// This creates the account if it doesn't exist +final authSuccess = await utils.authenticate( + session, + idToken: firebaseIdToken, + transaction: transaction, // optional +); +``` + +:::warning +Admin operations should only be called from secure server-side code. Do not expose these methods directly through client endpoints without proper authorization checks. +::: diff --git a/docs/06-concepts/11-authentication/04-providers/05-firebase/_category_.json b/docs/06-concepts/11-authentication/04-providers/05-firebase/_category_.json new file mode 100644 index 00000000..ef488ac2 --- /dev/null +++ b/docs/06-concepts/11-authentication/04-providers/05-firebase/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Firebase", + "collapsed": true +} From a3d3e9bda88ace6cdad1b4f8ab62c7475351421a Mon Sep 17 00:00:00 2001 From: Nabeel Parkar Date: Mon, 19 Jan 2026 04:04:29 +0530 Subject: [PATCH 2/2] docs: Address PR review comments for Firebase IDP docs --- .../04-providers/05-firebase/01-setup.md | 131 ++++++++++-------- .../05-firebase/03-customizing-the-ui.md | 97 +++++++++++-- 2 files changed, 161 insertions(+), 67 deletions(-) diff --git a/docs/06-concepts/11-authentication/04-providers/05-firebase/01-setup.md b/docs/06-concepts/11-authentication/04-providers/05-firebase/01-setup.md index 0038834e..4978aef6 100644 --- a/docs/06-concepts/11-authentication/04-providers/05-firebase/01-setup.md +++ b/docs/06-concepts/11-authentication/04-providers/05-firebase/01-setup.md @@ -8,22 +8,13 @@ This approach allows you to use any authentication method supported by Firebase You need to install the auth module before you continue, see [Setup](../../setup). ::: -## Prerequisites - -Before setting up Firebase authentication, ensure you have: - -- A Firebase project (create one at [Firebase Console](https://console.firebase.google.com/)) -- Firebase CLI installed (`npm install -g firebase-tools`) -- FlutterFire CLI installed (`dart pub global activate flutterfire_cli`) -- At least one authentication method enabled in your Firebase project - ## Create your credentials ### Generate Service Account Key The server needs service account credentials to verify Firebase ID tokens. To create a new key: -1. Go to the [Firebase Console](https://console.firebase.google.com/) +1. Go to the [Firebase Console](https://console.firebase.google.com/) (create a new project if you don't have one) 2. Select your project 3. Navigate to **Project settings** > **Service accounts** 4. Click **Generate new private key**, then **Generate key** @@ -123,21 +114,7 @@ class FirebaseIdpEndpoint extends FirebaseIdpBaseEndpoint {} ### Generate and Migrate -Run the following commands to generate client code and create the database migration: - -```bash -serverpod generate -``` - -Then create and apply the migration: - -```bash -serverpod create-migration -docker compose up --build --detach -dart run bin/main.dart --role maintenance --apply-migrations -``` - -More detailed instructions can be found in the general [identity providers setup section](../../setup#identity-providers-configuration). +Finally, run `serverpod generate` to generate the client code and create a migration to initialize the database for the provider. More detailed instructions can be found in the general [identity providers setup section](../../setup#identity-providers-configuration). ### Basic configuration options @@ -146,6 +123,8 @@ More detailed instructions can be found in the general [identity providers setup ## Client-side configuration +The client-side setup uses the official Firebase packages (`firebase_core`, `firebase_auth`, and optionally `firebase_ui_auth`) for authentication. The steps below follow standard Firebase usage - for troubleshooting, refer to the [official Firebase Flutter documentation](https://firebase.google.com/docs/flutter/setup). + ### Install Required Packages Add the Firebase and Serverpod authentication packages to your Flutter project: @@ -162,7 +141,14 @@ flutter pub add firebase_ui_auth ### Configure FlutterFire -Run the FlutterFire CLI to configure Firebase for your Flutter project: +If you haven't already, install the Firebase CLI and FlutterFire CLI: + +```bash +npm install -g firebase-tools +dart pub global activate flutterfire_cli +``` + +Then run the FlutterFire CLI to configure Firebase for your Flutter project: ```bash flutterfire configure @@ -211,48 +197,83 @@ void main() async { Understanding the Firebase authentication flow helps when building custom integrations: -1. **User initiates sign-in** with Firebase using `firebase_auth` or `firebase_ui_auth` -2. **Firebase authenticates** the user and returns a `firebase_auth.User` object -3. **Your app calls** `FirebaseAuthController.login(user)` with the Firebase user -4. **The controller extracts** the Firebase ID token from the user -5. **Token is sent** to your server's `firebaseIdp.login()` endpoint -6. **Server validates** the JWT using the service account credentials -7. **Server creates or updates** the user account and issues a Serverpod session token -8. **Client session is updated** and the user is authenticated with Serverpod +1. **User initiates sign-in** with Firebase using `firebase_auth` or `firebase_ui_auth`. +2. **Firebase authenticates** the user and returns a `firebase_auth.User` object. +3. **Your app calls** `FirebaseAuthController.login(user)` with the Firebase user. +4. **The controller extracts** the Firebase ID token from the user. +5. **Token is sent** to your server's `firebaseIdp.login()` endpoint. +6. **Server validates** the JWT using the service account credentials. +7. **Server creates or updates** the user account and issues a Serverpod session token. +8. **Client session is updated** and the user is authenticated with Serverpod in the Flutter app. :::info -The `initializeFirebaseSignIn()` call in the client setup automatically signs out from Firebase when the user signs out from Serverpod, keeping both systems in sync. +The `initializeFirebaseSignIn()` call in the client setup will ensure that the user gets automatically signed out from Firebase when signing out from Serverpod to keep both systems in sync. ::: ## Present the authentication UI -### Using FirebaseAuthController +### Using firebase_ui_auth -The `FirebaseAuthController` handles syncing Firebase authentication state with your Serverpod backend. After a user signs in with Firebase, pass the Firebase user to the controller: +The easiest way to add Firebase authentication is using the `firebase_ui_auth` package with its pre-built `SignInScreen`: ```dart import 'package:firebase_auth/firebase_auth.dart' as firebase_auth; +import 'package:firebase_ui_auth/firebase_ui_auth.dart' as firebase_ui; +import 'package:flutter/material.dart'; import 'package:serverpod_auth_idp_flutter_firebase/serverpod_auth_idp_flutter_firebase.dart'; -// Create the controller -final controller = FirebaseAuthController( - client: client, - onAuthenticated: () { - // User successfully synced with Serverpod - // NOTE: Do not navigate here - use client.auth.authInfoListenable instead - }, - onError: (error) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Error: $error')), - ); - }, -); +class SignInPage extends StatefulWidget { + const SignInPage({super.key}); + + @override + State createState() => _SignInPageState(); +} -// After user signs in with Firebase -final firebaseUser = firebase_auth.FirebaseAuth.instance.currentUser; -if (firebaseUser != null) { - await controller.login(firebaseUser); +class _SignInPageState extends State { + late final FirebaseAuthController controller; + + @override + void initState() { + super.initState(); + controller = FirebaseAuthController( + client: client, + onError: (error) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $error')), + ); + }, + ); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return firebase_ui.SignInScreen( + providers: [ + firebase_ui.EmailAuthProvider(), + ], + actions: [ + firebase_ui.AuthStateChangeAction((context, state) async { + final user = firebase_auth.FirebaseAuth.instance.currentUser; + if (user != null) { + await controller.login(user); + } + }), + firebase_ui.AuthStateChangeAction((context, state) async { + final user = firebase_auth.FirebaseAuth.instance.currentUser; + if (user != null) { + await controller.login(user); + } + }), + ], + ); + } } ``` -For details on building custom authentication UIs and integrating with `firebase_ui_auth`, see the [customizing the UI section](./customizing-the-ui). +For details on using the `FirebaseAuthController` directly and building custom authentication UIs, see the [customizing the UI section](./customizing-the-ui). diff --git a/docs/06-concepts/11-authentication/04-providers/05-firebase/03-customizing-the-ui.md b/docs/06-concepts/11-authentication/04-providers/05-firebase/03-customizing-the-ui.md index 7b21ded1..a43deba8 100644 --- a/docs/06-concepts/11-authentication/04-providers/05-firebase/03-customizing-the-ui.md +++ b/docs/06-concepts/11-authentication/04-providers/05-firebase/03-customizing-the-ui.md @@ -6,6 +6,79 @@ When using the Firebase identity provider, you build your authentication UI usin Unlike other Serverpod identity providers, Firebase does not provide built-in sign-in widgets. You use Firebase's official packages for the UI, then sync the result with Serverpod using `FirebaseAuthController`. ::: +## Using firebase_ui_auth SignInScreen + +The `firebase_ui_auth` package provides a complete `SignInScreen` widget that handles the entire authentication flow. Here's a full example: + +```dart +import 'package:firebase_auth/firebase_auth.dart' as firebase_auth; +import 'package:firebase_ui_auth/firebase_ui_auth.dart' as firebase_ui; +import 'package:flutter/material.dart'; +import 'package:serverpod_auth_idp_flutter_firebase/serverpod_auth_idp_flutter_firebase.dart'; + +class AuthScreen extends StatefulWidget { + const AuthScreen({super.key}); + + @override + State createState() => _AuthScreenState(); +} + +class _AuthScreenState extends State { + late final FirebaseAuthController controller; + + @override + void initState() { + super.initState(); + controller = FirebaseAuthController( + client: client, + onAuthenticated: () { + // User successfully synced with Serverpod + // NOTE: Do not navigate here - use client.auth.authInfoListenable instead + }, + onError: (error) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $error')), + ); + }, + ); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return firebase_ui.SignInScreen( + providers: [ + firebase_ui.EmailAuthProvider(), + // Add more providers as needed: + // firebase_ui.PhoneAuthProvider(), + // firebase_ui.GoogleProvider(clientId: '...'), + ], + actions: [ + firebase_ui.AuthStateChangeAction((context, state) async { + final user = firebase_auth.FirebaseAuth.instance.currentUser; + if (user != null) { + await controller.login(user); + } + }), + firebase_ui.AuthStateChangeAction((context, state) async { + final user = firebase_auth.FirebaseAuth.instance.currentUser; + if (user != null) { + await controller.login(user); + } + }), + ], + ); + } +} +``` + +The `SignInScreen` automatically handles the UI for all configured providers. You only need to connect the Firebase authentication result to Serverpod using the `FirebaseAuthController`. + ## Using the FirebaseAuthController The `FirebaseAuthController` manages the synchronization between Firebase authentication state and Serverpod sessions. @@ -56,10 +129,10 @@ controller.addListener(() { ### FirebaseAuthController States -- `FirebaseAuthState.idle` - Ready for user interaction -- `FirebaseAuthState.loading` - Processing authentication with Serverpod -- `FirebaseAuthState.error` - An error occurred -- `FirebaseAuthState.authenticated` - Successfully authenticated with Serverpod +- `FirebaseAuthState.idle` - Ready for user interaction. +- `FirebaseAuthState.loading` - Processing authentication with Serverpod. +- `FirebaseAuthState.error` - An error occurred. +- `FirebaseAuthState.authenticated` - Successfully authenticated with Serverpod. ### The login method @@ -80,10 +153,10 @@ if (firebaseUser != null) { For full control over the authentication UI, use the `firebase_auth` package directly. The basic flow is: -1. Build your own sign-in UI with text fields and buttons -2. Call Firebase authentication methods (e.g., `signInWithEmailAndPassword`) -3. On success, pass the `firebase_auth.User` to `controller.login()` -4. Handle errors from both Firebase and the Serverpod sync +1. Build your own sign-in UI with text fields and buttons. +2. Call Firebase authentication methods (e.g., `signInWithEmailAndPassword`). +3. On success, pass the `firebase_auth.User` to `controller.login()`. +4. Handle errors from both Firebase and the Serverpod sync. Refer to the [firebase_auth documentation](https://pub.dev/packages/firebase_auth) for available authentication methods. @@ -91,10 +164,10 @@ Refer to the [firebase_auth documentation](https://pub.dev/packages/firebase_aut For a pre-built UI experience, use the `firebase_ui_auth` package. This provides ready-made screens for various authentication methods. -1. Add the package: `flutter pub add firebase_ui_auth` -2. Use `firebase_ui.SignInScreen` with your desired providers -3. Add `AuthStateChangeAction` handlers for `SignedIn` and `UserCreated` events -4. In each handler, call `controller.login()` with the Firebase user +1. Add the package: `flutter pub add firebase_ui_auth`. +2. Use `firebase_ui.SignInScreen` with your desired providers. +3. Add `AuthStateChangeAction` handlers for `SignedIn` and `UserCreated` events. +4. In each handler, call `controller.login()` with the Firebase user. ```dart actions: [