From dec1a620868e8e7559153e7556af3a48530e5f33 Mon Sep 17 00:00:00 2001 From: developerjamiu Date: Fri, 31 Jul 2026 20:13:06 +0100 Subject: [PATCH 1/2] docs: Rework the Authentication core and platform pages for 4.0 --- .../04-authentication/01-get-started.md | 9 +- .../{01-setup.md => 02-setup.md} | 91 +++---- .../{02-basics.md => 03-basics.md} | 71 +++--- ...with-users.md => 04-working-with-users.md} | 66 +++-- .../01-anonymous/01-setup.md | 0 .../01-anonymous/02-configuration.md | 0 .../01-anonymous/03-customizing-the-ui.md | 0 .../01-anonymous/_category_.json | 0 .../02-email/01-setup.md | 0 .../02-email/02-configuration.md | 0 .../02-email/03-customizing-the-ui.md | 0 .../02-email/04-admin-operations.md | 0 .../02-email/_category_.json | 0 .../03-google/01-setup.md | 0 .../03-google/02-customizations.md | 0 .../03-google/03-customizing-the-ui.md | 0 .../03-google/04-troubleshooting.md | 0 .../03-google/_category_.json | 0 .../04-apple/01-setup.md | 0 .../04-apple/02-customizations.md | 0 .../04-apple/03-customizing-the-ui.md | 0 .../04-apple/04-troubleshooting.md | 0 .../04-apple/_category_.json | 0 .../05-facebook/01-setup.md | 0 .../05-facebook/02-customizations.md | 0 .../05-facebook/03-customizing-the-ui.md | 0 .../05-facebook/04-troubleshooting.md | 0 .../05-facebook/_category_.json | 0 .../06-firebase/01-setup.md | 0 .../06-firebase/02-customizations.md | 0 .../06-firebase/03-customizing-the-ui.md | 0 .../06-firebase/04-admin-operations.md | 0 .../06-firebase/05-troubleshooting.md | 0 .../06-firebase/_category_.json | 0 .../07-github/01-setup.md | 0 .../07-github/02-customizations.md | 0 .../07-github/03-customizing-the-ui.md | 0 .../07-github/04-troubleshooting.md | 0 .../07-github/_category_.json | 0 .../08-microsoft/01-setup.md | 0 .../08-microsoft/02-configuration.md | 0 .../08-microsoft/03-customizing-the-ui.md | 0 .../08-microsoft/_category_.json | 0 .../09-passkey/01-setup.md | 0 .../09-passkey/03-customizing-the-ui.md | 0 .../09-passkey/_category_.json | 0 .../10-custom-providers/01-overview.md | 0 .../02-oauth2-utility/01-setup.md | 0 ...ating-an-oauth2-based-identity-provider.md | 0 .../02-oauth2-utility/_category_.json | 0 .../10-custom-providers/_category_.json | 0 .../_category_.json | 0 .../05-token-managers/01-managing-tokens.md | 147 ----------- .../05-token-managers/_category_.json | 4 - .../06-token-managers/01-managing-tokens.md | 163 +++++++++++++ .../02-jwt-token-manager.md | 28 ++- .../03-server-side-sessions-token-manager.md | 29 ++- .../06-token-managers/_category_.json | 4 + ...6-ui-components.md => 07-ui-components.md} | 100 ++++---- ...profile-photos.md => 08-profile-photos.md} | 78 +++--- .../04-authentication/09-custom-overrides.md | 229 ------------------ ...tication.md => 09-macos-authentication.md} | 0 .../04-authentication/10-custom-overrides.md | 224 +++++++++++++++++ 63 files changed, 616 insertions(+), 627 deletions(-) rename docs/06-concepts/04-authentication/{01-setup.md => 02-setup.md} (55%) rename docs/06-concepts/04-authentication/{02-basics.md => 03-basics.md} (70%) rename docs/06-concepts/04-authentication/{03-working-with-users.md => 04-working-with-users.md} (72%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/01-anonymous/01-setup.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/01-anonymous/02-configuration.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/01-anonymous/03-customizing-the-ui.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/01-anonymous/_category_.json (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/02-email/01-setup.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/02-email/02-configuration.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/02-email/03-customizing-the-ui.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/02-email/04-admin-operations.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/02-email/_category_.json (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/03-google/01-setup.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/03-google/02-customizations.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/03-google/03-customizing-the-ui.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/03-google/04-troubleshooting.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/03-google/_category_.json (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/04-apple/01-setup.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/04-apple/02-customizations.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/04-apple/03-customizing-the-ui.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/04-apple/04-troubleshooting.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/04-apple/_category_.json (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/05-facebook/01-setup.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/05-facebook/02-customizations.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/05-facebook/03-customizing-the-ui.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/05-facebook/04-troubleshooting.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/05-facebook/_category_.json (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/06-firebase/01-setup.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/06-firebase/02-customizations.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/06-firebase/03-customizing-the-ui.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/06-firebase/04-admin-operations.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/06-firebase/05-troubleshooting.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/06-firebase/_category_.json (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/07-github/01-setup.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/07-github/02-customizations.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/07-github/03-customizing-the-ui.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/07-github/04-troubleshooting.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/07-github/_category_.json (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/08-microsoft/01-setup.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/08-microsoft/02-configuration.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/08-microsoft/03-customizing-the-ui.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/08-microsoft/_category_.json (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/09-passkey/01-setup.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/09-passkey/03-customizing-the-ui.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/09-passkey/_category_.json (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/10-custom-providers/01-overview.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/10-custom-providers/02-oauth2-utility/01-setup.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/10-custom-providers/02-oauth2-utility/02-creating-an-oauth2-based-identity-provider.md (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/10-custom-providers/02-oauth2-utility/_category_.json (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/10-custom-providers/_category_.json (100%) rename docs/06-concepts/04-authentication/{04-providers => 05-providers}/_category_.json (100%) delete mode 100644 docs/06-concepts/04-authentication/05-token-managers/01-managing-tokens.md delete mode 100644 docs/06-concepts/04-authentication/05-token-managers/_category_.json create mode 100644 docs/06-concepts/04-authentication/06-token-managers/01-managing-tokens.md rename docs/06-concepts/04-authentication/{05-token-managers => 06-token-managers}/02-jwt-token-manager.md (75%) rename docs/06-concepts/04-authentication/{05-token-managers => 06-token-managers}/03-server-side-sessions-token-manager.md (68%) create mode 100644 docs/06-concepts/04-authentication/06-token-managers/_category_.json rename docs/06-concepts/04-authentication/{06-ui-components.md => 07-ui-components.md} (64%) rename docs/06-concepts/04-authentication/{07-profile-photos.md => 08-profile-photos.md} (64%) delete mode 100644 docs/06-concepts/04-authentication/09-custom-overrides.md rename docs/06-concepts/04-authentication/{08-macos-authentication.md => 09-macos-authentication.md} (100%) create mode 100644 docs/06-concepts/04-authentication/10-custom-overrides.md diff --git a/docs/06-concepts/04-authentication/01-get-started.md b/docs/06-concepts/04-authentication/01-get-started.md index 89b923e6..84ddf70d 100644 --- a/docs/06-concepts/04-authentication/01-get-started.md +++ b/docs/06-concepts/04-authentication/01-get-started.md @@ -13,7 +13,7 @@ This guide walks you through that, then shows how to test signing up and signing ## Prerequisites -- A project created with `serverpod create` on Serverpod 4.0 or later. For older projects, see [Setup](./setup) first to add the auth module. +- A project created with `serverpod create` on Serverpod 4.0 or later. For older projects, see [Setup](./setup) first to add the authentication module manually. - The Flutter SDK installed, so you can run the app. - Docker installed and running, if your project uses a Docker Postgres. Projects on the embedded Postgres option don't need Docker. @@ -62,7 +62,7 @@ The app opens on the sign-in screen. To create an account: 2. Look in the server console for the verification code. While testing, it is logged instead of emailed: ```text - [EmailIdp] Registration code (you@example.com): 12345678 + Registration code for you@example.com: <12345678> ``` 3. Enter the code, then set a password to finish. @@ -71,8 +71,9 @@ Once you are signed in, the app shows your content with a sign-out button. ## Next steps -- Send real verification emails through an email service. See [Email provider setup](./providers/email/setup). -- Customize the sign-in screen or build your own. See [Customizing the UI](./providers/email/customizing-the-ui). +- Send verification emails from your own email service instead of the default. See [Email provider setup](./providers/email/setup). +- Customize the sign-in screen. See [UI components](./ui-components). +- Build your own sign-in UI with the email controller. See [Customizing the UI](./providers/email/customizing-the-ui). - Change password rules, code length, and rate limits. See [Email configuration](./providers/email/configuration). - Require sign-in on your endpoints. See [The basics](./basics#requiring-authentication-on-endpoints). - Add more sign-in options, like Google or Apple. See [Identity providers configuration](./setup#identity-providers-configuration). diff --git a/docs/06-concepts/04-authentication/01-setup.md b/docs/06-concepts/04-authentication/02-setup.md similarity index 55% rename from docs/06-concepts/04-authentication/01-setup.md rename to docs/06-concepts/04-authentication/02-setup.md index ec87da2f..0605d5c2 100644 --- a/docs/06-concepts/04-authentication/01-setup.md +++ b/docs/06-concepts/04-authentication/02-setup.md @@ -5,24 +5,20 @@ description: Authentication in Serverpod is provided by the serverpod_auth_idp m # Set up the authentication module -Serverpod comes with built-in user management and authentication. It is possible to build a [custom authentication implementation](custom-overrides), but the recommended way to authenticate users is to use the `serverpod_auth_idp` module. The module makes it easy to authenticate with email, social sign-ins and more. +Serverpod comes with built-in user management and authentication. It is possible to build a [custom authentication implementation](custom-overrides), but the recommended way to authenticate users is to use the `serverpod_auth_idp` module. The module lets users sign in through identity providers. An identity provider is a service that verifies who the user is, such as email with password, Google, or Apple. The module also handles basic user information, such as user names and profile pictures. -The list of identity providers is continuously growing and new providers are added as they are developed. If you want to contribute a new provider, please consider [contributing](/contribute) your code. See the [identity providers configuration](#identity-providers-configuration) section for details on all available providers. +This page walks through the manual setup: install the module on the server, the client, and the app, then wire up the sign-in UI. New projects created with `serverpod create` already include this setup. For that path, see [Get started](./get-started). ![Sign-in with Serverpod](/img/authentication/sign-in-widget-device.png) -## Installing the auth module - -Serverpod's auth module makes it easy to authenticate users through email or 3rd parties. The authentication module also handles basic user information, such as user names and profile pictures. Make sure to use the same version numbers as for Serverpod itself for all dependencies. - ## Server setup -Add the auth modules as dependencies to the server project's `pubspec.yaml`. +Add the authentication module as a dependency to the server project's `pubspec.yaml`. Use the same version numbers as for Serverpod itself for all dependencies. ```yaml dependencies: ... - serverpod_auth_idp_server: 4.0.0-beta.0 + serverpod_auth_idp_server: 4.0.0-beta.1 ``` The `serverpod_auth_idp_server` package contains all components required to configure authentication services. @@ -31,6 +27,8 @@ The `serverpod_auth_idp_server` package contains all components required to conf In your main `server.dart` file, configure the authentication system using the `pod.initializeAuthServices()` extension method. +The configuration needs at least one token manager. A token manager issues and validates the tokens that keep users signed in after they authenticate. The example below uses JWT tokens and reads its secrets from your password store. The [token manager configuration](#token-manager-configuration) section explains the options. + ```dart import 'package:serverpod/serverpod.dart'; import 'package:serverpod_auth_idp_server/core.dart'; @@ -64,7 +62,10 @@ void run(List args) async { await pod.start(); } ``` -Then extend the abstract endpoint for refreshing JWT tokens to expose it on the server. Create the file anywhere under your server's `lib/` directory (for example, `_server/lib/src/endpoints/`); the generator picks it up: + +The pepper in the example is a secret string mixed into values before they are hashed. The [storing secrets](#storing-secrets) section explains peppers and where to keep them. + +JWT-based authentication also needs a refresh endpoint. The app calls it to renew expired access tokens without asking the user to sign in again. Extend the abstract endpoint to expose it on the server. Create the file anywhere under your server's `lib/` directory, for example `_server/lib/src/endpoints/`. The generator picks it up: ```dart import 'package:serverpod_auth_idp_server/core.dart' as core; @@ -74,25 +75,29 @@ class RefreshJwtTokensEndpoint extends core.RefreshJwtTokensEndpoint {} ### Token manager configuration -The authentication system uses token managers to handle authentication tokens. You need to configure at least one token manager to be used as the primary token manager. Additional token managers can be configured to be used for validation and management operations. +Token managers issue and validate the tokens that keep users signed in. Configure at least one. The first one in the list is the primary manager, which issues new tokens. Any others are used to validate and manage the tokens they issued. Serverpod provides two built-in token manager builders: -- `JwtConfig` to use JWT-based authentication. See [JWT Token Manager](./token-managers/jwt-token-manager) for details. -- `ServerSideSessionsConfig` to use server-side sessions authentication. See [Server-Side Sessions Token Manager](./token-managers/server-side-sessions-token-manager) for details. +- `JwtConfig` to use JWT-based authentication. See [JWT token manager](./token-managers/jwt-token-manager) for details. +- `ServerSideSessionsConfig` to use server-side sessions authentication. See [Server-side sessions token manager](./token-managers/server-side-sessions-token-manager) for details. -For more details on how to configure token managers or create custom ones, see the dedicated [Token Managers](./token-managers/managing-tokens) documentation. +For more details on how to configure token managers or create custom ones, see the dedicated [token managers](./token-managers/managing-tokens) documentation. ### Identity providers configuration Identity providers handle authentication with different methods (Email, Google, Apple, etc.). Each provider has its own configuration: -- **Email**: Sign-up and sign-in with email and password. See [Email Provider](./providers/email/setup) for details. -- **Google**: Sign-in with Google. See [Google Provider](./providers/google/setup) for details. -- **Apple**: Sign-in with Apple. See [Apple Provider](./providers/apple/setup) for details. -- **Passkey (experimental)**: Sign-in with Passkey. See [Passkey Provider](./providers/passkey/setup) for details. +- **[Email](./providers/email/setup)**: sign-up and sign-in with email and password. +- **[Anonymous](./providers/anonymous/setup)** (experimental): accounts without any credentials, for trying the app before registering. +- **[Google](./providers/google/setup)**, **[Apple](./providers/apple/setup)**, **[Facebook](./providers/facebook/setup)**, **[GitHub](./providers/github/setup)**, and **[Microsoft](./providers/microsoft/setup)**: sign-in with the respective account. +- **[Firebase](./providers/firebase/setup)**: reuse Firebase Authentication, including its phone and social sign-ins. +- **[Passkey](./providers/passkey/setup)** (experimental): passwordless sign-in with passkeys. +- **[Custom providers](./providers/custom-providers/overview)**: build your own, including OAuth2-based ones. -By default, endpoints for all providers are disabled. To enable a provider, it is necessary to: +The list of identity providers keeps growing. If you want to contribute a new provider, see the [contribution guidelines](/contribute). + +By default, endpoints for all providers are disabled. To enable a provider: 1. Pass its config object to the `identityProviderBuilders` parameter of the `pod.initializeAuthServices()` method. @@ -105,7 +110,7 @@ By default, endpoints for all providers are disabled. To enable a provider, it i ``` :::tip - Some identity providers might require configuration on external services, such as the Google client secret. Such configuration will be required by the provider config object. + Some providers need credentials from an external service, such as the Google client secret. The provider's config object takes these as required parameters. ::: 2. Extend the identity provider abstract endpoint. @@ -125,12 +130,12 @@ By default, endpoints for all providers are disabled. To enable a provider, it i 4. Create and apply the migration that initializes the database for the provider. In the `serverpod start` terminal, press **M** to create the migration, then **A** to apply it. :::info - If this is the first time creating migrations after adding the module, besides the provider tables, all auth module tables will also be created. More detailed migration instructions can be found in the [migration guide](../data-and-the-database/database/migrations). + If this is the first time creating migrations after adding the module, besides the provider tables, all authentication module tables will also be created. More detailed migration instructions can be found in the [migration guide](../data-and-the-database/database/migrations). ::: ### Storing secrets -Secrets like peppers and private keys should be stored securely. The example above uses `pod.getPassword()` which reads from your `config/passwords.yaml` file or environment variables in the format `SERVERPOD_PASSWORD_='value'`. +A pepper is a secret string mixed into values before they are hashed, so stored hashes cannot be brute-forced from a database leak alone. It is one application-wide secret, kept outside the database. Peppers and private keys should be stored securely. The example above uses `pod.getPassword()` which reads from your `config/passwords.yaml` file or environment variables in the format `SERVERPOD_PASSWORD_='value'`. Add secrets to `config/passwords.yaml`: @@ -140,7 +145,7 @@ development: jwtRefreshTokenHashPepper: 'your-refresh-token-pepper-here' jwtHmacSha512PrivateKey: 'your-private-key-here' emailSecretHashPepper: 'your-email-pepper-here' - googleClientSecret: '{"type":"service_account",...}' + googleClientSecret: '{"web":{"client_id":"...","client_secret":"...","redirect_uris":["..."]}}' # ... other secrets ``` @@ -151,12 +156,12 @@ export SERVERPOD_PASSWORD_serverSideSessionKeyHashPepper='your-session-pepper-he export SERVERPOD_PASSWORD_jwtRefreshTokenHashPepper='your-refresh-token-pepper-here' export SERVERPOD_PASSWORD_jwtHmacSha512PrivateKey='your-private-key-here' export SERVERPOD_PASSWORD_emailSecretHashPepper='your-email-pepper-here' -export SERVERPOD_PASSWORD_googleClientSecret='{"type":"service_account",...}' +export SERVERPOD_PASSWORD_googleClientSecret='{"web":{"client_id":"...","client_secret":"...","redirect_uris":["..."]}}' # ... other secrets ``` :::info -When using the `config/passwords.yaml` file or environment variables, you can use a convenience version of token manager and identity provider builders that already load secrets using `pod.getPassword()` while still allowing you to pass additional configuration options. +Builders that need secrets have a `FromPasswords` variant that reads them from well-known key names, so you do not need to call `pod.getPassword()` yourself. Any other configuration options are still passed as parameters. The examples below are a few of them: ```dart final jwtConfig = JwtConfigFromPasswords(); @@ -166,6 +171,7 @@ final googleIdpConfig = GoogleIdpConfigFromPasswords(); final appleIdpConfig = AppleIdpConfigFromPasswords(); final passkeyIdpConfig = PasskeyIdpConfigFromPasswords(); ``` + ::: :::warning @@ -174,24 +180,24 @@ Never commit `config/passwords.yaml` to version control. Be sure to add it to yo ## Client setup -Add the `serverpod_auth_idp_client` package to your client project's `pubspec.yaml`. Make sure to use the same version numbers as for Serverpod itself for all dependencies. +The client is the generated Dart package that your app uses to call the server (the `_client` package in your project). Add the `serverpod_auth_idp_client` package to its `pubspec.yaml`. Use the same version numbers as for Serverpod itself for all dependencies. ```yaml dependencies: ... - serverpod_auth_idp_client: 4.0.0-beta.0 + serverpod_auth_idp_client: 4.0.0-beta.1 ``` ## App setup -First, add dependencies to your app's `pubspec.yaml` file for the methods of signing in that you want to support. +First, add these packages to your app's `pubspec.yaml` file. Some providers, such as Facebook and Firebase, need an extra package, which their setup page names. ```yaml dependencies: flutter: sdk: flutter - serverpod_auth_idp_flutter: 4.0.0-beta.0 - serverpod_flutter: 4.0.0-beta.0 + serverpod_auth_idp_flutter: 4.0.0-beta.1 + serverpod_flutter: 4.0.0-beta.1 your_client: path: ../your_client ``` @@ -226,7 +232,7 @@ void main() async { The `FlutterAuthSessionManager` provides useful properties and methods for managing authentication state. :::tip -In case you have an endpoint called `AuthEndpoint` - that will generate the `auth` getter on the client -, you can also get the `FlutterAuthSessionManager` from the client using the `client.authSessionManager` property. On the above example, you would replace the `client.auth.initialize()` call with `client.authSessionManager.initialize()`. +The `client.auth` getter is a shortcut for `client.authSessionManager`. If your project defines its own endpoint class named `AuthEndpoint`, the generated client uses the `auth` name for that endpoint instead. In that case, call `client.authSessionManager.initialize()` in the example above. ::: ### Initialize authentication @@ -248,14 +254,14 @@ macOS apps need a Keychain Sharing entitlement before authentication sessions ca ### Web callback page (`auth.html`) :::note -You only need this if your app targets the **web** platform and uses an identity provider that signs the user in through an OAuth2 redirect. That includes **GitHub** and **Google** (when configured for redirect-based web sign-in). Skip this section if your app does not target web, or if it only uses email, passkey, or anonymous sign-in. +You only need this if your app targets the **web** platform and uses an identity provider that signs the user in through an OAuth2 redirect. That includes **GitHub**, **Microsoft**, **Google** on web, and custom OAuth2-based providers. Skip this section if your app does not target web, or if it only uses email, anonymous, passkey, Apple, Facebook, or Firebase sign-in. ::: -When the user finishes signing in at the provider's page (for example, `accounts.google.com`), the provider redirects the browser to a URL on your site with the sign-in result attached. Your Flutter app cannot receive that redirect directly because the browser navigates fully away from it. `auth.html` is a small static page that catches the redirect, reads the result, and hands it back to your running Flutter app through `postMessage` (or `localStorage`, depending on how the sign-in was launched). +When the user finishes signing in at the provider's page (for example, `accounts.google.com`), the provider redirects the browser to a URL on your site with the sign-in result attached. Your Flutter app cannot receive that redirect directly because the browser navigates fully away from it. The `auth.html` file is a small static page that catches the redirect, reads the result, and hands it back to your running Flutter app through `postMessage` (or `localStorage`, depending on how the sign-in was launched). You create one `auth.html` and share it across every identity provider that needs it. -In your Flutter project's `web/` folder, add a file named `auth.html` with this content: +You have two ways to deliver it. If Serverpod also serves your Flutter web app, register the `FlutterWebAuth2CallbackRoute` from `serverpod_auth_idp_server` on your web server and point the provider at that route. The page hands the result back with `postMessage` aimed at its own origin, so the browser only delivers it when your app is served from the same origin (same scheme, host, and port). Otherwise, host the file yourself: in your Flutter project's `web/` folder, add a file named `auth.html` with this content, which is identical to what the route serves: ```html @@ -284,14 +290,14 @@ In your Flutter project's `web/` folder, add a file named `auth.html` with this When you set up a provider that uses this callback, you will register the full URL of `auth.html` in **two** places, and they must match exactly: -- **In the provider's OAuth client configuration** for example, **Authorized redirect URIs** in Google Cloud Console, or **Authorization callback URL** in a GitHub OAuth app. +- **In the provider's OAuth client configuration**: for example, **Authorized redirect URIs** in Google Cloud Console, or **Authorization callback URL** in a GitHub OAuth app. - **In the Flutter sign-in initializer**, via the `redirectUri` argument (e.g., `client.auth.initializeGoogleSignIn(..., redirectUri: ...)`). -The URL is your Flutter web app's origin plus `/auth.html`. For example, `http://localhost:49660/auth.html` during local development or `https://yourdomain.com/auth.html` in production. The provider's setup page walks through the exact values for that provider. +When you host the file yourself, the URL is your Flutter web app's origin plus `/auth.html`. For example, `http://localhost:49660/auth.html` during local development or `https://yourdomain.com/auth.html` in production. When the server serves the page, the URL is that same origin plus the path you registered the route at, for example `https://yourdomain.com/auth/callback`. The provider's setup page walks through the exact values for that provider. ### Present the authentication UI -The `serverpod_auth_idp_flutter` package provides a `SignInWidget` that automatically detects enabled authentication providers and displays the appropriate sign-in options. +The `serverpod_auth_idp_flutter` package provides a `SignInWidget` that automatically detects enabled identity providers and displays the appropriate sign-in options. ```dart import 'package:flutter/material.dart'; @@ -326,12 +332,15 @@ class SignInPage extends StatelessWidget { } ``` -This widget is a convenient way to use identity providers out-of-the-box, but you can also fully customize it or replace it with your own implementation. See the [UI Components](./ui-components) documentation for more details. +This widget is a convenient way to use identity providers out-of-the-box, but you can also fully customize it or replace it with your own implementation. See the [UI components](./ui-components) documentation for more details. #### Updating the UI based on authentication state -To update the UI based on authentication state, you must listen to authentication state changes using the `authInfoListenable` getter. See the [Client-side authentication](./basics#monitor-authentication-changes) section for more details. +Do not navigate to another screen from the `onAuthenticated` callback, or the user will have to sign in again every time they open the app. Instead, listen to authentication state changes with the `authInfoListenable` getter and switch screens based on the state. See [Client-side authentication](./basics#monitor-authentication-changes) for details. -:::warning -Do not navigate to the home screen using the `onAuthenticated` callback. This will cause the user to have to sign in again every time they open the app. -::: +## Related + +- [Get started](./get-started): the quick path for projects created with `serverpod create`. +- [The basics](./basics): how authentication works on the server and in the app. +- [Token managers](./token-managers/managing-tokens): choose between JWT and server-side sessions. +- [UI components](./ui-components): customize or replace the sign-in UI. diff --git a/docs/06-concepts/04-authentication/02-basics.md b/docs/06-concepts/04-authentication/03-basics.md similarity index 70% rename from docs/06-concepts/04-authentication/02-basics.md rename to docs/06-concepts/04-authentication/03-basics.md index 3560ede7..6741f55e 100644 --- a/docs/06-concepts/04-authentication/02-basics.md +++ b/docs/06-concepts/04-authentication/03-basics.md @@ -5,9 +5,9 @@ description: Authentication tokens are handled automatically by Serverpod. Learn # Authentication basics -Serverpod automatically checks if the user is logged in and if the user has the right privileges to access each endpoint. When using the Serverpod Authentication modules, you will not have to worry about keeping track of tokens, refreshing them or even including them in requests as this all happens automatically under the hood. +Serverpod automatically checks if the user is signed in and if the user has the right privileges to access each endpoint. When using the authentication module, tokens are tracked, refreshed, and included in requests for you. -The `Session` object provides information about the current user. Access the current authentication through the synchronous `authenticated` getter of the `Session` object. It exposes a `userIdentifier`, a `String` that uniquely identifies the signed-in user. Use this id whenever you refer to a user. +The `Session` object provides information about the current user. A `Session` is the request context that every endpoint method receives. It is not the user's login session. Access the current authentication through the synchronous `authenticated` getter of the `Session` object. It exposes a `userIdentifier`, a `String` that uniquely identifies the signed-in user. Use this id whenever you refer to a user. ```dart Future myMethod(Session session) async { @@ -28,7 +28,7 @@ Future myMethod(Session session) async { ## Requiring authentication on endpoints -It is common to want to restrict access to an endpoint to users that have signed in. You can do this by overriding the `requireLogin` property of the `Endpoint` class. +To restrict an endpoint to signed-in users, override the `requireLogin` property of the `Endpoint` class. ```dart class MyEndpoint extends Endpoint { @@ -49,11 +49,7 @@ In some cases, you may want to explicitly allow certain endpoints or methods to When an endpoint or method is annotated with `@unauthenticatedClientCall`: - No authentication will be added to the header on the client when calling it. -- The server will receive calls as if there is no user signed in. - -:::info -Under the hood, the `@unauthenticatedClientCall` annotation makes the client omit authentication headers for calls to the annotated endpoint or method. On the server side, it ensures that the session is treated as unauthenticated for those calls, regardless of any existing authentication state. -::: +- The server will treat the call as unauthenticated, regardless of any existing authentication state. You can use this annotation in two ways: @@ -87,10 +83,10 @@ You can use this annotation in two ways: } ``` -This is particularly useful for endpoints that must not receive authentication, such as JWT refresh endpoints. +This is particularly useful for endpoints that must not receive authentication, such as the [JWT refresh endpoint](./token-managers/jwt-token-manager). :::warning -Using `@unauthenticatedClientCall` on an endpoint or method that also has `requireLogin` set to true will lead to a conflict. Since the client will suppress sending authentication information, but the server will expect it, calls to such endpoints or methods will always fail with an authentication error. +Using `@unauthenticatedClientCall` on an endpoint or method that also has `requireLogin` set to true will lead to a conflict. The client suppresses the authentication header, but the server expects it, so calls to such endpoints or methods always fail with an authentication error. ::: ## Authorization on endpoints @@ -156,7 +152,7 @@ Keep scope names stable once deployed, as renaming a scope will revoke it from a You can also define shared scope requirements in a base endpoint class. See [Endpoint inheritance](../endpoints-and-apis/endpoint-inheritance) for details. :::caution -Keep in mind that a scope is merely an arbitrary string and can be written in any format you prefer. However, it's crucial to use unique strings for each scope, as duplicated scope strings may lead to unintentional data exposure. +A scope is identified by its string name only. If two scope constants share the same string, granting one also grants the other, since the server cannot tell them apart. Give every scope a unique string. ::: ### How scopes combine @@ -188,23 +184,11 @@ class UserEditEndpoint extends Endpoint { } ``` -An admin user with only `Scope.admin` can call `UserAnalyticsEndpoint` but not `UserEditEndpoint`. To allow editing, grant both scopes: - -```dart -import 'package:serverpod_auth_idp_server/core.dart'; - -await AuthServices.instance.authUsers.update( - session, - authUserId: authUserId, - scopes: {Scope.admin, CustomScope.userWrite}, -); -``` - -This lets you compose capabilities at the endpoint level instead of building nested roles. +An admin user with only `Scope.admin` can call `UserAnalyticsEndpoint` but not `UserEditEndpoint`. To allow editing, grant the user both scopes, as shown in [Managing scopes](#managing-scopes) below. This lets you compose capabilities at the endpoint level instead of building nested roles. ### Managing scopes -New users are created without any scopes. Scope changes take effect only for new sign-ins. Existing sessions and tokens reflect the scopes that were set when the user last signed in. +New users are created without any scopes. To update a user's scopes, use the `update` method from `AuthServices.instance.authUsers`. This method replaces all previously stored scopes: @@ -236,7 +220,7 @@ On macOS, the `FlutterAuthSessionManager` stores tokens in the Keychain. New pro ::: :::info -If you are building a pure Dart application using Serverpod, you can use the `ClientAuthSessionManager` declared in the `serverpod_auth_core_client` package instead of the `FlutterAuthSessionManager`. It has the same functionality, with the exception of a `authInfoListenable` getter that is tied to the Flutter framework. +If you are building a pure Dart application, use the `ClientAuthSessionManager` from the `serverpod_auth_core_client` package instead. It works the same way, except it has no `authInfoListenable` getter, which is tied to the Flutter framework. ::: ### Check authentication state @@ -271,7 +255,7 @@ This will persist the authentication information and refresh any open streaming ### Monitor authentication changes -The `FlutterAuthSessionManager` exposes an `authInfoListenable` that is a `ValueListenable` to be used for listening to changes. This is useful for updating the UI when the authentication state changes: +The `FlutterAuthSessionManager` exposes `authInfoListenable`, a `ValueListenable`. Listen to it to update the UI whenever the user signs in or out: ```dart @override @@ -308,31 +292,23 @@ Call `validateAuthentication` to check the current session against the server an await client.auth.validateAuthentication(); // throws on transient errors; retry if needed ``` -The method force-refreshes the token and confirms with the server that the user is still signed in. If the session is no longer valid, it signs the user out on the current device. A transient problem, such as a network error or timeout, does not sign the user out; the exception is thrown instead, so you can catch it and retry. +The method force-refreshes the token and confirms with the server that the user is still signed in. If the session is no longer valid, it signs the user out on the current device. A transient problem, such as a network error or timeout, does not sign the user out. The exception is thrown instead, so you can catch it and retry. At app startup, use `initialize` to restore a stored session and validate it in one step: ```dart -bool validated = await client.auth.initialize(); +await client.auth.initialize(); ``` -The `initialize` method runs `restore` followed by `validateAuthentication`. If the stored session has expired, the user is signed out. If validation cannot complete for a transient reason (network error, server error, or timeout), `initialize` returns `false` and leaves the stored session in place so you can retry later, which keeps offline users signed in. +The `initialize` method runs `restore` followed by `validateAuthentication`. It returns `true` when validation completed, which is not the same as the user being signed in. Read `client.auth.isAuthenticated` for that. If the stored session has expired, the user is signed out. If validation cannot complete because of a network or server error, `initialize` returns `false` and leaves the stored session in place so you can retry later, which keeps offline users signed in. If the validation call times out, the timeout is thrown instead, so catch it if you want the same retry behavior. Because signing out updates the authentication state, a listener registered on `authInfoListenable` (see [Monitor authentication changes](#monitor-authentication-changes)) fires when a session expires, so you can route the user back to a sign-in screen from one place. -## User authentication - -### Signing out users - -The `FlutterAuthSessionManager` provides methods for handling user sign-outs, whether from a single device or all devices. +## Signing out users -:::info -The below methods use the `StatusEndpoint` methods under the hood, which are also directly accessible on the client using the `client.modules.auth.status` getter. In addition to these methods, Serverpod provides more comprehensive tools for managing user authentication and sign-out processes across multiple devices. - -For more detailed information on managing and revoking authentication keys, please refer to the [Managing tokens](./token-managers/managing-tokens#revoking-tokens) section. -::: +The `FlutterAuthSessionManager` provides methods for handling user sign-outs, whether from a single device or all devices. They call the `StatusEndpoint` methods under the hood, which are also directly accessible on the client through the `client.modules.serverpod_auth_core.status` getter. For revoking authentication across devices from the server, see [Managing tokens](./token-managers/managing-tokens#revoking-tokens). -#### Sign out current device +### Sign out current device To sign the user out from the current device: @@ -340,9 +316,9 @@ To sign the user out from the current device: await client.auth.signOutDevice(); ``` -Returns `true` if the sign-out is successful, or `false` if it fails. Either way, the sign-out will be performed on the application and update the authentication state. +Returns `true` if the server call succeeds, or `false` if it fails. Either way, the app clears its local authentication state. -#### Sign out all devices +### Sign out all devices To sign the user out across all devices: @@ -350,4 +326,11 @@ To sign the user out across all devices: await client.auth.signOutAllDevices(); ``` -Returns `true` if the user is successfully signed out from all devices, or `false` if it fails. Also proceed with the sign-out on the application regardless of the result of the operation on the server. +Returns `true` if the user is successfully signed out from all devices, or `false` if it fails. Either way, the app clears its local authentication state. + +## Related + +- [Setup](./setup): install and configure the authentication module. +- [Working with users](./working-with-users): user profiles, callbacks, and admin operations. +- [Token managers](./token-managers/managing-tokens): how tokens are issued, validated, and revoked. +- [Custom overrides](./custom-overrides): replace the built-in authentication with your own. diff --git a/docs/06-concepts/04-authentication/03-working-with-users.md b/docs/06-concepts/04-authentication/04-working-with-users.md similarity index 72% rename from docs/06-concepts/04-authentication/03-working-with-users.md rename to docs/06-concepts/04-authentication/04-working-with-users.md index 32a5c7d6..82d8593e 100644 --- a/docs/06-concepts/04-authentication/03-working-with-users.md +++ b/docs/06-concepts/04-authentication/04-working-with-users.md @@ -8,25 +8,25 @@ The authentication module provides convenient ways to work with your authenticat ## Authenticated users -All authenticated users have an authentication identifier, that uniquely identifies them across the server. This can be retrieved from the `session` object as a `String` through the `userIdentifier` property or as a `UuidValue` from the `authUserId` extension provided by the authentication module. +All authenticated users have an authentication identifier that uniquely identifies them across the server. This can be retrieved from the `session` object as a `String` through the `userIdentifier` property or as a `UuidValue` from the `authUserId` extension provided by the authentication module. ```dart var userIdString = session.authenticated?.userIdentifier; // requires `import 'package:serverpod_auth_idp_server/core.dart';` -var userIdUuidValue = session.authenticated?.authUserId; +var userIdUuidValue = session.authenticated!.authUserId; ``` Further operations on the authenticated user can be performed using the `AuthUsers` class which is provided by the `AuthServices` instance. ```dart -await AuthServices.instance.authUsers.delete(session, userIdUuidValue); +await AuthServices.instance.authUsers.delete(session, authUserId: userIdUuidValue); ``` For the full list of operations, see the [AuthUsers](https://pub.dev/documentation/serverpod_auth_core_server/latest/serverpod_auth_core_server/AuthUsers-class.html) class documentation. ## Blocking users -You can block users to prevent them from signing in to your application. When a blocked user attempts to authenticate, an `AuthUserBlockedException` will be thrown, and the authentication will fail. +You can block users to prevent them from signing in to your app. When a blocked user attempts to authenticate, an `AuthUserBlockedException` will be thrown, and the authentication will fail. ### Blocking or unblocking a user @@ -41,6 +41,7 @@ await AuthServices.instance.authUsers.update( ``` Users can also be created with the blocked status set from the start: + ```dart await AuthServices.instance.authUsers.create( session, @@ -49,7 +50,7 @@ await AuthServices.instance.authUsers.create( ``` :::note -When a user is blocked, they will not be able to sign in until they are unblocked. However, blocking a user does not automatically revoke their existing sessions. Be sure to revoke existing sessions for a complete block operation. See [Revoking tokens](./token-managers/managing-tokens#revoking-tokens) for more details. +When a user is blocked, they will not be able to sign in until they are unblocked. However, blocking a user does not automatically revoke their existing tokens. Be sure to revoke existing sessions for a complete block operation. See [Revoking tokens](./token-managers/managing-tokens#revoking-tokens) for more details. ::: ## User creation callbacks @@ -77,7 +78,7 @@ pod.initializeAuthServices( ### Setting default scopes and blocked status -Use the `onBeforeAuthUserCreated` callback to set default scopes or blocked status for new auth users. The callback receives the session, the scopes and blocked value that would be used by default, and the transaction. Return a record with the `scopes` and `blocked` values you want to apply; you can add or remove scopes or force the user to be blocked. +Use the `onBeforeAuthUserCreated` callback to set default scopes or blocked status for new auth users. The callback receives the session, the scopes and blocked value that would be used by default, and the transaction. Return a record with the `scopes` and `blocked` values you want to apply. You can add or remove scopes, or force the user to be blocked. ```dart pod.initializeAuthServices( @@ -93,13 +94,15 @@ pod.initializeAuthServices( ## User profiles -By default, all authenticated users have a `UserProfile` object that contains information about the signed-in user. To access the `UserProfile` object, you can use the `userProfile` extension on the `AuthenticationInfo` object. +Authenticated users get a profile with information about the signed-in user. Read it with the `userProfile` extension on `AuthenticationInfo`. The extension returns a `UserProfileModel`, which is the read model sent to the app. The `UserProfile` class itself is only used for database access. ```dart var userProfile = await session.authenticated?.userProfile(session); ``` -The `UserProfile` contains a basic set of information about the user, such as their full name, email address, and profile picture. +The result is `null` only when no user is signed in. If the user is signed in but has no profile, the call throws `UserProfileNotFoundException`. + +The profile contains a basic set of information about the user, such as their full name, email address, and profile picture. This information is automatically populated when the user signs in. Based on the authentication method used, different data may be available. @@ -147,12 +150,10 @@ To access the user profile from your Flutter app, you can use the `userProfileIn final userProfile = await client.modules.serverpod_auth_core.userProfileInfo.get(); ``` -This returns a `UserProfileModel` object containing the logged-in user's profile information such as their name, email, and profile picture. +This returns a `UserProfileModel` object containing the signed-in user's profile information such as their name, email, and profile picture. ### Extending the user profile edit endpoint -For a step-by-step guide covering upload, display, validation, and storage, see [Manage user profile photos](./profile-photos). - The authentication module provides a `UserProfileEditBaseEndpoint` abstract class that you can extend to expose user profile editing functionality to your app. This base endpoint includes methods for: - Removing user images @@ -160,6 +161,8 @@ The authentication module provides a `UserProfileEditBaseEndpoint` abstract clas - Changing user names - Changing full names +For a step-by-step guide to profile photo upload, display, and storage, see [Manage user profile photos](./profile-photos). + To enable profile editing in your app, create a concrete endpoint class on your server by extending `UserProfileEditBaseEndpoint`: ```dart @@ -209,32 +212,11 @@ class UserProfileEditEndpoint extends UserProfileEditBaseEndpoint { ### Setting a default user image -When logging in from some providers, the user image is automatically fetched and set as the user's profile picture - such as with Google Sign In. However, when an image is not found or the provider does not expose the picture, you can set a default user image using the `onAfterUserProfileCreated` callback in `UserProfileConfig` (see [User profile callbacks](#user-profile-callbacks) for the full set of callbacks). - -```dart - pod.initializeAuthServices( - userProfileConfig: UserProfileConfig( - // NOTE: The `userImageGenerator` parameter is optional and defaults to - // the value below - which generates Gmail-style images. You can change - // this parameter to generate any kind of placeholder image. The function - // will be called when invoking the `setDefaultUserImage` method. - userImageGenerator: defaultUserImageGenerator, - onAfterUserProfileCreated: - (session, userProfile, {required transaction}) async { - await AuthServices.instance.userProfiles.setDefaultUserImage( - session, - userProfile.authUserId, - transaction: transaction, - ); - }, - ), - ... - ); -``` +Some providers, such as Google, expose the user's picture. In that case it is fetched and set as the profile picture automatically. When no picture is available, you can generate a default image with the `onAfterUserProfileCreated` callback in `UserProfileConfig` (see [User profile callbacks](#user-profile-callbacks) for the full set). For the configuration example and the image settings, see [Profile photos](./profile-photos#configure-image-size-and-format). ## Attaching additional information -The recommended way to attach additional information to an authenticated user is to use a relation in the Database. This makes it easy to query the data later based on the user's authentication identifier. +The recommended way to attach additional information to an authenticated user is to use a relation in the database. This makes it easy to query the data later based on the user's authentication identifier. ```yaml class: MyDomainData @@ -250,8 +232,10 @@ indexes: unique: true ``` +The model above creates a relation to the `AuthUser` table and ensures that each user can only have one `MyDomainData` object. The `onDelete=Cascade` ensures that when the `AuthUser` is deleted, the `MyDomainData` object is also deleted. + :::note -Note that the `AuthUser` model is declared in the `serverpod_auth_core` module, which is automatically included in your project as a dependency of the `serverpod_auth_idp` module. If you are not ignoring the generated files in your `analysis_options.yaml`, you might need to explicitly add the `serverpod_auth_core` module to your project to prevent `depend_on_referenced_packages` lint errors. The general recommendation, however, is to ignore linting on generated files: +The `AuthUser` model is declared in the `serverpod_auth_core` module, which is automatically included in your project as a dependency of the `serverpod_auth_idp` module. If you are not ignoring the generated files in your `analysis_options.yaml`, you might need to explicitly add the `serverpod_auth_core` module to your project to prevent `depend_on_referenced_packages` lint errors. The general recommendation, however, is to ignore linting on generated files: ```yaml # analysis_options.yaml @@ -259,16 +243,14 @@ analyzer: exclude: - lib/src/generated/** ``` + ::: :::tip When referencing module classes in your model files, you can use a nickname for the module instead of the full module name. See the [modules documentation](../server-fundamentals/modules) for more information. ::: - -The model above creates a relation to the `AuthUser` table and ensures that each user can only have one `MyDomainData` object. The `onDelete=Cascade` ensures that when the `AuthUser` is deleted, the `MyDomainData` object is also deleted. - -This makes it easy to query the additional information later based on the user's `authUserId`. +Query the additional information with the user's `authUserId`: ```dart final authUserId = session.authenticated?.authUserId; @@ -277,3 +259,9 @@ final additionalInfo = await MyDomainData.db.findFirstRow( where: (t) => t.authUserId.equals(authUserId!), ); ``` + +## Related + +- [The basics](./basics): authentication state, scopes, and endpoint access control. +- [Profile photos](./profile-photos): upload, display, and default profile images. +- [Setup](./setup): configure the authentication services these callbacks hook into. diff --git a/docs/06-concepts/04-authentication/04-providers/01-anonymous/01-setup.md b/docs/06-concepts/04-authentication/05-providers/01-anonymous/01-setup.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/01-anonymous/01-setup.md rename to docs/06-concepts/04-authentication/05-providers/01-anonymous/01-setup.md diff --git a/docs/06-concepts/04-authentication/04-providers/01-anonymous/02-configuration.md b/docs/06-concepts/04-authentication/05-providers/01-anonymous/02-configuration.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/01-anonymous/02-configuration.md rename to docs/06-concepts/04-authentication/05-providers/01-anonymous/02-configuration.md diff --git a/docs/06-concepts/04-authentication/04-providers/01-anonymous/03-customizing-the-ui.md b/docs/06-concepts/04-authentication/05-providers/01-anonymous/03-customizing-the-ui.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/01-anonymous/03-customizing-the-ui.md rename to docs/06-concepts/04-authentication/05-providers/01-anonymous/03-customizing-the-ui.md diff --git a/docs/06-concepts/04-authentication/04-providers/01-anonymous/_category_.json b/docs/06-concepts/04-authentication/05-providers/01-anonymous/_category_.json similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/01-anonymous/_category_.json rename to docs/06-concepts/04-authentication/05-providers/01-anonymous/_category_.json diff --git a/docs/06-concepts/04-authentication/04-providers/02-email/01-setup.md b/docs/06-concepts/04-authentication/05-providers/02-email/01-setup.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/02-email/01-setup.md rename to docs/06-concepts/04-authentication/05-providers/02-email/01-setup.md diff --git a/docs/06-concepts/04-authentication/04-providers/02-email/02-configuration.md b/docs/06-concepts/04-authentication/05-providers/02-email/02-configuration.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/02-email/02-configuration.md rename to docs/06-concepts/04-authentication/05-providers/02-email/02-configuration.md diff --git a/docs/06-concepts/04-authentication/04-providers/02-email/03-customizing-the-ui.md b/docs/06-concepts/04-authentication/05-providers/02-email/03-customizing-the-ui.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/02-email/03-customizing-the-ui.md rename to docs/06-concepts/04-authentication/05-providers/02-email/03-customizing-the-ui.md diff --git a/docs/06-concepts/04-authentication/04-providers/02-email/04-admin-operations.md b/docs/06-concepts/04-authentication/05-providers/02-email/04-admin-operations.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/02-email/04-admin-operations.md rename to docs/06-concepts/04-authentication/05-providers/02-email/04-admin-operations.md diff --git a/docs/06-concepts/04-authentication/04-providers/02-email/_category_.json b/docs/06-concepts/04-authentication/05-providers/02-email/_category_.json similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/02-email/_category_.json rename to docs/06-concepts/04-authentication/05-providers/02-email/_category_.json diff --git a/docs/06-concepts/04-authentication/04-providers/03-google/01-setup.md b/docs/06-concepts/04-authentication/05-providers/03-google/01-setup.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/03-google/01-setup.md rename to docs/06-concepts/04-authentication/05-providers/03-google/01-setup.md diff --git a/docs/06-concepts/04-authentication/04-providers/03-google/02-customizations.md b/docs/06-concepts/04-authentication/05-providers/03-google/02-customizations.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/03-google/02-customizations.md rename to docs/06-concepts/04-authentication/05-providers/03-google/02-customizations.md diff --git a/docs/06-concepts/04-authentication/04-providers/03-google/03-customizing-the-ui.md b/docs/06-concepts/04-authentication/05-providers/03-google/03-customizing-the-ui.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/03-google/03-customizing-the-ui.md rename to docs/06-concepts/04-authentication/05-providers/03-google/03-customizing-the-ui.md diff --git a/docs/06-concepts/04-authentication/04-providers/03-google/04-troubleshooting.md b/docs/06-concepts/04-authentication/05-providers/03-google/04-troubleshooting.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/03-google/04-troubleshooting.md rename to docs/06-concepts/04-authentication/05-providers/03-google/04-troubleshooting.md diff --git a/docs/06-concepts/04-authentication/04-providers/03-google/_category_.json b/docs/06-concepts/04-authentication/05-providers/03-google/_category_.json similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/03-google/_category_.json rename to docs/06-concepts/04-authentication/05-providers/03-google/_category_.json diff --git a/docs/06-concepts/04-authentication/04-providers/04-apple/01-setup.md b/docs/06-concepts/04-authentication/05-providers/04-apple/01-setup.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/04-apple/01-setup.md rename to docs/06-concepts/04-authentication/05-providers/04-apple/01-setup.md diff --git a/docs/06-concepts/04-authentication/04-providers/04-apple/02-customizations.md b/docs/06-concepts/04-authentication/05-providers/04-apple/02-customizations.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/04-apple/02-customizations.md rename to docs/06-concepts/04-authentication/05-providers/04-apple/02-customizations.md diff --git a/docs/06-concepts/04-authentication/04-providers/04-apple/03-customizing-the-ui.md b/docs/06-concepts/04-authentication/05-providers/04-apple/03-customizing-the-ui.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/04-apple/03-customizing-the-ui.md rename to docs/06-concepts/04-authentication/05-providers/04-apple/03-customizing-the-ui.md diff --git a/docs/06-concepts/04-authentication/04-providers/04-apple/04-troubleshooting.md b/docs/06-concepts/04-authentication/05-providers/04-apple/04-troubleshooting.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/04-apple/04-troubleshooting.md rename to docs/06-concepts/04-authentication/05-providers/04-apple/04-troubleshooting.md diff --git a/docs/06-concepts/04-authentication/04-providers/04-apple/_category_.json b/docs/06-concepts/04-authentication/05-providers/04-apple/_category_.json similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/04-apple/_category_.json rename to docs/06-concepts/04-authentication/05-providers/04-apple/_category_.json diff --git a/docs/06-concepts/04-authentication/04-providers/05-facebook/01-setup.md b/docs/06-concepts/04-authentication/05-providers/05-facebook/01-setup.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/05-facebook/01-setup.md rename to docs/06-concepts/04-authentication/05-providers/05-facebook/01-setup.md diff --git a/docs/06-concepts/04-authentication/04-providers/05-facebook/02-customizations.md b/docs/06-concepts/04-authentication/05-providers/05-facebook/02-customizations.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/05-facebook/02-customizations.md rename to docs/06-concepts/04-authentication/05-providers/05-facebook/02-customizations.md diff --git a/docs/06-concepts/04-authentication/04-providers/05-facebook/03-customizing-the-ui.md b/docs/06-concepts/04-authentication/05-providers/05-facebook/03-customizing-the-ui.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/05-facebook/03-customizing-the-ui.md rename to docs/06-concepts/04-authentication/05-providers/05-facebook/03-customizing-the-ui.md diff --git a/docs/06-concepts/04-authentication/04-providers/05-facebook/04-troubleshooting.md b/docs/06-concepts/04-authentication/05-providers/05-facebook/04-troubleshooting.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/05-facebook/04-troubleshooting.md rename to docs/06-concepts/04-authentication/05-providers/05-facebook/04-troubleshooting.md diff --git a/docs/06-concepts/04-authentication/04-providers/05-facebook/_category_.json b/docs/06-concepts/04-authentication/05-providers/05-facebook/_category_.json similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/05-facebook/_category_.json rename to docs/06-concepts/04-authentication/05-providers/05-facebook/_category_.json diff --git a/docs/06-concepts/04-authentication/04-providers/06-firebase/01-setup.md b/docs/06-concepts/04-authentication/05-providers/06-firebase/01-setup.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/06-firebase/01-setup.md rename to docs/06-concepts/04-authentication/05-providers/06-firebase/01-setup.md diff --git a/docs/06-concepts/04-authentication/04-providers/06-firebase/02-customizations.md b/docs/06-concepts/04-authentication/05-providers/06-firebase/02-customizations.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/06-firebase/02-customizations.md rename to docs/06-concepts/04-authentication/05-providers/06-firebase/02-customizations.md diff --git a/docs/06-concepts/04-authentication/04-providers/06-firebase/03-customizing-the-ui.md b/docs/06-concepts/04-authentication/05-providers/06-firebase/03-customizing-the-ui.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/06-firebase/03-customizing-the-ui.md rename to docs/06-concepts/04-authentication/05-providers/06-firebase/03-customizing-the-ui.md diff --git a/docs/06-concepts/04-authentication/04-providers/06-firebase/04-admin-operations.md b/docs/06-concepts/04-authentication/05-providers/06-firebase/04-admin-operations.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/06-firebase/04-admin-operations.md rename to docs/06-concepts/04-authentication/05-providers/06-firebase/04-admin-operations.md diff --git a/docs/06-concepts/04-authentication/04-providers/06-firebase/05-troubleshooting.md b/docs/06-concepts/04-authentication/05-providers/06-firebase/05-troubleshooting.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/06-firebase/05-troubleshooting.md rename to docs/06-concepts/04-authentication/05-providers/06-firebase/05-troubleshooting.md diff --git a/docs/06-concepts/04-authentication/04-providers/06-firebase/_category_.json b/docs/06-concepts/04-authentication/05-providers/06-firebase/_category_.json similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/06-firebase/_category_.json rename to docs/06-concepts/04-authentication/05-providers/06-firebase/_category_.json diff --git a/docs/06-concepts/04-authentication/04-providers/07-github/01-setup.md b/docs/06-concepts/04-authentication/05-providers/07-github/01-setup.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/07-github/01-setup.md rename to docs/06-concepts/04-authentication/05-providers/07-github/01-setup.md diff --git a/docs/06-concepts/04-authentication/04-providers/07-github/02-customizations.md b/docs/06-concepts/04-authentication/05-providers/07-github/02-customizations.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/07-github/02-customizations.md rename to docs/06-concepts/04-authentication/05-providers/07-github/02-customizations.md diff --git a/docs/06-concepts/04-authentication/04-providers/07-github/03-customizing-the-ui.md b/docs/06-concepts/04-authentication/05-providers/07-github/03-customizing-the-ui.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/07-github/03-customizing-the-ui.md rename to docs/06-concepts/04-authentication/05-providers/07-github/03-customizing-the-ui.md diff --git a/docs/06-concepts/04-authentication/04-providers/07-github/04-troubleshooting.md b/docs/06-concepts/04-authentication/05-providers/07-github/04-troubleshooting.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/07-github/04-troubleshooting.md rename to docs/06-concepts/04-authentication/05-providers/07-github/04-troubleshooting.md diff --git a/docs/06-concepts/04-authentication/04-providers/07-github/_category_.json b/docs/06-concepts/04-authentication/05-providers/07-github/_category_.json similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/07-github/_category_.json rename to docs/06-concepts/04-authentication/05-providers/07-github/_category_.json diff --git a/docs/06-concepts/04-authentication/04-providers/08-microsoft/01-setup.md b/docs/06-concepts/04-authentication/05-providers/08-microsoft/01-setup.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/08-microsoft/01-setup.md rename to docs/06-concepts/04-authentication/05-providers/08-microsoft/01-setup.md diff --git a/docs/06-concepts/04-authentication/04-providers/08-microsoft/02-configuration.md b/docs/06-concepts/04-authentication/05-providers/08-microsoft/02-configuration.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/08-microsoft/02-configuration.md rename to docs/06-concepts/04-authentication/05-providers/08-microsoft/02-configuration.md diff --git a/docs/06-concepts/04-authentication/04-providers/08-microsoft/03-customizing-the-ui.md b/docs/06-concepts/04-authentication/05-providers/08-microsoft/03-customizing-the-ui.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/08-microsoft/03-customizing-the-ui.md rename to docs/06-concepts/04-authentication/05-providers/08-microsoft/03-customizing-the-ui.md diff --git a/docs/06-concepts/04-authentication/04-providers/08-microsoft/_category_.json b/docs/06-concepts/04-authentication/05-providers/08-microsoft/_category_.json similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/08-microsoft/_category_.json rename to docs/06-concepts/04-authentication/05-providers/08-microsoft/_category_.json diff --git a/docs/06-concepts/04-authentication/04-providers/09-passkey/01-setup.md b/docs/06-concepts/04-authentication/05-providers/09-passkey/01-setup.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/09-passkey/01-setup.md rename to docs/06-concepts/04-authentication/05-providers/09-passkey/01-setup.md diff --git a/docs/06-concepts/04-authentication/04-providers/09-passkey/03-customizing-the-ui.md b/docs/06-concepts/04-authentication/05-providers/09-passkey/03-customizing-the-ui.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/09-passkey/03-customizing-the-ui.md rename to docs/06-concepts/04-authentication/05-providers/09-passkey/03-customizing-the-ui.md diff --git a/docs/06-concepts/04-authentication/04-providers/09-passkey/_category_.json b/docs/06-concepts/04-authentication/05-providers/09-passkey/_category_.json similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/09-passkey/_category_.json rename to docs/06-concepts/04-authentication/05-providers/09-passkey/_category_.json diff --git a/docs/06-concepts/04-authentication/04-providers/10-custom-providers/01-overview.md b/docs/06-concepts/04-authentication/05-providers/10-custom-providers/01-overview.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/10-custom-providers/01-overview.md rename to docs/06-concepts/04-authentication/05-providers/10-custom-providers/01-overview.md diff --git a/docs/06-concepts/04-authentication/04-providers/10-custom-providers/02-oauth2-utility/01-setup.md b/docs/06-concepts/04-authentication/05-providers/10-custom-providers/02-oauth2-utility/01-setup.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/10-custom-providers/02-oauth2-utility/01-setup.md rename to docs/06-concepts/04-authentication/05-providers/10-custom-providers/02-oauth2-utility/01-setup.md diff --git a/docs/06-concepts/04-authentication/04-providers/10-custom-providers/02-oauth2-utility/02-creating-an-oauth2-based-identity-provider.md b/docs/06-concepts/04-authentication/05-providers/10-custom-providers/02-oauth2-utility/02-creating-an-oauth2-based-identity-provider.md similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/10-custom-providers/02-oauth2-utility/02-creating-an-oauth2-based-identity-provider.md rename to docs/06-concepts/04-authentication/05-providers/10-custom-providers/02-oauth2-utility/02-creating-an-oauth2-based-identity-provider.md diff --git a/docs/06-concepts/04-authentication/04-providers/10-custom-providers/02-oauth2-utility/_category_.json b/docs/06-concepts/04-authentication/05-providers/10-custom-providers/02-oauth2-utility/_category_.json similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/10-custom-providers/02-oauth2-utility/_category_.json rename to docs/06-concepts/04-authentication/05-providers/10-custom-providers/02-oauth2-utility/_category_.json diff --git a/docs/06-concepts/04-authentication/04-providers/10-custom-providers/_category_.json b/docs/06-concepts/04-authentication/05-providers/10-custom-providers/_category_.json similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/10-custom-providers/_category_.json rename to docs/06-concepts/04-authentication/05-providers/10-custom-providers/_category_.json diff --git a/docs/06-concepts/04-authentication/04-providers/_category_.json b/docs/06-concepts/04-authentication/05-providers/_category_.json similarity index 100% rename from docs/06-concepts/04-authentication/04-providers/_category_.json rename to docs/06-concepts/04-authentication/05-providers/_category_.json diff --git a/docs/06-concepts/04-authentication/05-token-managers/01-managing-tokens.md b/docs/06-concepts/04-authentication/05-token-managers/01-managing-tokens.md deleted file mode 100644 index 235ee79b..00000000 --- a/docs/06-concepts/04-authentication/05-token-managers/01-managing-tokens.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -description: Token managers issue, validate, revoke, and list authentication tokens in Serverpod. Learn how they work and how to access them. ---- - -# Managing tokens - -The authentication system uses token managers to handle authentication tokens. Token managers are responsible for issuing, validating, revoking, and listing authentication tokens. - -## Default token managers - -Serverpod provides two built-in token managers: -- `JwtTokenManager` for JWT-based authentication. See [JWT Token Manager](./jwt-token-manager) for details. -- `ServerSideSessionsTokenManager` for session-based authentication. See [Server-Side Sessions Token Manager](./server-side-sessions-token-manager) for details. - -## Using the token managers - -After configuring at least one token manager using the `pod.initializeAuthServices()` method, you can access the token manager instance using the `AuthServices.instance.tokenManager` property. - -```dart -final tokenManager = AuthServices.instance.tokenManager; -``` - -It will return a `MultiTokenManager` instance that combines all the token managers configured for listing, validating and revoking tokens. - -The `MultiTokenManager` is a composite token manager that is automatically created when initializing the authentication services and combines multiple token managers. It: - -- Uses the primary token manager for issuing new tokens. -- Validates tokens against all managers (primary and additional). -- Delegates management operations to all managers. - -### Token validation flow - -When validating a token, the `MultiTokenManager`: - -1. Tries the primary token manager first. -2. If validation fails, tries each additional token manager in order. -3. Returns the first successful validation result. -4. Returns `null` if all managers fail to validate. - -This allows you to support multiple token types simultaneously, which is useful for: - -- Migrating from one token type to another. -- Supporting legacy tokens alongside new tokens. -- Using different token types for different use cases. - -## Token lifecycle management - -### Issuing tokens - -Tokens are issued automatically by identity providers when users authenticate. You can also issue tokens programmatically: - -```dart -final authSuccess = await AuthServices.instance.tokenManager.issueToken( - session, - authUserId: userId, - method: 'custom', - scopes: {Scope.admin}, -); -``` - -#### Attaching metadata to tokens - -It is possible to attach metadata to tokens using either global callbacks configured on each token manager or by inserting a metadata row right after issuing the token. For more details, see the specific configuration sections for [Server-Side Sessions](./server-side-sessions-token-manager#attaching-custom-metadata-to-sessions) and [JWT](./jwt-token-manager#attaching-custom-metadata-to-tokens). - -### Validating tokens - -Tokens are validated automatically by the authentication handler. You can also validate tokens manually: - -```dart -final authInfo = await AuthServices.instance.tokenManager.validateToken( - session, - token, -); - -if (authInfo != null) { - // Token is valid - final userId = authInfo.userIdentifier; -} else { - // Token is invalid or expired -} -``` - -### Revoking tokens - -Revoke specific tokens by token ID: - -```dart -await AuthServices.instance.tokenManager.revokeToken( - session, - tokenId: 'token-id-here', -); -``` - -When using custom metadata on [Server-Side Sessions](./server-side-sessions-token-manager#attaching-custom-metadata-to-sessions) or [JWT](./jwt-token-manager#attaching-custom-metadata-to-tokens), you can obtain token IDs from your metadata tables (for example, by device or user agent) and pass them to `revokeToken` to revoke by that criteria. - -Revoke all tokens for a user: - -```dart -await AuthServices.instance.tokenManager.revokeAllTokens( - session, - authUserId: userId, -); -``` - -Revoke tokens by method: - -```dart -await AuthServices.instance.tokenManager.revokeAllTokens( - session, - authUserId: userId, - method: 'email', -); -``` - -### Listing tokens - -List all tokens for a user: - -```dart -final tokens = await AuthServices.instance.tokenManager.listTokens( - session, - authUserId: userId, -); - -for (final token in tokens) { - print('Token: ${token.tokenId}, Method: ${token.method}'); -} -``` - -List tokens by method (i.e. "google", "email", "apple", etc.): - -```dart -final tokens = await AuthServices.instance.tokenManager.listTokens( - session, - authUserId: userId, - method: 'google', -); -``` - -## Accessing specific token managers - -In case more than one token manager is configured, you can access specific token manager types from the `AuthServices` instance using the `getTokenManager()` method. - -```dart -final jwtManager = AuthServices.getTokenManager(); -final sessionManager = AuthServices.getTokenManager(); -``` diff --git a/docs/06-concepts/04-authentication/05-token-managers/_category_.json b/docs/06-concepts/04-authentication/05-token-managers/_category_.json deleted file mode 100644 index 2e1ea7d4..00000000 --- a/docs/06-concepts/04-authentication/05-token-managers/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Token Managers", - "collapsed": true -} diff --git a/docs/06-concepts/04-authentication/06-token-managers/01-managing-tokens.md b/docs/06-concepts/04-authentication/06-token-managers/01-managing-tokens.md new file mode 100644 index 00000000..1b9028bf --- /dev/null +++ b/docs/06-concepts/04-authentication/06-token-managers/01-managing-tokens.md @@ -0,0 +1,163 @@ +--- +description: Token managers issue, validate, revoke, and list authentication tokens in Serverpod. Learn how they work and how to access them. +--- + +# Managing tokens + +An authentication token is the credential the app sends with each request to prove who the signed-in user is. Token managers issue these tokens when a user signs in, and validate, revoke, and list them afterwards. + +## Default token managers + +Serverpod provides two built-in token managers: + +- `JwtTokenManager` for JWT-based authentication. See [JWT token manager](./jwt-token-manager) for details. +- `ServerSideSessionsTokenManager` for session-based authentication. See [Server-side sessions token manager](./server-side-sessions-token-manager) for details. + +The main trade-off between them: + +| | `JwtTokenManager` | `ServerSideSessionsTokenManager` | +| ---------- | ------------------------------------------------------ | -------------------------------- | +| Validation | Stateless, no database query | Database query per validation | +| Revocation | Takes effect when the short-lived access token expires | Immediate | + +Pick JWT to avoid database load on every request. Pick server-side sessions when revocation must take effect immediately. + +## Using the token managers + +After you configure at least one token manager with `pod.initializeAuthServices()` (see [Setup](../setup)), access the token manager through the `AuthServices.instance.tokenManager` property. + +```dart +final tokenManager = AuthServices.instance.tokenManager; +``` + +The property returns a `MultiTokenManager`, a wrapper that combines all configured token managers. Serverpod creates it automatically when you initialize the authentication services. The first builder you pass becomes the primary token manager. The `MultiTokenManager`: + +- Uses the primary token manager for issuing new tokens. +- Validates tokens against all managers (primary and additional). +- Delegates management operations to all managers. + +### Token validation flow + +When validating a token, the `MultiTokenManager`: + +1. Tries the primary token manager first. +2. If validation fails, tries each additional token manager in order. +3. Returns the first successful validation result. +4. Returns `null` if all managers fail to validate. + +This allows you to support multiple token types simultaneously, which is useful for: + +- Migrating from one token type to another. +- Supporting legacy tokens alongside new tokens. +- Using different token types for different use cases. + +## Token lifecycle management + +### Issuing tokens + +Tokens are issued automatically by identity providers when users authenticate. You can also issue tokens programmatically: + +```dart +final authSuccess = await AuthServices.instance.tokenManager.issueToken( + session, + authUserId: userId, + method: 'custom', + scopes: {Scope.admin}, +); +``` + +The returned `AuthSuccess` carries everything the app needs: the token, its expiry, the granted scopes, and, for JWT, the refresh token. Send the whole object back to the app. + +#### Attaching metadata to tokens + +You can attach metadata to tokens in two ways. Configure a global callback on the token manager, or insert a metadata row right after issuing the token. For more details, see the specific configuration sections for [server-side sessions](./server-side-sessions-token-manager#attaching-custom-metadata-to-sessions) and [JWT](./jwt-token-manager#attaching-custom-metadata-to-tokens). + +### Validating tokens + +Tokens are validated automatically by the authentication handler, the hook Serverpod runs for every request that carries an authentication token. You can also validate tokens manually: + +```dart +final authInfo = await AuthServices.instance.tokenManager.validateToken( + session, + token, +); + +if (authInfo != null) { + // Token is valid + final authUserId = authInfo.authUserId; +} else { + // Token is invalid or expired +} +``` + +### Revoking tokens + +Revoke specific tokens by token ID: + +```dart +await AuthServices.instance.tokenManager.revokeToken( + session, + tokenId: 'token-id-here', +); +``` + +If you attach custom metadata to [server-side sessions](./server-side-sessions-token-manager#attaching-custom-metadata-to-sessions) or [JWT tokens](./jwt-token-manager#attaching-custom-metadata-to-tokens), you can look up token IDs in your metadata tables, for example by device or user agent. Pass those IDs to `revokeToken` to revoke exactly those tokens. + +Revoke all tokens for a user: + +```dart +await AuthServices.instance.tokenManager.revokeAllTokens( + session, + authUserId: userId, +); +``` + +Revoke tokens by method: + +```dart +await AuthServices.instance.tokenManager.revokeAllTokens( + session, + authUserId: userId, + method: 'email', +); +``` + +### Listing tokens + +List all tokens for a user: + +```dart +final tokens = await AuthServices.instance.tokenManager.listTokens( + session, + authUserId: userId, +); + +for (final token in tokens) { + print('Token: ${token.tokenId}, Method: ${token.method}'); +} +``` + +List tokens by method (for example, `'google'`, `'email'`, or `'apple'`): + +```dart +final tokens = await AuthServices.instance.tokenManager.listTokens( + session, + authUserId: userId, + method: 'google', +); +``` + +## Accessing specific token managers + +If you configure more than one token manager, retrieve a specific one with the static `AuthServices.getTokenManager()` method. + +```dart +final jwtManager = AuthServices.getTokenManager(); +final sessionManager = AuthServices.getTokenManager(); +``` + +## Related + +- [Setup](../setup): configure token managers with `initializeAuthServices`. +- [JWT token manager](./jwt-token-manager): stateless tokens with automatic refresh. +- [Server-side sessions token manager](./server-side-sessions-token-manager): database-backed sessions with immediate revocation. diff --git a/docs/06-concepts/04-authentication/05-token-managers/02-jwt-token-manager.md b/docs/06-concepts/04-authentication/06-token-managers/02-jwt-token-manager.md similarity index 75% rename from docs/06-concepts/04-authentication/05-token-managers/02-jwt-token-manager.md rename to docs/06-concepts/04-authentication/06-token-managers/02-jwt-token-manager.md index ac198415..5c3507da 100644 --- a/docs/06-concepts/04-authentication/05-token-managers/02-jwt-token-manager.md +++ b/docs/06-concepts/04-authentication/06-token-managers/02-jwt-token-manager.md @@ -2,9 +2,9 @@ description: The JwtTokenManager provides stateless JWT authentication with short-lived access tokens and refresh token rotation. Configure it on the server. --- -# JWT Token Manager +# JWT token manager -The `JwtTokenManager` uses JWT (JSON Web Tokens) for stateless authentication. This token manager provides: +The `JwtTokenManager` uses JWT (JSON Web Tokens) for stateless authentication. It works with two kinds of tokens. The access token is short-lived and is sent with every request. The refresh token is long-lived and is used only to obtain a new access token when the old one expires. This token manager provides: - Stateless authentication (no database queries for validation). - Access tokens with short lifetimes. @@ -13,7 +13,7 @@ The `JwtTokenManager` uses JWT (JSON Web Tokens) for stateless authentication. T ## Server-side configuration -This token manager is created by passing a `JwtConfig` object to the `pod.initializeAuthServices()` as a `tokenManagerBuilder`. +The `JwtTokenManager` is created by passing a `JwtConfig` object in the `tokenManagerBuilders` list of `pod.initializeAuthServices()`: ```dart pod.initializeAuthServices( @@ -33,7 +33,7 @@ pod.initializeAuthServices( ``` :::tip -You can use the `JwtConfigFromPasswords` constructor in replacement of the `JwtConfig` above to automatically load the credentials from the `config/passwords.yaml` file or environment variables. It will expect either the following keys on the file: +You can use the `JwtConfigFromPasswords` constructor instead of the `JwtConfig` above. It loads the credentials from the `config/passwords.yaml` file or environment variables. It expects either the following keys in the file: - `jwtRefreshTokenHashPepper` - `jwtHmacSha512PrivateKey` @@ -44,7 +44,7 @@ Or the following environment variables: - `SERVERPOD_PASSWORD_jwtHmacSha512PrivateKey` ::: -Then, extend the abstract endpoint to expose it on the server: +The app calls a refresh endpoint to renew expired access tokens without asking the user to sign in again. Extend the abstract endpoint to expose it on the server: ```dart import 'package:serverpod_auth_idp_server/core.dart' as core; @@ -52,14 +52,14 @@ import 'package:serverpod_auth_idp_server/core.dart' as core; class RefreshJwtTokensEndpoint extends core.RefreshJwtTokensEndpoint {} ``` -Finally, run `serverpod generate` to generate the client code and expose the endpoint on the server. +Finally, run `serverpod generate` so the app can call the endpoint. If your server runs with `serverpod start`, this happens automatically. ### Basic configuration options - `algorithm`: Required. The algorithm to use for signing tokens (HMAC SHA-512, HMAC SHA-256 or ECDSA SHA-512). -- `refreshTokenHashPepper`: Required. A secret pepper for hashing refresh tokens. Must be at least 10 characters long, but [the recommended length is 32 bytes](https://www.ietf.org/archive/id/draft-ietf-kitten-password-storage-04.html#name-storage-2). +- `refreshTokenHashPepper`: Required. A secret pepper for hashing refresh tokens (see [storing secrets](../setup#storing-secrets) for what a pepper is). Must be at least 10 characters long, but [the recommended length is 32 bytes](https://www.ietf.org/archive/id/draft-ietf-kitten-password-storage-04.html#name-storage-2). -#### Token Algorithms +#### Token algorithms There are three supported token algorithms: @@ -88,11 +88,11 @@ There are three supported token algorithms: ), ``` -As of now, the `JwtConfigFromPasswords` only supports HMAC SHA-512 and HMAC SHA-256. To use ECDSA SHA-512, you need to pass the private and public keys manually. +As of now, the `JwtConfigFromPasswords` only supports HMAC SHA-512. To use HMAC SHA-256 or ECDSA SHA-512, use `JwtConfig` and pass the keys manually. ### Extra configuration options -Below is an example of a non-exhaustive list of some of the most common configuration options for the `JwtTokenManager`. For more details on all options, check the `JwtConfig` in-code documentation. +Common configuration options for the `JwtTokenManager`. For more details on all options, check the `JwtConfig` in-code documentation. ```dart final jwtConfig = JwtConfigFromPasswords( @@ -217,8 +217,14 @@ await TokenMetadata.db.insertRow( ## Client-side configuration -When using the `JwtTokenManager` in the server, no extra configuration is needed on the client. It will automatically include the access token in requests to the server and eagerly refresh the token when it is 30 seconds away from expiring. In case the refresh token expires, the client will automatically sign the user out and redirect to the login page. +The `JwtTokenManager` needs no extra configuration in your app. The client includes the access token in requests automatically and refreshes it when it is 30 seconds from expiring. If the refresh token itself expires, the client signs the user out the next time it validates the tokens. Your app decides what to show next, for example by listening to [authentication state changes](../basics#monitor-authentication-changes). :::warning The deprecated `client.openStreamingConnection()` interface is not compatible with JWT authentication. If you are using JWT tokens, migrate to [streaming methods](../../endpoints-and-apis/streaming) instead. ::: + +## Related + +- [Managing tokens](./managing-tokens): issue, validate, revoke, and list tokens. +- [Server-side sessions token manager](./server-side-sessions-token-manager): the database-backed alternative. +- [Setup](../setup): where token managers are configured. diff --git a/docs/06-concepts/04-authentication/05-token-managers/03-server-side-sessions-token-manager.md b/docs/06-concepts/04-authentication/06-token-managers/03-server-side-sessions-token-manager.md similarity index 68% rename from docs/06-concepts/04-authentication/05-token-managers/03-server-side-sessions-token-manager.md rename to docs/06-concepts/04-authentication/06-token-managers/03-server-side-sessions-token-manager.md index d7f4f45b..a14449ee 100644 --- a/docs/06-concepts/04-authentication/05-token-managers/03-server-side-sessions-token-manager.md +++ b/docs/06-concepts/04-authentication/06-token-managers/03-server-side-sessions-token-manager.md @@ -2,18 +2,17 @@ description: The ServerSideSessionsTokenManager provides database-backed sessions with immediate revocation and inactivity timeouts. Configure it on the server. --- -# Server-side Sessions Token Manager +# Server-side sessions token manager -The `ServerSideSessionsTokenManager` uses session-based tokens stored in the database. This token manager provides: +The `ServerSideSessionsTokenManager` validates each token against a server-side session, a database record of who is signed in on which device. The database stores only a hash of the session key, never the token itself, which stays in the app. These auth sessions are unrelated to the `Session` object that endpoint methods receive, which is the context of a single request. This token manager provides: - Stateful authentication (database queries for validation). - Immediate session revocation. -- Immediate scopes update on existing sessions. - Support for session expiration and inactivity timeouts. ## Server-side configuration -This token manager is created by passing a `ServerSideSessionsConfig` object to the `pod.initializeAuthServices()` as a `tokenManagerBuilder`. +The `ServerSideSessionsTokenManager` is created by passing a `ServerSideSessionsConfig` object in the `tokenManagerBuilders` list of `pod.initializeAuthServices()`: ```dart pod.initializeAuthServices( @@ -27,16 +26,16 @@ pod.initializeAuthServices( ``` :::tip -You can use the `ServerSideSessionsConfigFromPasswords` constructor in replacement of the `ServerSideSessionsConfig` above to automatically load the credentials from the `config/passwords.yaml` file or environment variables. It will expect the `serverSideSessionKeyHashPepper` key on the file or the `SERVERPOD_PASSWORD_serverSideSessionKeyHashPepper` environment variable. +You can use the `ServerSideSessionsConfigFromPasswords` constructor instead of the `ServerSideSessionsConfig` above. It loads the pepper from the `config/passwords.yaml` file or environment variables. It expects the `serverSideSessionKeyHashPepper` key in the file, or the `SERVERPOD_PASSWORD_serverSideSessionKeyHashPepper` environment variable. ::: ### Basic configuration options -- `sessionKeyHashPepper`: Required. A secret pepper used for hashing session keys. Must be at least 10 characters long, but [the recommended length is 32 bytes](https://www.ietf.org/archive/id/draft-ietf-kitten-password-storage-04.html#name-storage-2). +- `sessionKeyHashPepper`: Required. A secret pepper used for hashing session keys (see [storing secrets](../setup#storing-secrets) for what a pepper is). Must be at least 10 characters long, but [the recommended length is 32 bytes](https://www.ietf.org/archive/id/draft-ietf-kitten-password-storage-04.html#name-storage-2). ### Extra configuration options -Below is an example of a non-exhaustive list of some of the most common configuration options for the `ServerSideSessionsTokenManager`. For more details on all options, check the `ServerSideSessionsConfig` in-code documentation. +Common configuration options for the `ServerSideSessionsTokenManager`. For more details on all options, check the `ServerSideSessionsConfig` in-code documentation. ```dart final serverSideSessionsConfig = ServerSideSessionsConfigFromPasswords( @@ -70,7 +69,7 @@ fields: ### The [ServerSideSession] this metadata belongs to serverSideSession: module:serverpod_auth_core:ServerSideSession?, relation(onDelete=Cascade) - ### The name of the token + ### A display name for the session name: String? ### Device information for the session @@ -113,15 +112,15 @@ ServerSideSessionsConfigFromPasswords( ), ``` -To revoke tokens based on your custom metadata, query the metadata table for the session IDs you want to revoke and call `revokeToken` for each: +To revoke sessions based on your custom metadata, query the metadata table for the session IDs you want to revoke and call `revokeToken` for each: ```dart -final tokenMetadata = await SessionMetadata.db.find( +final sessionMetadata = await SessionMetadata.db.find( session, where: (final row) => row.deviceName.equals('Old Device'), ); -for (final row in tokenMetadata) { +for (final row in sessionMetadata) { await AuthServices.instance.tokenManager.revokeToken( session, tokenId: row.serverSideSessionId.toString(), @@ -157,4 +156,10 @@ See [Issuing Tokens](./managing-tokens#issuing-tokens) in Managing tokens for mo ## Client-side configuration -When using the `ServerSideSessionsTokenManager` in the server, no extra configuration is needed on the client. It will automatically include the session token in requests to the server. In case the session expires or is revoked, the client will automatically sign the user out and redirect to the login page. +The `ServerSideSessionsTokenManager` needs no extra configuration in your app. The client includes the session token in requests automatically. If the session expires or is revoked, the client signs the user out the next time it validates the session. Your app decides what to show next, for example by listening to [authentication state changes](../basics#monitor-authentication-changes). + +## Related + +- [Managing tokens](./managing-tokens): issue, validate, revoke, and list tokens. +- [JWT token manager](./jwt-token-manager): the stateless alternative. +- [Setup](../setup): where token managers are configured. diff --git a/docs/06-concepts/04-authentication/06-token-managers/_category_.json b/docs/06-concepts/04-authentication/06-token-managers/_category_.json new file mode 100644 index 00000000..4987418f --- /dev/null +++ b/docs/06-concepts/04-authentication/06-token-managers/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Token managers", + "collapsed": true +} diff --git a/docs/06-concepts/04-authentication/06-ui-components.md b/docs/06-concepts/04-authentication/07-ui-components.md similarity index 64% rename from docs/06-concepts/04-authentication/06-ui-components.md rename to docs/06-concepts/04-authentication/07-ui-components.md index aef21013..008af9b5 100644 --- a/docs/06-concepts/04-authentication/06-ui-components.md +++ b/docs/06-concepts/04-authentication/07-ui-components.md @@ -1,64 +1,34 @@ --- -sidebar_label: UI Components -description: Authentication UI components and controllers let you build sign-in, registration, and password flows, including the all-in-one SignInWidget. +sidebar_label: UI components +description: The SignInWidget is an all-in-one sign-in UI that detects the providers you configured. Control which providers it shows, style its buttons, and translate its texts. --- # Authentication UI components -The authentication system provides a comprehensive set of UI components and controllers for building authentication interfaces. These components handle the complete authentication flow, including sign-in, registration, and password management. - -## Overview - -The UI component architecture consists of: - -- **Common widgets**: Reusable widgets for building custom authentication interfaces. -- **Provider-specific widgets**: Pre-built UI components that are used in specific providers. -- **Controllers**: Business logic classes that can be used with custom UI. +The authentication module ships with UI components and controllers for building sign-in interfaces. This page covers the all-in-one `SignInWidget`: how to control which providers it shows, how to style its buttons, and how to translate its texts. Each provider also has its own widgets and controllers for building a custom UI, covered on the provider pages. ## SignInWidget -The `SignInWidget` is an all-in-one widget that automatically detects available authentication providers and displays the appropriate sign-in options. +The `SignInWidget` is an all-in-one widget that automatically detects available authentication providers and displays the appropriate sign-in options. The [setup page](./setup#present-the-authentication-ui) shows the full wiring. In short: ```dart -import 'package:flutter/material.dart'; -import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart'; -import 'package:your_client/your_client.dart'; - -class SignInPage extends StatelessWidget { - final Client client; - - const SignInPage({required this.client, super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: SignInWidget( - 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')), - ); - }, - ), - ); - } -} +SignInWidget( + client: client, + onAuthenticated: () { + // Runs after a successful sign-in. Do not navigate here; listen to + // authentication state changes instead. + }, + onError: (error) { + // Show the error to the user. + }, +) ``` ### Disabling providers -It is not possible to forcefully enable a provider, since this depends on the configuration of the identity providers, as described in the [setup section](./setup#identity-providers-configuration). +You can hide specific providers in the app while keeping them available on the server. This is useful when you want to phase out a provider but keep compatibility with older app versions. -But, even though the `SignInWidget` automatically detects enabled providers, you can disable specific providers if you want to hide them on the client, but still keep them available on the server. - -This is useful if you want to gradually disable a provider, but still keep compatibility with older clients. +The reverse is not possible. A provider only appears when it is configured on the server, as described in [identity providers configuration](./setup#identity-providers-configuration). ```dart SignInWidget( @@ -77,7 +47,7 @@ SignInWidget( ### Customizing SignInWidget -You can customize individual provider widgets: +The `SignInWidget` builds a default widget for each identity provider it detects. Pass your own widget for a provider to replace the default one: ```dart final signInWidget = SignInWidget( @@ -93,7 +63,6 @@ final signInWidget = SignInWidget( ), googleSignInWidget: GoogleSignInWidget( client: client, - theme: GSIButtonTheme.filledBlack, scopes: const [ ...GoogleAuthController.defaultScopes, 'https://www.googleapis.com/auth/youtube', @@ -102,13 +71,6 @@ final signInWidget = SignInWidget( onError: _onError, // ... custom configuration ), - appleSignInWidget: AppleSignInWidget( - client: client, - style: AppleButtonStyle.black, - onAuthenticated: _onAuthenticated, - onError: _onError, - // ... custom configuration - ), onAuthenticated: _onAuthenticated, onError: _onError, ); @@ -122,7 +84,25 @@ void _onError(Object error) { } ``` -For more details on all options of each provider widget, see the "Customizing UI" section of the specific provider documentation. There you will also find information on how to build a custom UI with the controller. For example, see the [Email Provider](./providers/email/customizing-the-ui) documentation. +### Styling the buttons + +Inside `SignInWidget`, every provider button shares one neutral, theme-aware appearance. To change it, pass a `SignInButtonStyle` as `buttonStyle`: + +```dart +SignInWidget( + client: client, + buttonStyle: const SignInButtonStyle( + shape: SignInButtonShape.rounded, + text: SignInButtonTextVariant.signInWith, + ), + onAuthenticated: _onAuthenticated, + onError: _onError, +) +``` + +Fields set on `buttonStyle` apply to the provider buttons. Brand style presets, such as `GoogleButtonStyle.filledBlack`, only apply when a provider widget is used on its own, outside `SignInWidget`. + +For all options of each provider widget, see the "Customizing the UI" page for that provider, which also covers building a custom UI with the provider's controller. For example, see [the email provider](./providers/email/customizing-the-ui). ## Localization @@ -223,4 +203,10 @@ SignInLocalizationProvider( ### Switching locale -`SignInLocalizationProvider` hands the text objects to the widgets below it. It does not read Flutter's current `Locale` or pick a translation for you. In an app that supports several locales, select the text objects with your existing localization setup and rebuild the provider when the locale changes. +The `SignInLocalizationProvider` widget hands the text objects to the widgets below it. It does not read Flutter's current `Locale` or pick a translation for you. In an app that supports several locales, select the text objects with your existing localization setup and rebuild `SignInLocalizationProvider` when the locale changes. + +## Related + +- [Setup](./setup): wire the sign-in UI into your app. +- [The basics](./basics): react to authentication state changes. +- [Custom overrides](./custom-overrides): replace the built-in UI and endpoints entirely. diff --git a/docs/06-concepts/04-authentication/07-profile-photos.md b/docs/06-concepts/04-authentication/08-profile-photos.md similarity index 64% rename from docs/06-concepts/04-authentication/07-profile-photos.md rename to docs/06-concepts/04-authentication/08-profile-photos.md index 147b7101..3437d691 100644 --- a/docs/06-concepts/04-authentication/07-profile-photos.md +++ b/docs/06-concepts/04-authentication/08-profile-photos.md @@ -1,19 +1,19 @@ --- sidebar_label: Profile photos -description: Upload, update, and display user profile photos with Serverpod's auth module, including server setup, Flutter client code, and storage configuration. +description: User profile photos are handled by the authentication module and served from public storage. Expose the upload endpoint, display photos in your app, and configure image size, format, and storage. --- # Manage user profile photos Add profile photo upload and display to your Serverpod app using the authentication module's built-in image handling. When you're done, signed-in users can upload a photo from Flutter, and your app displays it from a public URL. -Profile photos are stored in **public** storage, square-cropped and resized on the server, and exposed as a public HTTP URL on `UserProfileModel.imageUrl`. The auth module registers a read-only `userProfileInfo` endpoint by default; you expose upload methods yourself. +Profile photos are stored in **public** storage, square-cropped and resized on the server, and exposed as a public HTTP URL on `UserProfileModel.imageUrl`. The authentication module registers a read-only `userProfileInfo` endpoint by default. You expose the upload methods yourself. ## Before you start - [Authentication is set up](./setup) and users can sign in. - Users have a `UserProfile` (created automatically by most identity providers on first sign-in). -- You have run `serverpod generate` at least once. +- You have run `serverpod generate` at least once, or started the server with `serverpod start`. For the upload UI, add these Flutter dependencies: @@ -21,13 +21,13 @@ For the upload UI, add these Flutter dependencies: dependencies: image_picker: ^1.0.0 image: ^4.0.15 - file_picker: '>=8.1.0 <11.0.0' # web - image_cropper: '>=7.0.0 <13.0.0' # mobile, optional + file_picker: '>=8.1.0 <11.0.0' # web + image_cropper: '>=7.0.0 <13.0.0' # mobile, optional ``` ## Expose the profile edit endpoint -The auth module registers `userProfileInfo` (read-only `get()`). Upload methods live on `UserProfileEditBaseEndpoint`, which you must expose on your server. +The authentication module registers `userProfileInfo` (read-only `get()`). Upload methods live on `UserProfileEditBaseEndpoint`, which you must expose on your server. Create a concrete endpoint: @@ -39,15 +39,15 @@ import 'package:serverpod_auth_idp_server/core.dart'; class UserProfileEndpoint extends UserProfileEditBaseEndpoint {} ``` -Run `serverpod generate`. Your Flutter client exposes methods on `client.userProfile` (the accessor matches your endpoint class name): +Run `serverpod generate`, or let `serverpod start` do it for you. Your app then calls the methods on `client.userProfile` (the accessor matches your endpoint class name): -| Method | Returns | Auth required | -| ------ | ------- | ------------- | -| `get()` | `UserProfileModel` | Yes | -| `setUserImage(ByteData image)` | `UserProfileModel` | Yes | -| `removeUserImage()` | `UserProfileModel` | Yes | -| `changeUserName(String? userName)` | `UserProfileModel` | Yes | -| `changeFullName(String? fullName)` | `UserProfileModel` | Yes | +| Method | Returns | Auth required | +| ---------------------------------- | ------------------ | ------------- | +| `get()` | `UserProfileModel` | Yes | +| `setUserImage(ByteData image)` | `UserProfileModel` | Yes | +| `removeUserImage()` | `UserProfileModel` | Yes | +| `changeUserName(String? userName)` | `UserProfileModel` | Yes | +| `changeFullName(String? fullName)` | `UserProfileModel` | Yes | ```dart final profile = await client.userProfile.setUserImage(byteData); @@ -62,7 +62,7 @@ final profile = await client.modules.serverpod_auth_core.userProfileInfo.get(); ### Restrict who can edit -There is no built-in `userCanEditUserImage` flag in `serverpod_auth_idp` (legacy `serverpod_auth` had this). Override the endpoint to restrict uploads: +The built-in endpoint always acts on the signed-in user, so any signed-in user can edit their own photo. To add your own rules, override the method on your endpoint class. The legacy `serverpod_auth` module had a `userCanEditUserImage` flag for this; `serverpod_auth_idp` does not. ```dart class UserProfileEndpoint extends UserProfileEditBaseEndpoint { @@ -78,7 +78,7 @@ class UserProfileEndpoint extends UserProfileEditBaseEndpoint { ## Upload a profile photo from Flutter -There is no built-in upload widget in the authentication module. Pick an image on the client, convert it to `ByteData`, and pass it to `setUserImage`. +There is no built-in upload widget in the authentication module. Pick an image in the app, convert it to `ByteData`, and pass it to `setUserImage`. The server decodes the image, crops it to a square, resizes it, and stores it. The format you send does not affect the stored format. @@ -134,12 +134,12 @@ On iOS and Android, use `image_cropper` with a circular crop before upload for b ### Error handling -| Failure | What happens | -| ------- | ------------ | -| User not signed in | `ServerpodUnauthenticatedException` | -| No profile exists | `UserProfileNotFoundException` | -| Invalid or corrupt image bytes | Server throws when image decode fails | -| User cancels picker | Return `null` on the client; no server call | +| Failure | What happens | +| ------------------------------ | ------------------------------------------------------------------------------------- | +| User not signed in | The call fails and surfaces as `ServerpodClientUnauthorized` in the app | +| No profile exists | The server throws `UserProfileNotFoundException`. The app sees a generic server error | +| Invalid or corrupt image bytes | Server throws when image decode fails | +| User cancels picker | Return `null` in the app. No server call is made | ## Display the profile photo @@ -159,7 +159,7 @@ CircleAvatar( For caching, use `cached_network_image` or `extended_image`. -The `imageUrl` field is public. If you fetch another user's `UserProfileModel` from your own endpoint, display `imageUrl` the same way. Only upload endpoints are scoped to the signed-in user. +The built-in endpoint methods always act on the signed-in user, including the read-only `get`. To show another user's photo, fetch their `UserProfileModel` from your own endpoint and display its `imageUrl` the same way. The URL itself is not access-controlled, so anything holding it can load the image. ## Remove or replace a photo @@ -177,13 +177,13 @@ Replace a photo by calling `setUserImage` again with new bytes. The server creat Pass `UserProfileConfig` to `initializeAuthServices` in `server.dart`: -| Setting | Default | Description | -| ------- | ------- | ----------- | -| `userImageSize` | `256` | Output width and height in pixels (square) | -| `userImageFormat` | `UserProfileImageType.jpg` | Stored format (`.jpg` or `.png`) | -| `userImageQuality` | `70` | JPG quality (ignored for PNG) | -| `userImageGenerator` | `defaultUserImageGenerator` | Used by `setDefaultUserImage()` | -| `imageFetchFunc` | `http.get` | Used when importing from a URL (social sign-in) | +| Setting | Default | Description | +| -------------------- | --------------------------- | ----------------------------------------------- | +| `userImageSize` | `256` | Output width and height in pixels (square) | +| `userImageFormat` | `UserProfileImageType.jpg` | Stored format (`.jpg` or `.png`) | +| `userImageQuality` | `70` | JPG quality (ignored for PNG) | +| `userImageGenerator` | `defaultUserImageGenerator` | Used by `setDefaultUserImage()` | +| `imageFetchFunc` | `http.get` | Used when importing from a URL (social sign-in) | ```dart pod.initializeAuthServices( @@ -205,17 +205,17 @@ pod.initializeAuthServices( ); ``` -The `defaultUserImageGenerator` function produces a colored circle with the first letter of the user name. +The `defaultUserImageGenerator` function produces a solid-colored square with the first letter of the user name. It looks circular when you display it in a circular widget such as `CircleAvatar`. The server automatically validates and optimizes uploads: it decodes bytes (must be a valid image), square-crops to `userImageSize`, re-encodes as JPG or PNG per config, and stores the file in public storage. -Recommended client-side checks (not enforced by the server): +Recommended checks in the app (not enforced by the server): - Allow only JPG and PNG extensions. - Reject files over roughly 5 to 10 MB before upload. - Verify `decodeImage` succeeds before calling `setUserImage`. -Google and similar providers automatically import a profile photo on first sign-in when the provider returns one and the user has no image yet. +Providers such as Google import a profile photo whenever the user signs in, the provider returns a photo, and the user has no image set. This is not limited to the first sign-in, so a photo the user removed can come back on their next sign-in with that provider. ## Configure storage for production @@ -229,7 +229,7 @@ http://localhost:8080/serverpod_cloud_storage?method=file&path=serverpod/user_im Set `publicHost`, `publicPort`, and `publicScheme` in `config/development.yaml` to match how clients reach your API server. -For production, configure object storage (S3, GCP, or R2) for `storageId: 'public'`. See [Uploading files](../endpoints-and-apis/file-uploads). Profile images require publicly accessible URLs because clients load them directly over HTTP. +For production, configure object storage (S3, Google Cloud Storage, or R2) for `storageId: 'public'`. See [Uploading files](../endpoints-and-apis/file-uploads). Profile images require publicly accessible URLs because clients load them directly over HTTP. ## Use server-side APIs for custom logic @@ -265,7 +265,7 @@ final profile = await session.authenticated!.userProfile(session); 3. Confirm the returned `imageUrl` is non-null and starts with your server's public host. 4. Open `imageUrl` in a browser. The image loads. 5. Confirm your UI updates after upload. -6. Call `removeUserImage()`. `imageUrl` becomes `null` and your placeholder appears. +6. Call `removeUserImage()`. The `imageUrl` field becomes `null` and your placeholder appears. ## Troubleshooting @@ -273,13 +273,13 @@ final profile = await session.authenticated!.userProfile(session); Create `UserProfileEndpoint extends UserProfileEditBaseEndpoint` and run `serverpod generate`. -### `UserProfileNotFoundException` +### `UserProfileNotFoundException` in the server logs -The signed-in user has no profile yet. Ensure your identity provider creates one on first sign-in. +The signed-in user has no profile yet. Ensure your identity provider creates one on first sign-in. The app only sees a generic server error, so check the server logs for this exception. ### Image URL does not load -Check `publicHost`, `publicPort`, and `publicScheme` in your server config match how clients reach the API server. +Check that `publicHost`, `publicPort`, and `publicScheme` under `apiServer` in your server config match how apps reach the API server. ### Invalid image error @@ -296,6 +296,6 @@ The `file_picker` package may return null bytes. Ensure you read bytes correctly ## Related - [Working with users](./working-with-users): profiles, names, and the edit endpoint overview -- [Authentication setup](./setup): initial auth module configuration +- [Authentication setup](./setup): initial configuration of the authentication module - [Uploading files](../endpoints-and-apis/file-uploads): cloud storage for production - [Legacy: displaying or editing user images](./legacy/working-with-users): legacy `serverpod_auth` widgets diff --git a/docs/06-concepts/04-authentication/09-custom-overrides.md b/docs/06-concepts/04-authentication/09-custom-overrides.md deleted file mode 100644 index 63012047..00000000 --- a/docs/06-concepts/04-authentication/09-custom-overrides.md +++ /dev/null @@ -1,229 +0,0 @@ ---- -sidebar_label: Custom overrides -description: Custom authentication overrides let you implement your own handling when the serverpod_auth_idp module does not fit your requirements. ---- - -# Custom authentication overrides - -It is recommended to use the `serverpod_auth_idp` package, but if you have special requirements it does not fulfill, you can implement your own authentication module. Serverpod is designed to make it easy to add custom authentication overrides. - -## Server setup - -When running a custom auth integration it is up to you to build the authentication model and issue auth tokens. - -### Token validation - -The token validation is performed by providing a custom `AuthenticationHandler` callback when initializing Serverpod. The callback should return an `AuthenticationInfo` object if the token is valid, otherwise `null`. - -```dart -// Initialize Serverpod and connect it with your generated code. -final pod = Serverpod( - args, - Protocol(), - Endpoints(), - authenticationHandler: (Session session, String token) async { - /// Custom validation handler - if (token != 'valid') return null; - - return AuthenticationInfo(1, {}); - }, -); -``` - -In the above example, the `authenticationHandler` callback is overridden with a custom validation method. The method returns an `AuthenticationInfo` object with `userIdentifier` `"1"` and no scopes if the token is the literal "valid", otherwise `null`. - -:::note -The `userIdentifier` passed to the `AuthenticationInfo` constructor, as the first parameter, will always be converted to a `String` and thus stored internally. Since the default implementation uses `UuidValue` for the users, there is a convenience getter `userId`, which returns the `UuidValue` value. -::: - -:::note -In the `authenticationHandler` callback the `authenticated` field on the session will always be `null` as it is the `authenticationHandler` that figures out who the user is. -::: - -:::info -By specifying the optional `authId` field in the `AuthenticationInfo` object you can link the user to a specific authentication id. This is useful when revoking authentication for a specific device. -::: - -#### Scopes - -The scopes returned from the `authenticationHandler` is used to grant access to scope restricted endpoints. The `Scope` class is a simple wrapper around a nullable `String` in dart. This means that you can format your scopes however you want as long as they are in a String format. - -Normally if you implement a JWT you would store the scopes inside the token. When extracting them all you have to do is convert the String stored in the token into a Scope object by calling the constructor. - -```dart -List scopes = extractScopes(token); -Set userScopes = scopes.map((scope) => Scope(scope)).toSet(); -``` - -### Handling revoked authentication - -When a user's authentication is revoked, the server must be notified to respect the changes (e.g. to close method streams). Invoke the `session.messages.authenticationRevoked` method and raise the appropriate event to notify the server. - -```dart -var userId = 1; -var revokedScopes = ['write']; -var message = RevokedAuthenticationScope( - scopes: revokedScopes, -); - -await session.messages.authenticationRevoked( - userId, - message, -); -``` - -##### Parameters - -- `userId` - The user id belonging to the `AuthenticationInfo` object to be revoked. -- `message` - The revoked authentication event message. See below for the different type of messages. - -#### Revoked authentication messages - -There are three types of `RevokedAuthentication` messages that are used to specify the extent of the authentication revocation: - -| Message type | Description | -|-----------|-------------| -| `RevokedAuthenticationUser` | All authentication is revoked for a user. | -| `RevokedAuthenticationAuthId` | A single authentication id is revoked for the user. This should match the `authId` field in the `AuthenticationInfo` object. | -| `RevokedAuthenticationScope` | List of scopes that have been revoked for a user. | - -Each message type provides a tailored approach to revoke authentication based on different needs. - -### Send token to client - -You are responsible for implementing the endpoints to authenticate/authorize the user. But as an example such an endpoint could look like the following. - -```dart -class UserEndpoint extends Endpoint { - Future login( - Session session, - String username, - String password, - ) async { - var identifier = await authenticateUser(session, username, password); - if (identifier == null) return null; - - return issueMyToken(identifier, scopes: {}); - } -} -``` - -In the above example, the `login` method authenticates the user and creates an auth token. The token is then returned to the client. - -## Client setup - -Enabling authentication in the client is as simple as configuring an auth key provider. If an auth key provider is configured, the client will automatically query the provider for an authentication header value and include it in communication with the server. - -### Configure auth key provider - -Auth key providers need to implement the `ClientAuthKeyProvider` interface. The provider is configured when creating the client by passing it as the named parameter `authKeyProvider`. If no provider is configured, the client will not include authentication headers in requests to the server. - -```dart -import 'package:serverpod_client/serverpod_client.dart'; - -class SimpleAuthKeyProvider implements ClientAuthKeyProvider { - String? _key; - - @override - Future get authHeaderValue async { - if (_key == null) return null; - return wrapAsBasicAuthHeaderValue(_key!); - } - - Future put(String key) async { - _key = key; - } - - Future remove() async { - _key = null; - } -} - -var client = Client('http://localhost:8080/') - ..authKeyProvider = SimpleAuthKeyProvider() - ..connectivityMonitor = FlutterConnectivityMonitor(); -``` - -In the above example, the `SimpleAuthKeyProvider` is configured as the client's authentication key provider. The `SimpleAuthKeyProvider` stores the token in memory and wraps it as a Basic auth header value using the `wrapAsBasicAuthHeaderValue` utility function. - -:::info -The `SimpleAuthKeyProvider` is not practical and should only be used for testing. A secure implementation of the auth key provider is available in the `serverpod_auth_core_flutter` package. It provides safe, persistent storage for the auth token. -::: - -The auth key provider is then available through the client's `authKeyProvider` field. It is useful to create a getter for it to avoid unnecessary casting. - -```dart -var authProvider = client.authKeyProvider as SimpleAuthKeyProvider; -``` - -It is the responsibility of the client to store the token in the auth key provider. - -## Authentication schemes - -By default Serverpod will pass the authentication token from client to server in accordance with the HTTP `authorization` header standard with the `basic` scheme name and encoding. This is securely transferred as the connection is TLS encrypted. - -The default implementation encodes and wraps the user-provided token in a `basic` scheme which is automatically unwrapped on the server side before being handed to the user-provided authentication handler described above. - -In other words the default transport implementation is "invisible" to user code. - -### Implementing your own authentication scheme - -If you are implementing your own authentication and are using the `basic` scheme, note that this is supported but will be automatically unwrapped i.e. decoded on the server side before being handed to your `AuthenticationHandler` implementation. It will in this case receive the decoded auth key value after the `basic` scheme name. - -If you are implementing a different authentication scheme, for example OAuth2 using bearer tokens, you should return the appropriate header value from the `authHeaderValue` getter of your `ClientAuthKeyProvider` implementation. You can use the utility functions `wrapAsBasicAuthHeaderValue` or `wrapAsBearerAuthHeaderValue` to format the token correctly. - -You will also need to implement the `AuthenticationHandler` accordingly, in order to process that header value server-side. - -The header value must be compliant with the HTTP header format defined in RFC 9110 HTTP Semantics, 11.6.2. Authorization. -See: - -- [HTTP Authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) -- [RFC 9110, 11.6.2. Authorization](https://httpwg.org/specs/rfc9110.html#field.authorization) - -An approach to adding OAuth handling might make changes to the above code akin to the following. - -Client side: - -```dart -import 'package:serverpod_client/serverpod_client.dart'; - -class MyOAuthKeyProvider implements ClientAuthKeyProvider { - String? _key; - - @override - Future get authHeaderValue async { - if (_key == null) return null; - return wrapAsBearerAuthHeaderValue(myBearerTokenObtainer(_key!)); - } - - Future put(String key) async { - _key = key; - } - - Future remove() async { - _key = null; - } -} - -var client = Client('http://localhost:8080/') - ..authKeyProvider = MyOAuthKeyProvider() - ..connectivityMonitor = FlutterConnectivityMonitor(); -``` - -Server side: - -```dart -// Initialize Serverpod and connect it with your generated code. -final pod = Serverpod( - args, - Protocol(), - Endpoints(), - authenticationHandler: (Session session, String token) async { - /// Bearer token validation handler - var (uid, scopes) = myBearerTokenValidator(token); - if (uid == null) return null; - - return AuthenticationInfo(uid, scopes); - }, -); -``` diff --git a/docs/06-concepts/04-authentication/08-macos-authentication.md b/docs/06-concepts/04-authentication/09-macos-authentication.md similarity index 100% rename from docs/06-concepts/04-authentication/08-macos-authentication.md rename to docs/06-concepts/04-authentication/09-macos-authentication.md diff --git a/docs/06-concepts/04-authentication/10-custom-overrides.md b/docs/06-concepts/04-authentication/10-custom-overrides.md new file mode 100644 index 00000000..f4a17a86 --- /dev/null +++ b/docs/06-concepts/04-authentication/10-custom-overrides.md @@ -0,0 +1,224 @@ +--- +sidebar_label: Custom overrides +description: Custom authentication overrides let you implement your own handling when the serverpod_auth_idp module does not fit your requirements. +--- + +# Custom authentication overrides + +The `serverpod_auth_idp` module covers most authentication needs, but you can replace it when you have requirements it does not meet. A custom override means two things: a server-side handler that turns a token into a signed-in user, and an app-side provider that sends that token with every request. This page covers both. + +## Server setup + +With a custom override you decide how users are stored and how tokens are issued and validated. + +### Token validation + +The token validation is performed by providing a custom `AuthenticationHandler` callback when initializing Serverpod. The callback should return an `AuthenticationInfo` object if the token is valid, otherwise `null`. + +```dart +// Initialize Serverpod and connect it with your generated code. +final pod = Serverpod( + args, + Protocol(), + Endpoints(), + authenticationHandler: (Session session, String token) async { + /// Custom validation handler + if (token != 'valid') return null; + + return AuthenticationInfo('1', {}, authId: 'device-1'); + }, +); +``` + +In the above example, the `authenticationHandler` callback is overridden with a custom validation method. The method returns an `AuthenticationInfo` object with `userIdentifier` `"1"` and no scopes if the token is the literal "valid", otherwise `null`. + +The `AuthenticationInfo` constructor takes the user's identifier as a `String` (an empty string throws). The required `authId` field links the authentication to a specific device or token, so it can be revoked individually later. + +:::note +Inside the `authenticationHandler` callback, the `authenticated` field on the session is always `null`, since it is the handler itself that figures out who the user is. +::: + +#### Scopes + +The scopes returned from the `authenticationHandler` are used to grant access to [scope-restricted endpoints](./basics#authorization-on-endpoints). The `Scope` class is a simple wrapper around a nullable `String`, so you can format your scopes however you want. + +A JWT (JSON Web Token) implementation would normally store the scopes inside the token. After extracting them, convert each string into a `Scope` object by calling the constructor: + +```dart +List scopes = extractScopes(token); +Set userScopes = scopes.map((scope) => Scope(scope)).toSet(); +``` + +### Handling revoked authentication + +When a user's authentication is revoked, the server must be told so it can act on the change, for example by closing method streams. Call `session.messages.authenticationRevoked` with the message type that matches the extent of the revocation. + +```dart +var userIdentifier = '1'; +var revokedScopes = ['write']; +var message = RevokedAuthenticationScope( + scopes: revokedScopes, +); + +await session.messages.authenticationRevoked( + userIdentifier, + message, +); +``` + +#### Parameters + +- `userIdentifier` - The `userIdentifier` of the `AuthenticationInfo` object to be revoked. +- `message` - The revoked authentication event message. See below for the different type of messages. + +#### Revoked authentication messages + +There are three types of `RevokedAuthentication` messages that are used to specify the extent of the authentication revocation: + +| Message type | Description | +|-----------|-------------| +| `RevokedAuthenticationUser` | All authentication is revoked for a user. | +| `RevokedAuthenticationAuthId` | A single authentication id is revoked for the user. This should match the `authId` field in the `AuthenticationInfo` object. | +| `RevokedAuthenticationScope` | List of scopes that have been revoked for a user. | + +### Send the token to the app + +You are responsible for implementing the endpoints that authenticate the user. The example below shows the shape of such an endpoint. The `authenticateUser` and `issueMyToken` functions are placeholders for your own logic. + +```dart +class UserEndpoint extends Endpoint { + Future login( + Session session, + String username, + String password, + ) async { + var identifier = await authenticateUser(session, username, password); + if (identifier == null) return null; + + return issueMyToken(identifier, scopes: {}); + } +} +``` + +In the above example, the `login` method authenticates the user and creates an auth token. The token is then returned to the app. + +## Client setup + +To authenticate from your app, configure an auth key provider on the client. The client then asks the provider for an authentication header value and includes it in every request to the server. + +### Configure auth key provider + +Auth key providers need to implement the `ClientAuthKeyProvider` interface. The provider is assigned to the client's `authKeyProvider` field after construction, as the example below does. If no provider is configured, the client will not include authentication headers in requests to the server. + +```dart +import 'package:serverpod_client/serverpod_client.dart'; + +class SimpleAuthKeyProvider implements ClientAuthKeyProvider { + String? _key; + + @override + Future get authHeaderValue async { + if (_key == null) return null; + return wrapAsBearerAuthHeaderValue(_key!); + } + + Future put(String key) async { + _key = key; + } + + Future remove() async { + _key = null; + } +} + +var client = Client('http://localhost:8080/') + ..authKeyProvider = SimpleAuthKeyProvider() + ..connectivityMonitor = FlutterConnectivityMonitor(); +``` + +In the above example, the `SimpleAuthKeyProvider` is configured as the client's auth key provider. The `SimpleAuthKeyProvider` stores the token in memory and wraps it as a Bearer auth header value using the `wrapAsBearerAuthHeaderValue` utility function. + +:::info +The `SimpleAuthKeyProvider` is not practical and should only be used for testing. A secure implementation of the auth key provider is available in the `serverpod_auth_core_flutter` package. It provides safe, persistent storage for the auth token. +::: + +Your app is responsible for storing the token in the auth key provider. Reach the provider through the client's `authKeyProvider` field, which you have to cast back to your own type. A getter saves you from repeating the cast: + +```dart +SimpleAuthKeyProvider get authProvider => + client.authKeyProvider as SimpleAuthKeyProvider; +``` + +## Authentication schemes + +The token travels from the app to the server in the HTTP `authorization` header. Serverpod does not pick a scheme for you. Whatever your auth key provider returns from `authHeaderValue` is sent as the header value. + +The server accepts three schemes: `Bearer`, `Basic`, and `Digest`. The scheme name is matched case-sensitively, and a header in any other scheme is rejected with a 400 response before your `AuthenticationHandler` runs. Set `validateHeaders` to `false` in your server configuration to turn that check off and receive the raw header value instead. + +Before calling your handler, the server unwraps the value. A `Bearer` value has its scheme prefix stripped, and a `Basic` value is base64-decoded, so your handler receives the plain token either way. + +Use `Bearer` for a token with no internal structure, which is what the built-in authentication module does and what the example above shows. The `Basic` scheme is only suitable for a key shaped like `username:password`, because it is rejected when the decoded value has no colon or either part is empty. + +### Implementing your own authentication scheme + +Return the header value you want from the `authHeaderValue` getter of your `ClientAuthKeyProvider` implementation. The utility functions `wrapAsBearerAuthHeaderValue` and `wrapAsBasicAuthHeaderValue` format the token for the two common schemes. + +The header value must be compliant with the HTTP header format defined in RFC 9110 HTTP Semantics, 11.6.2. Authorization. +See: + +- [HTTP Authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) +- [RFC 9110, 11.6.2. Authorization](https://httpwg.org/specs/rfc9110.html#field.authorization) + +The example below adapts the earlier code to bearer tokens. + +Client side: + +```dart +import 'package:serverpod_client/serverpod_client.dart'; + +class MyOAuthKeyProvider implements ClientAuthKeyProvider { + String? _key; + + @override + Future get authHeaderValue async { + if (_key == null) return null; + return wrapAsBearerAuthHeaderValue(myBearerTokenObtainer(_key!)); + } + + Future put(String key) async { + _key = key; + } + + Future remove() async { + _key = null; + } +} + +var client = Client('http://localhost:8080/') + ..authKeyProvider = MyOAuthKeyProvider() + ..connectivityMonitor = FlutterConnectivityMonitor(); +``` + +Server side: + +```dart +// Initialize Serverpod and connect it with your generated code. +final pod = Serverpod( + args, + Protocol(), + Endpoints(), + authenticationHandler: (Session session, String token) async { + /// Bearer token validation handler + var (uid, scopes) = myBearerTokenValidator(token); + if (uid == null) return null; + + return AuthenticationInfo(uid, scopes, authId: token); + }, +); +``` + +## Related + +- [Setup](./setup): the built-in authentication module, which these overrides replace. +- [The basics](./basics): how `requireLogin` and scopes restrict endpoints. +- [Token managers](./token-managers/managing-tokens): the built-in token machinery. From 1c5102557d64756d0ab5b9d98961edddd2139517 Mon Sep 17 00:00:00 2001 From: developerjamiu Date: Fri, 31 Jul 2026 20:37:48 +0100 Subject: [PATCH 2/2] docs: Note HTTPS for auth tokens in transit on the custom overrides page --- docs/06-concepts/04-authentication/10-custom-overrides.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/06-concepts/04-authentication/10-custom-overrides.md b/docs/06-concepts/04-authentication/10-custom-overrides.md index f4a17a86..84cfef87 100644 --- a/docs/06-concepts/04-authentication/10-custom-overrides.md +++ b/docs/06-concepts/04-authentication/10-custom-overrides.md @@ -151,7 +151,9 @@ SimpleAuthKeyProvider get authProvider => ## Authentication schemes -The token travels from the app to the server in the HTTP `authorization` header. Serverpod does not pick a scheme for you. Whatever your auth key provider returns from `authHeaderValue` is sent as the header value. +The token travels from the app to the server in the HTTP `authorization` header. Anyone who reads that header can use the token, so serve your production API over HTTPS. See [Security and TLS](../operations/security-and-tls). + +Serverpod does not pick a scheme for you. Whatever your auth key provider returns from `authHeaderValue` is sent as the header value. The server accepts three schemes: `Bearer`, `Basic`, and `Digest`. The scheme name is matched case-sensitively, and a header in any other scheme is rejected with a 400 response before your `AuthenticationHandler` runs. Set `validateHeaders` to `false` in your server configuration to turn that check off and receive the raw header value instead. @@ -169,7 +171,7 @@ See: - [HTTP Authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) - [RFC 9110, 11.6.2. Authorization](https://httpwg.org/specs/rfc9110.html#field.authorization) -The example below adapts the earlier code to bearer tokens. +The example below adapts the earlier code to bearer tokens, the scheme OAuth2 uses. Client side: