I Got Tired of Rewriting OAuth in Every Flutter App — So I Built a Package
Building oauth_connection — Flutter OAuth Without the Headache
How I built a reusable Flutter package to drop Google, Facebook, and Instagram sign-in into any project in minutes — not days.
Tags: flutter dart oauth bloc open-source
Every new project I started had the same painful ritual: configure Google OAuth, set up Facebook Auth, wrestle with Instagram's WebView flow, wire up a BLoC, handle errors, translate messages… all over again. So I stopped repeating myself and built oauth_connection — a Flutter package that bundles all of it into a single, drop-in integration.
The Problem Worth Solving
OAuth integration sounds simple on paper. In practice, it is three separate SDKs, three sets of platform setup steps, three state machines to manage, and a handful of edge cases — cancelled logins, token fetch failures, missing backend responses — that all need graceful handling. Multiply that by every project and you have a reliable recipe for wasted days.
I wanted a package with a clear contract: initialize once, drop a button widget anywhere, and get a clean token back (or a typed error state). No boilerplate scattered across your codebase.
💡 Design goal: The consuming app should only care about two things — the success token and the failure reason. Everything else is the package's problem.
What's Inside the Package
The package covers three providers, each with sign-in and sign-up flows:
| Provider | SDK Used | Flow Type |
|---|---|---|
google_sign_in ^7.2.0 |
Native SDK with event stream | |
flutter_facebook_auth ^7.1.6 |
Native SDK dialog | |
Custom (webview_flutter) |
WebView + one-time code intercept |
Each provider follows the same structure: a Controller (singleton, handles SDK calls and HTTP), a Cubit (BLoC state management), and ready-to-use Button widgets with built-in loading and error states.
Architecture at a Glance
The package is organized around a clean three-layer architecture. The UI never talks to the SDK directly — it only fires a Cubit method and listens to typed states.
┌─────────────────────────────────────────────┐
│ UI Layer │
│ SignInButton / SignUpButton │
└────────────────────┬────────────────────────┘
↕
┌─────────────────────────────────────────────┐
│ State Layer (BLoC / Cubit) │
│ GoogleAuthCubit / FacebookAuthCubit / │
│ InstagramAuthCubit │
└────────────────────┬────────────────────────┘
↕
┌─────────────────────────────────────────────┐
│ Controller Layer │
│ GoogleAuthController / │
│ FacebookAuthController / │
│ InstagramAuthController │
└────────────────────┬────────────────────────┘
↕
┌─────────────────────────────────────────────┐
│ SDK & Network Layer │
│ google_sign_in · flutter_facebook_auth · │
│ WebView · http │
└─────────────────────────────────────────────┘
Setting Up the Controllers
Each controller is a singleton initialized once — ideally in main() before runApp. Here's Google as an example:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Google OAuth controller once
await GoogleAuthController.init(
serverClientId: 'YOUR_SERVER_CLIENT_ID',
clientId: 'YOUR_CLIENT_ID',
host: 'api.yourapp.com',
loginEndpoint: '/auth/google/login',
registerEndpoint: '/auth/google/register',
debugLogs: true, // flip off in production
);
runApp(const MyApp());
}
Facebook and Instagram follow the same pattern — call FacebookAuthController.init() and InstagramAuthController.init() with your app credentials and backend endpoints.
Dropping a Button Into Your UI
Once initialized, adding sign-in is a single widget. The button manages its own loading state — it disables itself while the OAuth flow is in progress, so you never need a separate boolean in your page's state.
GoogleSignInButton(
showErrorMessage: true,
onSuccess: (GoogleAuthSuccess state) {
// state.accessToken is ready — store it, navigate, etc.
saveToken(state.accessToken);
navigateHome();
},
onFailure: (GoogleAuthFailure state) {
// state.translatedMessage is already localized
showSnackBar(state.translatedMessage);
},
)
The same API shape exists for FacebookSignInButton, FacebookSignUpButton, GoogleSignUpButton, InstagramSignInButton, and InstagramSignUpButton.
How the Google Flow Works Internally
The Google controller takes advantage of the new event-stream API introduced in google_sign_in v7. Rather than awaiting a one-shot future, it subscribes to a stream of authentication events — which handles both fresh logins and re-authentication cleanly.
// Inside GoogleAuthController.configure()
_googleSignIn.authenticationEvents.listen((event) async {
if (event is GoogleSignInAuthenticationEventSignIn) {
currentUser = event.user;
if (action == SIGN_IN) {
await _login(
email: event.user.email,
accessToken: await getAccessToken(),
);
} else if (action == SIGN_UP) {
await _register(
email: event.user.email,
accessToken: await getAccessToken(),
);
}
}
// Resolve the Completer so the Cubit knows we're done
_authCompleter?.complete();
});
A Completer bridges the event-driven SDK and the await-friendly Cubit interface — the Cubit simply does await controller.login() and knows the flow is finished when it returns, regardless of success or failure.
Instagram's Custom WebView Flow
Instagram's OAuth doesn't have an official Flutter SDK, so I built one inside the package. It opens a WebView pointed at Instagram's authorization URL, intercepts the redirect containing the one-time code, and pipes it to your backend.
final String fullAuthUrl =
"https://api.instagram.com/oauth/authorize"
"?client_id=$clientId"
"&redirect_uri=${Uri.encodeComponent(redirectUrl)}"
"&scope=instagram_business_basic"
"&response_type=code";
FlutterInstagramApi.login(
context,
instaConfig: InstaConfig(
clientId: clientId,
instaUrl: fullAuthUrl,
),
oneTimeCode: (code) async {
// Exchange the code with your backend
await _login(code);
onComplete();
},
onCancel: () {
errorCode = "CANCELLED";
onComplete();
},
);
Error Handling & Typed States
One of the design decisions I'm most satisfied with is the typed error system. The Cubit emits discrete states:
GoogleAuthInitialGoogleAuthLoadingGoogleAuthSuccess— carriesaccessTokenGoogleAuthFailure— carrieserrorCode+translatedMessage
The error codes map to real scenarios:
| Error Code | Meaning |
|---|---|
CANCELLED |
User dismissed the OAuth popup |
NOT_SUPPORTED |
Platform doesn't support the auth flow |
FETCH_TOKEN_FAILED |
Backend responded but returned no token |
FETCH_TOKEN_ERROR |
Network or parsing error with backend |
REGISTERING_USER_FAILED |
Backend rejected the new user registration |
LOGIN_ERROR |
Unexpected SDK-level error |
Built-in Localization — 4 Languages
The package ships with its own OAuthConnection.delegate that you add alongside your app's localization delegates. All error and UI strings are available in:
- 🇺🇸 English
- 🇪🇸 Spanish
- 🇫🇷 French
- 🇸🇦 Arabic (including RTL support)
MaterialApp(
localizationsDelegates: [
OAuthConnection.delegate, // ← add this
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: const [
Locale('en'),
Locale('es'),
Locale('fr'),
Locale('ar'),
],
)
Key Dependencies
| Package | Version | Role |
|---|---|---|
google_sign_in |
^7.2.0 | New stream-based auth event API |
flutter_facebook_auth |
^7.1.6 | Facebook login dialog + token exchange |
flutter_bloc |
^9.1.1 | Clean, testable state management |
webview_flutter |
^4.13.1 | Custom Instagram OAuth WebView |
sign_in_button |
^4.1.0 | Branded button styles out of the box |
font_awesome_flutter |
^10.8.0 | Provider icons for buttons |
Why I Built This
The Flutter ecosystem has individual packages for each OAuth provider — but nothing that unifies them behind a consistent API, manages state via BLoC, talks to your own backend, and handles errors in multiple languages. oauth_connection fills that gap.
The real value isn't the lines of code. It's that the next time I start a project that needs social login, I spend ten minutes on configuration, not two days on integration.
I plan to expand the package with Apple Sign-In support and a more flexible backend integration API. If you try it out or spot an improvement, I'd love to hear from you.
Built with Flutter · BLoC · Google Sign-In · Facebook Auth · Instagram OAuth
📦 Check out the package on GitHub: github.com/houssam15/oauth_connections
If it helped you, drop a ⭐ — it means a lot!
