Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Setup

:::warning
The Anonymous identity provider is **experimental** and can not be completely used yet due to the missing support for account linking. The missing parts will be added in the next releases.
:::

To properly configure Anonymous authentication, you must allow anonymous access in your Serverpod auth configuration.

:::caution
You need to install the auth module before you continue, see [Setup](../../setup).
:::

## Server-side configuration

In your main `server.dart` file, configure the anonymous identity provider using the `AnonymousIdpConfig` object and add it to your `pod.initializeAuthServices()` configuration:

```dart
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
import 'package:serverpod_auth_idp_server/providers/anonymous.dart';

void run(List<String> args) async {
final pod = Serverpod(
args,
Protocol(),
Endpoints(),
);

pod.initializeAuthServices(
tokenManagerBuilders: [
JwtConfigFromPasswords(),
],
identityProviderBuilders: [
// Configure the Anonymous Identity Provider
AnonymousIdpConfig(),
],
);

await pod.start();
}
```

Then, extend the abstract endpoint to expose it on the server:

```dart
import 'package:serverpod_auth_idp_server/providers/anonymous.dart';

class AnonymousIdpEndpoint extends AnonymousIdpBaseEndpoint {}
```

Then, run `serverpod generate` to generate the client code and create a migration to initialize the database for the provider. More detailed instructions can be found in the general [identity providers setup section](../../setup#identity-providers-configuration).

### Basic configuration options

Although the Anonymous IDP can be used directly with no other configuration, it is recommended to add some form of app attestation to prevent abuse on production environments. See the [Using a token for app attestation section](./configuration#using-a-token-for-app-attestation) for more details.

For other configuration options such as callbacks (before/after account creation) and rate limiting, see the [configuration section](./configuration).

## Client-side configuration

If you have configured the `SignInWidget` as described in the [setup section](../../setup#present-the-authentication-ui), the Anonymous identity provider will be automatically detected and displayed in the sign-in widget as a "Continue without account" option.

You can also use the `AnonymousSignInWidget` to include anonymous sign-in in your own custom UI:

```dart
import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart';

AnonymousSignInWidget(
client: client,
onAuthenticated: () {
// Do something when the user is authenticated.
//
// NOTE: You should not navigate to the home screen here, otherwise
// the user will have to sign in again every time they open the app.
},
onError: (error) {
// Handle errors
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $error')),
);
},
)
```

The widget displays a "Continue without account" button that creates an anonymous session when pressed. For details on customizing the button (size, shape), using a custom widget with `SignInWidget`, or building a fully custom UI with `AnonymousAuthController`, see the [customizing the UI section](./customizing-the-ui).
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Configuration

This page covers configuration options for the anonymous identity provider beyond the basic setup.

## Using a token for app attestation

The anonymous `login` endpoint accepts an optional **token** that is forwarded to your `onBeforeAnonymousAccountCreated` callback. This lets you tie anonymous sign-in to an app attestation or app-check provider (e.g. [Firebase App Check](https://firebase.google.com/docs/app-check)) so only requests from your real app can create anonymous accounts.

:::warning
Using the anonymous provider without a token for app attestation is not recommended due to the risk of abuse. Make sure to configure an attestation before releasing your app to the public.
:::

### Configuring the Flutter app

Obtain a token from your app-check provider and pass it to the login call by setting `createAnonymousToken` on `AnonymousSignInWidget` or `AnonymousAuthController`. That callback is invoked when the user taps "Continue without account". The returned token is sent to the server as the `token` argument of the anonymous login endpoint.

```dart
import 'package:firebase_app_check/firebase_app_check.dart';
import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart';

AnonymousSignInWidget(
client: client,
createAnonymousToken: () async {
// Get a Firebase App Check token (or similar) to prove the request comes
// from your app to prevent abuse.
final appCheckToken = await FirebaseAppCheck.instance.getToken();
return appCheckToken;
},
onAuthenticated: () { /* ... */ },
onError: (error) { /* ... */ },
)
```

### Configuring the Server

In `onBeforeAnonymousAccountCreated`, receive the optional `token` and verify it with your app-check provider. If verification fails or the token is missing (when you require it), throw an `AnonymousAccountBlockedException` with reason `denied` to block account creation.

```dart
AnonymousIdpConfig(
onBeforeAnonymousAccountCreated: (
Session session, {
String? token,
required Transaction? transaction,
}) async {
if (token == null || token.isEmpty) {
throw AnonymousAccountBlockedException(
reason: AnonymousAccountBlockedExceptionReason.denied,
);
}
// Verify the token with your app-check provider (e.g. Firebase App Check).
// Example: call Firebase's verifyAppCheckToken REST API or your provider's
// verification endpoint. If invalid, throw AnonymousAccountBlockedException.
final isValid = await _verifyAppCheckToken(session, token);
if (!isValid) {
throw AnonymousAccountBlockedException(
reason: AnonymousAccountBlockedExceptionReason.denied,
);
}
},
)
```

For Firebase App Check, you can verify the token from a custom backend using the [Firebase App Check REST API](https://firebase.google.com/docs/app-check/custom-resource-backend) (`verifyAppCheckToken`). Other app-check or attestation providers can be integrated the same way: client sends a token, server validates it in the callback and denies creation if invalid.

## Reacting to anonymous account creation

Beside the `onBeforeAnonymousAccountCreated` callback to allow or deny creation, you can also use the `onAfterAnonymousAccountCreated` callback to run logic after a new anonymous account has been created (e.g. analytics or side effects).

```dart
AnonymousIdpConfig(
onAfterAnonymousAccountCreated: (
Session session, {
required UuidValue authUserId,
required Transaction? transaction,
}) async {
// e.g. track creation for analytics or send to your logging service
},
)
```

## Rate limiting

The anonymous provider includes built-in rate limiting per IP address to prevent abuse. The default is 100 anonymous account creations per hour per IP. You can customize the rate limit in the `AnonymousIdpConfig` using the `perIpAddressRateLimit` parameter:

```dart
AnonymousIdpConfig(
perIpAddressRateLimit: const RateLimit(
maxAttempts: 50,
timeframe: Duration(hours: 1),
),
)
```

When the limit is exceeded, the provider throws an `AnonymousAccountBlockedException` with reason `tooManyAttempts`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Customizing the UI

When using the anonymous identity provider, you can customize the UI to your liking. You can use the `AnonymousSignInWidget` to display the anonymous sign-in button in your own layout, or you can use the `AnonymousAuthController` to build a completely custom authentication interface.

:::info
The `SignInWidget` uses the `AnonymousSignInWidget` internally when the anonymous provider is enabled. You can supply a custom `AnonymousSignInWidget` to the `SignInWidget` to override the default (e.g. to pass `createAnonymousToken` or change size and shape).

```dart
SignInWidget(
client: client,
anonymousSignInWidget: AnonymousSignInWidget(
client: client,
createAnonymousToken: () async => await getAppCheckToken(),
size: AnonymousButtonSize.medium,
shape: AnonymousButtonShape.rectangular,
),
)
```
:::

## Using the `AnonymousSignInWidget`

The `AnonymousSignInWidget` displays a single "Continue without account" button that starts the anonymous sign-in flow when pressed. You can customize the widget's behavior and appearance using its constructor parameters:

```dart
import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart';

AnonymousSignInWidget(
client: client,
createAnonymousToken: () async {
// Optional: provide a token for app attestation (e.g. Firebase App Check)
return await getAppCheckToken();
},
onAuthenticated: () {
// Do something when the user is authenticated.
//
// NOTE: You should not navigate to the home screen here, otherwise
// the user will have to sign in again every time they open the app.
},
onError: (error) {
// Handle errors
},
size: AnonymousButtonSize.large, // large (default), medium, or small
shape: AnonymousButtonShape.pill, // pill (default) or rectangular
)
```

Optionally, you can provide an externally managed `AnonymousAuthController` instance to the widget. When a controller is provided, `client`, `onAuthenticated`, and `onError` are ignored in favor of the controller's configuration.

```dart
AnonymousSignInWidget(
controller: controller,
size: AnonymousButtonSize.medium,
shape: AnonymousButtonShape.rectangular,
)
```

### Customizing the button appearance

The widget renders a single **TextButton** with the label "Continue without account". The button uses Flutter's material design system, so it reacts to your app's `Theme`. You can wrap the widget in a `Theme` (or `ThemeData`) to change colors and typography:

```dart
Theme(
data: Theme.of(context).copyWith(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: Colors.blue,
),
),
),
child: AnonymousSignInWidget(client: client),
)
```

The widget constrains the button to a minimum width of 240 and maximum width of 400; you can place it in a `SizedBox`, `Expanded`, or `Flex` to control layout.

## Building a custom UI with the `AnonymousAuthController`

For full control over the UI, use the `AnonymousAuthController` class. It provides the anonymous sign-in logic without any built-in widget, so you can trigger login from your own button or flow and build a completely custom layout.

```dart
import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart';

final controller = AnonymousAuthController(
client: client,
createAnonymousToken: () async => await getAppCheckToken(),
onAuthenticated: () {
// Do something when the user is authenticated.
},
onError: (error) {
// Handle errors
},
);
```

### AnonymousAuthController state management

The controller notifies listeners when its state changes. Use these properties to drive your UI:

```dart
// Check if a request is in progress
final isLoading = controller.isLoading;

// Check current state (idle, loading, error, authenticated)
final state = controller.state;

// Listen to state changes
controller.addListener(() {
setState(() {
// Rebuild when controller state changes
});
});
```

### AnonymousAuthController methods

The controller exposes a single action for anonymous sign-in:

```dart
// Start anonymous sign-in.
// Obtains token if `createAnonymousToken` is set, then calls the login endpoint.
await controller.login();
```

Call `controller.login()` from your custom button's `onPressed`, or from any other trigger (e.g. after a delay or when the user performs an action). The controller handles loading state, success, and errors and invokes `onAuthenticated` or `onError` as appropriate.

:::tip
Remember to dispose the controller when it is no longer needed (e.g. in your widget's `dispose`), unless the widget manages its own controller and disposes it for you.
:::
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"label": "Anonymous",
"collapsed": true
}