Flutter

Flutter Setup

Initialize authyra_flutter, wire OAuth2 deep links, and configure SecureAuthStorage.

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-use flutter_secure_storage implementation
  • OAuth2CallbackHandler — deep-link router for OAuth redirects

1. Install

pubspec.yaml
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.

lib/main.dart
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)
  • WeblocalStorage with Web Crypto encryption

No configuration needed beyond passing it to AuthyraClient:

storage: SecureAuthStorage()
Never use SharedPreferences or plain localStorage for token storage — they are not encrypted. Always use SecureAuthStorage (or your own encrypted implementation of AuthStorage) in production.

OAuth2 providers redirect back to your app via a custom URI scheme after the user authenticates. You need to:

  1. Register each provider with OAuth2CallbackHandler
  2. 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,
);

Use app_links (or uni_links) to receive incoming URIs and pass them to the handler:

lib/main.dart
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());
}

You must also register the custom URI scheme in your platform manifests:

Androidandroid/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>

iOSios/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:

lib/router.dart
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();
  }
});

Next steps

Copyright © 2026