-
Notifications
You must be signed in to change notification settings - Fork 79
docs: Add GitHub IDP docs #402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
marcelomendoncasoares
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the quick iteration with the docs, @vfiruz97! Docs looks good overall, following the standard of the other IDPs very well. Made a few suggestions to clarify some points and reorganize some details to improve the UX.
On the custom overrides, I think we'll need to convert it into a menu following the same style of the IDPs - after all, it is just like an IDP. The file is currently very long and with parts that can be removed to avoid redundancy.
Ping me back when you are ready for another review!
| @@ -0,0 +1,252 @@ | |||
| # 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. Since this provider is built on a generic OAuth2 utility, you can learn how to create a custom provider in the [Custom Providers](../10-custom-providers/02-oauth2-utility.md) section. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: No need to mention the custom utility here, since users of the GitHub provider do not need to know it. It is more relevant on the contrary: mention the GitHub IDP as an example on the custom provider docs.
| 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. Since this provider is built on a generic OAuth2 utility, you can learn how to create a custom provider in the [Custom Providers](../10-custom-providers/02-oauth2-utility.md) section. | |
| 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. |
| - **GitHub Apps** (Recommended): | ||
| - Supports multiple redirect URIs (up to 10). | ||
| - Allows custom URI schemes (essential for mobile apps). | ||
| - More flexible and secure for modern applications. | ||
| - **OAuth Apps**: | ||
| - Only one redirect URI (must be HTTPS). | ||
| - No support for custom schemes (not ideal for mobile). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: It might be more interesting to describe the difference rom both by the conceptual difference rather than configurations only. For example:
| - **GitHub Apps** (Recommended): | |
| - Supports multiple redirect URIs (up to 10). | |
| - Allows custom URI schemes (essential for mobile apps). | |
| - More flexible and secure for modern applications. | |
| - **OAuth Apps**: | |
| - Only one redirect URI (must be HTTPS). | |
| - No support for custom schemes (not ideal for mobile). | |
| - **GitHub Apps**: more suitable when building an integration or bot that should belong to an organization or repo, have its own bot identity, keep working regardless of which human users come and go, and only see the repositories and permissions that are explicitly granted. | |
| - **OAuth Apps**: preferred when the main need is to “Sign in with GitHub” or to perform actions purely as the currently logged‑in user using broad OAuth scopes. It is similar to any other third‑party OAuth provider, like Google or Apple, and can access the user’s GitHub resources within the broad scopes the user has granted. |
| - No support for custom schemes (not ideal for mobile). | ||
|
|
||
| :::tip | ||
| For most use cases, especially mobile, use [GitHub Apps](https://github.com/settings/apps). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Add a link to the official docs that describe the purpose of each.
| 1. Go to [GitHub Developer Settings](https://github.com/settings/apps). | ||
| 2. Click **New GitHub App** (recommended) or **New OAuth App**. | ||
| 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** as needed | ||
| 4. Save and generate the **Client ID** and **Client Secret**. | ||
|
|
||
|  | ||
|  | ||
|  | ||
| Copy the **Client ID** and **Client Secret** for later use. | ||
|  |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Move the images to after the steps they are related to so users can follow through guided by the images.
| 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** as needed | ||
| 4. Save and generate the **Client ID** and **Client Secret**. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Create an HTML callback page in your `./web` folder (e.g., `auth.html`): | ||
|
|
||
| ```html | ||
| <!DOCTYPE html> | ||
| <title>Authentication complete</title> | ||
| <p>Authentication is complete. If this does not happen automatically, please close the window.</p> | ||
| <script> | ||
| function postAuthenticationMessage() { | ||
| const message = { | ||
| 'flutter-web-auth-2': window.location.href | ||
| }; | ||
|
|
||
| if (window.opener) { | ||
| window.opener.postMessage(message, window.location.origin); | ||
| window.close(); | ||
| } else if (window.parent && window.parent !== window) { | ||
| window.parent.postMessage(message, window.location.origin); | ||
| } else { | ||
| localStorage.setItem('flutter-web-auth-2', window.location.href); | ||
| window.close(); | ||
| } | ||
| } | ||
|
|
||
| postAuthenticationMessage(); | ||
| </script> | ||
| ``` | ||
|
|
||
| Configure your redirect URI to point to this file: `https://yourdomain.com/auth.html` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: If I have the GitHub IDP configured, can I reuse the same file? If so, we should add a note that one file is enough and shared between all IDPs that use the Oauth2 utility. We should also have this note on each IDP.
| Create a server-side configuration for token exchange: | ||
|
|
||
| ```dart | ||
| import 'package:serverpod_auth_idp_server/core.dart'; | ||
|
|
||
| final config = OAuth2PkceServerConfig( | ||
| // Token endpoint URL for exchanging authorization codes | ||
| tokenEndpointUrl: Uri.https('oauth.provider.com', '/oauth/token'), | ||
|
|
||
| // OAuth client ID (must match client-side) | ||
| clientId: pod.getPassword('myProviderClientId')!, | ||
|
|
||
| // OAuth client secret (keep secure!) | ||
| clientSecret: pod.getPassword('myProviderClientSecret')!, | ||
|
|
||
| // Function to extract access token from provider response | ||
| parseAccessToken: (data) { | ||
| // Handle provider errors | ||
| final error = data['error'] as String?; | ||
| if (error != null) { | ||
| throw OAuth2InvalidResponseException( | ||
| 'Provider error: $error', | ||
| ); | ||
| } | ||
|
|
||
| // Extract access token | ||
| final accessToken = data['access_token'] as String?; | ||
| if (accessToken == null) { | ||
| throw const OAuth2MissingAccessTokenException( | ||
| 'No access token in response', | ||
| ); | ||
| } | ||
|
|
||
| return accessToken; | ||
| }, | ||
|
|
||
| // Optional: Where to send credentials (default: header) | ||
| credentialsLocation: OAuth2CredentialsLocation.header, | ||
|
|
||
| // Optional: Custom parameter names for credentials | ||
| clientIdKey: 'client_id', | ||
| clientSecretKey: 'client_secret', | ||
|
|
||
| // Optional: Custom headers for token requests | ||
| tokenRequestHeaders: { | ||
| 'Accept': 'application/json', | ||
| 'Content-Type': 'application/x-www-form-urlencoded', | ||
| }, | ||
|
|
||
| // Optional: Additional parameters for token exchange | ||
| tokenRequestParams: { | ||
| 'grant_type': 'authorization_code', | ||
| }, | ||
| ); | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: We can simplify this snippet on the guide by replacing the parse function body by a generic comment like // Your parse logic here. Every detail that we can simplify in the first guide without sacrificing the complete configuration of a working usage is worthy.
| ### Performance | ||
|
|
||
| 1. **Reuse OAuth2PkceUtil Instances**: Create instances once and reuse them rather than recreating for each request. | ||
| 2. **Handle Token Expiration**: Implement token refresh logic if your provider supports refresh tokens. | ||
| 3. **Cache User Information**: Cache user profile data to reduce API calls to the provider. | ||
|
|
||
| ## Reference | ||
|
|
||
| ### Client-Side API | ||
|
|
||
| #### OAuth2PkceProviderClientConfig | ||
|
|
||
| Configuration for client-side OAuth2 flow. | ||
|
|
||
| **Constructor Parameters:** | ||
|
|
||
| - `authorizationEndpoint` (Uri): Provider's authorization endpoint | ||
| - `clientId` (String): OAuth client ID | ||
| - `redirectUri` (String): Callback URI | ||
| - `callbackUrlScheme` (String): URL scheme for callback | ||
| - `defaultScopes` (List\<String\>): Default permission scopes | ||
| - `additionalAuthParams` (Map\<String, String\>): Extra authorization parameters | ||
| - `scopeSeparator` (String): Scope joining separator (default: ' ') | ||
| - `enableState` (bool): Enable state parameter (default: true) | ||
| - `enablePKCE` (bool): Enable PKCE (default: true) | ||
|
|
||
| #### OAuth2PkceUtil (Client) | ||
|
|
||
| Manages client-side OAuth2 authorization flow. | ||
|
|
||
| **Constructor:** | ||
|
|
||
| ```dart | ||
| OAuth2PkceUtil({ | ||
| required OAuth2PkceProviderClientConfig config, | ||
| bool? useWebview, | ||
| }) | ||
| ``` | ||
|
|
||
| **Methods:** | ||
|
|
||
| - `authorize({List<String>? scopes, Map<String, String>? authCodeParams})`: Initiates authorization flow | ||
|
|
||
| **Returns:** `OAuth2PkceResult` with `code` and `codeVerifier?` | ||
|
|
||
| ### Server-Side API | ||
|
|
||
| #### OAuth2PkceServerConfig | ||
|
|
||
| Configuration for server-side token exchange. | ||
|
|
||
| **Constructor Parameters:** | ||
|
|
||
| - `tokenEndpointUrl` (Uri): Provider's token endpoint | ||
| - `clientId` (String): OAuth client ID | ||
| - `clientSecret` (String): OAuth client secret | ||
| - `parseAccessToken` (Function): Token parsing function | ||
| - `clientIdKey` (String): Client ID parameter name (default: 'client_id') | ||
| - `clientSecretKey` (String): Client secret parameter name (default: 'client_secret') | ||
| - `credentialsLocation` (OAuth2CredentialsLocation): Where to send credentials (default: header) | ||
| - `tokenRequestHeaders` (Map\<String, String\>): Request headers | ||
| - `tokenRequestParams` (Map\<String, dynamic\>): Extra request parameters | ||
|
|
||
| #### OAuth2PkceUtil (Server) | ||
|
|
||
| Manages server-side token exchange. | ||
|
|
||
| **Constructor:** | ||
|
|
||
| ```dart | ||
| OAuth2PkceUtil({ | ||
| required OAuth2PkceServerConfig config, | ||
| }) | ||
| ``` | ||
|
|
||
| **Methods:** | ||
|
|
||
| - `exchangeCodeForToken({required String code, String? codeVerifier, required String redirectUri, http.Client? httpClient})`: Exchanges authorization code for access token | ||
|
|
||
| **Returns:** `String` (access token) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: This feels redundant with what was already presented.
| ## Complete Example: Custom Provider | ||
|
|
||
| Here's a complete example implementing a custom OAuth2 provider: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: The complete implementation is interesting as a starting point for users, but it bloats the guide a lot. We should move this to a separate page instead and reference it here.
| ### Step 1: Client Configuration | ||
|
|
||
| ```dart | ||
| // lib/src/my_provider_config.dart | ||
| import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart'; | ||
|
|
||
| class MyProviderConfig { | ||
| static OAuth2PkceProviderClientConfig get clientConfig { | ||
| return OAuth2PkceProviderClientConfig( | ||
| authorizationEndpoint: Uri.https( | ||
| 'oauth.myprovider.com', | ||
| '/oauth/authorize', | ||
| ), | ||
| clientId: const String.fromEnvironment('MY_PROVIDER_CLIENT_ID'), | ||
| redirectUri: 'myapp://auth-callback', | ||
| callbackUrlScheme: 'myapp', | ||
| defaultScopes: ['profile', 'email'], | ||
| ); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Step 2: Client Service | ||
|
|
||
| ```dart | ||
| // lib/src/my_provider_service.dart | ||
| import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart'; | ||
| import 'my_provider_config.dart'; | ||
|
|
||
| class MyProviderService { | ||
| static final instance = MyProviderService._(); | ||
| MyProviderService._(); | ||
|
|
||
| late final OAuth2PkceUtil _oauth2Util; | ||
|
|
||
| void initialize() { | ||
| _oauth2Util = OAuth2PkceUtil( | ||
| config: MyProviderConfig.clientConfig, | ||
| ); | ||
| } | ||
|
|
||
| Future<OAuth2PkceResult> signIn() async { | ||
| return await _oauth2Util.authorize(); | ||
| } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Also follow the flow of server > client here.

Docs serverpod/serverpod#4546.
@marcelomendoncasoares, please, review.