Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/06-concepts/04-authentication/02-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
:::
Expand Down Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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()`:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()`:
Expand Down
90 changes: 90 additions & 0 deletions docs/06-concepts/04-authentication/10-web-authentication.md
Original file line number Diff line number Diff line change
@@ -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` | `<name>_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.
11 changes: 11 additions & 0 deletions docs/11-upgrading/01-upgrade-to-four.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<project>_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.
Expand All @@ -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`.

Expand Down
Loading