diff --git a/docs/06-concepts/11-authentication/04-providers/03-google/01-setup.md b/docs/06-concepts/11-authentication/04-providers/03-google/01-setup.md index d5d72451..920f40c1 100644 --- a/docs/06-concepts/11-authentication/04-providers/03-google/01-setup.md +++ b/docs/06-concepts/11-authentication/04-providers/03-google/01-setup.md @@ -1,109 +1,151 @@ # Setup -To set up Sign in with Google, you will need a Google account for your organization and set up a new project. You will also need to add the `serverpod_auth_idp_flutter` package to your app and do some additional setup depending on each platform. +Sign in with Google requires a Google Cloud project. You also need platform-specific OAuth credentials depending on which platforms you target. -:::note -Right now, we have official support for iOS, Android, and Web for Google Sign In. -::: +## Get your Google credentials -:::caution -You need to install the auth module before you continue, see [Setup](../../setup). -::: +All platforms require a Web application OAuth client (used by the server). iOS and Android additionally require their own platform-specific OAuth clients. + +### Create a Google Cloud project -## Create your credentials +1. Go to [Create a project](https://console.cloud.google.com/projectcreate). -To implement Google Sign In, you need a Google Cloud project. You can create one in the [Google Cloud Console](https://console.cloud.google.com/). +2. Enter a **Project name** (e.g. `My Serverpod App`) and click **Create**. + +3. Create a new project (or select an existing one). ### Enable People API -To be allowed to access user data and use the authentication method in Serverpod we have to enable the People API in our project. +The People API is required for Serverpod to access basic user profile data during sign-in. + +1. Navigate to the [People API page](https://console.cloud.google.com/apis/library/people.googleapis.com) in your project. -[Enable it here](https://console.cloud.google.com/apis/library/people.googleapis.com) or find it yourself by navigating to the _Library_ section under _APIs & Services_. Search for _Google People API_, select it, and click on _Enable_. +2. Click **Enable**. + +![Enable People API](/img/authentication/providers/google/6-people-api.png) ### Configure Google Auth Platform -If you haven't already, enable the Google Auth Platform for your project. Navigate to the [Google Auth Platform overview](https://console.cloud.google.com/auth/overview) and click _Get started_. +1. Navigate to the [Google Auth Platform overview](https://console.cloud.google.com/auth/overview) and click **Get started** if you haven't enabled it yet. -![Google Auth Platform Overview](/img/authentication/providers/google/4-auth-platform-overview.png) + ![Google Auth Platform overview](/img/authentication/providers/google/4-auth-platform-overview.png) -Configure the following settings in the Google Auth Platform: +2. **Project configuration**: Complete the setup wizard by filling in the required fields across each step (App Information, Audience, Contact Information) and click **Create**. -1. **Data Access**: Navigate to the [Data Access](https://console.cloud.google.com/auth/scopes) page to add the required scopes. Add the scopes `.../auth/userinfo.email` and `.../auth/userinfo.profile`. + ![Project configuration wizard](/img/authentication/providers/google/4b-project-configuration.png) -![Scopes](/img/authentication/providers/google/1-scopes.png) +3. **Branding**: After completing the wizard, navigate to the [Branding](https://console.cloud.google.com/auth/branding) page from the sidebar. Fill in the remaining fields: app logo, app homepage link, privacy policy link, terms of service link, developer contact email, and **authorized domains**. These details appear on the OAuth consent screen shown to users during sign-in. -:::tip -If you need access to additional Google APIs (e.g., Calendar, Drive), you can add more scopes here. See [Accessing Google APIs](./configuration#accessing-google-apis) for details on requesting additional scopes and using them with the `getExtraGoogleInfoCallback` on the server. -::: + Add any domains you will use in production (e.g., `my-awesome-project.serverpod.space`) to **Authorized domains**. Google will reject redirect URIs that use domains not listed here. -1. **Audience**: Navigate to the [Audience](https://console.cloud.google.com/auth/audience) page to add test users. Add your email so you can test your integration in development mode. + ![Branding configuration](/img/authentication/providers/google/10-branding.png) -:::tip -For production apps, you can configure additional branding options on the [Branding](https://console.cloud.google.com/auth/branding) page. See the [Google Auth Platform documentation](https://developers.google.com/identity/protocols/oauth2) for more details. -::: +4. **Data access**: Navigate to the [Data Access](https://console.cloud.google.com/auth/scopes) page and add the required scopes: `.../auth/userinfo.email` and `.../auth/userinfo.profile`. -## Server-side configuration + ![Scopes configuration](/img/authentication/providers/google/1-scopes.png) + + :::tip + If you need access to additional Google APIs (e.g., Calendar, Drive), you can add more scopes here. See [Accessing Google APIs](./customizations#accessing-google-apis) for details on requesting additional scopes and using them with the `getExtraGoogleInfoCallback` on the server. + ::: + +5. **Audience**: Navigate to the [Audience](https://console.cloud.google.com/auth/audience) page. While in development, the app is in **Testing** mode, which means only users you explicitly add as test users can sign in (up to 100). Add your email as a test user so you can test the integration. + + ![Audience and test users](/img/authentication/providers/google/7-audience.png) -Create the server credentials in the Google Auth Platform. Navigate to _Clients_ and click _Create Client_. Configure the OAuth client as a _**Web application**_. If you have a domain add it to the `Authorized JavaScript origins` and `Authorized redirect URIs`. For development purposes, we can add `http://localhost:8082` to both fields, which is the address to the web server. + :::tip + Leave the app in **Testing** mode for now. You can [publish it](#publishing-to-production) after verifying that sign-in works end to end. + ::: -![Google credentials](/img/authentication/providers/google/5-clients.png) +### Create the server OAuth client (Web application) -Download the JSON file for your web application OAuth client. This file contains both the client id and the client secret. You will need to supply the contents of the file to the `clientSecret` property of the `GoogleIdpConfig` object. +All platforms (iOS, Android, and Web) require a **Web application** OAuth client for the server. This is the only client type that provides a **client secret**, which Serverpod needs to verify sign-in tokens on the server side. -This can be done by pasting the contents of the JSON file into the `googleClientSecret` key in the `config/passwords.yaml` file or setting as value of the `SERVERPOD_PASSWORD_googleClientSecret` environment variable. Alternatively, you can read the file contents directly using the `GoogleClientSecret.fromJsonFile()` method. +1. In the Google Auth Platform, navigate to **Clients** and click **Create Client**. + +2. Select **Web application** as the application type. + +3. Add the following URIs: + + - **Authorized JavaScript origins**: The origin that is allowed to make requests to Google's OAuth servers. For Serverpod, this is your **web server** address. + - **Authorized redirect URIs**: The URL Google redirects the user back to after they sign in. Serverpod handles this callback on the web server as well. + + Serverpod runs three servers locally (see `config/development.yaml`): the API server on port 8080, the Insights server on 8081, and the **web server on port 8082**. The Google OAuth flow uses the web server, so both fields should point to port 8082: + + | Environment | Authorized JavaScript origins | Authorized redirect URIs | + | --- | --- | --- | + | Local development | `http://localhost:8082` | `http://localhost:8082` | + | Production | Your web server's public URL (e.g., `https://my-awesome-project.serverpod.space`) | Your web server's public URL | + + You can find these ports in your server's `config/development.yaml` under `webServer`. + + ![Clients configuration](/img/authentication/providers/google/5-clients.png) + +4. Click **Create**. + +5. Copy the **Client ID** and **Client secret** shown on screen. You will need both in the next step. + +### Store your credentials + +Your server's `config/passwords.yaml` already has `development:`, `staging:`, and `production:` sections from the project template. Add the `googleClientSecret` key to the `development:` section using the client ID and client secret you just copied: ```yaml development: + # ... existing keys (database, redis, serviceSecret, etc.) ... googleClientSecret: | { "web": { "client_id": "your-client-id.apps.googleusercontent.com", - "project_id": "your-project-id", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_secret": "your-client-secret", "redirect_uris": ["http://localhost:8082"] } } ``` -In your main `server.dart` file, configure the Google identity provider: +Replace `your-client-id` and `your-client-secret` with the values from the Google Auth Platform. The `redirect_uris` must match the **Authorized redirect URIs** you configured in the previous step. + +For production, add the same `googleClientSecret` entry to the `production:` section of `passwords.yaml` (with your production redirect URI), or set the `SERVERPOD_PASSWORD_googleClientSecret` environment variable on your production server. + +:::warning +**Never commit `config/passwords.yaml` to version control.** It contains your OAuth client secret. Use environment variables or a secrets manager in production. +::: + +:::note +**Carefully maintain correct indentation for YAML block scalars.** The `googleClientSecret` block uses a `|`; any indentation error will silently break the JSON, resulting in authentication failures. +::: + +## Server-side configuration + +### Add the Google identity provider + +Your server's `server.dart` file (e.g., `my_project_server/lib/server.dart`) should already contain a `pod.initializeAuthServices()` call if your project was created with the Serverpod project template (`serverpod create`). If it's not there, see [Setup](../../setup) first to configure the auth module and JWT settings. + +Add the Google import and `GoogleIdpConfigFromPasswords()` to the existing `identityProviderBuilders` list: ```dart -import 'package:serverpod/serverpod.dart'; -import 'package:serverpod_auth_idp_server/core.dart'; import 'package:serverpod_auth_idp_server/providers/google.dart'; +``` -void run(List args) async { - final pod = Serverpod( - args, - Protocol(), - Endpoints(), - ); - - pod.initializeAuthServices( - tokenManagerBuilders: [ - JwtConfigFromPasswords(), - ], - identityProviderBuilders: [ - GoogleIdpConfig( - clientSecret: GoogleClientSecret.fromJsonString( - pod.getPassword('googleClientSecret')!, - ), - ), - ], - ); - - await pod.start(); -} +```dart +pod.initializeAuthServices( + tokenManagerBuilders: [ + JwtConfigFromPasswords(), + ], + identityProviderBuilders: [ + // ... any existing providers (e.g., EmailIdpConfigFromPasswords) ... + GoogleIdpConfigFromPasswords(), + ], +); ``` +`GoogleIdpConfigFromPasswords()` automatically loads the client secret from the `googleClientSecret` key in `config/passwords.yaml` (or the `SERVERPOD_PASSWORD_googleClientSecret` environment variable). + :::tip -You can use the `GoogleIdpConfigFromPasswords` constructor in replacement of the `GoogleIdpConfig` above to automatically load the client secret from the `config/passwords.yaml` file or environment variables. It will expect the `googleClientSecret` key on the file or the `SERVERPOD_PASSWORD_googleClientSecret` environment variable. +If you need more control over how the client secret is loaded, you can use `GoogleIdpConfig(clientSecret: GoogleClientSecret.fromJsonString(...))` instead. See the [customizations](./customizations) page for details. ::: -Then, extend the abstract endpoint to expose it on the server: +### Create the endpoint + +Create a new endpoint file in your server project (e.g., `my_project_server/lib/src/auth/google_idp_endpoint.dart`) alongside the existing auth endpoints. Extending the base class registers the sign-in methods with your server so the Flutter client can call them to complete the authentication flow: ```dart import 'package:serverpod_auth_idp_server/providers/google.dart'; @@ -111,45 +153,53 @@ import 'package:serverpod_auth_idp_server/providers/google.dart'; class GoogleIdpEndpoint extends GoogleIdpBaseEndpoint {} ``` -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). +### Generate code and apply migrations -### Basic configuration options +Run the following commands from your server project directory (e.g., `my_project_server/`) to generate client code and apply the database migration: -- `clientSecret`: Required. Google OAuth client secret loaded from JSON. See the [configuration section](./configuration) for details on different ways to load the client secret. - -For more details on configuration options, such as customizing account validation, accessing Google APIs, and more, see the [configuration section](./configuration). +```bash +serverpod generate +serverpod create-migration +dart run bin/main.dart --apply-migrations +``` -:::warning -The `google_client_secret.json` contains a private key and should not be version controlled. Store it securely using environment variables or secret management. +:::note +Skipping the migration will cause the server to crash at runtime when the Google provider tries to read or write user data. More detailed instructions can be found in the general [identity providers setup section](../../setup#identity-providers-configuration). ::: ## Client-side configuration -For our client-side configurations, we have to first create client-side credentials and include the credentials files in our projects. The Android and iOS integrations use the [google_sign_in](https://pub.dev/packages/google_sign_in) package under the hood, so any documentation there should also apply to this setup. +The Android and iOS integrations use the [google_sign_in](https://pub.dev/packages/google_sign_in) package under the hood, so any documentation there should also apply to this setup. ### iOS -Create the client credentials in the Google Auth Platform. Navigate to _Clients_ and click _Create Client_. Configure the OAuth client as Application type _**iOS**_. +1. In the Google Auth Platform, navigate to **Clients** and click **Create Client**. -Fill in all the required information and create the credentials. Download the `plist` file - you'll need to extract values from it to configure your app. +2. Select **iOS** as the application type. -Open your `ios/Runner/Info.plist` file and add the following keys: +3. Fill in your app's **Bundle ID** and any other required information. -```xml - - ... - GIDClientID - your_ios_client_id - GIDServerClientID - your_server_client_id - -``` +4. Click **Create** and download the `.plist` file. -Replace `your_ios_client_id` with the `CLIENT_ID` value from the downloaded plist file, and `your_server_client_id` with the client ID from the server credentials JSON file. + ![Create iOS OAuth client](/img/authentication/providers/google/8-ios-client-create.png) + +5. Open the `Info.plist` file in your Flutter project (e.g., `my_project_flutter/ios/Runner/Info.plist`) and add the following keys inside the top-level ``: + + ```xml + + ... + GIDClientID + your_ios_client_id + GIDServerClientID + your_server_client_id + + ``` + + Replace `your_ios_client_id` with the `CLIENT_ID` value from the downloaded plist file, and `your_server_client_id` with the client ID from the [Web application OAuth client](#create-the-server-oauth-client-web-application) you created earlier. #### Add the URL scheme -To allow navigation back to the app after sign-in, add the URL scheme to your `Info.plist`. The scheme is the reversed client ID of your iOS app (found as `REVERSED_CLIENT_ID` in the downloaded plist file). +To allow navigation back to the app after sign-in, add the URL scheme to the same `Info.plist` file. The scheme is the reversed client ID of your iOS app (found as `REVERSED_CLIENT_ID` in the downloaded plist file). Add the following inside the top-level ``: ```xml @@ -170,85 +220,82 @@ To allow navigation back to the app after sign-in, add the URL scheme to your `I Replace the URL scheme with your actual reversed client ID. -:::info -If you have any social logins in your app you also need to integrate "Sign in with Apple" to publish your app to the app store. ([Read more](https://developer.apple.com/sign-in-with-apple/get-started/)). +:::warning +Without the URL scheme, the OAuth callback never returns to your app and sign-in silently hangs. ::: ### Android -Create the client credentials in the Google Auth Platform. Navigate to _Clients_ and click _Create Client_. Configure the OAuth client as Application type _**Android**_. +1. In the Google Auth Platform, navigate to **Clients** and click **Create Client**. -Fill in all required information, you can get the debug SHA-1 hash by running `./gradlew signingReport` in your Android project directory. Create the credentials and download the JSON file. +2. Select **Android** as the application type. -Put the file inside the `android/app/` directory and rename it to `google-services.json`. +3. Fill in your app's **Package name** and **SHA-1 certificate fingerprint**. You can get the debug SHA-1 hash by running this from your Flutter project's `android/` directory (e.g., `my_project_flutter/android/`): -:::info -If your `google-services.json` does not include a web OAuth client entry, you may need to provide client IDs programmatically as described on the [configuration page](./configuration#configuring-client-ids-on-the-app). -::: + ```bash + ./gradlew signingReport + ``` -:::info -For a production app you need to get the SHA-1 key from your production keystore! This can be done by running this command: ([Read more](https://support.google.com/cloud/answer/6158849#installedapplications&android&zippy=%2Cnative-applications%2Candroid)). +4. Click **Create** and download the JSON file. -```bash -$ keytool -list -v -keystore /path/to/keystore -``` -::: + ![Create Android OAuth client](/img/authentication/providers/google/9-android-client-create.png) -:::tip -If you encounter issues with Google Sign-In on Android, check the [official troubleshooting guide](https://pub.dev/packages/google_sign_in_android#troubleshooting) for common solutions. +5. Place the file inside your Flutter project's `android/app/` directory (e.g., `my_project_flutter/android/app/`) and rename it to `google-services.json`. + +:::warning +The downloaded `google-services.json` may not include a web OAuth client entry, which is required for Google Sign-In to resolve the server client ID. If sign-in fails, provide the client IDs programmatically as described on the [customizations](./customizations#configuring-client-ids-on-the-app) page. ::: ### Web -There is no need to create any client credentials for the web, since it uses the same client ID as the server. However, you have to modify the server credentials in the Google Auth Platform. +Web uses the same server OAuth client you created earlier, so you don't need a separate client. However, for web, the sign-in request originates from the Flutter app running in the browser, not from the Serverpod web server. Google requires this origin to be listed as well. -Navigate to _Clients_ and select the server credentials (the one configured as a _**Web application**_). Under `Authorized JavaScript origins` and `Authorized redirect URIs` add the domain for your Flutter app, for development, this is `http://localhost:` where the port is the port you are using. +1. **Choose a fixed port for your Flutter web app.** Google OAuth requires exact origin matches, and Flutter picks a random port on each run by default. To keep things consistent, run Flutter on a fixed port using `--web-port`: -:::info -Force flutter to run on a specific port by running. + ```bash + flutter run -d chrome --web-hostname localhost --web-port=49660 + ``` -```bash -$ flutter run -d chrome --web-port=49660 -``` -::: + - `-d chrome`: Run on the Chrome browser. + - `--web-hostname localhost`: Bind to localhost. + - `--web-port=49660`: Use a fixed port (pick any available port). This is the value you will add to **Authorized JavaScript origins** in the next step. -Set up the redirect URI where the user will navigate after sign-in. For development, add `http://localhost:8082` inside `Authorized redirect URIs`. In production, use your server's domain (e.g., `https://example.com`). +2. **Update the server OAuth client.** Go back to the server OAuth client you created in the [previous section](#create-the-server-oauth-client-web-application) and add your Flutter web app's origin to **Authorized JavaScript origins**: -Then, on the `web/index.html` file, add the following to the `` section: + - For local development: `http://localhost:49660` (or whichever port you chose) + - For production: your Flutter web app's domain (e.g., `https://my-awesome-project.serverpod.space`) -```html - - ... - - -``` + The **Authorized redirect URIs** should already contain your Serverpod web server's address (`http://localhost:8082`) from the earlier setup. You don't need to change it. -![Google credentials](/img/authentication/providers/google/2-credentials.png) + ![Web credentials configuration](/img/authentication/providers/google/2-credentials.png) -## Present the authentication UI +3. **Add the client ID to your Flutter project's `web/index.html`** (e.g., `my_project_flutter/web/index.html`). In the `` section, add: -### Initializing the `GoogleSignInService` + ```html + + ... + + + ``` -To use the GoogleSignInService, you need to initialize it in your main function. The initialization is done from the `initializeGoogleSignIn()` extension method on the `FlutterAuthSessionManager`. + Replace `your_server_client_id` with the client ID from your Web application OAuth client. -```dart -import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart'; -import 'package:your_client/your_client.dart'; +## Present the authentication UI + +### Initialize the Google sign-in service -final client = Client('http://localhost:8080/') - ..authSessionManager = FlutterAuthSessionManager(); +In your Flutter app's `main.dart` file (e.g., `my_project_flutter/lib/main.dart`), the template already sets up the `Client` and calls `client.auth.initialize()`. Add `client.auth.initializeGoogleSignIn()` right after it: -void main() { - client.auth.initialize(); - client.auth.initializeGoogleSignIn(); -} +```dart +client.auth.initialize(); +client.auth.initializeGoogleSignIn(); ``` -### Using GoogleSignInWidget +### Add the sign-in widget If you have configured the `SignInWidget` as described in the [setup section](../../setup#present-the-authentication-ui), the Google identity provider will be automatically detected and displayed in the sign-in widget. -You can also use the `GoogleSignInWidget` to include the Google authentication flow in your own custom UI. +You can also use the `GoogleSignInWidget` directly in your widget tree to include the Google authentication flow in your own custom UI: ```dart import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart'; @@ -270,10 +317,83 @@ GoogleSignInWidget( ) ``` +This renders a Google sign-in button like this: + +![Google sign-in button](/img/authentication/providers/google/3-button.png) + The widget automatically handles: + - Google Sign-In flow for iOS, Android, and Web. - Lightweight sign-in (One Tap, FedCM) support. - Token management. - Underlying Google Sign-In package error handling. For details on how to customize the Google Sign-In UI in your Flutter app, see the [customizing the UI section](./customizing-the-ui). + +:::tip +If you run into issues, see the [troubleshooting guide](./troubleshooting). +::: + +## Publishing to production + +Before going live, complete the following steps: + +### 1. Update the OAuth redirect URIs + +Go back to the [server OAuth client](#create-the-server-oauth-client-web-application) in the Google Auth Platform and add your production server's public URL to both **Authorized JavaScript origins** and **Authorized redirect URIs**: + +- **Authorized JavaScript origins**: `https://your-domain.serverpod.space` +- **Authorized redirect URIs**: `https://your-domain.serverpod.space` + +Replace the URL with your actual production web server address. + +### 2. Store the production credentials + +Add the `googleClientSecret` entry to the `production:` section of `config/passwords.yaml`, using the production redirect URI: + +```yaml +production: + # ... existing keys ... + googleClientSecret: | + { + "web": { + "client_id": "your-client-id.apps.googleusercontent.com", + "client_secret": "your-client-secret", + "redirect_uris": ["https://your-domain.serverpod.space"] + } + } +``` + +Alternatively, set the `SERVERPOD_PASSWORD_googleClientSecret` [environment variable](../../../07-configuration.md#2-via-environment-variables) on your production server with the same JSON value. + +If you're deploying to Serverpod Cloud, set the password with the `scloud` CLI instead. Save the JSON to a file and run: + +```bash +scloud password set googleClientSecret --from-file path/to/google-client-secret.json +``` + +See the [Serverpod Cloud passwords guide](https://docs.serverpod.dev/cloud/guides/passwords) for more details. + +### 3. Update the Android OAuth client with the release SHA-1 (Android only) + +The Android OAuth client you created during setup uses your debug SHA-1 fingerprint. Release builds are signed with a different key, so you need to add the release SHA-1 as well. + +If you use Google Play App Signing (the default for new apps), get the SHA-1 from the Play Console: **Setup** > **App integrity** > **App signing key certificate**. Make sure to use the **app signing key** SHA-1, not the upload key SHA-1. + +If you manage your own release keystore, get the SHA-1 from it directly: + +```bash +keytool -list -v -keystore your-release-key.jks -alias your-key-alias +``` + +Once you have the SHA-1, go back to your Android OAuth client in the Google Auth Platform and add it under **SHA-1 certificate fingerprint**. + +:::warning +Forgetting this step is one of the most common reasons Google Sign-In works in debug builds but silently fails after publishing to the Play Store. +::: + +### 4. Publish the OAuth consent screen + +While the app is in **Testing** mode, only the test users you added on the [Audience](https://console.cloud.google.com/auth/audience) page, in the Google Auth Platform, can sign in. All other users will see an error. + +Navigate to the **Audience** page and click **Publish App** to allow any Google account to sign in. If your app uses sensitive or restricted scopes, Google may require a verification review before publishing. diff --git a/docs/06-concepts/11-authentication/04-providers/03-google/02-configuration.md b/docs/06-concepts/11-authentication/04-providers/03-google/02-customizations.md similarity index 65% rename from docs/06-concepts/11-authentication/04-providers/03-google/02-configuration.md rename to docs/06-concepts/11-authentication/04-providers/03-google/02-customizations.md index 85a4a34a..99c44fbd 100644 --- a/docs/06-concepts/11-authentication/04-providers/03-google/02-configuration.md +++ b/docs/06-concepts/11-authentication/04-providers/03-google/02-customizations.md @@ -1,14 +1,25 @@ -# Configuration +# Customizations -This page covers configuration options for the Google identity provider beyond the basic setup. +This page covers additional configuration options for the Google 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 `GoogleIdpConfig` in-code documentation. -### Loading Google Client Secret +The Google identity provider can be configured using one of two classes: -You can load the Google client secret in several ways: +- **`GoogleIdpConfigFromPasswords`**: Automatically loads the client secret from the `googleClientSecret` key in `passwords.yaml` (or the `SERVERPOD_PASSWORD_googleClientSecret` environment variable). This is the class used in the [setup guide](./setup) and is recommended for most projects. +- **`GoogleIdpConfig`**: Requires you to pass a `GoogleClientSecret` object directly. Use this when you need to load credentials from a custom source, such as a JSON file, a secrets manager, or a programmatically constructed map. + +`GoogleIdpConfigFromPasswords` is a convenience wrapper around `GoogleIdpConfig` that handles credential loading for you. + +Both classes accept the same optional callbacks shown in the sections below. The examples on this page use `GoogleIdpConfigFromPasswords` unless the section specifically demonstrates manual client secret loading. + +### Load the client secret using GoogleIdpConfig + +When using `GoogleIdpConfig`, you must provide the client secret explicitly. + +You can load the secret in several ways: **From JSON string (recommended for production):** @@ -39,7 +50,7 @@ final googleIdpConfig = GoogleIdpConfig( 'client_id': 'your-client-id.apps.googleusercontent.com', 'client_secret': 'your-client-secret', 'redirect_uris': [ - 'http://localhost:8080/auth/google/callback', + 'http://localhost:8082', ], }, }), @@ -70,7 +81,7 @@ The default setup allows access to basic user information, such as email, profil - Add the required scopes to the [Data Access](./setup#configure-google-auth-platform) page in the Google Auth Platform. - Request access to the scopes when signing in. Do this by setting the `scopes` parameter of the `GoogleSignInWidget` or `GoogleAuthController`. -A full list of available scopes can be found [here](https://developers.google.com/identity/protocols/oauth2/scopes). +For a full list of available scopes, see the [Google OAuth 2.0 Scopes reference](https://developers.google.com/identity/protocols/oauth2/scopes). :::info Adding additional scopes may require approval by Google. On the OAuth consent screen, you can see which of your scopes are considered sensitive. @@ -101,33 +112,43 @@ final googleIdpConfig = GoogleIdpConfigFromPasswords( ); ``` -### Reacting to account creation +### Reacting to auth user creation -You can use the `onAfterGoogleAccountCreated` callback to run logic after a new Google account has been created and linked to an auth user. This callback is only invoked for new accounts, not for returning users. +The `onBeforeAuthUserCreated` and `onAfterAuthUserCreated` hooks are global callbacks configured on `AuthUsersConfig` in `initializeAuthServices`. They are not specific to Google -- they fire for every identity provider. See the [working with users](../../working-with-users#reacting-to-the-user-created-event) page 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. +`onBeforeAuthUserCreated` receives the default scopes and blocked status for the new user and must return the final values. Use it to assign custom scopes at creation time: ```dart -final googleIdpConfig = GoogleIdpConfigFromPasswords( - onAfterGoogleAccountCreated: ( - session, - authUser, - googleAccount, { - required transaction, - }) async { - // e.g. store additional data, send a welcome email, or log for analytics - }, +pod.initializeAuthServices( + tokenManagerBuilders: [ + JwtConfigFromPasswords(), + ], + identityProviderBuilders: [ + GoogleIdpConfigFromPasswords(), + ], + 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. -::: - -:::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` in combination with `getExtraGoogleInfoCallback` to fetch and store the data you need before the auth user is created. -::: - ### Lightweight Sign-In on the Flutter app Lightweight sign-in is a feature that attempts to authenticate users previously logged in with Google automatically with minimal or no user interaction. When enabled, the Google authentication controller will try to sign in users seamlessly using platform-specific lightweight authentication methods. This feature is disabled by default, but can be enabled from the `GoogleSignInWidget` or `GoogleAuthController`. @@ -194,3 +215,11 @@ This approach is useful when you need to: :::tip You can also set these environment variables in your IDE's run configuration or CI/CD pipeline to avoid passing them manually each time. ::: + +## GoogleIdpConfig parameter reference + +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `clientSecret` | `GoogleClientSecret` | Yes | The Google OAuth client secret loaded from JSON. Can be loaded via `fromJsonString`, `fromJsonFile`, or `fromJson`. | +| `googleAccountDetailsValidation` | `GoogleAccountDetailsValidation?` | No | Custom validation callback for Google account details before allowing sign-in. Throws an exception to reject the account. | +| `getExtraGoogleInfoCallback` | `GetExtraGoogleInfoCallback?` | No | Callback that receives the access token after sign-in, allowing you to call additional Google APIs and store extra user data. | diff --git a/docs/06-concepts/11-authentication/04-providers/03-google/03-customizing-the-ui.md b/docs/06-concepts/11-authentication/04-providers/03-google/03-customizing-the-ui.md index d7d060e2..4eb8d407 100644 --- a/docs/06-concepts/11-authentication/04-providers/03-google/03-customizing-the-ui.md +++ b/docs/06-concepts/11-authentication/04-providers/03-google/03-customizing-the-ui.md @@ -15,6 +15,7 @@ SignInWidget( ), ) ``` + ::: ## Using the `GoogleSignInWidget` diff --git a/docs/06-concepts/11-authentication/04-providers/03-google/04-troubleshooting.md b/docs/06-concepts/11-authentication/04-providers/03-google/04-troubleshooting.md new file mode 100644 index 00000000..339aec05 --- /dev/null +++ b/docs/06-concepts/11-authentication/04-providers/03-google/04-troubleshooting.md @@ -0,0 +1,205 @@ +# Troubleshooting + +This page helps you identify common Sign in with Google failures, explains why they occur, and shows how to resolve them. For platform-specific issues with the underlying Flutter package, see the [google_sign_in_android troubleshooting guide](https://pub.dev/packages/google_sign_in_android#troubleshooting). + +## Setup checklist + +Go through this before investigating a specific error. Most problems come from a missed step. + +#### Google Cloud + +- [ ] Create a **Google Cloud project** in the [Google Cloud Console](https://console.cloud.google.com/). +- [ ] Enable the **People API** in your project. +- [ ] In **Google Auth Platform**, complete the initial setup (wizard) and add the required scopes on **Data Access** (`.../auth/userinfo.email` and `.../auth/userinfo.profile`). +- [ ] On **Branding** ([Branding](https://console.cloud.google.com/auth/branding)), complete the OAuth consent screen (logo, homepage, privacy policy, terms of service, and developer contact) and add every hostname you will use under **Authorized domains** (redirect URIs must use a listed domain). +- [ ] Add **test users** on **Audience** while in **Testing** mode ([Audience](https://console.cloud.google.com/auth/audience)), or **Publish app** when everyone should be able to sign in. +- [ ] Create a **Web application** OAuth client with **Authorized JavaScript origins** and **Authorized redirect URIs** set to your Serverpod **web server** (`http://localhost:8082` locally, not port `8080`). Copy the **Client ID** and **Client secret**. +- [ ] Add `googleClientSecret` to `config/passwords.yaml` with your client ID, client secret, and matching `redirect_uris`. For production, use the live web server URL in Google Cloud and in `production:` (or env vars) as in [Publishing to production](./setup#publishing-to-production). + +#### Server + +- [ ] For new or customized servers, confirm auth services and JWT are configured per [Authentication setup](../../setup#identity-providers-configuration) before adding Google. +- [ ] Add `GoogleIdpConfigFromPasswords()` to `identityProviderBuilders` in `server.dart`. +- [ ] Create a `GoogleIdpEndpoint` file in `lib/src/auth/`. +- [ ] Run `serverpod generate`, then `serverpod create-migration`, then apply migrations using `--apply-migrations`. + +#### Client + +- [ ] Add `client.auth.initializeGoogleSignIn()` after `client.auth.initialize()` in your Flutter app's `main.dart`. +- [ ] Surface Google sign-in in the UI with `SignInWidget` or `GoogleSignInWidget` (see [Present the authentication UI](./setup#present-the-authentication-ui)). +- [ ] Create an **iOS** OAuth client in the **same** Google Cloud project as the Web client, using the same **Bundle ID** as the app; set `GIDClientID` from the iOS client, `GIDServerClientID` to the **Web** client's ID, and add the reversed-client-ID **URL scheme** in `Info.plist` (*iOS only*). +- [ ] Create an **Android** OAuth client in the **same** project, with the same **package name** and **SHA-1** as the build you run; place `google-services.json` in `android/app/` (*Android only*). +- [ ] Add the `google-signin-client_id` **meta tag** to `web/index.html` (*Web only*). +- [ ] On **Web**, list **both** the Serverpod web server origin (e.g., `http://localhost:8082`) and your Flutter app origin under **Authorized JavaScript origins** on the Web OAuth client; use a **fixed** `--web-port` for Flutter so that second origin does not change every run (see [Web setup](./setup#web)). + +## Sign-in fails with redirect_uri_mismatch + +**Problem:** The OAuth flow fails with a `redirect_uri_mismatch` error from Google. + +**Cause:** The redirect URI in your OAuth client configuration does not match the URI your app is actually using. Google requires an exact match. + +**Resolution:** In the Google Auth Platform, navigate to **Clients**, select your Web application client, and verify that the URIs under **Authorized JavaScript origins** and **Authorized redirect URIs** match your server's address exactly. For local development, both should be `http://localhost:8082` (the Serverpod **web server** port, not the API server port 8080). Trailing slashes, port differences, and `http` vs `https` all count as mismatches. + +Common mistakes: + +* Using port `8080` (the API server) instead of `8082` (the web server). Check `config/development.yaml` under `webServer` for the correct port. +* Adding a trailing slash (e.g., `http://localhost:8082/` instead of `http://localhost:8082`). +* For Web apps: not adding the Flutter web app's origin (e.g., `http://localhost:49660`) to **Authorized JavaScript origins**. This is separate from the Serverpod web server address. See the [Web setup section](./setup#web) for details. + +## Sign-in works for you but not for other users + +**Problem:** Sign-in works for your Google account but other users get an error screen from Google saying the app is not verified or access is denied. + +**Cause:** Your Google Auth Platform app is still in **Testing** mode. Only users explicitly added as test users can sign in (up to 100). + +**Resolution:** Navigate to the [Audience](https://console.cloud.google.com/auth/audience) page and click **Publish App** to allow any Google account to sign in. If your app uses sensitive or restricted scopes, Google may require a verification review before publishing. + +## Production redirect URIs rejected by Google + +**Problem:** When adding your production domain to Authorized redirect URIs, Google rejects it with an error about unauthorized domains. + +**Cause:** Your production domain is not listed under **Authorized domains** on the Branding page. + +**Resolution:** Navigate to the [Branding](https://console.cloud.google.com/auth/branding) page and add your production domain (e.g., `my-awesome-project.serverpod.space`) to **Authorized domains**. Google requires redirect URIs to use domains listed here. + +## Flutter web sign-in fails with origin mismatch + +**Problem:** Google Sign-In on Flutter web fails with an origin mismatch error, even though you added `http://localhost:8082` to the OAuth client. + +**Cause:** The Flutter web app runs on a different port than the Serverpod web server. The sign-in request originates from the Flutter app's port (e.g., `http://localhost:49660`), which also needs to be listed in **Authorized JavaScript origins**. Flutter also picks a random port by default, so the origin changes on every run. + +**Resolution:** Run Flutter on a fixed port and add that origin to your OAuth client: + +```bash +flutter run -d chrome --web-hostname localhost --web-port=49660 +``` + +Then add `http://localhost:49660` to **Authorized JavaScript origins** in the Google Auth Platform. + +## Server fails to parse googleClientSecret from passwords.yaml + +**Problem:** The server crashes on startup with a JSON parsing error related to `googleClientSecret`. + +**Cause:** The YAML block scalar indentation is incorrect. The `googleClientSecret` key uses `|` (literal block scalar), which requires every line of the JSON to be indented at the same level relative to the key. + +**Resolution:** Make sure the JSON block is indented consistently under the `|`: + +```yaml +development: + googleClientSecret: | + { + "web": { + "client_id": "...", + "client_secret": "..." + } + } +``` + +Every line of the JSON must be indented by at least one level more than `googleClientSecret:`. Mixing tabs and spaces can also cause issues. + +## Sign-in fails on Android with PlatformException(sign_in_failed) or clientConfigurationError + +**Problem:** Google Sign-In throws a `PlatformException(sign_in_failed, ...)` or a `GoogleSignInException` with `clientConfigurationError` on Android but works on other platforms. + +**Cause:** The SHA-1 fingerprint registered in your Android OAuth client does not match the signing key used to build the app. This commonly happens when switching between debug and release builds, or when the app is signed with a different keystore than the one registered. + +**Resolution:** + +1. Check which SHA-1 your debug build is using: + + ```bash + ./gradlew signingReport + ``` + +2. In the Google Auth Platform, navigate to **Clients** and verify your Android OAuth client has the correct SHA-1 fingerprint. + +3. If you are testing a release build, use the SHA-1 from your production keystore: + + ```bash + keytool -list -v -keystore /path/to/keystore + ``` + +4. After updating the SHA-1, it can take a few minutes for Google to propagate the change. + +## Sign-in works in debug but fails in release + +**Problem:** Google Sign-In works in debug mode but fails silently or with `sign_in_failed` in a release build. + +**Cause:** Debug and release builds use different signing keys. The SHA-1 fingerprint registered in your Android OAuth client only matches the debug keystore. + +**Resolution:** Register the SHA-1 fingerprint from your release keystore as an additional fingerprint in the Google Auth Platform. You can add multiple SHA-1 fingerprints to the same Android OAuth client, or create separate clients for debug and release. + +## Missing web client entry in google-services.json + +**Problem:** Sign-in fails on Android with an error about a missing server client ID, or `serverClientId` is null. + +**Cause:** The `google-services.json` file does not contain a web OAuth client entry. This happens when no Web application OAuth client exists in the same Google Cloud project. + +**Resolution:** Make sure you have created a Web application OAuth client in the same project as your Android OAuth client. Re-download `google-services.json` after creating the Web client. Alternatively, provide client IDs programmatically as described on the [customizations page](./customizations#configuring-client-ids-on-the-app). + +## People API not enabled + +**Problem:** Sign-in completes on the client but the server returns an error when fetching user profile data. The server logs show a `403` or `PERMISSION_DENIED` error from the People API. + +**Cause:** The People API is not enabled in your Google Cloud project. + +**Resolution:** Navigate to the [People API page](https://console.cloud.google.com/apis/library/people.googleapis.com) and click **Enable**. + +## Server crashes on first Google sign-in with "no such table" + +**Problem:** The server builds and starts, but crashes when a user tries Google sign-in. The error cites a missing table (like `serverpod_auth_idp_google_account`). + +**Cause:** `serverpod generate` has been run, but you didn't create or apply the accompanying database migration. + +**Resolution:** Create and apply the migration: + +```bash +serverpod generate +serverpod create-migration +dart run bin/main.dart --apply-migrations +``` + +## Lightweight sign-in (One Tap) not appearing + +**Problem:** You enabled `attemptLightweightSignIn: true` but the One Tap prompt never appears on Web, or the silent sign-in doesn't trigger on mobile. + +**Cause:** Lightweight sign-in requires the user to have previously signed in with Google on this device or browser. It also depends on platform-specific conditions: on Web, FedCM or One Tap must be supported by the browser; on mobile, the user must have a Google account configured on the device. + +**Resolution:** This is expected behavior for first-time users. The lightweight sign-in prompt only appears for returning users. If the user dismisses One Tap multiple times, Google may suppress it temporarily. The regular sign-in button remains available as a fallback. + +## iOS sign-in prompt doesn't show + +**Problem:** Tapping the Google Sign-In button on iOS has no effect or throws an error about a missing client ID. + +**Cause:** The `GIDClientID` or `GIDServerClientID` keys are missing or incorrect in `Info.plist`, or the URL scheme is not registered. + +**Resolution:** + +1. Open `ios/Runner/Info.plist` and verify that `GIDClientID` is set to the `CLIENT_ID` from your iOS OAuth client plist, and `GIDServerClientID` is set to the client ID from your Web application OAuth client. +2. Verify the URL scheme (`CFBundleURLSchemes`) contains the reversed client ID from the iOS plist (the `REVERSED_CLIENT_ID` value). +3. Clean the build and run again. + +## Web sign-in button doesn't render + +**Problem:** The Google Sign-In button doesn't appear on Web, or the page shows a JavaScript error related to Google Identity Services. + +**Cause:** The `google-signin-client_id` meta tag is missing from `web/index.html`, or its value doesn't match the server's Web application client ID. + +**Resolution:** Add or verify the meta tag in `web/index.html`: + +```html + + ... + + +``` + +Replace `your_server_client_id` with the `client_id` from your Web application OAuth client JSON file. + +## Google API calls fail after one hour on Web + +**Problem:** Your app calls Google APIs (e.g., Calendar, Drive) using the access token from sign-in, but requests start returning `401 Unauthorized` after about an hour. This only affects the Web platform. + +**Cause:** On Web, the `accessToken` returned by the `google_sign_in` package expires after 3,600 seconds (one hour) and is not automatically refreshed. + +**Resolution:** When making Google API calls on Web, check the token age and prompt the user to re-authenticate if the token has expired. On mobile platforms, the token is refreshed automatically and this is not an issue. See the [google_sign_in_web documentation](https://pub.dev/packages/google_sign_in_web) for details on token lifecycle. diff --git a/static/img/authentication/providers/google/1-scopes.png b/static/img/authentication/providers/google/1-scopes.png index 4d513ce1..de47da0a 100644 Binary files a/static/img/authentication/providers/google/1-scopes.png and b/static/img/authentication/providers/google/1-scopes.png differ diff --git a/static/img/authentication/providers/google/10-branding.png b/static/img/authentication/providers/google/10-branding.png new file mode 100644 index 00000000..3a9e0181 Binary files /dev/null and b/static/img/authentication/providers/google/10-branding.png differ diff --git a/static/img/authentication/providers/google/2-credentials.png b/static/img/authentication/providers/google/2-credentials.png index 338f5a9c..f24e384b 100644 Binary files a/static/img/authentication/providers/google/2-credentials.png and b/static/img/authentication/providers/google/2-credentials.png differ diff --git a/static/img/authentication/providers/google/3-button.png b/static/img/authentication/providers/google/3-button.png index 37b290cb..4f245633 100644 Binary files a/static/img/authentication/providers/google/3-button.png and b/static/img/authentication/providers/google/3-button.png differ diff --git a/static/img/authentication/providers/google/4-auth-platform-overview.png b/static/img/authentication/providers/google/4-auth-platform-overview.png index 8da2914d..7df2ee0a 100644 Binary files a/static/img/authentication/providers/google/4-auth-platform-overview.png and b/static/img/authentication/providers/google/4-auth-platform-overview.png differ diff --git a/static/img/authentication/providers/google/4b-project-configuration.png b/static/img/authentication/providers/google/4b-project-configuration.png new file mode 100644 index 00000000..0f55416d Binary files /dev/null and b/static/img/authentication/providers/google/4b-project-configuration.png differ diff --git a/static/img/authentication/providers/google/5-clients.png b/static/img/authentication/providers/google/5-clients.png index b9a1a07d..4a68768e 100644 Binary files a/static/img/authentication/providers/google/5-clients.png and b/static/img/authentication/providers/google/5-clients.png differ diff --git a/static/img/authentication/providers/google/6-people-api.png b/static/img/authentication/providers/google/6-people-api.png new file mode 100644 index 00000000..7e974b1e Binary files /dev/null and b/static/img/authentication/providers/google/6-people-api.png differ diff --git a/static/img/authentication/providers/google/7-audience.png b/static/img/authentication/providers/google/7-audience.png new file mode 100644 index 00000000..3dc692c9 Binary files /dev/null and b/static/img/authentication/providers/google/7-audience.png differ diff --git a/static/img/authentication/providers/google/8-ios-client-create.png b/static/img/authentication/providers/google/8-ios-client-create.png new file mode 100644 index 00000000..c2b4add3 Binary files /dev/null and b/static/img/authentication/providers/google/8-ios-client-create.png differ diff --git a/static/img/authentication/providers/google/9-android-client-create.png b/static/img/authentication/providers/google/9-android-client-create.png new file mode 100644 index 00000000..8b6a0aa5 Binary files /dev/null and b/static/img/authentication/providers/google/9-android-client-create.png differ