Skip to content

Commit e2f365b

Browse files
committed
docs: Document serverpod_auth_email setup and migrating from serverpod_auth to the new provider using serverpod_auth_migration
Closes serverpod/serverpod#3663
1 parent b893c13 commit e2f365b

File tree

2 files changed

+334
-0
lines changed

2 files changed

+334
-0
lines changed
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# Setup (`serverpod_auth_*`)
2+
3+
Serverpod comes with built-in support for authentication. It is possible to build a [custom authentication implementation](custom-overrides), but the recommended way to authenticate users is to use one of the provided `serverpod_auth_*` modules. The modules make it easy to authenticate with email or social sign-ins and currently supports signing in with email, Google, Apple, and Firebase.
4+
5+
Future versions of the authentication module will include more options. If you write another authentication module, please consider [contributing](/contribute) your code.
6+
7+
We provide the following packages of ready-to use authentication providers. They all include a basic user profile courtesy of `serverpod_auth_profile`, and session management through `serverpod_auth_session`.
8+
9+
|Package|Functionality|
10+
|-|-|
11+
|`serverpod_auth_email`|Ready to use email authentication.|
12+
|`serverpod_auth_apple`|Ready to use "Sign in with Apple" authentication.|
13+
|`serverpod_auth_google`|Ready to use "Sign in with Google authentication.|
14+
15+
If you would like the basic authentication offered by these packages, but combine them with a different approach to session management or another kind of user profile have [a look at the underlying packages below](#low-level-building-blocks).
16+
17+
## Sessions
18+
19+
When using any of the "ready-to-use" authentication providers listed above, session management based on `serverpod_auth_session` is already included.
20+
21+
Just follow any of the individual authentication provider guides to set the `AuthSessions`' `authenticationHandler` on your `Serverpod` instance.
22+
23+
## Email
24+
25+
To get started with email based authentications, add `serverpod_auth_email` to your project. This will add a sign-up flow with email verification, and support logins and session management (through `serverpod_auth_session`). By default, this adds user profiles for each registration through `serverpod_auth_profile`.
26+
27+
The only requirement for using this module is having a way to send out emails, so users can receive the initial verification email and also request a password reset later on.
28+
29+
30+
### Server setup
31+
32+
Add the module as a dependency to the server project's `pubspec.yaml`.
33+
34+
```sh
35+
$ dart pub add serverpod_auth_email_server
36+
```
37+
38+
39+
As the email auth module does not expose any endpoint by default, but rather just an [`abstract` endpoint](../working-with-endpoints#endpoint-method-inheritance), a subclass of the default implementation has to be added to the current project in order to expose its APIs to outside client.
40+
41+
For this add an `email_account_endpoint.dart` file to the project:
42+
43+
```dart
44+
import 'package:serverpod_auth_email_server/serverpod_auth_email_server.dart'
45+
as email_account;
46+
47+
/// Endpoint for email-based authentication.
48+
class EmailAccountEndpoint extends email_account.EmailAccountEndpoint {}
49+
```
50+
51+
In this `class` `@override`s could be used to augment the default endpoint implementation.
52+
53+
Next, add the authentication handler to the Serverpod instance.
54+
55+
```dart
56+
import 'package:serverpod_auth_email_server/serverpod_auth_email_server.dart';
57+
58+
void run(List<String> args) async {
59+
var pod = Serverpod(
60+
args,
61+
Protocol(),
62+
Endpoints(),
63+
authenticationHandler: AuthSessions.authenticationHandler, // Add this line
64+
);
65+
66+
...
67+
}
68+
```
69+
70+
In order to generate server and client the code for the newly added endpoint, run:
71+
72+
```bash
73+
$ serverpod generate
74+
```
75+
76+
Additionally the database schema will need to be extended to add new tables for the email accounts. Create a new migration using the `create-migration` command.
77+
78+
```bash
79+
$ serverpod create-migration
80+
```
81+
82+
As the last step, the email authentication package needs to be configured.
83+
For this set the `EmailAccounts.config` (from package `serverpod_auth_email_account_server`), which contains the business logic used by the endpoint. This configuration should be added before the `await pod.start();` call. Callbacks need to be provided for at least `sendRegistrationVerificationCode` and `sendPasswordResetVerificationCode`, while the rest can be left to their default values.
84+
85+
```dart
86+
import 'package:serverpod_auth_email_server/serverpod_auth_email_server.dart';
87+
88+
EmailAccounts.config = EmailAccountConfig(
89+
sendRegistrationVerificationCode: (
90+
final session, {
91+
required final email,
92+
required final accountRequestId,
93+
required final verificationCode,
94+
required final transaction,
95+
}) {
96+
// Send out actual email with the verification code
97+
},
98+
sendPasswordResetVerificationCode: (
99+
final session, {
100+
required final email,
101+
required final passwordResetRequestId,
102+
required final transaction,
103+
required final verificationCode,
104+
}) {
105+
// Send out actual email with the verification code
106+
},
107+
);
108+
```
109+
110+
If you're not hosting on Serverpod Cloud, you might consider an email sendout provider like [Mailjet](https://www.mailjet.com/) or [SendGrid](https://sendgrid.com/en-us).
111+
112+
It is up to the application to decide how to use the callbacks. Basically there are 2 primary approaches possible:
113+
- Send out the `verificationCode` and require that the client initiating the request completes the operation (account creation or password reset). In that case the user could copy/paste or retype the (short) verification into a form on the client.
114+
- Alternatively emails could contain a deep link with both the respective request ID and the verification code, which would then even support them being opened on any device (e.g. on a desktop, even if the original request was made on mobile).
115+
116+
Additionally you need to update the `passwords.yaml` file to include secrets for both `serverpod_auth_session_sessionKeyHashPepper` and `serverpod_auth_email_account_passwordHashPepper`.
117+
These should be random and at least 10 characters long. These pepper values must not be changed after the initial deployment of the server, as they are baked into every session key and stored password, and thus a roatation would invalidate previously created credentials.
118+
119+
After a restart of the Serverpod the new endpoints will be usable from the client.
120+
121+
### Client setup
122+
123+
First, add a dependency on `serverpod_auth_session_flutter` to your app's `pubspec.yaml`, to be able to make use of the sessions generated by the newly created email endpoint.
124+
125+
```yaml
126+
dependencies:
127+
flutter:
128+
sdk: flutter
129+
serverpod_flutter: ^3.0.0
130+
auth_example_client: # You generated client, name may differ
131+
path: ../auth_example_client
132+
133+
serverpod_auth_session_flutter: ^3.0.0 # Add this line
134+
```
135+
136+
Next, add the `SessionManager` to your app, passing it to the generated Serverpod `Client`.
137+
138+
```dart
139+
import 'package:serverpod_auth_session_flutter/serverpod_auth_session_flutter.dart' show SessionManager;
140+
141+
late SessionManager sessionManager;
142+
late Client client;
143+
144+
void main() async {
145+
// Need to call this as we are using Flutter bindings before runApp is called.
146+
WidgetsFlutterBinding.ensureInitialized();
147+
148+
// The session manager keeps track of the signed-in state of the user. You
149+
// can query it to see if the user is currently signed in and get information
150+
// about the user.
151+
sessionManager = SessionManager();
152+
await sessionManager.init();
153+
154+
// The android emulator does not have access to the localhost of the machine.
155+
// const ipAddress = '10.0.2.2'; // Android emulator ip for the host
156+
157+
// On a real device replace the ipAddress with the IP address of your computer.
158+
const ipAddress = 'localhost';
159+
160+
// Sets up a singleton client object that can be used to talk to the server from
161+
// anywhere in our app. The client is generated from your server code.
162+
// The client is set up to connect to a Serverpod running on a local server on
163+
// the default port. You will need to modify this to connect to staging or
164+
// production servers.
165+
client = Client(
166+
'http://$ipAddress:8080/',
167+
authenticationKeyManager: sessionManager,
168+
)..connectivityMonitor = FlutterConnectivityMonitor();
169+
170+
runApp(MyApp());
171+
}
172+
```
173+
174+
### User consolidation
175+
176+
<!-- TODO: Explain how to connect with other providers -->
177+
<!-- Other providers should just refer to this section, as the setup will be similar -->
178+
179+
## Low-level building blocks
180+
181+
### Session Management
182+
183+
|Package|Functionality|
184+
|-|-|
185+
|`serverpod_auth_session`|Database-backed session handling, with flexible configuration per session.|
186+
|`serverpod_auth_jwt`|JWT-based session implemented, which can also generate access token with public/private key cryptography to use with 3rd party services.|
187+
188+
### Authentication
189+
190+
The following package provide the core authentication functionality, but without providing a default `Endpoint` base implementation. Thus they can be combined with another session package and include further modification, like for example a custom user profile.
191+
192+
|Package|Functionality|
193+
|-|-|
194+
|`serverpod_auth_email_account`|Basic email authentication.|
195+
|`serverpod_auth_apple_account`|Basic "Sign in with Apple" authentication.|
196+
|`serverpod_auth_google_account`|Basic "Sign in with Google authentication.|
197+
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Upgrading from `serverpod_auth`
2+
3+
With the release of Serverpod 3.0, the single legacy `serverpod_auth` package was replaced with a set of modular packages providing flexible modules for users, authentication providers, profiles, and session management.
4+
5+
For an existing Serverpod application which makes use of `serverpod_auth` (which also still works with Serverpod 3.0) to upgrade to the new package, there exists `serverpod_auth_migration` to facilitate moving over all authentication- and user-related data into the new modules.
6+
Once the migration is complete, the legacy `serverpod_auth` module and the `serverpod_auth_migration` dependencies can be removed, which will also remove the then obsolete tables from the database.
7+
8+
Due to the modular approach, each used authentication provider (email, Apple, Google, etc.) needs to be configured individually for the migration.
9+
No matter through which authentication provider(s) a user is migrated, their profile will always be migrated as well by default.
10+
11+
## General timeline
12+
13+
The suggested migration timeline applies to all auth providers.
14+
15+
1. Add and configure the new auth modules for the desired providers, e.g. [email](../concepts/authentication/setup_new#email)
16+
2. Add the migration module and configure each auth provider and set up a background migration job <!-- TODO: Build and document Future call for migration -->
17+
3. Switch over all clients to use the new authentication endpoints
18+
4. After a sufficient percentage of the userbase has migrated to the new endpoints, disable sign-ups throught the legacy package <!-- TODO: Build and showcase "off switch" in legacy package -->
19+
5. Later on also disable logins and password resets on the `serverpod_auth` APIs
20+
6. Once all users have been migrated (some of them via the background processes, albeit without passwords) remove the dependency on `serverpod_auth` and `serverpod_auth_migration` and delete all obsolete code
21+
7. The next migration will then also remove obsolete tables
22+
23+
If the Serverpod application stores data with a relation to the `int` `UserInfo` ID, this needs to be migrated to the new `UUID` id of the `AuthUser`.
24+
To support this the `serverpod_auth_migration` packages provides a single hooks which is run for each user migration in which any related entities from your app can be migrated as well.
25+
26+
## Sessions
27+
28+
In order to support both sessions created by the legacy `serverpod_auth` and through the new `serverpod_auth_session` package, the `authenticationHandler` has to be updated to try both of them.
29+
Since an invalid/unknown session key just yields a `null` result, they can be chained like this:
30+
31+
```dart
32+
import 'package:serverpod_auth_server/serverpod_auth_server.dart' as auth;
33+
import 'package:serverpod_auth_email/serverpod_auth_email.dart' show AuthSessions;
34+
35+
36+
final pod = Serverpod(
37+
args,
38+
Protocol(),
39+
Endpoints(),
40+
authenticationHandler: (session, key) async {
41+
return await AuthSessions.authenticationHandler(session, key) ??
42+
await auth.authenticationHandler(session, key);
43+
},
44+
);
45+
```
46+
47+
All high-level authentication provider package use `serverpod_auth_session` under the hood, and only a single handler needs to be registered to support all of them.
48+
49+
## Migrating Email Authentications
50+
51+
Before starting with the email account migration, the email authentication from `serverpod_auth_email` must be [set up as described](../concepts/authentication/setup_new#email).
52+
53+
Since the password storage format changed between the legacy and new modules, and because we only have access to the plain text password during `login` (when it's sent from the client), there are 2 scenarios of migrating user accounts and the email authentication.
54+
55+
During a login, we can verify the incoming credentials, and then migrate the entire account including the password.
56+
In all other cases (e.g. a password reset or a background migration job) we can migrate the user profile and account, but not set its password. The password could be set on a subsequent login (if it matches the one from the legacy module), or in case the user did not log in during the migration phase, they will have to resort to a password reset.
57+
58+
In order to avoid creating duplicate accounts for the "same" user in both the legacy and new system, one needs to ensure that the migration is always called before the new module would attempt any user lookups or creations.
59+
To support this in the new endpoint, which now exists as a subclass in the Serverpod application, the migration methods need to be added to each affected endpoint.
60+
61+
```dart
62+
class EmailAccountEndpointWithMigration
63+
extends email_account.EmailAccountEndpoint {
64+
@override
65+
Future<String> login(
66+
final Session session, {
67+
required final String email,
68+
required final String password,
69+
}) async {
70+
// Add this before the call to `super` in the endpoint subclass
71+
await AuthMigrationEmail.migrateOnLogin(
72+
session,
73+
email: email,
74+
password: password,
75+
);
76+
77+
return super.login(session, email: email, password: password);
78+
}
79+
80+
@override
81+
Future<void> startRegistration(
82+
final Session session, {
83+
required final String email,
84+
required final String password,
85+
}) async {
86+
// Add this before the call to `super` in the endpoint subclass
87+
await AuthMigrationEmail.migrateOnLogin(
88+
session,
89+
email: email,
90+
password: password,
91+
);
92+
93+
return super.startRegistration(session, email: email, password: password);
94+
}
95+
96+
@override
97+
Future<void> startPasswordReset(
98+
final Session session, {
99+
required final String email,
100+
}) async {
101+
// Add this before the call to `super` in the endpoint subclass
102+
await AuthMigrationEmail.migrateWithoutPassword(session, email: email);
103+
104+
return super.startPasswordReset(session, email: email);
105+
}
106+
}
107+
```
108+
109+
After this modification, the endpoint will always attempt a migration first, before then proceeding with the desired request (e.g. registering a new account if none exists yet).
110+
111+
The migration works fully out of the box. But in case the existing `UserInfo` should not be moved to a new `serverpod_auth_profile` `UserProfile`, this can be disabled through the `AuthMigrationEmailConfig`:
112+
113+
```dart
114+
AuthMigrationEmail.config = AuthMigrationConfig(
115+
importProfile: false,
116+
userMigrationHook: (session, {required newAuthUserId, required oldUserId, transaction}) => …,
117+
);
118+
```
119+
120+
## User ID Migration
121+
122+
The `userMigrationHook` shown above is also the place where you should migraten all references to a legacy `UserInfo` id to the new `AuthUser` `UUID`.
123+
124+
Commonly there are 2 approach how this could be handled:
125+
126+
One way would be to extend any database table that currently points to a `UserInfo` to also gain an optional `AuthUser` id column.
127+
Then during the migration identify all rows belonging to the user and set their `AuthUser` id value. Later on the `UserInfo`-relation column will just be dropped when the migration is done.
128+
Care needs to be taken that one does not keep writing new rows with just a `UserInfo` relation, as the migration for the previous user will not be run again, and thus this would become unreachable with the user's new ID.
129+
A benefit of this approach is that existing queries for the user's entities (e.g. team memberships) could easily be accomodated by looking for the `int` or `UUID` value depending on the ID type of the `AuthenticationInfo`.
130+
131+
Alternatively, especially if one wants to make further changes to one schema in conjunction with the user migration, one could migrate to altogether new tables in the migration, which only point to `AuthUser`s and where the relation would not need to be optional.
132+
All future reads and writes (for migrated users) should then go to the new tables – which might not be feasible if the data is shared between both legacy and new users. But potentially one could just run a migration for all users in a team/company so all users in that group could start using the new tables.
133+
<!-- For this to work with old session, we would probably need another migration layer to look up the "new ID" in a request (which was started with a legacy session) (on the session? might easily get races on app start up) -->
134+
135+
## Future calls (background migration)
136+
137+
<!-- TODO -->

0 commit comments

Comments
 (0)