How to Test Social Login on Flutter (Complete Guide)
How to Test Social Login on Flutter (Complete Guide)
How to Test Social Login on Flutter (Complete Guide)
Social login is a common entry point for users, yet it hides a surprising number of failure modes that only surface after release. Misconfigured OAuth redirects, missing consent screens, token‑handling bugs, and accessibility gaps can lead to abandoned sign‑ups, security incidents, or store‑policy rejections. This guide walks you through a complete testing strategy for Flutter apps that integrate Facebook, Google, Apple, or any other provider. You’ll learn why social login matters, build a detailed test matrix, execute manual and automated checks, leverage Flutter‑specific tooling, and see how autonomous, persona‑driven exploration uncovers issues that scripted tests never consider.
Why Social Login Testing Matters in Flutter Apps
Social login sits at the intersection of user experience, security, and platform policy. When a user taps “Continue with Google”, the Flutter layer hands off control to platform‑specific code (Android Intent, iOS ASWebAuthenticationSession, or web popup). If any step fails—network timeout, mismatched redirect URI, or a missing google-services.json—the app may appear to hang, show a cryptic error, or silently drop the user back to the landing screen. In production, these glitches translate into lost conversions, negative reviews, and potential violations of provider terms (e.g., storing raw tokens insecurely).
Flutter’s plugin ecosystem abstracts much of the OAuth flow, but the abstraction can hide version mismatches. A plugin may work on Flutter 3.7 but break after a Dart SDK update because the underlying AndroidX library changed its ActivityResult contract. Moreover, Flutter’s hot‑reload development cycle encourages rapid UI iteration, but the native credentials manager (Smart Lock, Keychain) is not exercised unless you run a full build on a real device or emulator with Google Play services installed. Consequently, teams often discover social login bugs only after a staged rollout, when real users encounter edge cases like revoked permissions or device‑level account removal.
Testing social login therefore requires:
- End‑to‑end validation of the OAuth handshake, including redirect URL parsing and token exchange.
- State‑synchronization between Flutter UI, native plugins, and backend services (e.g., linking a social account to an existing email/password account).
- Failure injection to simulate network loss, server errors, consent denial, and token expiry.
- Accessibility and privacy checks to satisfy WCAG 2.1 AA and provider‑specific UI guidelines.
- Cross‑persona validation to ensure that a power user, a novice, or an accessibility‑assistive‑technology user can all complete the flow.
The sections below break these requirements into actionable steps, beginning with a comprehensive test matrix you can copy into your test‑management tool.
Test Matrix for Social Login on Flutter
| Test ID | Scenario | Description | Expected Result | Notes |
|---|---|---|---|---|
| SL‑01 | Happy path – Google | User taps Google button, completes consent, returns with valid ID token. | Flutter receives token, exchanges for backend session, navigates to home screen. | Verify token not logged. |
| SL‑02 | Happy path – Facebook | Same as SL‑01 using Facebook Login. | Successful login, access token stored securely. | Check that AccessToken is not exposed via debug console. |
| SL‑03 | Happy path – Apple | User authenticates via Apple ID, shares email if permitted. | Backend receives authorization code, exchanges for token. | Test both with and without email sharing. |
| SL‑04 | Denied consent – Google | User cancels at Google consent screen. | Plugin returns error code CANCELED, UI shows “Try again” prompt. | Ensure no partial state left. |
| SL‑05 | Network loss during redirect | Disable Wi‑Fi/cellular after user taps button but before redirect completes. | Plugin throws NETWORK_ERROR, UI shows retry button, no crash. | Use emulator’s cellular off or adb shell svc wifi disable. |
| SL‑06 | Invalid redirect URI | Misconfigured google-services.json or Info.plist with wrong REVERSED_CLIENT_ID. | Login fails with INVALID_REQUEST or REDIRECT_URI_MISMATCH. | Verify error surface in logs. |
| SL‑07 | Token expiry handling | Use a short‑lived test token (Facebook test user with 1‑hour expiry). | After expiry, silent refresh fails, UI prompts re‑login. | Test silent refresh logic. |
| SL‑08 | Account linking – existing email | User signs up with email/password, later links Google account. | Backend merges profiles, future Google logins go to same account. | Verify no duplicate user records. |
| SL‑09 | Account linking – conflict | Google account already linked to another Flutter user. | Login blocked, UI shows “Account already in use” with option to unlink. | Test provider‑specific error mapping. |
| SL‑10 | Permission revoked – Facebook | User logs in, then manually removes app permissions from Facebook Settings. | Subsequent login triggers consent screen again; if denied, error handled. | Simulate via Facebook developer dashboard. |
| SL‑11 | Accessibility – button label | Social login button has proper label for TalkBack/VoiceOver. | Screen reader announces “Sign in with Google”. | Use semantics wrapper. |
| SL‑12 | Accessibility – contrast | Button meets WCAG AA contrast ratio (4.5:1) against background. | Verify with contrast checker. | Important for outdoor usage. |
| SL‑13 | Security – token storage | Tokens stored in Flutter Secure Storage or Keychain, not in plain SharedPreferences. | No token appears in adb shell run-as . | Run on rooted device or emulator. |
| SL‑14 | Security – logout | Logout clears tokens from secure storage and revokes server‑side session. | Subsequent login forces fresh consent. | Verify server revocation endpoint called. |
| SL‑15 | Interruption – phone call | Incoming call during OAuth web view; after call, flow resumes. | Login completes or gracefully fails with retry option. | Use adb shell am start -a android.intent.action.CALL. |
| SL‑16 | Dark mode adaptation | Button and web view adapt to system dark mode. | No clipped text, readable contrast. | Test with ThemeMode.dark. |
| SL‑17 | Multiple rapid taps | User taps Google button three times quickly. | Only one OAuth flow launched; extra taps ignored or queued. | Prevents overlapping intents. |
| SL‑18 | Web‑view user‑agent spoofing | Some providers block unknown user‑agents; ensure Flutter webview sends correct UA. | Login succeeds; no “unsupported browser” error. | Override via userAgent property if needed. |
| SL‑19 | Console‑login fallback | On desktop/web, if native plugin unavailable, fallback to popup window. | Popup opens, handles redirect, returns token. | Test on Chrome, Firefox, Safari. |
| SL‑20 | Enterprise SSO – custom OIDC | Use a generic OIDC provider (e.g., Azure AD) with custom scope. | Token received, claims mapped correctly. | Verify aud and iss claims. |
Each row represents a distinct verification point that can be automated (where feasible) or checked manually. The matrix covers the happy path, explicit error paths, edge cases that only appear under specific device states, accessibility, and security concerns. Use it as a living checklist: add rows for provider‑specific quirks (e.g., Twitter’s oauth_version=2.0 requirement) or for your own business rules (e.g., mandatory email verification after social login).
Manual Testing Approach
Manual testing remains valuable for exploratory checks, especially when validating platform‑specific behavior that automated scripts may mock too narrowly. Below is a step‑by‑step workflow you can follow on a physical device or emulator.
Setting up test devices/emulators
- Android – Create an AVD with Google Play services (API 33+). Enable “Google Play Store” so that the Google Sign‑in SDK can resolve the
com.google.android.gmspackage. - iOS – Use a simulator with Xcode 15+; ensure “Sign in with Apple” capability is enabled in the Xcode project’s Signing & Certificates pane.
- Web – Install Chrome, Firefox, and Safari; enable “Disable cache” in dev tools to force fresh loads.
- Provider accounts – Create test users in each developer console (Google Cloud, Facebook Developers, Apple Developer). For Facebook, enable “Test Users” and generate a limited‑access token. For Apple, use a private email relay address (e.g.,
username@privaterelay.appleid.com).
Step‑by‑step checklist
| Step | Action | Observation | |
|---|---|---|---|
| 1 | Launch app, navigate to login screen. | Social buttons visible, correctly labeled. | |
| 2 | Tap Google button. | Android: system account picker appears; iOS: ASWebAuthenticationSession opens Safari view controller. | |
| 3 | Choose a test Google account, grant requested scopes. | Consent screen shows correct app name and scopes. | |
| 4 | Return to app. | Flutter receives GoogleSignInAccount, extracts idToken. No raw token appears in Logcat (`adb logcat | grep idToken`). |
| 5 | Verify backend exchange (optional). | If you have a dev backend, check that the token is validated and a session cookie/JWT is issued. | |
| 6 | Repeat steps 2‑5 for Facebook and Apple. | Observe platform‑specific UI (Facebook’s custom tab, Apple’s modal sheet). | |
| 7 | Simulate network loss: enable airplane mode after step 2 but before step 4. | App shows error toast, no crash, retry button appears. | |
| 8 | Revoke permission: go to provider’s web dashboard, remove app access. Retry login. | Consent screen reappears; if denied, appropriate error shown. | |
| 9 | Test accessibility: enable TalkBack (Android) or VoiceOver (iOS). Focus each button. | Screen reader announces purpose (“Sign in with Google”, etc.). | |
| 10 | Test contrast: use a screenshot and a contrast‑checking tool (e.g., WebAIM Contrast Checker). | Ratio ≥ 4.5:1 for normal text. | |
| 11 | Log out from app, then attempt login again. | Fresh consent screen appears; tokens cleared from secure storage. | |
| 12 | Rotate device, switch to dark mode, repeat steps 2‑5. | UI adapts, no clipped elements. | |
| 13 | Perform rapid triple‑tap on a button. | Only one login flow initiates; no duplicate network calls. | |
| 14 | (Web only) Disable third‑party cookies, attempt login. | Popup still works if using window.open with proper redirect URI; otherwise, fallback to redirect mode. | |
| 15 | After successful login, navigate to profile page. | User’s display name and avatar (if requested) appear correctly. | |
| 16 | Link accounts: sign up with email/password, then link Google from settings. | Backend shows single user record with both auth methods. | |
| 17 | Unlink Google, then login with Google again. | New account created or prompted to link to existing email (depending on your policy). | |
| 18 | Capture logs: adb logcat -v brief > log.txt (Android) or xcrun simctl spawn booted log stream --predicate 'process == "YourApp"' > log.txt (iOS). Search for ERROR, Exception, null. | No unexpected stack traces. |
Handling OAuth redirects
Flutter plugins typically rely on a custom URL scheme (e.g., com.example.app:/oauth2redirect) or a universal link. During manual testing, verify that:
- The scheme is registered in
AndroidManifest.xml() andInfo.plist(CFBundleURLTypes). - The redirect URI entered in the provider console exactly matches the scheme + host (e.g.,
yourapp://oauth2redirect). - After the provider redirects back, the Flutter engine receives the URL via
onNewIntent(Android) orapplication:openURL:options:(iOS) and forwards it to the plugin. - If you use a web view for the OAuth flow (common for custom OIDC), ensure that the
navigationDelegateintercepts the redirect URL and calls the plugin’shandleRedirectUrl.
Automated checks can assert that the redirect URL contains the expected code or token query parameters and that the plugin’s completion callback is invoked within a reasonable timeout (e.g., 30 seconds).
Automated Testing with Flutter Tools
Automated verification reduces regression risk and enables CI gating. Flutter offers several layers: unit/widget tests for pure Dart logic, integration tests for end‑to‑end flows on devices, and golden tests for UI consistency. Below we detail how to apply each layer to social login.
Unit/widget tests with mock_auth
Most social login plugins expose an abstract AuthService interface. By mocking this interface, you can test UI reactions without hitting the network.
// auth_service.dart
abstract class AuthService {
Future<UserCredential?> signInWithGoogle();
Future<UserCredential?> signInWithFacebook();
Future<UserCredential?> signInWithApple();
Future<void> signOut();
}
// mock_auth_service.dart
import 'package:mockito/mockito.dart';
class MockAuthService extends Mock implements AuthService {}
void main() {
group('LoginPage widget tests', () {
late MockAuthService mockAuth;
late WidgetTester tester;
setUp(() {
mockAuth = MockAuthService();
tester = WidgetTester();
});
testWidgets('shows error when Google sign‑in fails', (WidgetTester wt) async {
when(mockAuth.signInWithGoogle())
.thenThrow(Exception('network error'));
await wt.pumpWidget(
Provider<AuthService>.value(
value: mockAuth,
child: MaterialApp(home: LoginPage()),
),
);
await wt.tap(find.byTooltip('Sign in with Google'));
await wt.pump();
expect(find.textContaining('Sign‑in failed'), findsOneWidget);
verify(mockAuth.signInWithGoogle()).called(1);
});
testWidgets('navigates to home after successful Facebook login', (WidgetTester wt) async {
when(mockAuth.signInWithFacebook())
.thenAnswer((_) async => UserCredential(
user: User(displayName: 'Foo', email: 'foo@example.com'),
));
await wt.pumpWidget(
Provider<AuthService>.value(
value: mockAuth,
child: MaterialApp(home: LoginPage()),
),
);
await wt.tap(find.byTooltip('Sign in with Facebook'));
await wt.pumpAndSettle();
expect(find.byType(HomePage), findsOneWidget);
expect(find.text('Welcome, Foo'), findsOneWidget);
});
});
}
*Key points*: Use mockito or mocktail to stub the plugin’s async methods. Verify that UI shows loading indicators, error messages, and correct navigation. This layer catches bugs in UI state machine (e.g., forgetting to dismiss a loading dialog on failure).
Integration tests using flutter_test and integration_test
Integration tests run on a real device or emulator and exercise the actual plugin code. They are slower but catch native‑side issues like missing GoogleServices.json or mismatched redirect URIs.
// integration_test/social_login_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:your_app/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Social login end‑to‑end', () {
testWidgets('Google login succeeds with valid test account', (WidgetTester wt) async {
app.main(); // starts the app
await wt.pumpAndSettle();
// Ensure we are on login screen
expect(find.text('Sign in with Google'), findsOneWidget);
// Tap Google button
await wt.tap(find.byTooltip('Sign in with Google'));
await wt.pumpAndSettle();
// Wait for the OAuth flow to complete (max 30 s)
final bool success = await wt.waitUntil(
() => find.text('Welcome').evaluates().isNotEmpty,
timeout: const Timeout(Duration(seconds: 30)),
);
expect(success, isTrue, reason: 'Google login did not complete in time');
expect(find.textContaining('Welcome'), findsOneWidget);
});
testWidgets('Facebook login handles cancelled consent', (WidgetTester wt) async {
app.main();
await wt.pumpAndSettle();
await wt.tap(find.byTooltip('Sign in with Facebook'));
await wt.pumpAndSettle();
// Simulate user pressing cancel on Facebook consent screen.
// On Android we can send BACK key; on iOS we shake to trigger cancel.
if (Platform.isAndroid) {
await wt.sendKeyDownEvent(const LogicalKeyboardKey(androidKeyBack));
} else if (Platform.isIOS) {
await wt.performGesture(
const Offset(200, 200),
const Offset(200, 200),
); // placeholder for shake
}
await wt.pumpAndSettle();
expect(find.textContaining('Login cancelled'), findsOneWidget);
});
});
}
Run with:
flutter drive --target=integration_test/social_login_test.dart -d emulator-5554
*Tips*:
- Use
await tester.pumpAndSettle(const Duration(seconds: 5));after each UI interaction to let animations finish. - For web, add
-d chromeand ensure the web server is running (flutter run -d chrome --web-port=8080). - Capture screenshots on failure with
await tester.takeScreenshot('failure_google_login.png');to aid debugging.
Using golden tests for UI
Golden tests ensure that the social login buttons render correctly across themes and locales.
// test/golden/social_login_golden_test.dart
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Google button matches goldens in light theme', (WidgetTester wt) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(),
home: SocialLoginButtons(),
),
);
await tester.pumpAndSettle();
await expectLater(
find.byType(SocialLoginButtons),
matchesGoldenFile('google_button_light.png'),
);
});
testWidgets('Google button matches goldens in dark theme', (WidgetTester wt) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.dark(),
home: SocialLoginButtons(),
),
);
await tester.pumpAndSettle();
await expectLater(
find.byType(SocialLoginButtons),
matchesGoldenFile('google_button_dark.png'),
);
});
}
Generate goldens on a reference device (e.g., Pixel 4 API 33) and commit them to version control. CI can then fail the build if any pixel deviates beyond the allowed threshold.
Using Firebase Auth emulator
If your backend relies on Firebase Authentication, the Firebase Local Emulator Suite lets you test token exchange without hitting production endpoints.
- Start the emulator suite:
firebase emulators:start --only auth
- In your Flutter app, point to the emulator:
FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
- Write an integration test that signs in with Google and asserts that a Firebase user record appears in the emulator’s UI (accessible at
http://localhost:4000).
This approach validates that the ID token received from Google is correctly exchanged for a Firebase uid and that custom claims (e.g., role) are set as expected.
Tooling and Libraries
Choosing the right libraries and auxiliary tools streamlines both manual and automated testing. Below is a comparison of popular Flutter social login plugins and ancillary utilities.
| Provider | Flutter Package | Pub Points | Native Dependencies | Notable Features |
|---|---|---|---|---|
google_sign_in | 110 | com.google.android.gms:play-services-auth (Android), GoogleSignIn (iOS) | Supports server‑side code exchange, offline access, token refresh. | |
flutter_facebook_auth | 105 | com.facebook.android:facebook-login (Android), FBSDKLoginKit (iOS) | Handles login, logout, token refresh, graph API calls. | |
| Apple | sign_in_with_apple | 100 | AuthenticationServices (iOS only) | Provides AuthorizationCredentialAppleID, supports real‑user vs private‑email detection. |
| Generic OIDC | appauth (via flutter_appauth) | 95 | net.openid:appauth (Android), AppAuth (iOS) | Custom scopes, PKCE, refresh token flow, works with Azure AD, Keycloak. |
| Twitter (X) | twitter_login | 90 | com.twitter.sdk.android:twitter-core (Android), TwitterKit (iOS) | OAuth 1.0a flow, email optional. |
| Microsoft | flutter_azure_ad_b2c | 85 | Microsoft.Identity.Client (Android/iOS) | B2C policies, token cache, conditional access. |
Mock server and network simulation
- simulation
- WireMock – Run locally to emulate provider token endpoints. Define stubs that return
invalid_grant,expired_token, or malformed JSON to test error handling. - Ngrok – Expose a local mock server to a public URL so that the Flutter app (running on a device) can reach it without altering DNS.
- Facebook’s Graph API Explorer – Use the “Get Token” button to generate short‑lived test tokens for manual validation.
- Apple’s TestFlight – Distribute a beta build to testers; the “Sign in with Apple” flow behaves identically to App Store builds when using a development Apple ID.
SUSA autonomous testing (optional mention)
SUSA’s autonomous QA agent can be pointed at a Flutter APK or an app URL and will explore social login flows using a variety of user personas. It automatically detects:
- Missing or misconfigured redirect URIs (by observing failed web view loads).
- Accessibility problems (e.g., unlabeled buttons) via its built‑in WCAG scanner.
- Security concerns such as tokens logged to
logcator stored in plain text.
Because SUA does not rely on pre‑written scripts, it can stumble upon edge cases like a provider returning an unexpected error query parameter that your integration test never asserted against. The agent’s cross‑session learning means repeated runs grow smarter, pruning dead ends and focusing on under‑tested paths.
Logging and diagnostics
- Android Studio Logcat – Filter by tag
GoogleSignInorFBSDKLogto see plugin‑level messages. - Firebase Crashlytics – Enable native crash reporting to catch ANRs that happen during the OAuth web view lifecycle.
- Dart DevTools – Use the timeline view to spot long UI frames caused by blocking waits on the platform channel.
- Secure Storage Verifier – On a rooted device, run
adb shell run-asto confirm no tokens appear in plain text.cat data/data/ /files/secure_storage.xml
Persona‑Driven Exploration with Autonomous QA
Scripted tests excel at verifying known paths, but real users behave unpredictably. Autonomous exploration injects variability that surfaces hidden defects. Below we describe how a persona‑driven engine like SUSA would approach social login testing and what kinds of bugs it tends to uncover.
How SUSA explores social login
- Startup profiling – The agent installs the APK, launches the app, and builds a state graph of screens reachable via taps, scrolls, and text input. Social login buttons are identified via semantics labels (
Sign in with Google,Continue with Facebook) or via known plugin widget types. - Persona behavior models – Each persona defines a probability distribution over actions:
- *Curious*: taps every visible element, reads dialog text, tries long‑presses.
- *Impatient*: rapid taps, quickly backs out if a loading spinner exceeds 2 seconds.
- *Novice*: follows hints, avoids advanced menus, may miss subtle error text.
- *Adversarial*: inputs malformed data, attempts to trigger error states, disables network mid‑flow.
- *Elderly*: prefers larger touch targets, may double‑tap inadvertently.
- *Accessibility*: relies on TalkBack/VoiceOver, navigates via swipe gestures, expects audible feedback.
- *Power user*: uses shortcuts, attempts to log in with multiple accounts in succession, checks account linking UI.
- Exploration loop – The agent selects a persona, executes its behavior policy on the current state, observes the result (screen change, toast, log entry), and updates the graph. If a social login button leads to a new state (e.g., a web view), the agent follows the OAuth redirect, records the final URL, and notes whether a token was returned.
- Oracles – Built‑in checks fire on each transition:
- Crash or ANR detection via tombstone logs.
- Unhandled exceptions in Flutter error widget.
- Accessibility violations (missing labels, insufficient contrast).
- Security leaks (search for token strings in logs or clipboard).
- Policy violations (e.g., exceeding allowed redirect URI length).
- Cross‑session memory – The agent remembers which URLs led to dead ends (e.g., a provider returning
error=access_deniedwithout a fallback). Subsequent runs prioritize alternative paths (different scopes, different prompt=consent values).
Findings that scripts miss
| Discovered Issue | Persona that Triggered It | Why Scripts Missed It |
|---|---|---|
Provider returns error=invalid_scope when requesting email+public_profile on a Facebook test app that hasn’t been approved for those scopes. | Adversarial (tries atypical scope combos) | Unit tests mocked the success path; integration tests used a pre‑approved production app. |
TalkBack reads the Google button as “button” only, missing the localized label due to a missing semanticsLabel param. | Accessibility | Manual tester glanced at the screen; automated golden test only checked visual pixels, not accessibility tree. |
Rapid triple‑tap on Facebook button launches two concurrent OAuth intents, causing a IllegalStateException: Concurrent modification in the Android plugin. | Impatient (fast taps) | Integration test inserted a 2‑second delay between taps, masking the race condition. |
After a network loss during the redirect, the iOS plugin leaves the ASWebAuthenticationSession presented, blocking the UI until the user manually dismisses it. | Elderly (may not notice the lingering sheet) | Scripts waited for a success/failure callback; they did not verify that the native view controller was dismissed. |
Token string appears in Logcat when debugPrint is used inside a plugin’s callback for logging. | Power user (enables verbose logging) | Unit tests suppressed dart:developer logs; CI build stripped debug symbols, so the leak was invisible. |
The consent screen displays a garbled app name when the app’s AndroidManifest.xml android:label contains a non‑Unicode character. | Curious (changes device language to Arabic) | Tests ran with default en_US locale; the bug only manifested under RTL layout. |
These examples illustrate how autonomous exploration can surface defects that arise from interaction between user behavior, platform quirks, and configuration drift—areas that are hard to anticipate in a scripted matrix.
Accessibility and Security Considerations
Social login buttons are high‑touchpoints; any flaw here disproportionately affects users with disabilities or exposes sensitive data.
WCAG checks for social login buttons
| Check | How to Test | Pass Criteria |
|---|---|---|
| Label | Enable TalkBack/VoiceOver, focus each button. | Announces purpose (“Sign in with Google”, etc.). |
| Contrast | Use a screenshot and a contrast analyzer (e.g., Stark plugin). | Minimum 4.5:1 for normal text, 3:1 for large text. |
| Touch target size | Measure with UI Inspector or flutter_driver gesture bounds. | Minimum 48 dp × 48 dp (Android) / 44 pt × 44 pt (iOS). |
| Motion sensitivity | Ensure no auto‑playing animations that cannot be paused. | Animations respect prefers-reduced-motion. |
| Error message accessibility | Trigger a failure (e.g., network off) and verify that error text is announced. | Error conveyed via live region or alert dialog. |
Automated accessibility testing can be added to your CI pipeline using the flutter_launcher_icons package’s flutter_test integration with the accessibility_test plugin, or by running Google’s androidx.test.espresso.accessibility.AccessibilityCheck on the generated APK.
Security and privacy best practices
- Never log raw tokens – Remove any
print(token)ordebugPrintstatements from plugin callbacks. Usedart:developeronly in debug builds withassert(!kReleaseMode). - Store tokens in secure storage – Prefer
flutter_secure_storage(Android Keystore / iOS Keychain) overshared_preferences. Verify on a rooted device that the file is encrypted. - Bind tokens to app instance – Include the app’s package name or bundle ID in the
stateparameter during OAuth initiation; verify it on the redirect to prevent CSRF. - Implement PKCE for public clients – If you use
appauthor a custom OIDC flow, enable PKCE to mitigate authorization code interception attacks. - Short‑lived access tokens – Request
offline_accessonly if you truly need refresh tokens; otherwise rely on short‑lived ID tokens (typically 1 hour). - Revoke on logout – Call the provider’s token revocation endpoint (Google:
https://oauth2.googleapis.com/revoke, Facebook:https://graph.facebook.com/me/permissions) and clear local storage. - Limit data requested – Only ask for scopes essential to your feature (e.g.,
emailandprofile). Unnecessary scopes increase friction and may trigger provider review delays. - Privacy policy link – Ensure the consent screen shows a link to your privacy policy; some providers reject apps that omit it.
Run regular dependency scans (flutter pub outdated, OWASP Dependency-Check) to catch known vulnerabilities in the social login plugins themselves.
Edge Cases Only Visible in Production
Even with exhaustive test matrices, certain failure modes surface only when the app runs at scale or under specific real‑world conditions.
Network interruptions and captive portals
- Scenario: User on a hotel Wi‑Fi that redirects to a login portal before granting internet access. The OAuth web view loads the portal’s HTML instead of the provider’s consent screen, resulting in a
Page not founderror that the plugin treats as a generic failure. - Detection: Inject a captive‑portal simulation using
iptablesto redirect HTTP requests to a local server that returns a 200 OK with a custom HTML form. Verify that your app displays a helpful message (“Please connect to the internet”) rather than crashing. - Mitigation: Inspect the redirected URL for known portal strings (
login.wifi,guestnetwork) and fallback to a system browser that can handle portal authentication.
Token expiry and silent refresh
- Scenario: Your app acquires a short‑lived access token (1 hour) and relies on silent refresh via iframe or hidden web view. In
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free