diff --git a/docs/06-concepts/11-authentication/05-token-managers/01-managing-tokens.md b/docs/06-concepts/11-authentication/05-token-managers/01-managing-tokens.md index 29ed96c7..587fa0ae 100644 --- a/docs/06-concepts/11-authentication/05-token-managers/01-managing-tokens.md +++ b/docs/06-concepts/11-authentication/05-token-managers/01-managing-tokens.md @@ -54,6 +54,10 @@ final authSuccess = await AuthServices.instance.tokenManager.issueToken( ); ``` +#### 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: @@ -74,7 +78,7 @@ if (authInfo != null) { ### Revoking Tokens -Revoke specific tokens: +Revoke specific tokens by token ID: ```dart await AuthServices.instance.tokenManager.revokeToken( @@ -83,6 +87,8 @@ await AuthServices.instance.tokenManager.revokeToken( ); ``` +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 diff --git a/docs/06-concepts/11-authentication/05-token-managers/02-jwt-token-manager.md b/docs/06-concepts/11-authentication/05-token-managers/02-jwt-token-manager.md index 2082a80d..a46dc86d 100644 --- a/docs/06-concepts/11-authentication/05-token-managers/02-jwt-token-manager.md +++ b/docs/06-concepts/11-authentication/05-token-managers/02-jwt-token-manager.md @@ -106,6 +106,103 @@ final jwtConfig = JwtConfigFromPasswords( ); ``` +### Attaching custom metadata to tokens + +You can attach custom metadata to each JWT refresh token by providing an `onRefreshTokenCreated` callback. This is useful for storing device information, IP address, user agent, or any other data you need to query or use later (for example, to list or revoke tokens by device). The callback runs when a refresh token is created, within the same transaction as the token insert. + +Define a server-only table that relates to `RefreshToken` and store your metadata there. Example schema: + +```yaml +class: TokenMetadata +serverOnly: true +table: token_metadata +fields: + ### The [RefreshToken] this metadata belongs to + refreshToken: module:serverpod_auth_core:RefreshToken?, relation(onDelete=Cascade) + + ### The name of the token + name: String? + + ### Device information for the token + deviceName: String? + + ### IP address from which the token was created + ipAddress: String? + + ### User agent string + userAgent: String? + +indexes: + refresh_token_id_unique_idx: + fields: refreshTokenId + unique: true +``` + +Then configure the callback in your JWT config: + +```dart +JwtConfigFromPasswords( + onRefreshTokenCreated: + ( + final session, { + required final authUserId, + required final refreshTokenId, + required final transaction, + }) async { + await TokenMetadata.db.insertRow( + session, + TokenMetadata( + refreshTokenId: refreshTokenId, + name: 'general-token', + ipAddress: session.request?.connectionInfo.remote.address.toString(), + userAgent: session.request?.headers.userAgent, + ), + transaction: transaction, + ); + }, +), +``` + +To revoke tokens based on your custom metadata, query the metadata table for the token IDs you want to revoke and call `revokeToken` for each: + +```dart +final tokenMetadata = await TokenMetadata.db.find( + session, + where: (final row) => row.deviceName.equals('Old Device'), +); + +for (final row in tokenMetadata) { + await AuthServices.instance.tokenManager.revokeToken( + session, + tokenId: row.refreshTokenId.toString(), + ); +} +``` + +#### Attaching metadata when issuing tokens from an endpoint + +The `onRefreshTokenCreated` callback is global and runs for every new refresh token (including those created by identity providers). When you create a token from an endpoint—for example, a personal access token (PAT) or CLI token—you often have endpoint-specific parameters (e.g. a token name or label) that the callback cannot see. In that case, issue the token with `AuthServices.instance.tokenManager.issueToken`, then use the returned `AuthSuccess.jwtRefreshTokenId` to insert your metadata with the endpoint's parameters: + +```dart +final authSuccess = await AuthServices.instance.tokenManager.issueToken( + session, + authUserId: userId, + method: 'pat', + scopes: {Scope.admin}, +); + +await TokenMetadata.db.insertRow( + session, + TokenMetadata( + refreshTokenId: authSuccess.jwtRefreshTokenId, + name: tokenName, // from your endpoint parameter + deviceName: deviceName, // from your endpoint parameter + ipAddress: session.request?.connectionInfo.remote.address.toString(), + userAgent: session.request?.headers.userAgent, + ), +); +``` + ## 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. diff --git a/docs/06-concepts/11-authentication/05-token-managers/03-server-side-sessions-token-manager.md b/docs/06-concepts/11-authentication/05-token-managers/03-server-side-sessions-token-manager.md index 5fb59f45..10b64b69 100644 --- a/docs/06-concepts/11-authentication/05-token-managers/03-server-side-sessions-token-manager.md +++ b/docs/06-concepts/11-authentication/05-token-managers/03-server-side-sessions-token-manager.md @@ -52,6 +52,105 @@ final serverSideSessionsConfig = ServerSideSessionsConfigFromPasswords( ); ``` +### Attaching custom metadata to sessions + +You can attach custom metadata to each server-side session by providing an `onSessionCreated` callback. This is useful for storing device information, IP address, user agent, or any other data you need to query or display later (for example, in a "sessions" or "devices" list). The callback runs when a session is created, within the same transaction as the session insert. + +Define a server-only table that relates to `ServerSideSession` and store your metadata there. Example schema: + +```yaml +class: SessionMetadata +serverOnly: true +table: session_metadata +fields: + ### The [ServerSideSession] this metadata belongs to + serverSideSession: module:serverpod_auth_core:ServerSideSession?, relation(onDelete=Cascade) + + ### The name of the token + name: String? + + ### Device information for the session + deviceName: String? + + ### IP address from which the session was created + ipAddress: String? + + ### User agent string + userAgent: String? + +indexes: + server_side_session_id_unique_idx: + fields: serverSideSessionId + unique: true +``` + +Then configure the callback in your server-side sessions config: + +```dart +ServerSideSessionsConfigFromPasswords( + onSessionCreated: + ( + final session, { + required final authUserId, + required final serverSideSessionId, + required final transaction, + }) async { + await SessionMetadata.db.insertRow( + session, + SessionMetadata( + serverSideSessionId: serverSideSessionId, + name: 'general-session', + ipAddress: session.request?.connectionInfo.remote.address.toString(), + userAgent: session.request?.headers.userAgent, + ), + transaction: transaction, + ); + }, +), +``` + +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: + +```dart +final tokenMetadata = await SessionMetadata.db.find( + session, + where: (final row) => row.deviceName.equals('Old Device'), +); + +for (final row in tokenMetadata) { + await AuthServices.instance.tokenManager.revokeToken( + session, + tokenId: row.serverSideSessionId.toString(), + ); +} +``` + +#### Attaching metadata when issuing tokens from an endpoint + +The `onSessionCreated` callback is global and runs for every new session (including those created by identity providers). When you create a token from an endpoint—for example, a personal access token (PAT) or CLI token—you often have endpoint-specific parameters (e.g. a token name or label) that the callback cannot see. In that case, issue the token with `AuthServices.instance.tokenManager.issueToken`, then use the returned `AuthSuccess.serverSideSessionId` to insert your metadata with the endpoint's parameters: + +```dart +final authSuccess = await AuthServices.instance.tokenManager.issueToken( + session, + authUserId: userId, + method: 'pat', + scopes: {Scope.admin}, +); + +await SessionMetadata.db.insertRow( + session, + SessionMetadata( + serverSideSessionId: authSuccess.serverSideSessionId, + name: tokenName, // from your endpoint parameter + deviceName: deviceName, // from your endpoint parameter + ipAddress: session.request?.connectionInfo.remote.address.toString(), + userAgent: session.request?.headers.userAgent, + ), +); +``` + +See [Issuing Tokens](./managing-tokens#issuing-tokens) in Managing tokens for more context. + ## 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.