Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
351 changes: 218 additions & 133 deletions docs/06-concepts/11-authentication/04-providers/06-firebase/01-setup.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@

This page covers configuration options for the Firebase identity provider beyond the basic setup.

## Configuration options
## Loading credentials with FirebaseIdpConfig

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.
The [setup guide](./setup) uses `FirebaseIdpConfigFromPasswords`, which loads the service account key from `passwords.yaml` for you. When you need to load credentials from a different source (a file path, a secrets manager, or just a project ID), use `FirebaseIdpConfig` directly and pass a `FirebaseServiceAccountCredentials` instance.

### Loading Firebase Credentials
`FirebaseServiceAccountCredentials` provides four constructors. These are the only supported ways to construct it:

You can load Firebase service account credentials in several ways:

**From JSON string (recommended for production):**
**From a JSON string** (use this when reading the JSON from a secrets manager or environment variable):

```dart
final firebaseIdpConfig = FirebaseIdpConfig(
Expand All @@ -20,7 +18,7 @@ final firebaseIdpConfig = FirebaseIdpConfig(
);
```

**From JSON file:**
**From a JSON file** (useful for local development or when secrets are mounted as files):

```dart
import 'dart:io';
Expand All @@ -32,7 +30,7 @@ final firebaseIdpConfig = FirebaseIdpConfig(
);
```

**From JSON map:**
**From a JSON map** (useful when credentials are assembled programmatically):

```dart
final firebaseIdpConfig = FirebaseIdpConfig(
Expand All @@ -49,31 +47,28 @@ final firebaseIdpConfig = FirebaseIdpConfig(
);
```

### 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:
**Project ID only** (only token verification, no admin operations like deleting Firebase accounts):

```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();
}
}
final firebaseIdpConfig = FirebaseIdpConfig(
credentials: const FirebaseServiceAccountCredentials(
projectId: 'your-project-id',
),
);
```

:::note
Only `projectId` is required to verify Firebase ID tokens. The full service account JSON is only needed if you also use the [admin operations](./admin-operations) on the server.
:::

## 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 without an email).

To customize validation, provide your own `firebaseAccountDetailsValidation` function:

```dart
final firebaseIdpConfig = FirebaseIdpConfig(
credentials: FirebaseServiceAccountCredentials.fromJsonString(
pod.getPassword('firebaseServiceAccountKey')!,
),
final firebaseIdpConfig = FirebaseIdpConfigFromPasswords(
firebaseAccountDetailsValidation: (accountDetails) {
// Require verified email (even for phone auth)
if (accountDetails.verifiedEmail != true) {
Expand All @@ -89,64 +84,63 @@ final firebaseIdpConfig = FirebaseIdpConfig(
);
```

### FirebaseAccountDetails
### FirebaseAccountDetails properties

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');
}
},
```
- `userIdentifier` (`String`): Firebase UID.
- `email` (`String?`): Email address, or `null` for phone-only sign-in.
- `fullName` (`String?`): Display name from Firebase.
- `image` (`Uri?`): Profile image URL.
- `verifiedEmail` (`bool?`): Whether the email is verified.
- `phone` (`String?`): Phone number, only populated for phone authentication.

:::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.
:::
Which properties are populated depends on the Firebase sign-in method the user chose. For example, `phone` is only populated for phone authentication, and `email` may be `null` if the user signed in with phone only.

### Reacting to account creation
## Reacting to auth user creation

You can use the `onAfterFirebaseAccountCreated` callback to run logic after a new Firebase account has been created and linked to an auth user. This callback is only invoked for new accounts, not for returning users.
[`onBeforeAuthUserCreated`](https://pub.dev/documentation/serverpod_auth_idp_server/latest/core/AuthUsersConfig/onBeforeAuthUserCreated.html) and [`onAfterAuthUserCreated`](https://pub.dev/documentation/serverpod_auth_idp_server/latest/core/AuthUsersConfig/onAfterAuthUserCreated.html) are global callbacks on `AuthUsersConfig`. They fire for every identity provider, not just Firebase. See [Working with users](../../working-with-users#reacting-to-the-user-created-event) for full details.

This callback is complimentary to the [core `onAfterAuthUserCreated` callback](../../working-with-users#reacting-to-the-user-created-event) to perform side-effects that are specific to a login on this provider - like storing analytics, sending a welcome email, or storing additional data.
The example below uses Firebase phone numbers as the trigger for assigning a `phone-verified` scope at sign-up, and persists the Firebase UID for later admin lookups:

```dart
final firebaseIdpConfig = FirebaseIdpConfigFromPasswords(
onAfterFirebaseAccountCreated: (
session,
authUser,
firebaseAccount, {
required transaction,
}) async {
// e.g. store additional data, send a welcome email, or log for analytics
},
pod.initializeAuthServices(
tokenManagerBuilders: [
JwtConfigFromPasswords(),
],
identityProviderBuilders: [
FirebaseIdpConfigFromPasswords(),
],
authUsersConfig: AuthUsersConfig(
onBeforeAuthUserCreated: (
session,
scopes,
blocked, {
required transaction,
}) {
return (
scopes: {...scopes, Scope('user')},
blocked: blocked,
);
},
onAfterAuthUserCreated: (
session,
authUser, {
required transaction,
}) async {
// e.g. send a welcome email, log for analytics
},
),
);
```

:::info
This callback runs inside the same database transaction as the account creation. Throwing an exception inside this callback will abort the process. If you perform external side-effects, make sure to safeguard them with a try/catch to prevent unwanted failures.
:::warning
Both callbacks run inside the same database transaction as the account creation. Throwing an exception inside either callback aborts the sign-up. Wrap external side-effects (email sending, analytics) in `try`/`catch` so a third-party outage does not block new sign-ups.
:::

:::caution
If you need to assign Serverpod scopes based on provider account data, note that updating the database alone (via `AuthServices.instance.authUsers.update()`) is **not enough** for the current login session. The token issuance uses the in-memory `authUser.scopes`, which is already set before this callback runs. You would need to update `authUser.scopes` as well for the scopes to be reflected in the issued tokens. For assigning scopes at creation time, consider using `onBeforeAuthUserCreated` to set scopes based on data collected earlier in the flow.
:::
## FirebaseIdpConfig parameter reference

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `credentials` | `FirebaseServiceAccountCredentials` | Yes | Firebase service account credentials for verifying ID tokens. Can be loaded via `fromJsonString`, `fromJsonFile`, or `fromJson`. When using `FirebaseIdpConfigFromPasswords`, this is loaded automatically from the `firebaseServiceAccountKey` key in `passwords.yaml` or the `SERVERPOD_PASSWORD_firebaseServiceAccountKey` environment variable. |
| `firebaseAccountDetailsValidation` | `FirebaseAccountDetailsValidation?` | No | Custom validation callback for Firebase account details before allowing sign-in. By default, validates that email is verified when present (phone-only auth is allowed). |
Loading
Loading