Skip to content

Authentication

Sign-up, sign-in, two-factor, one-time codes, OAuth, anonymous users and admin sign-in with the Dart SDK.

Every flow of an auth collection is on vb.auth.collection(name); admin sign-in is on vb.auth.admin. A sign-in stores the session in vb.authStore, and every request after it carries the token.

final users = vb.auth.collection('users');

Sign-ins that can stop halfway return a sealed AuthResult, so a switch covers every case:

final result = await users.login(email, password);
switch (result) {
case SignedIn(:final record):
print('signed in as ${record['email']}');
case MfaRequired(:final mfaToken):
// two-factor is on: nothing is stored yet
await users.loginMfa(mfaToken, code: codeFromAuthenticator);
case MergeRequired():
// only from OAuth; see below
}

SignedIn carries token and record. Flows that can’t stop halfway (anonymous, promote, loginMfa) return SignedIn directly.

vb.auth.isSignedIn; // a token that hasn't expired
vb.authStore.record; // the signed-in user (or admin) as a map
vb.authStore.onChange; // Stream<StoredAuth?>: sign-in, refresh, sign-out
await vb.auth.me(); // the user, fresh from the server
await vb.auth.refresh(); // a new token now; stored
await vb.auth.logout(); // revokes the token on the server and clears the store

Refreshing is automatic: 60 seconds before the token expires, and once after a 401. You rarely call refresh() yourself.

final user = await users.register('[email protected]', 'a-long-password',
data: {'name': 'Ann'}); // doesn't sign in
await users.login('[email protected]', 'a-long-password');

Sign-up must be open on the collection (its create rule).

Verification, password reset, email change

Section titled “Verification, password reset, email change”
await users.requestVerification(); // emails the signed-in user
await users.confirmVerification(tokenFromLink);
await users.requestPasswordReset('[email protected]');
await users.confirmPasswordReset(tokenFromLink, 'new-password');
await users.requestEmailChange('[email protected]');
await users.confirmEmailChange(tokenFromLink, 'current-password'); // signs out
await users.acceptInvite(tokenFromInvite, 'chosen-password');
await users.requestOtp('[email protected]');
await users.authWithOtp(email: '[email protected]', code: '123456');
// or, from the emailed link:
await users.authWithOtp(token: tokenFromLink);
final setup = await users.setupTotp();
// show setup.otpauthUrl as a QR code (or setup.secret to type in)
final recoveryCodes = await users.confirmTotp(codeFromApp); // show these once
await users.recoveryCodeStatus(); // (total: 10, remaining: 9)
await users.regenerateRecoveryCodes(codeFromApp);
await users.disableTotp(codeFromApp);

A sign-in then returns MfaRequired; finish it with loginMfa(mfaToken, code: ...) or loginMfa(mfaToken, recoveryCode: ...).

await users.anonymous(); // a new guest account
await users.promote('[email protected]', 'a-long-password'); // same id, keeps its records

Anonymous sign-in is off by default (auth.anonymous.enabled).

In a Flutter app, use signInWithOAuth2, which handles the browser: see Flutter. The steps underneath, for other set-ups:

final providers = await users.oauth2Providers();
final start = await users.oauth2Authorize('github', redirectUri, usePkce: true);
// send the user to start.authorizeUrl; the provider redirects to redirectUri
final result = await users.oauth2ExchangeRedirect('github', redirectUri, redirectedTo);

The client remembers the state secret from oauth2Authorize; if another client instance handles the redirect, pass stateSecret: yourself.

An ID token from the platform’s sign-in sheet (Sign in with Apple, Google sign-in) signs in without a browser:

final nonce = await users.idTokenNonce('apple'); // required for Apple
// ... the sheet, given the nonce, returns an ID token ...
await users.signInWithIdToken('apple', idToken, nonce: nonce, name: name);

See Flutter for both providers end to end.

When the provider’s email matches an existing account, the result is MergeRequired. Linking needs the account’s password, when it has one:

await users.oauth2MergeConfirm(merge.mergeToken, password: password);
await users.oauth2Unlink('github');
final status = await vb.auth.admin.setupStatus(); // (hasAdmin: ..., keyRequired: ...)
await vb.auth.admin.login('[email protected]', password);
await vb.auth.admin.invite('users', '[email protected]', data: {'name': 'Bo'});
// A session for a user, not stored here: use the token in another client.
final session = await vb.auth.admin.impersonate('users', userId);