# 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** | `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:

```dart
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.

```dart
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.

```dart
// 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.

```dart
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)

```dart
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](https://github.com/houssam15/oauth_connections)
 
If it helped you, drop a ⭐ — it means a lot!

