diff --git a/docs/06-concepts/11-authentication/04-providers/07-github/01-setup.md b/docs/06-concepts/11-authentication/04-providers/07-github/01-setup.md index 0e4b8eac..2ff21928 100644 --- a/docs/06-concepts/11-authentication/04-providers/07-github/01-setup.md +++ b/docs/06-concepts/11-authentication/04-providers/07-github/01-setup.md @@ -1,114 +1,150 @@ # Setup -To set up **Sign in with GitHub**, you must create OAuth2 credentials on [GitHub](https://github.com/settings/apps) and configure your Serverpod application accordingly. +Sign in with GitHub uses OAuth2 credentials from a **GitHub App** registered on GitHub. -:::caution -You need to install the auth module before you continue, see [Setup](../../setup). +## Prerequisites + +Before following this guide, make sure you have: + +- A GitHub account with access to [Developer Settings](https://github.com/settings/apps). +- A running Serverpod project (server, client, and Flutter app packages from `serverpod create`). +- The Serverpod auth module installed and configured per the [authentication setup](../../setup). If your project was generated with an older Serverpod version, follow that guide first to add `serverpod_auth_idp_server` and `serverpod_auth_idp_flutter` and to configure `pod.initializeAuthServices()` before continuing. + +:::note +If you specifically need an **OAuth App** instead of a GitHub App, the setup flow below is the same except OAuth Apps allow only a single Callback URL (GitHub Apps allow up to 10). See GitHub's [Differences between GitHub Apps and OAuth Apps](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/differences-between-github-apps-and-oauth-apps). ::: -## Choosing Your GitHub App Type +## Get your GitHub credentials -GitHub offers two ways to obtain OAuth2 credentials: +### Register a new GitHub App -- **GitHub Apps**: more suitable when building integrations or bots that belong to an organization or repository, operate with their own identity, continue functioning regardless of which users come and go, and only access the repositories and permissions explicitly granted. They provide fine‑grained control, short‑lived tokens, and are the modern, secure choice for most automation and service scenarios. -- **OAuth Apps**: preferred when the primary need is authenticating users with "Sign in with GitHub" or performing actions strictly as the currently logged‑in user under broad OAuth scopes. Similar to other OAuth providers like Google or Apple, they allow access to a user’s GitHub resources within the scopes granted, but lack the flexibility and security of GitHub Apps. +1. Go to [Register new GitHub App](https://github.com/settings/apps/new). -:::tip -[GitHub Apps](https://github.com/settings/apps) are the preferred choice for most scenarios — especially mobile and modern integrations. -See the official comparison here: [Differences between GitHub Apps and OAuth Apps](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/differences-between-github-apps-and-oauth-apps). -::: +2. Fill in the basics: -## Create your credentials + - **GitHub App name** (required, up to 34 characters, unique across GitHub). + - **Homepage URL** (required). + - **Description** (optional, shown to users during install). -1. Go to [GitHub Developer Settings](https://github.com/settings/apps). -2. Click **New GitHub App** (recommended) or **New OAuth App**. +![GitHub App basics](/img/authentication/providers/github/1-app-basics.png) - ![GitHub App Setup](/img/authentication/providers/github/1-register-app.png) +### Configure the callback URL -3. Fill in the required fields: - - **App name** - - **Homepage URL** - - **Callback URL(s)** (use your app's redirect URI, e.g., `myapp://auth` for mobile) - - **Permissions** (at minimum: account permission = profile; add others as needed) - - **Webhook URL** (disable if not required; serves to receive events like commits, pull requests, and repo changes) +The callback URL is where GitHub redirects the user after they authorize your app. For Serverpod sign-in, this must match the redirect URI you pass to `initializeGitHubSignIn` in your Flutter app, and the registered URL in the web callback page setup. - ![GitHub App Setup](/img/authentication/providers/github/2-add-permission.png) +1. In the **Callback URL** field, enter the redirect URI for your app. GitHub Apps accept up to 10 entries, one per line. Add every platform you target: - ![GitHub App Setup](/img/authentication/providers/github/3-add-permission.png) + | Platform | Example value | + | --- | --- | + | iOS and Android | `com.example.yourapp://auth` (custom scheme registered in `AndroidManifest.xml` and `Info.plist`) | + | Web (Serverpod-hosted Flutter) | `https://my-awesome-project.serverpod.space/auth/callback` | + | Web (Flutter dev server) | `http://localhost:49660/auth.html` | -:::tip -Webhooks let your GitHub App automatically receive notifications about repository activity. -If your app doesn’t need to react to events (like commits or pull requests), it’s best to disable the webhook URL to reduce unnecessary traffic and complexity. + The web entries depend on how your Flutter web app is served. See [Web](#web) below for the two flows (Serverpod-hosted using a built-in callback route, or separately-hosted using a static `auth.html` file). + + :::tip + For the mobile scheme, use a value unique to your app (reverse-DNS of your bundle ID is a good convention, for example `com.example.yourapp://auth`). Generic schemes like `myapp://auth` work but can collide with other apps installed on the device. + ::: + + ![Callback URL field](/img/authentication/providers/github/2-callback-url.png) + +2. Leave **Expire user authorization tokens** enabled (GitHub's default). Token expiration is recommended for sign-in flows so leaked tokens have a short useful lifetime. Serverpod handles the refresh flow automatically; you do not need to write any refresh logic. + +3. Leave **Request user authorization (OAuth) during installation** unchecked unless you need the installation to immediately trigger an OAuth sign-in. + +### Disable webhooks + +Webhooks let your GitHub App receive events like pushes, pull requests, and issue activity. They are unrelated to sign-in and add complexity if you do not need them, so turn the section off for now. You can re-enable webhooks later without affecting the auth flow. + +Under **Webhook**, uncheck **Active** to disable the whole section. + +![Disable webhook](/img/authentication/providers/github/3-webhook-disable.png) + +### Set permissions + +GitHub Apps use fine-grained **permissions** instead of OAuth **scopes**. For sign-in you only need access to the user's profile. + +1. Under **Account permissions**, set **Email addresses** to **Read-only**. This lets your app read the user's primary verified email. + +2. Leave all other permissions at **No access** unless your app needs them for non-auth reasons. + + ![Account permissions](/img/authentication/providers/github/4-permissions.png) + +:::note +GitHub users can keep their email private. Even with the **Email addresses** permission, the `email` field on the account may be `null`. See [Custom account validation](./customizations#custom-account-validation) for how to handle this without blocking sign-in. ::: -4. Click **Create GitHub App** (for GitHub Apps) or **Register application** (for OAuth Apps). This will save your app and generate the **Client ID**. +### Choose the installation scope + +1. Under **Where can this GitHub App be installed?**, choose: + + - **Only on this account** for development or internal-only apps. + - **Any account** for production apps that anyone can sign in to. -5. After the app is created, click **Generate a new client secret** to obtain the **Client Secret**. Copy both the **Client ID** and **Client Secret** for later use. +### Create the app and copy credentials - ![GitHub App Setup](/img/authentication/providers/github/4-get-credentials.png) +1. Click **Create GitHub App**. GitHub takes you to the new app's settings page. -## Server-side Configuration +2. Copy the **Client ID** shown on that page. -### Store the Credentials +3. Click **Generate a new client secret**, then copy the secret immediately. GitHub only shows the secret once. -This can be done by adding your credentials to the `githubClientId` and `githubClientSecret` keys in the `config/passwords.yaml` file, or by setting them as the values of the `SERVERPOD_PASSWORD_githubClientId` and `SERVERPOD_PASSWORD_githubClientSecret` environment variables. + ![Client ID and Generate secret button](/img/authentication/providers/github/5-credentials.png) + +:::warning +**Keep the Client Secret confidential.** Do not commit it to version control. Use `config/passwords.yaml` (excluded from git) or environment variables. +::: + +## Server-side configuration + +### Store your credentials + +Your server's `config/passwords.yaml` already has `development:`, `staging:`, and `production:` sections from the project template. Add `githubClientId` and `githubClientSecret` to the `development:` section using the values you just copied: ```yaml development: - githubClientId: 'YOUR_GITHUB_CLIENT_ID' - githubClientSecret: 'YOUR_GITHUB_CLIENT_SECRET' + # ... existing keys (database, redis, serviceSecret, etc.) ... + githubClientId: 'your-github-client-id' + githubClientSecret: 'your-github-client-secret' ``` +For production, add the same two keys to the `production:` section, or set the `SERVERPOD_PASSWORD_githubClientId` and `SERVERPOD_PASSWORD_githubClientSecret` environment variables on your production server. See [Publishing to production](#publishing-to-production) below for the full prod walkthrough. + :::warning -Keep your Client Secret confidential. Never commit this value to version control. Store it securely using environment variables or secret management. +**Never commit `config/passwords.yaml` to version control.** It contains your client secret. Use environment variables or a secrets manager in production. ::: -### Configure the GitHub Identity Provider +### Add the GitHub 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. -In your main `server.dart` file, configure the GitHub identity provider: +Add the GitHub import and `GitHubIdpConfigFromPasswords()` 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/github.dart'; - -void run(List args) async { - final pod = Serverpod( - args, - Protocol(), - Endpoints(), - ); - - pod.initializeAuthServices( - tokenManagerBuilders: [ - JwtConfigFromPasswords(), - ], - identityProviderBuilders: [ - GitHubIdpConfig( - clientId: pod.getPassword('githubClientId')!, - clientSecret: pod.getPassword('githubClientSecret')!, - ), - ], - ); - - await pod.start(); -} ``` -:::tip -You can use `GitHubIdpConfigFromPasswords()` to automatically load credentials from `config/passwords.yaml` or the `SERVERPOD_PASSWORD_githubClientId` and `SERVERPOD_PASSWORD_githubClientSecret` environment variables: - ```dart -identityProviderBuilders: [ - GitHubIdpConfigFromPasswords(), -], +pod.initializeAuthServices( + tokenManagerBuilders: [ + JwtConfigFromPasswords(), + ], + identityProviderBuilders: [ + // ... any existing providers (e.g., EmailIdpConfigFromPasswords) ... + GitHubIdpConfigFromPasswords(), + ], +); ``` +`GitHubIdpConfigFromPasswords()` automatically loads the client ID and secret from the `githubClientId` and `githubClientSecret` keys in `config/passwords.yaml` (or the matching `SERVERPOD_PASSWORD_*` environment variables). + +:::tip +If you need more control over how the credentials are loaded, use `GitHubIdpConfig(clientId: ..., clientSecret: ...)` instead. See [Customizations](./customizations) for details. ::: -### Expose the Endpoint +### Create the endpoint -Create an endpoint that extends `GitHubIdpBaseEndpoint` to expose the GitHub authentication API: +Create a new endpoint file in your server project (e.g., `my_project_server/lib/src/auth/github_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/github.dart'; @@ -116,30 +152,33 @@ import 'package:serverpod_auth_idp_server/providers/github.dart'; class GitHubIdpEndpoint extends GitHubIdpBaseEndpoint {} ``` -### Generate and Migrate +### Generate code and apply migrations -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). +Run the following commands from your server project directory (e.g., `my_project_server/`) to generate client code and apply the database migration: -### Basic configuration options - -- `clientId`: Required. The Client ID of your GitHub App or OAuth App. -- `clientSecret`: Required. The Client Secret generated for your GitHub App or OAuth App. +```bash +serverpod generate +serverpod create-migration +dart run bin/main.dart --apply-migrations +``` -For more details on configuration options, see the [configuration section](./configuration). +:::warning +Skipping the migration will cause the server to crash at runtime when the GitHub 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 -Add the `serverpod_auth_idp_flutter` package to your Flutter app. The GitHub provider uses [`flutter_web_auth_2`](https://pub.dev/packages/flutter_web_auth_2) to handle the OAuth2 flow, so any documentation there should also apply to this setup. +The GitHub identity provider uses [`flutter_web_auth_2`](https://pub.dev/packages/flutter_web_auth_2) under the hood to drive the OAuth2 redirect on every platform. The configuration differs slightly between platforms because each one has a different way of receiving the callback URL. + +### iOS and macOS -### iOS and MacOS +No special configuration is needed for a standard custom-scheme callback URL (e.g., `myapp://auth`). The `flutter_web_auth_2` package handles the redirect using `ASWebAuthenticationSession`. -There is no special configuration needed for iOS and MacOS for "normal" authentication flows. -However, if you are using **Universal Links** on iOS, they require redirect URIs to use **https**. -Follow the instructions in the [flutter_web_auth_2](https://pub.dev/packages/flutter_web_auth_2#ios) documentation. +If you use **Universal Links** instead, your redirect URI must use `https` and you must follow the [flutter_web_auth_2 iOS setup](https://pub.dev/packages/flutter_web_auth_2#ios) to register the associated domain. ### Android -In order to capture the callback url, the following activity needs to be added to your `AndroidManifest.xml`. Be sure to replace `YOUR_CALLBACK_URL_SCHEME_HERE` with your actual callback url scheme registered in your GitHub app. +To capture the custom-scheme callback URL, add the following activity to your Flutter project's `android/app/src/main/AndroidManifest.xml`. Replace `your-callback-scheme` with the scheme you registered on your GitHub App (e.g., `myapp` for `myapp://auth`): ```xml @@ -153,7 +192,7 @@ In order to capture the callback url, the following activity needs to be added t - + @@ -161,9 +200,48 @@ In order to capture the callback url, the following activity needs to be added t ``` +:::warning +The scheme in `AndroidManifest.xml` must exactly match the scheme in your GitHub App's **Callback URL** and the `redirectUri` you pass to `initializeGitHubSignIn`. A mismatch causes the OAuth callback to never reach your app. +::: + ### Web -On the web, you need a specific endpoint to capture the OAuth2 callback. To set this up, create an HTML file (e.g., `auth.html`) inside your project's `./web` folder and add the following content: +On web, GitHub sign-in redirects the browser to a callback page that posts the result back to your Flutter app via `postMessage`. The browser enforces same-origin on `postMessage`, so the callback page must be served from the same host and port as your Flutter web app. + +Serverpod can host this callback for you when the Flutter web app is served by the same Serverpod server (the default project template, which copies the Flutter web build into Serverpod's `web/app/` directory via the `flutter_build` script). Use the static `auth.html` fallback only if your Flutter web app is hosted on a different origin (for example, a separate dev server during local development, or a CDN in production). + +#### Serverpod-hosted Flutter web + +1. In `server.dart`, before `pod.start()`, register the callback route: + + ```dart + import 'package:serverpod_auth_idp_server/core.dart'; + + // ... + + pod.webServer.addRoute( + FlutterWebAuth2CallbackRoute(host: 'my-awesome-project.serverpod.space'), + '/auth/callback', + ); + ``` + + Set `host` to the domain that serves your Flutter web app so the route only responds to requests on that origin. The second argument to `addRoute` is the path; use any path you like, as long as it matches the callback URL registered on your GitHub App and the `redirectUri` you pass to `initializeGitHubSignIn`. + + The route is provider-agnostic. Register it once and reuse the same callback URL across every OAuth2 PKCE provider (GitHub, Google, etc.). + +2. Register `https://my-awesome-project.serverpod.space/auth/callback` as a **Callback URL** on your GitHub App. + +3. Pass the same URL to `initializeGitHubSignIn` via the `redirectUri` argument when you initialize the client (covered in [Present the authentication UI](#present-the-authentication-ui) below). + +:::note +`FlutterWebAuth2CallbackRoute` requires `serverpod_auth_idp_server` 3.5.0-beta.8 or later. On earlier versions, use the [Separately-hosted Flutter web](#separately-hosted-flutter-web-or-local-flutter-dev-server) flow instead. +::: + +#### Separately-hosted Flutter web (or local Flutter dev server) + +If your Flutter web app is served on a different origin from Serverpod, the `postMessage` from the callback page is blocked by the browser. This is the situation during local development when you run `flutter run -d chrome --web-port=49660` and Serverpod's web server is on `localhost:8082`, or in production if you host the Flutter web build on a CDN separate from your Serverpod API server. + +In that case, place a static `auth.html` file in your Flutter project's `web/` folder. A single copy is shared across every identity provider that uses an OAuth2 redirect, so create it once. ```html @@ -190,81 +268,94 @@ On the web, you need a specific endpoint to capture the OAuth2 callback. To set ``` -:::note -You only need a single callback file (e.g. `auth.html`) in your `./web` folder. -This file is shared across all IDPs that use the OAuth2 utility, as long as your redirect URIs point to it. -::: +Register the full URL of this file (for example, `http://localhost:49660/auth.html` for the Flutter dev server) as a **Callback URL** on your GitHub App, and pass the same URL to `initializeGitHubSignIn` via `redirectUri`. GitHub Apps accept up to 10 callback URLs, so dev and prod entries can coexist with mobile schemes. ## Present the authentication UI -### Initializing the `GitHubSignInService` - -To use the GitHubSignInService, you need to initialize it in your main function. The initialization is done from the `initializeGitHubSignIn()` extension method on the `FlutterAuthSessionManager`. +### Initialize the GitHub sign-in service -```dart -import 'package:flutter/material.dart'; -import 'package:flutter/material.dart'; -import 'package:serverpod_flutter/serverpod_flutter.dart'; -import 'package:your_client/your_client.dart'; +Open your Flutter app's `main.dart` (e.g., `my_project_flutter/lib/main.dart`). The Serverpod template already creates the `Client` and calls `client.auth.initialize()` inside `main()`. Add `client.auth.initializeGitHubSignIn(...)` on the line immediately after it. -late Client client; +The GitHub provider requires `clientId` and `redirectUri` on every platform because GitHub does not have native platform-specific clients (unlike Google or Apple): +```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); - // Create the Serverpod client - client = Client('http://localhost:8080/') + final serverUrl = await getServerUrl(); + + client = Client(serverUrl) ..connectivityMonitor = FlutterConnectivityMonitor() ..authSessionManager = FlutterAuthSessionManager(); - // Initialize Serverpod auth await client.auth.initialize(); - - // Initialize GitHub Sign-In - // Note: For Web, ensure the redirectUri matches your auth.html location. await client.auth.initializeGitHubSignIn( - clientId: 'YOUR_GITHUB_CLIENT_ID', - redirectUri: Uri.parse('https://example.com/auth.html'), + clientId: 'your-github-client-id', + redirectUri: Uri.parse('com.example.yourapp://auth'), ); runApp(const MyApp()); } ``` -:::info -**Important**: Ensure the redirect URIs used in your code are also explicitly listed in your **GitHub App Dashboard** under "Callback URLs". For Android, you must also register this scheme in your `AndroidManifest.xml`. +Replace `your-github-client-id` with the **Client ID** from your GitHub App, and `redirectUri` with the matching callback URL you registered: a reverse-DNS custom scheme for mobile, `https://your-domain.com/auth/callback` for Serverpod-hosted web, or the full `auth.html` URL when Flutter web is separately hosted. + +:::tip +To keep these values out of `main.dart` and vary them per build, read them from `--dart-define`. See [Configuring client IDs on the app](./customizations#configuring-client-ids-on-the-app) for the pattern. ::: -### Using GitHubSignInWidget +### Show the GitHub sign-in button -If you have configured the `GitHubSignInWidget` as described in the [setup section](#present-the-authentication-ui), the GitHub identity provider will be automatically detected and displayed in the sign-in widget. +The Serverpod template ships with a `SignInScreen` widget at `lib/screens/sign_in_screen.dart`. It listens to `client.auth.authInfoListenable` and swaps between `SignInWidget` while the user is signed out and the `child` you pass it once they sign in. `SignInWidget` auto-detects which identity provider endpoints are registered on the server, so once `GitHubIdpEndpoint` is exposed and `serverpod generate` has run, the GitHub button appears inside it. -You can also use the `GitHubSignInWidget` to include the GitHub authentication flow in your own custom UI. +To customize the GitHub button or build a fully custom UI, see [Customizing the UI](./customizing-the-ui). -```dart -import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart'; - -GitHubSignInWidget( - client: client, - onAuthenticated: () { - // Do something when the user is authenticated. - // - // NOTE: You should not navigate to the home screen here, otherwise - // the user will have to sign in again every time they open the app. - }, - onError: (error) { - // Handle errors - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Error: $error')), - ); - }, -) +## Publishing to production + +Before going live, complete the following steps: + +### 1. Add the production callback URL + +Go back to your GitHub App's settings and add your production callback URL to **Callback URL** alongside the development one. Both should remain registered so dev and prod work simultaneously. + +- For Serverpod-hosted Flutter web (standard project template), the production callback is `https://my-awesome-project.serverpod.space/auth/callback`. Make sure `pod.webServer.addRoute(FlutterWebAuth2CallbackRoute(host: 'my-awesome-project.serverpod.space'), '/auth/callback')` is called in `server.dart` so the route is registered in production. +- For separately-hosted Flutter web, use the production `auth.html` URL (for example, `https://app.example.com/auth.html`). +- For mobile custom schemes (e.g., `com.example.yourapp://auth`), no change is needed between dev and prod. + +### 2. Set production credentials + +Production runs out of the `production:` section of `passwords.yaml`, which is separate from the `development:` section you populated during setup. Adding production credentials does not replace your development ones, both stay in place and Serverpod picks the right set based on the run mode. + +If you use the same GitHub App for development and production, you can reuse the same `githubClientId` and `githubClientSecret`. For separate environments, [register a second GitHub App](https://github.com/settings/apps/new) first and use its values. + +#### Self-hosted + +Add `githubClientId` and `githubClientSecret` to the `production:` section of `passwords.yaml`: + +```yaml +production: + # ... existing keys ... + githubClientId: 'your-github-client-id' + githubClientSecret: 'your-github-client-secret' ``` -The widget automatically handles: +Alternatively, set the `SERVERPOD_PASSWORD_githubClientId` and `SERVERPOD_PASSWORD_githubClientSecret` [environment variables](../../../07-configuration.md#2-via-environment-variables) on your production server with the same values. -- GitHub Sign-In flow for iOS, macOS, Android, and Web. -- Token management. -- Underlying GitHub Sign-In package error handling. +#### Serverpod Cloud + +Use `scloud password set` to upload each value. The Client ID is public, so pass it as a positional argument. The Client Secret is sensitive, so read it from a file with `--from-file` to keep it out of your shell history: + +```bash +scloud password set githubClientId your-github-client-id +scloud password set githubClientSecret --from-file path/to/github-client-secret.txt +``` -For details on how to customize the GitHub Sign-In UI in your Flutter app, see the [customizing the UI section](./customizing-the-ui). +Run these from your linked server project directory, or pass `--project ` on each call (the flag is required unless the project is linked). See the [Serverpod Cloud passwords guide](https://docs.serverpod.dev/cloud/guides/passwords) for project linking and other options. + +### 3. Verify the redirect URI in the Flutter build + +The production build of your Flutter app must initialize `GitHubSignInService` with the production `redirectUri`. The cleanest pattern is to read it from `--dart-define` so a single `main.dart` works in dev and prod. See [Configuring client IDs on the app](./customizations#configuring-client-ids-on-the-app). + +:::tip +If you run into issues, see the [troubleshooting guide](./troubleshooting). +::: diff --git a/docs/06-concepts/11-authentication/04-providers/07-github/02-configuration.md b/docs/06-concepts/11-authentication/04-providers/07-github/02-configuration.md deleted file mode 100644 index c724d9bf..00000000 --- a/docs/06-concepts/11-authentication/04-providers/07-github/02-configuration.md +++ /dev/null @@ -1,125 +0,0 @@ -# Configuration - -This page covers configuration options for the GitHub 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 `GitHubIdpConfig` in-code documentation. - -### Custom Account Validation - -You can customize the validation for GitHub account details before allowing sign-in. By default, the validation checks that the received account details contain a non-empty userIdentifier. - -```dart -final githubIdpConfig = GitHubIdpConfig( - // Optional: Custom validation for GitHub account details - githubAccountDetailsValidation: (GitHubAccountDetails accountDetails) { - // Throw an exception if account doesn't meet custom requirements - if (accountDetails.userIdentifier.isEmpty) { - throw GitHubUserInfoMissingDataException(); - } - }, -); -``` - -:::note -GitHub users can keep their email private, so email may be null even for valid accounts. To avoid blocking real users with private profiles from signing in, adjust your validation function with care. -::: - -### GitHubAccountDetails - -The `githubAccountDetailsValidation` callback receives a `GitHubAccountDetails` record with the following properties: - -| Property | Type | Description | -| ---------- | ------ | ------------- | -| `userIdentifier` | `String` | The GitHub user's unique identifier (UID) | -| `email` | `String?` | The user's email address (may be null if private) | -| `name` | `String?` | The user's display name from GitHub | -| `image` | `Uri?` | URL to the user's profile image | - -Example of accessing these properties: - -```dart -githubAccountDetailsValidation: (accountDetails) { - print('GitHub UID: ${accountDetails.userIdentifier}'); - print('Email: ${accountDetails.email}'); - print('Display name: ${accountDetails.name}'); - print('Profile image: ${accountDetails.image}'); - - // Custom validation logic - if (accountDetails.email == null) { - throw GitHubUserInfoMissingDataException(); - } -}, -``` - -:::info -The properties available depend on user privacy settings and granted permissions. -::: - -### Reacting to account creation - -You can use the `onAfterGitHubAccountCreated` callback to run logic after a new GitHub account has been created and linked to an auth user. This callback is only invoked for new accounts, not for returning users. - -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. - -```dart -final githubIdpConfig = GitHubIdpConfigFromPasswords( - onAfterGitHubAccountCreated: ( - session, - authUser, - githubAccount, { - required transaction, - }) async { - // e.g. store additional data, send a welcome email, or 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 `getExtraGitHubInfoCallback` to fetch and store the data you need before the auth user is created. -::: - -### Configuring Client IDs on the App - -#### Passing Client IDs in Code - -You can pass the `clientId` and `redirectUri` directly during initialization the GitHub Sign-In service: - -```dart -await client.auth.initializeGitHubSignIn( - clientId: 'YOUR_GITHUB_CLIENT_ID', - redirectUri: 'test-app://github/auth', -); -``` - -This approach is useful when you need different client IDs per platform and want to manage them in your Dart code. - -#### Using Environment Variables - -Alternatively, you can pass client IDs during build time using the `--dart-define` option. The GitHub Sign-In provider supports the following environment variables: - -- `GITHUB_CLIENT_ID`: Your GitHub OAuth client ID. -- `GITHUB_REDIRECT_URI`: The callback URI. - -**Example usage:** - -```bash -flutter run -d \ - --dart-define="GITHUB_CLIENT_ID=your_id" \ - --dart-define="GITHUB_REDIRECT_URI=test-app://github/auth" -``` - -This approach is useful when you need to: - -- Manage separate client IDs for different platforms (Android, iOS, Web) in a centralized way -- Avoid committing client IDs to version control -- Configure different credentials for different build environments (development, staging, production) - -:::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. -::: diff --git a/docs/06-concepts/11-authentication/04-providers/07-github/02-customizations.md b/docs/06-concepts/11-authentication/04-providers/07-github/02-customizations.md new file mode 100644 index 00000000..c0a4bd3a --- /dev/null +++ b/docs/06-concepts/11-authentication/04-providers/07-github/02-customizations.md @@ -0,0 +1,238 @@ +# Customizations + +This page covers additional configuration options for the GitHub 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 `GitHubIdpConfig` in-code documentation. + +The GitHub identity provider can be configured using one of two classes: + +- **`GitHubIdpConfigFromPasswords`**: Automatically loads the client ID and secret from the `githubClientId` and `githubClientSecret` keys in `passwords.yaml` (or the matching `SERVERPOD_PASSWORD_*` environment variables). This is the class used in the [setup guide](./setup) and is recommended for most projects. +- **`GitHubIdpConfig`**: Requires you to pass the client ID and secret directly. Use this when you load credentials from a custom source, such as a secrets manager or a programmatically constructed config. + +`GitHubIdpConfigFromPasswords` is a convenience wrapper around `GitHubIdpConfig` that handles credential loading for you. + +Both classes accept the same optional callbacks shown in the sections below. The examples on this page use `GitHubIdpConfigFromPasswords` unless the section specifically demonstrates manual credential loading. + +### Load credentials using GitHubIdpConfig + +When using `GitHubIdpConfig`, you must provide the client ID and secret explicitly. Read them from any source you want: + +```dart +final githubIdpConfig = GitHubIdpConfig( + clientId: pod.getPassword('githubClientId')!, + clientSecret: pod.getPassword('githubClientSecret')!, +); +``` + +Or from a secrets manager, hard-coded values for tests, or a custom loader: + +```dart +final githubIdpConfig = GitHubIdpConfig( + clientId: await mySecretsManager.fetch('github-client-id'), + clientSecret: await mySecretsManager.fetch('github-client-secret'), +); +``` + +### Custom account validation + +You can customize the validation for GitHub account details before allowing sign-in. By default, the validation only checks that the received account details contain a non-empty `userIdentifier`. + +```dart +final githubIdpConfig = GitHubIdpConfigFromPasswords( + githubAccountDetailsValidation: (accountDetails) { + // Throw an exception if account doesn't meet custom requirements + if (accountDetails.userIdentifier.isEmpty) { + throw GitHubUserInfoMissingDataException(); + } + }, +); +``` + +:::note +GitHub users can keep their email private, so `email` may be `null` even for valid accounts. Similarly, `name` is optional on GitHub profiles. To avoid blocking real users with private profiles from signing in, adjust your validation function with care. +::: + +#### GitHubAccountDetails + +The `githubAccountDetailsValidation` callback receives a `GitHubAccountDetails` record with the following properties: + +| Property | Type | Description | +| --- | --- | --- | +| `userIdentifier` | `String` | The GitHub user's unique identifier (UID) | +| `email` | `String?` | The user's email address (may be `null` if private) | +| `name` | `String?` | The user's display name from GitHub | +| `image` | `Uri?` | URL to the user's profile image | + +Example of accessing these properties: + +```dart +githubAccountDetailsValidation: (accountDetails) { + print('GitHub UID: ${accountDetails.userIdentifier}'); + print('Email: ${accountDetails.email}'); + print('Display name: ${accountDetails.name}'); + print('Profile image: ${accountDetails.image}'); + + // Custom validation logic + if (accountDetails.email == null) { + throw GitHubUserInfoMissingDataException(); + } +}, +``` + +### Accessing GitHub APIs on the server + +On the server side, you can call GitHub's REST API using the access token returned by sign-in. The `getExtraGitHubInfoCallback` on `GitHubIdpConfig` receives the access token on every authentication attempt and can be used to fetch and store additional user data: + +```dart +import 'package:http/http.dart' as http; + +final githubIdpConfig = GitHubIdpConfigFromPasswords( + getExtraGitHubInfoCallback: (session, { + required accountDetails, + required accessToken, + required transaction, + }) async { + final response = await http.get( + Uri.https('api.github.com', '/user/orgs'), + headers: { + 'Authorization': 'Bearer $accessToken', + 'Accept': 'application/vnd.github+json', + }, + ); + // Parse response and store organization membership in your own table, + // linked by accountDetails.userIdentifier. + }, +); +``` + +:::warning +**Do not create `GitHubAccount`, `UserProfile`, or `AuthUser` models inside this callback.** The authentication flow already creates them. Creating them here breaks new-account detection and skips critical setup steps. Store any extra data in your own custom tables, linked by `accountDetails.userIdentifier`. +::: + +:::info +This callback runs on **every** sign-in, not just the first. Keep operations lightweight or guard expensive work behind a check for whether the data already exists. +::: + +### Reacting to GitHub account creation + +Use the `onAfterGitHubAccountCreated` callback to run logic after a new GitHub account has been created and linked to an auth user. This callback only fires for new accounts, not returning users. + +This callback is complementary to the global [`onAfterAuthUserCreated`](#reacting-to-auth-user-creation) hook and is for side-effects specific to a GitHub sign-in, like storing GitHub-specific analytics or sending a GitHub-themed welcome email. + +```dart +final githubIdpConfig = GitHubIdpConfigFromPasswords( + onAfterGitHubAccountCreated: ( + session, + authUser, + githubAccount, { + required transaction, + }) async { + // e.g. store additional data, send a welcome email, or log for analytics + }, +); +``` + +:::info +This callback runs inside the same database transaction as the account creation. Throwing an exception inside this callback aborts the process. If you perform external side-effects, guard them with `try`/`catch` to prevent unwanted failures. +::: + +:::caution +If you need to assign Serverpod scopes based on provider account data, updating the database alone (via `AuthServices.instance.authUsers.update()`) is **not enough** for the current login session. 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 scope assignment at creation time, use [`onBeforeAuthUserCreated`](#reacting-to-auth-user-creation) in combination with `getExtraGitHubInfoCallback` to fetch and store the data you need before the auth user is created. +::: + +### Reacting to auth user creation + +The `onBeforeAuthUserCreated` and `onAfterAuthUserCreated` hooks are global callbacks configured on `AuthUsersConfig` in `initializeAuthServices`. They are not specific to GitHub; they fire for every identity provider. See the [working with users](../../working-with-users#reacting-to-the-user-created-event) page for full details. + +`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 +pod.initializeAuthServices( + tokenManagerBuilders: [ + JwtConfigFromPasswords(), + ], + identityProviderBuilders: [ + GitHubIdpConfigFromPasswords(), + ], + 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 + }, + ), +); +``` + +### Configuring client IDs on the app + +#### Passing client IDs in code + +You can pass the `clientId` and `redirectUri` directly when initializing the GitHub Sign-In service: + +```dart +await client.auth.initializeGitHubSignIn( + clientId: 'your-github-client-id', + redirectUri: Uri.parse('com.example.yourapp://auth'), +); +``` + +This approach is useful when you need different `redirectUri` values per platform and want to keep them in your Dart code. + +#### Using environment variables + +Alternatively, pass them at build time using `--dart-define`. The GitHub Sign-In provider supports the following environment variables: + +- `GITHUB_CLIENT_ID`: Your GitHub OAuth client ID. +- `GITHUB_REDIRECT_URI`: The callback URI. Use the value matching the platform you build for: a reverse-DNS scheme for mobile, `https://your-domain.com/auth/callback` for Serverpod-hosted Flutter web, or the full `auth.html` URL for separately-hosted Flutter web. + +If `clientId` and `redirectUri` are not supplied when initializing the service, the provider automatically falls back to these environment variables. + +**Example usage:** + +```bash +flutter run -d chrome --web-port=49660 \ + --dart-define="GITHUB_CLIENT_ID=your-github-client-id" \ + --dart-define="GITHUB_REDIRECT_URI=http://localhost:49660/auth.html" +``` + +```bash +flutter build web \ + --dart-define="GITHUB_CLIENT_ID=your-github-client-id" \ + --dart-define="GITHUB_REDIRECT_URI=https://my-awesome-project.serverpod.space/auth/callback" +``` + +This approach is useful when you need to: + +- Configure different credentials for different build environments (development, staging, production). +- Avoid committing client IDs to version control. +- Inject platform-specific redirect URIs from your CI/CD pipeline. + +:::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. +::: + +## GitHubIdpConfig parameter reference + +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `clientId` | `String` | Yes | The Client ID from your GitHub App or OAuth App. | +| `clientSecret` | `String` | Yes | The Client Secret generated for your GitHub App or OAuth App. | +| `githubAccountDetailsValidation` | `GitHubAccountDetailsValidation` | No | Custom validation callback for GitHub account details before allowing sign-in. Throws an exception to reject the account. Defaults to validating only that `userIdentifier` is non-empty. | +| `getExtraGitHubInfoCallback` | `GetExtraGitHubInfoCallback?` | No | Callback that receives the access token after sign-in, allowing you to call additional GitHub APIs and store extra user data. Runs on every sign-in. | +| `onAfterGitHubAccountCreated` | `AfterGitHubAccountCreatedFunction?` | No | Callback invoked after a new GitHub account is created and linked to an auth user. Fires only for new accounts. | diff --git a/docs/06-concepts/11-authentication/04-providers/07-github/03-customizing-the-ui.md b/docs/06-concepts/11-authentication/04-providers/07-github/03-customizing-the-ui.md index beba0ab7..fbcd8c76 100644 --- a/docs/06-concepts/11-authentication/04-providers/07-github/03-customizing-the-ui.md +++ b/docs/06-concepts/11-authentication/04-providers/07-github/03-customizing-the-ui.md @@ -52,6 +52,10 @@ GitHubSignInWidget( ) ``` +:::note +The `scopes` argument applies to **OAuth Apps**. For a **GitHub App**, the App's [Permissions](./setup#set-permissions) configured on the GitHub side control access and the `scopes` argument is ignored. +::: + ## Building a custom UI with the `GitHubAuthController` For more control over the UI, you can use the `GitHubAuthController` class, which provides all the authentication logic without any UI components. This allows you to build a completely custom authentication interface. diff --git a/docs/06-concepts/11-authentication/04-providers/07-github/04-troubleshooting.md b/docs/06-concepts/11-authentication/04-providers/07-github/04-troubleshooting.md new file mode 100644 index 00000000..f1f60777 --- /dev/null +++ b/docs/06-concepts/11-authentication/04-providers/07-github/04-troubleshooting.md @@ -0,0 +1,196 @@ +# Troubleshooting + +This page helps you identify common Sign in with GitHub failures, explains why they occur, and shows how to resolve them. For underlying issues with the OAuth callback library, see the [flutter_web_auth_2 documentation](https://pub.dev/packages/flutter_web_auth_2). + +## Setup checklist + +Go through this before investigating a specific error. Most problems come from a missed step. + +#### GitHub Developer Settings + +- [ ] Created a **GitHub App** at [Developer Settings](https://github.com/settings/apps) (or an **OAuth App** if you specifically need one; OAuth Apps allow only a single Callback URL). +- [ ] Registered every redirect URI you actually use under **Callback URL**: reverse-DNS custom schemes for mobile (e.g., `com.example.yourapp://auth`), and the appropriate web URL: `https://your-domain.com/auth/callback` for Serverpod-hosted Flutter web, or `https://your-domain.com/auth.html` for separately-hosted Flutter web. GitHub Apps accept up to 10 entries; OAuth Apps accept only one. +- [ ] Set **Account permissions > Email addresses** to **Read-only** if you need the user's email. +- [ ] Disabled **Active** under the Webhook section, unless you actually use webhooks for non-auth reasons. +- [ ] Set **Where can this GitHub App be installed?** to **Any account** if users outside your account need to sign in. +- [ ] Generated a **Client secret** and stored it (GitHub only shows it once). + +#### Server + +- [ ] Added `githubClientId` and `githubClientSecret` to `config/passwords.yaml` under the matching environment (`development:` for local, `production:` for prod), or set the matching `SERVERPOD_PASSWORD_githubClientId` and `SERVERPOD_PASSWORD_githubClientSecret` environment variables. +- [ ] Added `GitHubIdpConfigFromPasswords()` to `identityProviderBuilders` in `server.dart`. +- [ ] Created a `GitHubIdpEndpoint` file in `lib/src/auth/`. +- [ ] Ran `serverpod generate`, then `serverpod create-migration`, then applied migrations with `--apply-migrations`. + +#### Client + +- [ ] Added `client.auth.initializeGitHubSignIn(clientId: ..., redirectUri: ...)` after `client.auth.initialize()` in your Flutter app's `main.dart`. +- [ ] Both `clientId` and `redirectUri` match values registered on the GitHub App. +- [ ] On **Android**, added the `flutter_web_auth_2` `CallbackActivity` to `AndroidManifest.xml` with the **exact** scheme used in your callback URL. +- [ ] On **Web (Serverpod-hosted Flutter)**, registered `FlutterWebAuth2CallbackRoute` via `pod.webServer.addRoute(...)` in `server.dart` before `pod.start()`. On **Web (separately-hosted Flutter)**, created `web/auth.html` in your Flutter project. See [Web](./setup#web) for both flows. +- [ ] On **Web**, ran Flutter on a fixed `--web-port` matching the port registered in the GitHub App's callback URL. + +## Sign-in fails with redirect_uri_mismatch + +**Problem:** The OAuth flow fails with a `redirect_uri_mismatch` error, or GitHub shows a "The redirect_uri MUST match the registered callback URL for this application" error page. + +**Cause:** The `redirectUri` your Flutter app sent to GitHub does not exactly match any of the **Callback URL** entries on your GitHub App. + +**Resolution:** Open your GitHub App's settings and verify the **Callback URL** entries match your client's `redirectUri` exactly. The match is strict: scheme, host, port, path, casing, and trailing slashes all count. + +Common mistakes: + +- Trailing slashes (`https://your-domain.com/auth/callback/`) or port differences. +- Wrong scheme (`http` vs `https`, or mismatched custom scheme like `myapp:` vs `MyApp:`). +- For Serverpod-hosted Flutter web, forgetting to register `FlutterWebAuth2CallbackRoute` via `pod.webServer.addRoute(...)` so the `/auth/callback` route is never registered. Hitting the URL returns 404. +- For separately-hosted Flutter web, missing the `auth.html` file in the Flutter project's `web/` folder. +- Flutter dev server running on a random port. Pass `--web-port=` to `flutter run` so the origin is stable across restarts. + +## Callback never returns to the Flutter app + +**Problem:** The user authorizes the app on GitHub successfully, but the Flutter app never receives the result. The browser sits on a blank page or the OAuth window hangs. + +**Cause:** The browser was redirected to a URL that does not serve the callback page (web), or the callback custom scheme is not registered with the platform (mobile). + +**Resolution:** + +- **Web (Serverpod-hosted Flutter)**: Confirm `pod.webServer.addRoute(FlutterWebAuth2CallbackRoute(host: ...), '/auth/callback')` is called in `server.dart` and that `host` matches the domain serving your Flutter web app. Open `https://your-domain.com/auth/callback` directly in a browser tab; you should see the "Authentication complete" page. Also confirm Flutter web and the route share scheme + host + port (`postMessage` is blocked across origins). +- **Web (separately-hosted Flutter)**: Confirm `web/auth.html` exists in your Flutter project and contains the script shown in [Web](./setup#web). Open the redirect URL directly in a browser tab; you should see the "Authentication complete" page. +- **Android**: Verify the `` value in `AndroidManifest.xml` matches the scheme in your callback URL exactly. +- **iOS / macOS**: Universal Links require HTTPS callback URLs and associated-domain entitlements. Standard custom-scheme callbacks work without extra configuration. + +## Sign-in succeeds but the user has no email + +**Problem:** The user signs in successfully on the client, but the server-side `GitHubAccountDetails.email` value is `null`. + +**Cause:** GitHub users can keep their email private, and the OAuth response will return `null` for `email` in that case. Your app may have a custom validator that rejects accounts without an email and blocks the sign-in. + +**Resolution:** + +- If you do not strictly need an email, relax your validator. The default validator only checks that `userIdentifier` is non-empty, which works for private-email users. See [Custom account validation](./customizations#custom-account-validation). +- If you do need an email, confirm your GitHub App requests the **Account permissions > Email addresses** permission as **Read-only**. Even with permission, GitHub returns `null` if the user does not have a verified primary email. +- Surface a helpful message to the user instead of failing silently. + +## clientId or redirectUri missing at initialization + +**Problem:** The app throws on startup with an error about `clientId` or `redirectUri` being missing or empty. + +**Cause:** Unlike Google or Apple, GitHub does not have native platform-specific config files. The `clientId` and `redirectUri` must be passed explicitly to `initializeGitHubSignIn` (or read from `--dart-define`). + +**Resolution:** Either pass the values directly: + +```dart +await client.auth.initializeGitHubSignIn( + clientId: 'your-github-client-id', + redirectUri: Uri.parse('myapp://auth'), +); +``` + +Or read them from `--dart-define`: + +```bash +flutter run \ + --dart-define=GITHUB_CLIENT_ID=your-github-client-id \ + --dart-define=GITHUB_REDIRECT_URI=myapp://auth +``` + +See [Configuring client IDs on the app](./customizations#configuring-client-ids-on-the-app). + +## Sign-in works in dev but fails after deploy + +**Problem:** GitHub sign-in works locally but fails in production with `redirect_uri_mismatch` or a similar error. + +**Cause:** The production callback URL is not registered on the GitHub App, or the production Flutter build is using the dev `redirectUri`. + +**Resolution:** + +1. Open your GitHub App's settings and confirm the production callback URL is listed under **Callback URL** alongside the development one. Both should remain registered so dev and prod work simultaneously. +2. Confirm your production Flutter build is initialized with the production `redirectUri`. The simplest way is to read it from `--dart-define` and pass the production value in your CI/CD or `flutter_build` step. See [Publishing to production](./setup#publishing-to-production). + +## Sign-in works for you but not for other users + +**Problem:** Sign-in works for your own GitHub account but other users get an error from GitHub when they try to authorize the app. + +**Cause:** Your GitHub App's **Where can this GitHub App be installed?** is set to **Only on this account**. Users outside your account cannot install or authorize the app. + +**Resolution:** Open your GitHub App's settings and change **Where can this GitHub App be installed?** to **Any account**. The change takes effect immediately. + +## Organization blocks the OAuth authorization + +**Problem:** A user tries to sign in but sees a GitHub message about the organization restricting access to third-party applications, or the sign-in flow returns with no authorization. + +**Cause:** The user's GitHub organization has **OAuth App access restrictions** enabled, and your app has not been approved for that organization. This is independent of your app's own settings; the org controls it. + +**Resolution:** The user (or an organization owner) needs to request approval for your GitHub App in the organization's **Settings > Third-party Access** page on GitHub. There is nothing you can do server-side to bypass this. Surface a clear error message to the user explaining the org policy. + +## Sign-in fails with bad_verification_code + +**Problem:** GitHub returns `bad_verification_code` when your server exchanges the authorization code for a token. + +**Cause:** The authorization code is single-use and short-lived (10 minutes). This error usually means the same code was sent twice (e.g., page refresh during the callback) or the code expired before the server received it. + +**Resolution:** + +- Make sure the OAuth callback fires only once. Refreshing the `auth.html` page or navigating back to it after authorization re-sends the now-spent code. +- If the user genuinely took too long to complete sign-in, the code expired. Have them start the flow again from your sign-in button. +- This is a transient error if it only happens occasionally. Investigate the client only if it reproduces consistently. + +## GitHub API calls from getExtraGitHubInfoCallback fail or rate-limit + +**Problem:** Calls made inside `getExtraGitHubInfoCallback` start returning `401`, `403`, or `X-RateLimit-Remaining: 0`. + +**Cause:** Either the access token has been revoked (the user revoked the app's authorization, or **Expire user authorization tokens** caused it to expire), or you are exceeding GitHub's per-user rate limit (5,000 authenticated requests per hour by default). + +**Resolution:** + +- Cache the data you fetch instead of calling the API on every sign-in. `getExtraGitHubInfoCallback` runs on **every** authentication attempt; if you fetch the same data every time, you will burn through rate limits quickly. +- For long-lived background work, store the access token (encrypted) and refresh it on demand rather than re-running expensive fetches on every sign-in. +- If a single user triggers many sign-ins (e.g., dev iteration), expect to hit the per-user limit; wait an hour or test with a different account. + +## Permission changes on the GitHub App do not take effect + +**Problem:** You changed permissions on your GitHub App (for example, added **Email addresses** read access), but existing users still see the old behavior or get errors about missing scopes. + +**Cause:** GitHub does not silently re-grant new permissions to users who authorized the app before the change. Each user must re-authorize the app for the new permissions to apply. + +**Resolution:** Have affected users sign out and sign in again. GitHub will prompt them to approve the updated permissions. For users who never signed in before the change, the new permissions apply immediately. + +## Server fails to parse githubClientSecret from passwords.yaml + +**Problem:** The server crashes on startup with an error about a missing `githubClientId` or `githubClientSecret` key. + +**Cause:** The keys are missing from `config/passwords.yaml`, are spelled differently, or are not present under the active environment section. + +**Resolution:** Confirm both keys exist under the section matching your run mode (`development:` for `dart run bin/main.dart`, `production:` when deployed): + +```yaml +development: + githubClientId: 'your-github-client-id' + githubClientSecret: 'your-github-client-secret' +``` + +Quotes are required because the values are strings; YAML interprets unquoted values that look like numbers or booleans differently. + +## Server crashes on first GitHub sign-in with "no such table" + +**Problem:** The server builds and starts, but crashes when a user tries to sign in with GitHub. The error cites a missing table such as `serverpod_auth_idp_github_account`. + +**Cause:** `serverpod generate` has been run, but the accompanying database migration was not created or applied. + +**Resolution:** Create and apply the migration: + +```bash +serverpod generate +serverpod create-migration +dart run bin/main.dart --apply-migrations +``` + +## Android sign-in opens GitHub but the callback never fires + +**Problem:** On Android, tapping the GitHub sign-in button opens the GitHub authorization page in a browser, but after authorizing, the browser stays open and the Flutter app never resumes. + +**Cause:** The `CallbackActivity` in `AndroidManifest.xml` is missing, has a wrong scheme, or `android:exported` is not set to `true`. + +**Resolution:** Open `android/app/src/main/AndroidManifest.xml` and confirm the `CallbackActivity` block exists with `android:exported="true"` and the `` value matches the scheme in your callback URL exactly. The block is shown in [Android setup](./setup#android). + +After editing the manifest, run `flutter clean` and rebuild. diff --git a/static/img/authentication/providers/github/1-app-basics.png b/static/img/authentication/providers/github/1-app-basics.png new file mode 100644 index 00000000..dfc56a39 Binary files /dev/null and b/static/img/authentication/providers/github/1-app-basics.png differ diff --git a/static/img/authentication/providers/github/2-callback-url.png b/static/img/authentication/providers/github/2-callback-url.png new file mode 100644 index 00000000..e7b8f9b1 Binary files /dev/null and b/static/img/authentication/providers/github/2-callback-url.png differ diff --git a/static/img/authentication/providers/github/3-webhook-disable.png b/static/img/authentication/providers/github/3-webhook-disable.png new file mode 100644 index 00000000..533c21f7 Binary files /dev/null and b/static/img/authentication/providers/github/3-webhook-disable.png differ diff --git a/static/img/authentication/providers/github/4-permissions.png b/static/img/authentication/providers/github/4-permissions.png new file mode 100644 index 00000000..40aecf61 Binary files /dev/null and b/static/img/authentication/providers/github/4-permissions.png differ diff --git a/static/img/authentication/providers/github/5-credentials.png b/static/img/authentication/providers/github/5-credentials.png new file mode 100644 index 00000000..6b74fa23 Binary files /dev/null and b/static/img/authentication/providers/github/5-credentials.png differ