Deep Links
Any OAuth2Provider (or a prebuilt subclass: GoogleProvider, GitHubProvider) opens an external browser for the authorization step, then needs the resulting redirect routed back into your app. OAuth2CallbackHandler is the dispatcher that does that routing.
Two providers are the exception and need no OAuth2CallbackHandler wiring at all:
AppleProvider(authyra_apple), the native SDK resolves the sign-in directly, no deep link involved. See Apple Provider →.ProxyOAuthProvider, it manages its own deep-link routing viahandleDeepLink. See ProxyOAuthProvider →.
1. Wire the dispatcher, once
No per-provider registration step exists. Each OAuth2Provider.signIn() call registers its own CSRF state with OAuth2CallbackHandler for the duration of the attempt and unregisters it when it settles. You only wire the dispatcher itself, before Authyra.initialize():
import 'package:app_links/app_links.dart';
AppLinks().uriLinkStream.listen(OAuth2CallbackHandler.handleCallback);
Or forward manually from wherever your app already sees the URI (a custom onGenerateRoute, a GoRouter redirect):
final incoming = Uri.parse(link);
OAuth2CallbackHandler.handleCallback(incoming);
handleCallback reads the state query parameter (falling back to the URI fragment for implicit-flow redirects), routes it to the matching pending sign-in, and logs a warning (does not throw) if state is missing or matches no pending attempt.
state rather than by URI scheme means two OAuth2Providers can safely share the same redirect scheme (e.g. myapp://auth/callback for both GitHub and a custom Discord provider). state is a fresh, cryptographically random value per sign-in attempt, so there is no collision risk to manage yourself.2. Initialize Authyra
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
AppLinks().uriLinkStream.listen(OAuth2CallbackHandler.handleCallback);
await Authyra.initialize(
client: AuthyraClient(
providers: [discordProvider],
storage: SecureAuthStorage(),
),
);
runApp(const MyApp());
}
Platform setup
Register the custom URI scheme with each platform, independent of Authyra:
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="myapp" android:host="auth" android:pathPrefix="/callback" />
</intent-filter>
iOS, ios/Runner/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
Cleanup
Each OAuth2Provider.signIn() call unregisters its own pending state once it settles, there is nothing to clean up manually in application code. In tests, clear every pending registration between cases:
tearDown(OAuth2CallbackHandler.clearAll);
Troubleshooting
See Troubleshooting → OAuth2 callback never arrives for the full checklist (scheme mismatch, missing registration, redirect URI mismatch, timeout).