diff --git a/docs/06-concepts/11-authentication/02-basics.md b/docs/06-concepts/11-authentication/02-basics.md index 41202243..0a12a853 100644 --- a/docs/06-concepts/11-authentication/02-basics.md +++ b/docs/06-concepts/11-authentication/02-basics.md @@ -90,13 +90,17 @@ Using `@unauthenticatedClientCall` on an endpoint or method that also has `requi ## Authorization on endpoints -Serverpod also supports scopes for restricting access. One or more scopes can be associated with a user. For instance, this can be used to give admin access to a specific user. To restrict access for an endpoint, override the `requiredScopes` property. Note that setting `requiredScopes` implicitly sets `requireLogin` to true. +Scopes define what an authenticated user is allowed to do. Each scope is an independent capability identified by a string name. Scopes do not inherit from each other — holding `Scope.admin` does not grant any other scope. + +Each endpoint exposes two properties for access control: + +- `requireLogin` — the user must be signed in, with no specific scope required. +- `requiredScopes` — the user must hold every scope in the set. + +To restrict access by scope, override the `requiredScopes` property: ```dart class MyEndpoint extends Endpoint { - @override - bool get requireLogin => true; - @override Set get requiredScopes => {Scope.admin}; @@ -107,9 +111,60 @@ class MyEndpoint extends Endpoint { } ``` +Serverpod ships with `Scope.admin` (named `serverpod.admin`) for built-in admin functionality in Serverpod modules. Define your own scope names for application-specific access control. + +:::info +When `requiredScopes` is non-empty, authentication is required even if `requireLogin` is false. Setting both `requireLogin` and `requiredScopes` has the same effect as scopes alone. +::: + +### How scopes combine + +When an endpoint lists multiple scopes in `requiredScopes`, the user must have all of them. Serverpod evaluates scopes with AND logic — there is no OR matching and no scope hierarchy. + +Consider a user management feature with two admin endpoints: one for aggregated user analytics and one for editing user data. Not every admin should be able to modify users, so the endpoints require different scope combinations: + +```dart +class UserAnalyticsEndpoint extends Endpoint { + @override + Set get requiredScopes => {Scope.admin}; + + Future getStats(Session session) async { + ... + } +} + +class UserEditEndpoint extends Endpoint { + @override + Set get requiredScopes => { + Scope.admin, + const Scope('userWrite'), + }; + + Future updateUser(Session session, UserData data) async { + ... + } +} +``` + +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, const Scope('userWrite')}, +); +``` + +This lets you compose capabilities at the endpoint level instead of building nested roles. + ### Managing scopes -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. +New users are created without any scopes. Scopes are stored on the auth user and copied into the session or token at sign-in. On each request, the server reads scopes from that token snapshot — not from a live database lookup. + +To update a user's scopes, use the `update` method from `AuthServices.instance.authUsers`. This method replaces all previously stored scopes: ```dart import 'package:serverpod_auth_idp_server/core.dart'; @@ -121,9 +176,11 @@ await AuthServices.instance.authUsers.update( ); ``` +Changing a user's scopes does not affect existing sessions or tokens until the user signs in again or their tokens are revoked. For open streaming connections, Serverpod closes method streams when a revoked scope overlaps what the endpoint requires. See [Managing tokens](./token-managers/managing-tokens#revoking-tokens) for revoking tokens across devices. + ### Custom scopes -You may need more granular access control for specific endpoints. To create custom scopes, extend the Scope class, as shown below: +Define constants for your domain by extending the `Scope` class: ```dart class CustomScope extends Scope { @@ -134,13 +191,10 @@ class CustomScope extends Scope { } ``` -Then use the custom scopes like this: +Then use the custom scopes on your endpoints: ```dart class MyEndpoint extends Endpoint { - @override - bool get requireLogin => true; - @override Set get requiredScopes => {CustomScope.userRead, CustomScope.userWrite}; @@ -151,10 +205,23 @@ class MyEndpoint extends Endpoint { } ``` +The user must hold both `userRead` and `userWrite` to access this endpoint. You can reuse the same scope constants across multiple endpoints to build different access combinations. + +Scope names are plain strings stored in the database and embedded in tokens. Keep them stable once deployed. + +You can also define shared scope requirements in a base endpoint class. See [Endpoint inheritance](../working-with-endpoints/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. ::: +### HTTP responses + +When access is denied, Serverpod returns: + +- **401 Unauthorized** — no token was provided, or the token is invalid. +- **403 Forbidden** — the user is authenticated but missing one or more required scopes. + ## Client-side authentication On the client side, authentication state is managed through the `FlutterAuthSessionManager`, which is accessible via `client.auth`. @@ -220,7 +287,9 @@ void _onAuthStateChanged() { } ``` -The listener is triggered whenever the user's sign-in state changes.## User authentication +The listener is triggered whenever the user's sign-in state changes. + +## User authentication ### Signing out users