Flutter Setup
authyra_flutter is the Flutter layer of Authyra. It re-exports the entire authyra core and adds:
- OAuth2 providers (
GoogleProvider,GitHubOAuth2Provider,AppleProvider,OAuth2Provider,ProxyOAuthProvider) SecureAuthStorage— ready-to-useflutter_secure_storageimplementationOAuth2CallbackHandler— deep-link router for OAuth redirects
1. Install
dependencies:
authyra_flutter: ^0.1.0
flutter pub get
One import covers everything:
import 'package:authyra_flutter/authyra_flutter.dart';
2. Initialize at app startup
Call Authyra.initialize before runApp. This loads any session persisted from a previous run and makes Authyra.instance available throughout the app.
import 'package:authyra_flutter/authyra_flutter.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Authyra.initialize(
client: AuthyraClient(
providers: [
CredentialsProvider.withTokens(
id: 'email',
authorize: (creds) async {
// call your backend
final res = await myApi.post('/auth/login', body: creds);
if (res.statusCode != 200) return null;
return AuthSignInResult(
user: AuthUser(id: res.data['id'], email: res.data['email']),
accessToken: res.data['accessToken'],
refreshToken: res.data['refreshToken'],
expiresAt: DateTime.parse(res.data['expiresAt']),
);
},
),
GoogleProvider(clientId: 'YOUR_GOOGLE_CLIENT_ID'),
],
storage: SecureAuthStorage(),
),
);
runApp(const MyApp());
}
Authyra.initialize is idempotent — calling it a second time returns the existing instance unchanged.
3. Configure SecureAuthStorage
SecureAuthStorage wraps flutter_secure_storage:
- iOS / macOS → Keychain
- Android → Keystore (EncryptedSharedPreferences)
- Web →
localStoragewith Web Crypto encryption
No configuration needed beyond passing it to AuthyraClient:
storage: SecureAuthStorage()
SharedPreferences or plain localStorage for token storage — they are not encrypted. Always use SecureAuthStorage (or your own encrypted implementation of AuthStorage) in production.4. Wire OAuth2 deep links
OAuth2 providers redirect back to your app via a custom URI scheme after the user authenticates. You need to:
- Register each provider with
OAuth2CallbackHandler - Forward matching deep links from your link handler
Register providers
Do this once, before or alongside Authyra.initialize:
// Register each provider under the URI scheme it handles
OAuth2CallbackHandler.registerProvider(
'com.googleusercontent.apps.YOUR_CLIENT_ID', // Google's reverse client ID
googleProvider,
);
OAuth2CallbackHandler.registerProvider(
'myapp', // your custom scheme for GitHub, etc.
githubProvider,
);
Forward deep links
Use app_links (or uni_links) to receive incoming URIs and pass them to the handler:
import 'package:app_links/app_links.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Wire OAuth2 callback handler before Authyra.initialize
final googleProvider = GoogleProvider(clientId: 'YOUR_GOOGLE_CLIENT_ID');
OAuth2CallbackHandler.registerProvider(
'com.googleusercontent.apps.YOUR_CLIENT_ID',
googleProvider,
);
// Forward all incoming links to Authyra's handler
AppLinks().uriLinkStream.listen(OAuth2CallbackHandler.handleCallback);
await Authyra.initialize(
client: AuthyraClient(
providers: [googleProvider],
storage: SecureAuthStorage(),
),
);
runApp(const MyApp());
}
Platform deep-link configuration
You must also register the custom URI scheme in your platform manifests:
Android — android/app/src/main/AndroidManifest.xml:
<intent-filter>
<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.googleusercontent.apps.YOUR_CLIENT_ID" />
</intent-filter>
iOS — ios/Runner/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>com.googleusercontent.apps.YOUR_CLIENT_ID</string>
</array>
</dict>
</array>
5. Connect to GoRouter
authStateChanges is a Stream<AuthState> — wire it to GoRouter's refreshListenable for automatic redirects:
import 'package:authyra_flutter/authyra_flutter.dart';
import 'package:go_router/go_router.dart';
final router = GoRouter(
refreshListenable: StreamToListenable(Authyra.instance.authStateChanges),
redirect: (context, state) {
final authenticated = Authyra.instance.isAuthenticated;
final goingToLogin = state.uri.path.startsWith('/login');
if (!authenticated && !goingToLogin) return '/login';
if (authenticated && goingToLogin) return '/home';
return null;
},
routes: [
GoRoute(path: '/login', builder: (_, __) => const LoginPage()),
GoRoute(path: '/home', builder: (_, __) => const HomePage()),
],
);
StreamToListenable is a small adapter that bridges Dart Stream to Flutter's Listenable interface. Implement it yourself or use the one from go_router_builder or your routing package.6. Reactive UI
StreamBuilder
StreamBuilder<AuthState>(
stream: Authyra.instance.authStateChanges,
builder: (context, snapshot) {
final state = snapshot.data ?? AuthState.unauthenticated();
return switch (state.type) {
AuthStateType.authenticated => HomePage(user: state.user!),
AuthStateType.unauthenticated => const LoginPage(),
AuthStateType.error => ErrorPage(state.error!),
};
},
);
Riverpod
final authStateProvider = StreamProvider<AuthState>((ref) {
return Authyra.instance.authStateChanges;
});
// In a widget:
final state = ref.watch(authStateProvider).value ?? AuthState.unauthenticated();
Synchronous reads (safe in build())
final user = Authyra.instance.currentUser; // AuthUser?
final isAuth = Authyra.instance.isAuthenticated; // bool
final state = Authyra.instance.currentState; // AuthState
7. Teardown (tests only)
In tests, reset the singleton between test cases:
tearDown(() async {
if (Authyra.isInitialized) {
await Authyra.instance.dispose();
}
});