diff --git a/docs/06-concepts/01-working-with-endpoints/01-working-with-endpoints.md b/docs/06-concepts/01-working-with-endpoints/01-working-with-endpoints.md index 949d6c30..56d10eb8 100644 --- a/docs/06-concepts/01-working-with-endpoints/01-working-with-endpoints.md +++ b/docs/06-concepts/01-working-with-endpoints/01-working-with-endpoints.md @@ -43,7 +43,7 @@ On the client side, you can now call the method by calling: var result = await client.example.hello('World'); ``` -The client is initialized like this: +Initialize the generated client once and keep it somewhere that the rest of your app can reuse, such as a service locator: ```dart // Sets up a singleton client object that can be used to talk to the server from @@ -57,7 +57,7 @@ var client = Client('http://localhost:8080/') If you run the app in an Android emulator, use `10.0.2.2` instead of `localhost`, since `10.0.2.2` is the IP address of the host machine from inside the emulator. To access the server from a different device on the same network (such as a physical phone), replace `localhost` with the local IP address of your machine. You can find the local IP by running `ifconfig` (Linux/macOS) or `ipconfig` (Windows). -Make sure to also update the `publicHost` in the development config to make sure the server always serves the client with the correct path to assets etc. +Make sure to also update the `publicHost` in the development config so the server always serves the client with the correct path to assets and other resources. ```yaml # your_project_server/config/development.yaml @@ -69,6 +69,8 @@ apiServer: publicScheme: http ``` +See [Configure HTTP calls](./working-with-endpoints/configure-http-calls) for transport-specific configuration such as browser credentials or platform-native HTTP clients. + ## Passing parameters There are some limitations to how endpoint methods can be implemented. Parameters and return types can be of type `bool`, `int`, `double`, `String`, `UuidValue`, `Duration`, `DateTime`, `ByteData`, `Uri`, `BigInt`, or generated serializable objects (see next section). A typed `Future` should always be returned. Null safety is supported. When passing a `DateTime` it is always converted to UTC. diff --git a/docs/06-concepts/01-working-with-endpoints/03-configure-http-calls.md b/docs/06-concepts/01-working-with-endpoints/03-configure-http-calls.md new file mode 100644 index 00000000..5cd74d54 --- /dev/null +++ b/docs/06-concepts/01-working-with-endpoints/03-configure-http-calls.md @@ -0,0 +1,114 @@ +# Configure HTTP calls + +The generated `Client` accepts an optional `httpClientOverride` parameter that controls the underlying HTTP transport used for API calls. Use it when you need to customize how requests are sent, such as enabling browser credentials or using platform-native HTTP stacks. + +## Include CORS credentials on web + +By default, browser requests do not include cookies or HTTP authentication credentials in cross-origin requests. If your app relies on cookie-based sessions or similar mechanisms, pass a `BrowserClient` with `withCredentials` enabled: + +```dart +import 'package:http/browser_client.dart'; + +final client = Client( + serverUrl, + httpClientOverride: BrowserClient()..withCredentials = true, +); +``` + +On the server, Serverpod adds CORS headers to API responses by default through `httpResponseHeaders` and `httpOptionsResponseHeaders` on the `Serverpod` constructor. The defaults allow cross-origin `POST` requests from any origin (`Access-Control-Allow-Origin: *`) and permit common request headers such as `Authorization` on preflight `OPTIONS` requests. + +Credential-aware requests require stricter headers: the browser rejects `Access-Control-Allow-Origin: *` when credentials are included, and the server must respond with `Access-Control-Allow-Credentials: true` and a specific origin. Override the defaults in your `lib/server.dart` (or wherever you construct `Serverpod`): + +```dart +import 'package:serverpod/serverpod.dart'; + +import 'src/generated/protocol.dart'; +import 'src/generated/endpoints.dart'; + +/// The starting point of the Serverpod server. +void run(List args) async { + // Initialize Serverpod and connect it with your generated code. + final pod = Serverpod( + args, + Protocol(), + Endpoints(), + httpResponseHeaders: Headers.build((mh) { + mh.accessControlAllowOrigin = AccessControlAllowOriginHeader.origin( + origin: Uri.parse('http://localhost:49660'), // Your Flutter web app origin + ); + mh.accessControlAllowCredentials = true; + mh.accessControlAllowMethods = AccessControlAllowMethodsHeader.methods( + [Method.post], + ); + }), + httpOptionsResponseHeaders: Headers.build((mh) { + mh.accessControlAllowHeaders = AccessControlAllowHeadersHeader.headers([ + 'Content-Type', + 'Authorization', + 'Accept', + 'User-Agent', + 'X-Requested-With', + ]); + }), + ); + + // Start the server + await pod.start(); +} +``` + +Set `origin` to the exact origin of your Flutter web app (scheme, host, and port). In production, list each allowed origin explicitly. + +## Use platform-native HTTP clients + +You can also override the default HTTP client with a platform-native HTTP client. On iOS and macOS, you can use [cupertino_http](https://pub.dev/packages/cupertino_http) to route traffic through `NSURLSession`. On Android, you can use [cronet_http](https://pub.dev/packages/cronet_http) to use the Cronet network stack. + +Below is an example of how to override the default HTTP client with platform-native HTTP clients. + +```dart +import 'dart:io'; + +import 'package:cronet_http/cronet_http.dart'; +import 'package:cupertino_http/cupertino_http.dart'; +import 'package:http/http.dart' as http; + +import 'package:my_project_client/my_project_client.dart'; + +void main() async { + http.Client? httpClient; + + if (Platform.isAndroid) { + final engine = CronetEngine.build( + cacheMode: CacheMode.memory, + cacheMaxSize: 2 * 1024 * 1024, + userAgent: 'Book Agent'); + httpClient = CronetClient.fromCronetEngine(engine, closeEngine: true); + } else if (Platform.isIOS || Platform.isMacOS) { + final config = URLSessionConfiguration.ephemeralSessionConfiguration() + ..cache = URLCache.withCapacity(memoryCapacity: 2 * 1024 * 1024) + ..httpAdditionalHeaders = {'User-Agent': 'Book Agent'}; + httpClient = CupertinoClient.fromSessionConfiguration(config); + } + + final client = Client( + serverUrl, + httpClientOverride: httpClient, + ); +} +``` + +### Support web with conditional imports + +The above example does not work if your app also targets web, since `dart:io` is not available there. Put the platform-specific `http.Client` creation logic behind a conditional import instead: + +```dart +import 'src/http_client_stub.dart' + if (dart.library.io) 'src/http_client_io.dart'; + +final client = Client( + serverUrl, + httpClientOverride: createHttpClient(), +); +``` + +Add the corresponding package to your Flutter app's `pubspec.yaml` before using these clients. diff --git a/docs/06-concepts/01-working-with-endpoints/03-middleware.md b/docs/06-concepts/01-working-with-endpoints/04-middleware.md similarity index 100% rename from docs/06-concepts/01-working-with-endpoints/03-middleware.md rename to docs/06-concepts/01-working-with-endpoints/04-middleware.md diff --git a/docs/06-concepts/21-security-configuration.md b/docs/06-concepts/21-security-configuration.md index 84e2b27f..ff7e661a 100644 --- a/docs/06-concepts/21-security-configuration.md +++ b/docs/06-concepts/21-security-configuration.md @@ -2,12 +2,12 @@ :::info -In a **production environment**, TLS termination is **normally handled by a load balancer** or **reverse proxy** (e.g., Nginx, AWS ALB, or Cloudflare). +In a **production environment**, TLS termination is **normally handled by a load balancer** or **reverse proxy** (e.g., Nginx, AWS ALB, or Cloudflare). However, Serverpod also supports setting up **TLS/SSL directly on the server**, allowing you to provide your own certificates if needed. ::: -Serverpod supports **TLS/SSL security configurations** through the **Dart configuration object**. +Serverpod supports **TLS/SSL security configurations** through the **Dart configuration object**. To enable SSL/TLS, you must pass a **`SecurityContextConfig`** to the `Serverpod` constructor. ## Server Security Configuration @@ -45,10 +45,31 @@ To enable SSL/TLS when using the Serverpod client, pass a **`SecurityContext`** final securityContext = SecurityContext() ..setTrustedCertificates('path/to/server_cert.pem'); - final client = Client( 'https://yourserver.com', securityContext: securityContext, ... ); ``` + +#### Using `SecurityContext` with `httpClientOverride` + +If you use the [`httpClientOverride` parameter](./working-with-endpoints/configure-http-calls), provide the security context through the HTTP client you pass in. You cannot set `securityContext` and `httpClientOverride` on the same `Client` instance. + +For example, on `dart:io` platforms you can create an `HttpClient` with your trusted certificates and wrap it in an `IOClient`: + +```dart +import 'dart:io'; + +import 'package:http/io_client.dart'; + +final securityContext = SecurityContext() + ..setTrustedCertificates('path/to/server_cert.pem'); + +final client = Client( + 'https://yourserver.com', + httpClientOverride: IOClient( + HttpClient(context: securityContext), + ), +); +```