Skip to main content

Command Palette

Search for a command to run...

I Got Tired of Rewriting OAuth in Every Flutter App — So I Built a Package

Published
7 min read
H
Flutter Mobile Developer with experience building scalable Android & iOS apps. Skilled in BLoC state management, REST APIs, real-time sockets, and e-commerce/payment integrations, with a focus on clean, maintainable architecture.

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 google_sign_in ^7.2.0 Native SDK with event stream
Facebook flutter_facebook_auth ^7.1.6 Native SDK dialog
Instagram 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:

  • GoogleAuthInitial
  • GoogleAuthLoading
  • GoogleAuthSuccess — carries accessToken
  • GoogleAuthFailure — carries errorCode + 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!

6 views