Troubleshooting
NotInitializedException
Message: Authyra has not been initialized. Call Authyra.initialize() before accessing Authyra.instance.
Cause: Authyra.instance was accessed before Authyra.initialize() completed.
Fix: ensure initialize is awaited before runApp:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Authyra.initialize(client: client); // must be awaited
runApp(const MyApp());
}
Also check that Authyra.instance isn't accessed at module level (top-level variable initializers run before main).
ProviderNotFoundException
Message: No provider registered with id 'google'.
Cause: signIn('google', ...) was called but no provider with id == 'google' was registered with AuthyraClient.
Fix: confirm the provider is in the providers list and the id matches exactly:
// Provider
GoogleProvider(clientId: '...') // id is 'google'
// Sign-in call
await Authyra.instance.signIn('google'); // matches ✓
AuthenticationFailedException
Message: Provider "email" returned null — credentials may be invalid.
Cause: your authorize callback returned null. This is the expected way to signal wrong credentials.
Fix in the callback: return null only for invalid credentials; throw for infrastructure errors:
authorize: (creds) async {
final res = await myApi.post('/auth/login', body: creds);
if (res.statusCode == 401) return null; // wrong password → null
if (res.statusCode != 200) {
throw AuthenticationFailedException( // server error → throw
'Login failed: HTTP ${res.statusCode}',
providerName: 'email',
);
}
return AuthSignInResult(user: AuthUser(...), ...);
},
Fix in the caller: always catch the exception in your UI:
try {
await Authyra.instance.signIn('email', params: {...});
} on AuthenticationFailedException {
setState(() => _error = 'Invalid email or password.');
}
OAuth2 callback never arrives
Symptom: sign-in with Google/GitHub hangs; the browser opens but the app never receives the callback.
Checklist:
- Deep-link scheme registered — verify the URI scheme is in
AndroidManifest.xml(Android) andInfo.plist(iOS). See Flutter Setup →. - Provider registered with
OAuth2CallbackHandler— confirmregisterProvideris called with the matching scheme prefix beforeAuthyra.initialize:OAuth2CallbackHandler.registerProvider( 'com.googleusercontent.apps.YOUR_ID', // must match redirect URI scheme googleProvider, ); uriLinkStreamforwarded — confirm incoming URIs reach the handler:AppLinks().uriLinkStream.listen(OAuth2CallbackHandler.handleCallback);- Redirect URI matches — the URI registered in the OAuth console must exactly match
OAuth2Config.redirectUri(including scheme and path). - Timeout — by default
OAuth2Config.timeoutis 5 minutes. If the user never completes auth,AuthenticationCancelledExceptionis thrown.
StorageException on initialize
Message: StorageException: Failed to initialize storage.
Cause: the AuthStorage implementation threw during initialize().
Common causes for SecureAuthStorage:
- Android: missing
minSdkVersion 18inandroid/app/build.gradle - iOS: missing Keychain entitlement — add
Keychain Sharingcapability in Xcode - Web: running in a context without
localStorageaccess (e.g., private browsing with strict settings)
TokenExpiredException
Message: Token for user X has expired.
Cause: the session's expiresAt is in the past and the provider does not support refresh (or the refresh token itself is expired).
Fix:
- Enable
AuthConfig(autoRefresh: true)to let Authyra refresh automatically. - Catch
TokenExpiredExceptionand send the user back to the login screen:try { final token = await Authyra.instance.getAccessToken(); } on TokenExpiredException { await Authyra.instance.signOut(); }
ProviderAlreadyRegisteredException
Message: A provider with id 'email' is already registered.
Cause: two providers with the same id were added to AuthyraClient, or registerProvider was called twice with the same id.
Fix: ensure each provider has a unique id. If you're dynamically adding providers, guard with a check:
if (!client.providers.any((p) => p.id == 'email')) {
client.registerProvider(myEmailProvider);
}
InMemoryStorage doesn't persist across restarts
Expected behaviour — InMemoryStorage is intentionally non-persistent. It's designed for tests and development.
Use SecureAuthStorage (Flutter) or your own AuthStorage implementation (backend) for production.
Tests fail with "singleton already initialized"
Cause: Authyra.instance was initialized in one test and not disposed before the next test ran.
Fix: dispose the singleton in tearDown:
setUp(() async {
client = AuthyraClient(providers: [...], storage: InMemoryStorage());
await Authyra.initialize(client: client);
});
tearDown(() async {
if (Authyra.isInitialized) await Authyra.instance.dispose();
});
Alternatively, use AuthyraClient directly in tests — it has no singleton and requires no teardown:
setUp(() async {
client = AuthyraClient(providers: [...], storage: InMemoryStorage());
await client.initialize();
});