From 870107366a3b26f849cc8c7b18d67dfc3d776373 Mon Sep 17 00:00:00 2001 From: Preston Date: Fri, 21 Aug 2026 09:54:29 -0500 Subject: [PATCH] Add web authentication with httpOnly cookies documentation. --- .../06-concepts/04-authentication/02-setup.md | 5 ++ .../06-token-managers/02-jwt-token-manager.md | 4 + .../03-server-side-sessions-token-manager.md | 4 + .../10-web-authentication.md | 90 +++++++++++++++++++ ...om-overrides.md => 11-custom-overrides.md} | 0 .../{11-legacy => 12-legacy}/01-setup.md | 0 .../{11-legacy => 12-legacy}/02-basics.md | 0 .../03-working-with-users.md | 0 .../04-providers/01-email.md | 0 .../04-providers/02-google.md | 0 .../04-providers/03-apple.md | 0 .../04-providers/05-firebase.md | 0 .../04-providers/06-custom-providers.md | 0 .../04-providers/_category_.json | 0 .../05-custom-overrides.md | 0 .../{11-legacy => 12-legacy}/_category_.json | 0 docs/11-upgrading/01-upgrade-to-four.md | 11 +++ 17 files changed, 114 insertions(+) create mode 100644 docs/06-concepts/04-authentication/10-web-authentication.md rename docs/06-concepts/04-authentication/{10-custom-overrides.md => 11-custom-overrides.md} (100%) rename docs/06-concepts/04-authentication/{11-legacy => 12-legacy}/01-setup.md (100%) rename docs/06-concepts/04-authentication/{11-legacy => 12-legacy}/02-basics.md (100%) rename docs/06-concepts/04-authentication/{11-legacy => 12-legacy}/03-working-with-users.md (100%) rename docs/06-concepts/04-authentication/{11-legacy => 12-legacy}/04-providers/01-email.md (100%) rename docs/06-concepts/04-authentication/{11-legacy => 12-legacy}/04-providers/02-google.md (100%) rename docs/06-concepts/04-authentication/{11-legacy => 12-legacy}/04-providers/03-apple.md (100%) rename docs/06-concepts/04-authentication/{11-legacy => 12-legacy}/04-providers/05-firebase.md (100%) rename docs/06-concepts/04-authentication/{11-legacy => 12-legacy}/04-providers/06-custom-providers.md (100%) rename docs/06-concepts/04-authentication/{11-legacy => 12-legacy}/04-providers/_category_.json (100%) rename docs/06-concepts/04-authentication/{11-legacy => 12-legacy}/05-custom-overrides.md (100%) rename docs/06-concepts/04-authentication/{11-legacy => 12-legacy}/_category_.json (100%) diff --git a/docs/06-concepts/04-authentication/02-setup.md b/docs/06-concepts/04-authentication/02-setup.md index 9762d900..9edf9c9a 100644 --- a/docs/06-concepts/04-authentication/02-setup.md +++ b/docs/06-concepts/04-authentication/02-setup.md @@ -228,6 +228,10 @@ void main() async { The `FlutterAuthSessionManager` provides useful properties and methods for managing authentication state. +:::info Web apps +On the web, enable [cookie-based authentication](./web-authentication) so sign-in tokens are kept in `httpOnly` cookies instead of JavaScript-readable storage. +::: + :::tip 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. ::: @@ -344,4 +348,5 @@ Do not navigate to another screen from the `onAuthenticated` callback, or the us - [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. +- [Web setup](./web-authentication): keep web sign-in tokens in httpOnly cookies. - [UI components](./ui-components): customize or replace the sign-in UI. diff --git a/docs/06-concepts/04-authentication/06-token-managers/02-jwt-token-manager.md b/docs/06-concepts/04-authentication/06-token-managers/02-jwt-token-manager.md index 5c3507da..e534685e 100644 --- a/docs/06-concepts/04-authentication/06-token-managers/02-jwt-token-manager.md +++ b/docs/06-concepts/04-authentication/06-token-managers/02-jwt-token-manager.md @@ -11,6 +11,10 @@ The `JwtTokenManager` uses JWT (JSON Web Tokens) for stateless authentication. I - Refresh tokens for long-term authentication. - Automatic token rotation. +:::info Web apps +With [cookie-based web authentication](../web-authentication) enabled, browsers keep the access token in memory only and receive the refresh token as an `httpOnly` cookie. +::: + ## Server-side configuration The `JwtTokenManager` is created by passing a `JwtConfig` object in the `tokenManagerBuilders` list of `pod.initializeAuthServices()`: diff --git a/docs/06-concepts/04-authentication/06-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 index a14449ee..28498b10 100644 --- a/docs/06-concepts/04-authentication/06-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 @@ -10,6 +10,10 @@ The `ServerSideSessionsTokenManager` validates each token against a server-side - Immediate session revocation. - Support for session expiration and inactivity timeouts. +:::info Web apps +With [cookie-based web authentication](../web-authentication) enabled, browsers receive the session token as an `httpOnly` cookie instead of in the response body. +::: + ## Server-side configuration The `ServerSideSessionsTokenManager` is created by passing a `ServerSideSessionsConfig` object in the `tokenManagerBuilders` list of `pod.initializeAuthServices()`: diff --git a/docs/06-concepts/04-authentication/10-web-authentication.md b/docs/06-concepts/04-authentication/10-web-authentication.md new file mode 100644 index 00000000..61014d03 --- /dev/null +++ b/docs/06-concepts/04-authentication/10-web-authentication.md @@ -0,0 +1,90 @@ +--- +sidebar_label: Web setup +description: Keep web sign-in tokens in httpOnly cookies so JavaScript can never read them, with server configuration, client setup, CORS implications, and local development notes. +--- + +# Set up authentication on the web + +Browsers have no secure storage: anything kept in `localStorage`, `sessionStorage`, or IndexedDB is readable by any JavaScript running on the page, so a single XSS vulnerability can steal a signed-in user's token and replay it from anywhere. Serverpod's cookie mode keeps web tokens in `httpOnly` cookies instead, which scripts cannot read. Cookie mode is opt-in and recommended for any app with signed-in users on the web. + +Native and desktop apps are unaffected: they keep their tokens in secure OS storage (such as the Keychain) and need no changes. + +## Before you start + +- A Serverpod project with the [authentication module](./setup) enabled. +- Your app served over `https` in production. Auth cookies are marked `Secure` by default; relax this only for `http://localhost` during development. + +## Configure the server + +Enable cookie auth by adding an `authCookie` section to your server configuration (`config/development.yaml`, `config/production.yaml`, and so on), together with the list of origins your web app is served from: + +```yaml +authCookie: + # secure: false # Uncomment only for http://localhost development. +allowedOrigins: + - https://app.example.com +``` + +`allowedOrigins` is required when `authCookie` is set: it backs the CSRF origin checks and credentialed CORS, which cannot use a wildcard origin. List every browser origin that calls your server. With cookie auth enabled, browsers on origins that are not in the list lose cross-origin access, including to public endpoints. + +All `authCookie` fields are optional: + +| Field | Default | Purpose | +| ------------- | ------------------------ | -------------------------------------------------------------- | +| `name` | `serverpod_auth` | Name of the auth cookie. | +| `refreshName` | `_refresh` | Name of the JWT refresh cookie. | +| `domain` | host-only | Set to `example.com` to share the cookie across subdomains. | +| `path` | `/` | Cookie path; also the base path behind a reverse proxy. | +| `secure` | `true` | Set to `false` only for `http://localhost` development. | +| `sameSite` | `lax` | `lax`, `strict`, or `none` (`none` requires `secure`). | + +Each field can also be set through environment variables (`SERVERPOD_AUTH_COOKIE_NAME`, `SERVERPOD_AUTH_COOKIE_REFRESH_NAME`, `SERVERPOD_AUTH_COOKIE_DOMAIN`, `SERVERPOD_AUTH_COOKIE_PATH`, `SERVERPOD_AUTH_COOKIE_SECURE`, `SERVERPOD_AUTH_COOKIE_SAME_SITE`, and `SERVERPOD_ALLOWED_ORIGINS`), which override the YAML values. + +## Configure the client + +Turn on cookie transport when the app runs on the web, immediately after constructing the client and before making any calls: + +```dart +import 'package:flutter/foundation.dart'; + +client = Client(serverUrl) + ..cookieAuth = kIsWeb + ..connectivityMonitor = FlutterConnectivityMonitor() + ..authSessionManager = FlutterAuthSessionManager(); +``` + +Everything else is unchanged: sign-in flows, the `client.auth` session manager, and endpoint calls work as on other platforms. Setting `cookieAuth` to `true` on a non-web platform throws, since those transports have no browser cookie jar. + +## How it works + +- With **server-side sessions**, the session token is delivered as an `httpOnly` cookie and never appears in the response body. +- With **JWT**, the access token is kept in memory only, and the refresh token is delivered as an `httpOnly` cookie scoped to the refresh endpoint's path. On page load, the session is restored by refreshing from the cookie. Multiple tabs coordinate their refreshes through the browser's Web Locks API, so a shared refresh token is only rotated by one tab at a time. +- **Signing out** clears the cookies and revokes the session on the server. +- **Method streams** authenticate from the cookie at the WebSocket handshake. When the signed-in user changes (sign-in or sign-out), open method streams are closed gracefully — subscriptions receive `onDone` without an error — and new streams connect with the current identity. This applies on every platform, not only the web. +- **Switching users requires a sign-out first.** Signing in as a different user from an already-authenticated session is rejected with a `SignInWhileAuthenticatedException` on all platforms. + +## Cross-site request protection + +Cookie auth is protected against CSRF in layers: cookies default to `SameSite=Lax`, the server validates the request `Origin` against `allowedOrigins`, and the cookie only authenticates requests carrying a marker header set by the client, which a cross-site form cannot add without a CORS preflight. Nothing needs configuring beyond `allowedOrigins`. Set `sameSite: none` only if your app is embedded cross-site, and keep `secure: true` with it. + +## Cross-subdomain cookies + +To share the signed-in session between `app.example.com` and other subdomains, set `authCookie.domain` to the registrable domain: + +```yaml +authCookie: + domain: example.com +``` + +By default the cookie is host-only. + +## Local development + +- On `http://localhost`, set `authCookie.secure: false` so the browser accepts the cookies. +- On a plain-`http` LAN address (testing from another device), browsers disable the Web Locks API outside secure contexts, so cross-tab refresh coordination is off. The client logs a warning, and refreshing in several tabs at the same moment can occasionally sign the user out. This is an artifact of the insecure test origin; production `https` deployments are unaffected. + +## Related + +- [Setup](./setup): install and configure the authentication module. +- [Token managers](./token-managers/managing-tokens): choose between JWT and server-side sessions. +- [Streaming](../endpoints-and-apis/streaming): how method streams work. diff --git a/docs/06-concepts/04-authentication/10-custom-overrides.md b/docs/06-concepts/04-authentication/11-custom-overrides.md similarity index 100% rename from docs/06-concepts/04-authentication/10-custom-overrides.md rename to docs/06-concepts/04-authentication/11-custom-overrides.md diff --git a/docs/06-concepts/04-authentication/11-legacy/01-setup.md b/docs/06-concepts/04-authentication/12-legacy/01-setup.md similarity index 100% rename from docs/06-concepts/04-authentication/11-legacy/01-setup.md rename to docs/06-concepts/04-authentication/12-legacy/01-setup.md diff --git a/docs/06-concepts/04-authentication/11-legacy/02-basics.md b/docs/06-concepts/04-authentication/12-legacy/02-basics.md similarity index 100% rename from docs/06-concepts/04-authentication/11-legacy/02-basics.md rename to docs/06-concepts/04-authentication/12-legacy/02-basics.md diff --git a/docs/06-concepts/04-authentication/11-legacy/03-working-with-users.md b/docs/06-concepts/04-authentication/12-legacy/03-working-with-users.md similarity index 100% rename from docs/06-concepts/04-authentication/11-legacy/03-working-with-users.md rename to docs/06-concepts/04-authentication/12-legacy/03-working-with-users.md diff --git a/docs/06-concepts/04-authentication/11-legacy/04-providers/01-email.md b/docs/06-concepts/04-authentication/12-legacy/04-providers/01-email.md similarity index 100% rename from docs/06-concepts/04-authentication/11-legacy/04-providers/01-email.md rename to docs/06-concepts/04-authentication/12-legacy/04-providers/01-email.md diff --git a/docs/06-concepts/04-authentication/11-legacy/04-providers/02-google.md b/docs/06-concepts/04-authentication/12-legacy/04-providers/02-google.md similarity index 100% rename from docs/06-concepts/04-authentication/11-legacy/04-providers/02-google.md rename to docs/06-concepts/04-authentication/12-legacy/04-providers/02-google.md diff --git a/docs/06-concepts/04-authentication/11-legacy/04-providers/03-apple.md b/docs/06-concepts/04-authentication/12-legacy/04-providers/03-apple.md similarity index 100% rename from docs/06-concepts/04-authentication/11-legacy/04-providers/03-apple.md rename to docs/06-concepts/04-authentication/12-legacy/04-providers/03-apple.md diff --git a/docs/06-concepts/04-authentication/11-legacy/04-providers/05-firebase.md b/docs/06-concepts/04-authentication/12-legacy/04-providers/05-firebase.md similarity index 100% rename from docs/06-concepts/04-authentication/11-legacy/04-providers/05-firebase.md rename to docs/06-concepts/04-authentication/12-legacy/04-providers/05-firebase.md diff --git a/docs/06-concepts/04-authentication/11-legacy/04-providers/06-custom-providers.md b/docs/06-concepts/04-authentication/12-legacy/04-providers/06-custom-providers.md similarity index 100% rename from docs/06-concepts/04-authentication/11-legacy/04-providers/06-custom-providers.md rename to docs/06-concepts/04-authentication/12-legacy/04-providers/06-custom-providers.md diff --git a/docs/06-concepts/04-authentication/11-legacy/04-providers/_category_.json b/docs/06-concepts/04-authentication/12-legacy/04-providers/_category_.json similarity index 100% rename from docs/06-concepts/04-authentication/11-legacy/04-providers/_category_.json rename to docs/06-concepts/04-authentication/12-legacy/04-providers/_category_.json diff --git a/docs/06-concepts/04-authentication/11-legacy/05-custom-overrides.md b/docs/06-concepts/04-authentication/12-legacy/05-custom-overrides.md similarity index 100% rename from docs/06-concepts/04-authentication/11-legacy/05-custom-overrides.md rename to docs/06-concepts/04-authentication/12-legacy/05-custom-overrides.md diff --git a/docs/06-concepts/04-authentication/11-legacy/_category_.json b/docs/06-concepts/04-authentication/12-legacy/_category_.json similarity index 100% rename from docs/06-concepts/04-authentication/11-legacy/_category_.json rename to docs/06-concepts/04-authentication/12-legacy/_category_.json diff --git a/docs/11-upgrading/01-upgrade-to-four.md b/docs/11-upgrading/01-upgrade-to-four.md index bb1d1bb0..d2389807 100644 --- a/docs/11-upgrading/01-upgrade-to-four.md +++ b/docs/11-upgrading/01-upgrade-to-four.md @@ -221,6 +221,16 @@ Your production build needs to switch from `dart compile exe` to `dart build cli Copy the updated Dockerfile from the [4.0 framework template](https://github.com/serverpod/serverpod/blob/main/templates/serverpod_templates/projectname_server/Dockerfile) or a fresh 4.0 project's `_server/Dockerfile`. The key changes vs. the 3.4 pattern: build from the project root (not the server directory), copy the bundle directory, update `ENTRYPOINT` to point at the bundled binary, and bump the Dart SDK base image to 3.10.x or newer. +## Authentication changes + +4.0 changes a few authentication behaviors that can affect existing apps: + +- **The `?auth=` query parameter is no longer accepted.** Credentials never appear in URLs anymore: HTTP calls authenticate through the `Authorization` header (or an auth cookie on the web), and streaming connections authenticate in-band when the stream opens. Clients from before 4.0 that relied on the query parameter must be upgraded. +- **Signing in on top of another account is rejected.** Issuing a token for a different user from an already-authenticated session throws a `SignInWhileAuthenticatedException` on every platform; users must sign out before switching accounts. Server code that mints tokens on behalf of another user (such as an admin flow) calls the token manager's `createToken` instead, which skips this policy and returns the secrets in the response body. +- **Custom token managers extend a base class.** `TokenIssuer` and `TokenManager` are now base classes: implement `createToken` for the actual minting, and leave `issueToken` alone — it is non-virtual and applies the sign-in policy and cookie delivery for every token type. +- **Method streams close when the signed-in user changes.** On sign-in and sign-out, open method streams are closed gracefully (subscriptions receive `onDone` without an error) on all platforms, and new streams connect with the current identity. A same-identity token refresh keeps streams running. +- **Opt-in cookie auth for the web.** Enabling the new `authCookie` configuration requires listing every browser origin in `allowedOrigins`; browsers on unlisted origins lose cross-origin access, including to public endpoints. See [web authentication](../concepts/authentication/web-authentication). + ## What's new in 4.0 - **`serverpod start` TUI**: hot reload on save, **R** to hot restart, **M** to create and apply a migration, **P** to create and apply a repair migration. @@ -235,6 +245,7 @@ Copy the updated Dockerfile from the [4.0 framework template](https://github.com - **`upsert` and `upsertRow`** on the ORM, and **`asc()` / `desc()`** convenience methods on orderable columns. - **Recurring future calls** via the new claim-based scheduling. - **OAuth2 PKCE Flutter web redirect** for sign-in flows. +- **httpOnly cookie authentication for the web**, keeping browser sign-in tokens out of JavaScript-readable storage. See [web authentication](../concepts/authentication/web-authentication). - **Health endpoints** on the built-in webserver. - **IDE and agent selection** in `serverpod create`.