AuthOwl for Flutter
Flutter widgets, provider, and session management for AuthOwl.
dependencies:
authowl: ^0.1.0
flutter_secure_storage: ^9.0.0
Import the provider, managed authentication screens, and headless client from one canonical entrypoint:
import 'package:authowl/authowl.dart';
Setup
AuthOwlProvider(
publishableKey: const String.fromEnvironment('AUTHOWL_PUBLISHABLE_KEY'),
apiUrl: 'https://api.authowl.dev',
storage: keychainStorage, // see below
child: const MyApp(),
)
Built-in controls use AuthOwl gold (#F5B84C) by default. The project's
dashboard color replaces it automatically. An app-level override has highest
priority when needed:
AuthOwlProvider(
primaryColor: const Color(0xff0ea5a4),
// ...publishableKey, apiUrl, storage, and child
)
The session cookie is a bearer credential - anything holding it is the
signed-in user until it expires - so it belongs in the OS keychain, never in
SharedPreferences, which is unencrypted:
class KeychainStorage implements AuthOwlStorage {
final _store = const FlutterSecureStorage();
@override
Future<String?> read(String key) => _store.read(key: key);
@override
Future<void> write(String key, String value) => _store.write(key: key, value: value);
@override
Future<void> delete(String key) => _store.delete(key: key);
}
A pk_live_… key against a plain-http origin is refused outright: passwords
and the session cookie would travel in the clear. Loopback http works with a
pk_test_… key for local development.
Widgets
AuthOwlSignIn(
onSignedIn: () => context.go('/home'),
onSecondFactorRequired: () => context.go('/mfa'),
onMfaEnrollmentRequired: () => context.go('/mfa/enrol'),
)
AuthOwlSignUp(onSignedUp: ({required sessionCreated}) =>
context.go(sessionCreated ? '/home' : '/check-your-email'))
AuthOwlEmailOtpForm(onSignedIn: () => context.go('/home'))
AuthOwlSignIn reports success only once a session exists. A two-factor
challenge calls onSecondFactorRequired; a session held at mandatory MFA
enrolment calls onMfaEnrollmentRequired. Neither path calls onSignedIn.
AuthOwlSignUp reports whether a session was created. Projects requiring email
verification create none.
The provider loads the project's public configuration once. Built-in widgets hide disabled methods, enforce the configured password length, collect required legal consent, and send the accepted consent version automatically.
Session state
final scope = AuthOwlProvider.of(context);
if (scope.session.isSignedIn) {
Text('Hi, ${scope.session.user!.email}');
}
A session held at required-MFA enrolment is deliberately not signed in - treating it as authenticated would let an app skip the enrolment gate.
Localization
AuthOwlProvider(locale: 'ar', ...)
Strings come from lib/src/i18n/catalog.g.dart, generated from
@authowl/core so the wording matches the web and React Native SDKs exactly.
CI fails if it drifts. It is not auto-detected from the device: a phone set to
Arabic does not mean the app is localized, and switching only the auth screens
is worse than defaulting.
The provider also applies the matching right-to-left text direction to its
subtree.
To regenerate after changing the shared catalogs:
pnpm --filter @authowl/core run build && node scripts/generate-dart-i18n.mjs
Social sign-in and passkeys
Use the headless client with your provider's native SDK:
final result = await scope.client.signInWithIdToken(
provider: 'google',
idToken: googleCredential.idToken!,
);
Redirect OAuth is unsupported on purpose - it completes inside a system browser whose cookie jar this client cannot read, so the session would land somewhere the app can never see it.
License
MIT
Libraries
- authowl
- AuthOwl for Flutter: provider, widgets, and localized auth screens.
- authowl_client
- AuthOwl headless client SDK for Flutter apps.