# How to Implement OAuth 2.0 in FlutterFlow for Secure Authentication

- Tool: FlutterFlow
- Difficulty: Intermediate
- Time required: 45-60 min
- Compatibility: FlutterFlow Free+ for Firebase providers; FlutterFlow Pro+ for Cloud Function custom OAuth
- Last updated: March 2026

## TL;DR

FlutterFlow supports OAuth 2.0 through two paths: Firebase Auth built-in providers (Google, Apple, GitHub — enable in Firebase Console and FlutterFlow Settings, zero custom code needed) and custom OAuth providers via a Firebase Cloud Function that implements the Authorization Code flow server-side. Never store OAuth client_secret in client-side Custom Action code.

## Two Paths to OAuth 2.0 in FlutterFlow

OAuth 2.0 is the industry standard for delegated authorization — it lets your users authenticate using their existing accounts on Google, Apple, GitHub, Slack, LinkedIn, and dozens of other providers. In FlutterFlow, the implementation path depends on which provider you need. For Google, Apple, Facebook, and GitHub, Firebase Auth has native OAuth 2.0 support built in — you configure it in the Firebase Console and enable it in FlutterFlow Settings with no custom code. For any other provider (Slack, Microsoft Azure AD, LinkedIn, custom SSO, etc.), you need to implement the OAuth 2.0 Authorization Code flow in a Firebase Cloud Function, because the client_secret used to exchange authorization codes for tokens must never be exposed in client-side code. This tutorial covers both paths with a focus on security.

## Before you start

- FlutterFlow project with Firebase connected
- Firebase project with Blaze (pay-as-you-go) plan for Cloud Functions (custom OAuth path)
- OAuth application credentials from the provider (client_id and client_secret)
- Basic understanding of FlutterFlow Action Flows and Custom Actions
- Node.js knowledge for writing the Firebase Cloud Function (custom OAuth path only)

## Step-by-step guide

### 1. Enable Firebase built-in OAuth providers (Google, Apple, GitHub)

For supported providers, open the Firebase Console at console.firebase.google.com. Navigate to your project → Authentication → Sign-in method. Click 'Google', toggle it to Enabled, enter your support email, and click Save. Repeat for 'Apple' (requires Apple Developer account and Service ID) and 'GitHub' (requires a GitHub OAuth App with your app's callback URL). Back in FlutterFlow, go to Settings (gear icon) → Firebase → Authentication tab. Enable the same providers you activated in Firebase Console. FlutterFlow automatically generates the sign-in buttons and Action Flow wiring — you do not need to write any Dart code for built-in providers. Add a 'Sign In with Google' button to your login page, open its Action Flow, and select 'Log In → Google' as the action type.

**Expected result:** The selected OAuth providers are enabled in both Firebase Console and FlutterFlow Settings, and a sign-in button is added to the login page with the appropriate Log In action.

### 2. Configure OAuth callback URLs

Every OAuth provider requires you to register the exact redirect URIs (callback URLs) that your app will use. For Firebase Auth, the callback URL follows the pattern: https://{your-project-id}.firebaseapp.com/__/auth/handler. Copy this URL from Firebase Console → Authentication → Sign-in method → your provider → Authorized redirect URIs. Paste it into your OAuth provider's application settings (for GitHub: OAuth App → Authorization callback URL; for Google: Authorized redirect URIs in Google Cloud Console). An incorrect callback URL causes a 'redirect_uri_mismatch' error that blocks the entire OAuth flow. For FlutterFlow Web apps, also add your custom domain if you have one configured.

**Expected result:** The OAuth provider's application settings show the correct firebaseapp.com callback URL, and test sign-in does not produce a redirect_uri_mismatch error.

### 3. Create a Firebase Cloud Function for custom OAuth providers

For providers not supported by Firebase Auth (Slack, LinkedIn, custom SAML, etc.), you need a server-side function that handles the OAuth 2.0 Authorization Code flow. The flow has two parts: (1) generateAuthUrl — takes no parameters, returns the OAuth provider's authorization URL with your client_id, redirect_uri, scope, and a CSRF state parameter. (2) handleCallback — takes the authorization code and state from the provider's redirect, exchanges the code for tokens using your client_secret (server-side only), retrieves the user's profile, creates or updates a Firebase Auth user via Admin SDK, and returns a Firebase custom token that FlutterFlow can use to sign in. Deploy this function to Firebase and store client_id and client_secret in Firebase environment config, never in client code.

```
// firebase/functions/index.js
// Deploy: firebase deploy --only functions

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const axios = require('axios');
admin.initializeApp();

// Step 1: Generate the OAuth authorization URL
exports.getSlackAuthUrl = functions.https.onCall(async (data, context) => {
  const clientId = functions.config().slack.client_id;
  const redirectUri = functions.config().slack.redirect_uri;
  const state = Math.random().toString(36).substring(2);

  const url = `https://slack.com/oauth/v2/authorize` +
    `?client_id=${clientId}` +
    `&scope=identity.basic,identity.email` +
    `&redirect_uri=${encodeURIComponent(redirectUri)}` +
    `&state=${state}`;

  return { url, state };
});

// Step 2: Exchange code for token (NEVER expose client_secret to client)
exports.slackCallback = functions.https.onCall(async (data, context) => {
  const { code } = data;
  const clientId = functions.config().slack.client_id;
  const clientSecret = functions.config().slack.client_secret; // server-side only
  const redirectUri = functions.config().slack.redirect_uri;

  const tokenRes = await axios.post('https://slack.com/api/oauth.v2.access', null, {
    params: { client_id: clientId, client_secret: clientSecret,
              code, redirect_uri: redirectUri }
  });
  if (!tokenRes.data.ok) throw new functions.https.HttpsError('unauthenticated', 'Slack OAuth failed');

  const profile = tokenRes.data.authed_user;
  const uid = `slack:${profile.id}`;
  await admin.auth().setCustomUserClaims(
    (await admin.auth().createUser({ uid, email: profile.email }).catch(() =>
      admin.auth().getUser(uid))).uid,
    { slack: true }
  );
  const customToken = await admin.auth().createCustomToken(uid);
  return { customToken };
});
```

**Expected result:** Firebase Cloud Functions deploy successfully and the functions appear in the Firebase Console → Functions tab.

### 4. Create Custom Actions to call the Cloud Functions

In FlutterFlow's Custom Code panel, create two Custom Actions: 'initiateSlackOAuth' and 'completeSlackOAuth'. The first action calls the getSlackAuthUrl Cloud Function, retrieves the authorization URL, and opens it in an in-app WebView (or the device browser using the url_launcher package). The second action receives the authorization code (passed back to the app via a deep link or WebView URL change listener), calls the slackCallback Cloud Function, receives the Firebase custom token, and calls FirebaseAuth.instance.signInWithCustomToken() to sign the user in. After signInWithCustomToken succeeds, update the Firestore user document with the user's Slack profile data.

```
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_functions/cloud_functions.dart';

// Custom Action: completeSlackOAuth
// Parameters: code (String), state (String)
// Returns: Boolean (true = signed in successfully)
Future<bool> completeSlackOAuth(
  String code,
  String state,
) async {
  try {
    final callable = FirebaseFunctions.instance.httpsCallable('slackCallback');
    final result = await callable.call({'code': code, 'state': state});
    final customToken = result.data['customToken'] as String?;
    if (customToken == null) {
      debugPrint('completeSlackOAuth: no custom token returned.');
      return false;
    }
    await FirebaseAuth.instance.signInWithCustomToken(customToken);
    debugPrint('completeSlackOAuth: signed in as ${FirebaseAuth.instance.currentUser?.uid}');
    return true;
  } catch (e) {
    debugPrint('completeSlackOAuth error: $e');
    return false;
  }
}
```

**Expected result:** Both Custom Actions compile successfully in FlutterFlow and appear in the Custom Actions list.

### 5. Wire the OAuth login button in an Action Flow

Navigate to your login page and add a 'Sign in with Slack' button. Open its Action Flow editor. Add step 1: 'Custom Action' → 'initiateSlackOAuth' — this opens the Slack authorization page in a WebView. Configure the WebView to listen for the redirect URI callback (your app's deep link scheme, e.g., yourapp://oauth/callback). When the WebView detects the callback URL, add step 2 in the WebView's 'On URL Change' action: 'Custom Action' → 'completeSlackOAuth', passing the 'code' and 'state' URL parameters. Add a conditional after completeSlackOAuth: if it returns true, navigate to the Home page; if false, show a Snackbar with an error message.

**Expected result:** Tapping the Slack login button opens the Slack authorization page, and after user approval, the app signs in and navigates to the home screen.

### 6. Test both OAuth paths and verify token handling

Test the built-in Firebase providers first using Test Mode — click Sign In with Google and complete the flow. Verify the user appears in Firebase Console → Authentication → Users with the correct provider listed. For custom OAuth (Slack), use Run Mode on a physical device since deep links require native app handling. Check three things: (1) The authorization URL opens correctly and shows the provider's consent screen. (2) After approval, the app receives the callback and calls the Cloud Function. (3) The Firebase custom token signs the user in and the user appears in Firebase Auth. Use FlutterFlow's DevTools console and Firebase Functions logs (Firebase Console → Functions → Logs) to diagnose any failures.

**Expected result:** Users can successfully sign in via both Google (Firebase built-in) and Slack (custom Cloud Function), and their accounts appear in Firebase Authentication with the correct provider information.

## Complete code example

File: `oauth_custom_action.dart`

```dart
// ─── Custom Action: completeCustomOAuth ──────────────────────────────────────
// Exchanges an OAuth authorization code for a Firebase custom token
// via a server-side Cloud Function, then signs the user into Firebase Auth.
//
// Parameters:
//   provider (String) — e.g. 'slack', 'linkedin'
//   code (String)     — authorization code from OAuth callback
//   state (String)    — CSRF state parameter for verification
//
// Returns: Boolean (true = success)
//
// SECURITY: The Cloud Function holds client_secret. Never pass it here.

import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_functions/cloud_functions.dart';

Future<bool> completeCustomOAuth(
  String provider,
  String code,
  String state,
) async {
  if (code.isEmpty) {
    debugPrint('completeCustomOAuth: code is empty — did OAuth callback fire?');
    return false;
  }
  if (state.isEmpty) {
    debugPrint('completeCustomOAuth: state is empty — CSRF check failed.');
    return false;
  }

  try {
    // Call the appropriate Cloud Function based on provider
    final functionName = '${provider}Callback';
    debugPrint('completeCustomOAuth: calling function $functionName');

    final callable =
        FirebaseFunctions.instance.httpsCallable(functionName);
    final result = await callable.call({'code': code, 'state': state});

    final customToken = result.data['customToken'] as String?;
    if (customToken == null || customToken.isEmpty) {
      debugPrint('completeCustomOAuth: Cloud Function returned no token.');
      return false;
    }

    // Sign into Firebase with the custom token from the server
    final credential =
        await FirebaseAuth.instance.signInWithCustomToken(customToken);
    debugPrint('completeCustomOAuth: signed in uid=${credential.user?.uid}');
    return true;
  } on FirebaseFunctionsException catch (e) {
    debugPrint(
        'completeCustomOAuth FunctionsException: ${e.code} — ${e.message}');
    return false;
  } on FirebaseAuthException catch (e) {
    debugPrint(
        'completeCustomOAuth AuthException: ${e.code} — ${e.message}');
    return false;
  } catch (e) {
    debugPrint('completeCustomOAuth unexpected error: $e');
    return false;
  }
}

// ─── Security reminder ───────────────────────────────────────────────────────
// DO NOT add client_secret as a parameter to this action.
// client_secret belongs in Firebase Functions environment config only:
//   firebase functions:config:set provider.client_secret="your_secret"
// Exposing client_secret in client code allows token forgery attacks.
```

## Common mistakes

- **Storing the OAuth client_secret in a Custom Action parameter or hardcoded string** — FlutterFlow Custom Actions compile to client-side Dart code that runs on the user's device. Any string in that code — including function parameters — can be extracted from the compiled app binary using reverse engineering tools. An exposed client_secret allows attackers to impersonate your app with the OAuth provider. Fix: The client_secret must only exist in server-side code. Use a Firebase Cloud Function (or any server you control) to exchange the authorization code for tokens. The Cloud Function reads client_secret from Firebase environment config, which is never sent to the client.
- **Using the wrong callback URL in the OAuth provider's application settings** — OAuth providers validate the redirect_uri parameter in the authorization request against a whitelist of registered URIs. If the URI does not match exactly (including trailing slashes and protocol), the provider returns a redirect_uri_mismatch error and the user cannot complete login. Fix: Copy the exact Firebase Auth callback URL (https://{project-id}.firebaseapp.com/__/auth/handler) from the Firebase Console and paste it verbatim into the OAuth provider's application settings.
- **Skipping the CSRF state parameter in the custom OAuth flow** — Without validating the state parameter on the callback, an attacker can initiate a login flow with their own authorization code and trick the app into signing in as the attacker. This is a CSRF attack against the OAuth flow. Fix: Generate a random state string in the getAuthUrl function, store it temporarily (app state or a short-lived Firestore document), and verify it matches the state parameter received in the callback before exchanging the code.
- **Attempting to call Firebase's signInWithCustomToken() from inside a Firebase Cloud Function instead of on the client** — signInWithCustomToken() is a client-side Firebase Auth SDK method. It cannot be called from inside a Cloud Function. The Cloud Function generates the custom token using the Admin SDK, returns it to the client, and the client calls signInWithCustomToken(). Fix: Your Cloud Function should return the custom token string in its response body (e.g., { customToken: '...' }). The FlutterFlow Custom Action receives the response, extracts the token, and calls FirebaseAuth.instance.signInWithCustomToken() on the device.

## Best practices

- Always implement OAuth 2.0 Authorization Code flow server-side (Cloud Function) — never implement the token exchange in client-side Dart code.
- Use the state parameter for CSRF protection in every custom OAuth implementation, even for internal or low-risk providers.
- Set short expiration times on Firebase custom tokens (the default is 1 hour) and require re-authentication for sensitive actions.
- Add the OAuth provider name as a custom claim on the Firebase user so your Firestore Security Rules can restrict access by authentication method.
- Test OAuth flows on all target platforms (iOS, Android, Web) since deep link handling, redirect behavior, and WebView capabilities differ across platforms.
- Log failed authentication attempts (provider, error code, timestamp) to Firestore for security monitoring — but never log tokens or authorization codes.
- For Apple Sign In on iOS, Apple requires the Sign In with Apple capability in your App ID — configure this in Apple Developer Console before testing.
- Review the OAuth provider's token scopes and request only the minimum permissions your app needs — avoid requesting broad write scopes if you only need the user's email.

## Frequently asked questions

### Does FlutterFlow support Google Sign-In without writing any code?

Yes. Google Sign-In, Apple Sign In, Facebook, GitHub, Microsoft, Twitter, and email link auth are all supported natively through Firebase Auth. Enable the provider in Firebase Console → Authentication → Sign-in method, enable it in FlutterFlow Settings → Firebase → Authentication, then add a Log In action to a button. No Custom Actions or Dart code required.

### What providers require the custom Cloud Function approach?

Any OAuth provider not listed in Firebase Auth's built-in providers requires the Cloud Function approach. Common examples include Slack, LinkedIn, Salesforce, Microsoft Azure AD B2C (custom tenant), Discord, Notion, and any enterprise SSO using OIDC or SAML that is not Google Workspace.

### Can I store the OAuth client_secret in FlutterFlow's environment variables?

FlutterFlow does not have a server-side environment variable system for Flutter apps — environment variables in FlutterFlow (or Dart's --dart-define) are compiled into the app binary and are accessible to anyone who decompiles the app. The client_secret must be stored in your server (Firebase Functions config, Supabase Edge Function secrets, or any backend environment that runs server-side code only).

### How does signInWithCustomToken work with my existing Firestore user document?

When you call signInWithCustomToken(), Firebase Auth creates a new user account if one does not exist for the given UID, or signs in the existing user if the UID already has an account. Your Cloud Function should set the UID to a stable, provider-specific value (e.g., 'slack:U12345678') so the same Firestore user document is accessed consistently across sessions.

### What is the CSRF state parameter and why is it required?

The state parameter is a random string you generate before redirecting the user to the OAuth provider. After the user approves, the provider includes your state value in the callback URL. You verify it matches what you sent. This prevents cross-site request forgery attacks where an attacker tricks your app into accepting a login flow the user did not initiate.

### Will the OAuth Cloud Function work on Firebase's free Spark plan?

No. Firebase Cloud Functions require the Blaze (pay-as-you-go) plan. The Spark plan does not allow Cloud Function deployment. The Blaze plan has a generous free tier (2 million invocations per month free), so for most apps the actual cost is zero, but you do need to upgrade to Blaze to enable functions.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-implement-oauth2-0-in-flutterflow-for-secure-authentication
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-implement-oauth2-0-in-flutterflow-for-secure-authentication
