Skip to content

Flutter

Sessions that survive restarts, OAuth in the system browser, and app lifecycle handling with vaultbase_flutter.

vaultbase_flutter adds three things to the client for apps. It re-exports package:vaultbase, so import only this:

import 'package:vaultbase_flutter/vaultbase_flutter.dart';

The default MemoryAuthStore forgets the session when the app exits. Pass a persistent store instead:

StoreWhere the session goesUse on
SecureAuthStore()Keychain (iOS, macOS), Keystore-backed storage (Android), via flutter_secure_storageMobile and desktop
PrefsAuthStore()Shared preferences: localStorage on the webThe web, or where secure storage isn’t available
final vb = Vaultbase('https://api.example.com',
authStore: kIsWeb ? PrefsAuthStore() : SecureAuthStore());
await vb.authStore.ready;

The saved session loads when the store is created. Requests and the realtime connection wait for it, so awaiting ready isn’t required, but it lets the first frame know whether the user is signed in.

Options:

SecureAuthStore(
storage: const FlutterSecureStorage(/* platform options */),
key: 'vaultbase_auth', // one key per server, if the app talks to several
);
PrefsAuthStore(key: 'vaultbase_auth');

Any other storage works through AsyncAuthStore (in package:vaultbase):

final store = AsyncAuthStore(
read: () => myStorage.read('session'), // Future<String?>
write: (value) => value == null
? myStorage.delete('session')
: myStorage.write('session', value),
);

Writes run one at a time, in order. Saved data that doesn’t parse counts as no session.

final lifecycle = VaultbaseLifecycle(vb);
// lifecycle.dispose() if the client goes away before the app does
  • In the background for 30 seconds (pauseAfter), the realtime connection closes. Mobile systems drop idle sockets anyway; this makes it predictable.
  • Back in the foreground, it reconnects and subscribes again. Changes made meanwhile aren’t replayed, so reload what’s on screen if that matters (listen to vb.realtime.states for open).
  • On resume, a session that expires within 5 minutes (refreshWithin) is refreshed.

signInWithOAuth2 runs the whole flow: it asks the server for the provider’s sign-in URL, opens it in the system’s sign-in browser (ASWebAuthenticationSession on iOS and macOS, Custom Tabs on Android, a popup on the web), and exchanges the code it comes back with.

try {
final result = await vb.auth.collection('users').signInWithOAuth2('github',
redirectUri: 'com.example.app://oauth');
switch (result) {
case SignedIn():
break; // stored; authStore.onChange fires
case MfaRequired(:final mfaToken):
// ask for a code, then loginMfa(mfaToken, code: ...)
case MergeRequired(:final mergeToken, :final email):
// the email has an account: oauth2MergeConfirm(mergeToken, password: ...)
}
} on CancelledException {
// the user closed the browser
}

The server keeps the PKCE verifier (pass usePkce: false to turn it off). Behind it are oauth2Authorize and oauth2ExchangeRedirect, if you need the steps yourself.

On the server:

  • Configure the provider (oauth2.<provider>.* settings).
  • Allow the redirect: add com.example.app://oauth* to oauth2.allowed_redirect_uris (or the provider’s own list). See OAuth2.

At the provider, register the same redirect URI as the callback. Some providers only accept https callbacks; for those, use an https page that forwards to the app scheme, and pass that page as redirectUri.

On each platform (details in the flutter_web_auth_2 set-up):

  • Android: register the scheme for the callback activity in android/app/src/main/AndroidManifest.xml:

    <activity
    android:name="com.linusu.flutter_web_auth_2.CallbackActivity"
    android:exported="true"
    android:taskAffinity="">
    <intent-filter android:label="flutter_web_auth_2">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="com.example.app" />
    </intent-filter>
    </activity>

    An https redirect on Android also needs httpsHost and httpsPath in options: FlutterWebAuth2Options(...).

  • iOS and macOS: nothing to register; the sign-in session catches the scheme itself.

  • Web: the redirect must be a page of the app (same origin) that posts the URL back to it: add web/auth.html from the flutter_web_auth_2 instructions and use https://your.app/auth.html as redirectUri.

The platform’s own sign-in sheet (Sign in with Apple, Google sign-in) instead of a browser tab. The sheet comes from the platform packages, which you add to the app yourself; it hands back an ID token, and signInWithIdToken sends it to the server. Needs server 0.28 or later.

With sign_in_with_apple:

import 'package:sign_in_with_apple/sign_in_with_apple.dart';
final users = vb.auth.collection('users');
final nonce = await users.idTokenNonce('apple');
final apple = await SignInWithApple.getAppleIDCredential(
scopes: [AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName],
nonce: nonce,
);
final result = await users.signInWithIdToken('apple', apple.identityToken!,
nonce: nonce,
// Apple gives the name only on the first sign-in, never in the token.
name: [apple.givenName, apple.familyName].whereType<String>().join(' '));

On the server: enable apple and add the app’s bundle ID to oauth2.apple.native_client_ids (Settings → OAuth2 → Apple → App bundle IDs). In Xcode, add the Sign in with Apple capability. The nonce is required for Apple.

With google_sign_in:

import 'package:google_sign_in/google_sign_in.dart';
// Once, at start-up. On Android, serverClientId is the web client ID.
await GoogleSignIn.instance.initialize(serverClientId: webClientId);
// On the button:
final account = await GoogleSignIn.instance.authenticate();
final result = await vb.auth
.collection('users')
.signInWithIdToken('google', account.authentication.idToken!);

google_sign_in takes its nonce once, in initialize, so a single-use nonce from the server doesn’t fit; leave it out. Each ID token still signs in only once.

On the server: enable google, and list the iOS and Android OAuth client IDs in oauth2.google.native_client_ids (Settings → OAuth2 → Google → App client IDs). The web client ID in client_id is accepted too; Android tokens carry it when the app passes it as serverClientId.

The same results as the browser flow: SignedIn, MfaRequired, or MergeRequired when the email belongs to an existing account. A user who signs in with Google in the browser and with Google in the app is the same user. Server details: Native sign-in.

Flutter 3.27 (Dart 3.6), Android minSdk 21 (flutter_secure_storage may ask for more), iOS 13.