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.
Prerequisites
- 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
Enable Firebase built-in OAuth providers (Google, Apple, GitHub)
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.
Configure OAuth callback URLs
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.
Create a Firebase Cloud Function for custom OAuth providers
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.
1// firebase/functions/index.js2// Deploy: firebase deploy --only functions34const functions = require('firebase-functions');5const admin = require('firebase-admin');6const axios = require('axios');7admin.initializeApp();89// Step 1: Generate the OAuth authorization URL10exports.getSlackAuthUrl = functions.https.onCall(async (data, context) => {11 const clientId = functions.config().slack.client_id;12 const redirectUri = functions.config().slack.redirect_uri;13 const state = Math.random().toString(36).substring(2);1415 const url = `https://slack.com/oauth/v2/authorize` +16 `?client_id=${clientId}` +17 `&scope=identity.basic,identity.email` +18 `&redirect_uri=${encodeURIComponent(redirectUri)}` +19 `&state=${state}`;2021 return { url, state };22});2324// Step 2: Exchange code for token (NEVER expose client_secret to client)25exports.slackCallback = functions.https.onCall(async (data, context) => {26 const { code } = data;27 const clientId = functions.config().slack.client_id;28 const clientSecret = functions.config().slack.client_secret; // server-side only29 const redirectUri = functions.config().slack.redirect_uri;3031 const tokenRes = await axios.post('https://slack.com/api/oauth.v2.access', null, {32 params: { client_id: clientId, client_secret: clientSecret,33 code, redirect_uri: redirectUri }34 });35 if (!tokenRes.data.ok) throw new functions.https.HttpsError('unauthenticated', 'Slack OAuth failed');3637 const profile = tokenRes.data.authed_user;38 const uid = `slack:${profile.id}`;39 await admin.auth().setCustomUserClaims(40 (await admin.auth().createUser({ uid, email: profile.email }).catch(() =>41 admin.auth().getUser(uid))).uid,42 { slack: true }43 );44 const customToken = await admin.auth().createCustomToken(uid);45 return { customToken };46});Expected result: Firebase Cloud Functions deploy successfully and the functions appear in the Firebase Console → Functions tab.
Create Custom Actions to call the Cloud Functions
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.
1import 'package:firebase_auth/firebase_auth.dart';2import 'package:cloud_functions/cloud_functions.dart';34// Custom Action: completeSlackOAuth5// Parameters: code (String), state (String)6// Returns: Boolean (true = signed in successfully)7Future<bool> completeSlackOAuth(8 String code,9 String state,10) async {11 try {12 final callable = FirebaseFunctions.instance.httpsCallable('slackCallback');13 final result = await callable.call({'code': code, 'state': state});14 final customToken = result.data['customToken'] as String?;15 if (customToken == null) {16 debugPrint('completeSlackOAuth: no custom token returned.');17 return false;18 }19 await FirebaseAuth.instance.signInWithCustomToken(customToken);20 debugPrint('completeSlackOAuth: signed in as ${FirebaseAuth.instance.currentUser?.uid}');21 return true;22 } catch (e) {23 debugPrint('completeSlackOAuth error: $e');24 return false;25 }26}Expected result: Both Custom Actions compile successfully in FlutterFlow and appear in the Custom Actions list.
Wire the OAuth login button in an Action Flow
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.
Test both OAuth paths and verify token handling
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 working example
1// ─── Custom Action: completeCustomOAuth ──────────────────────────────────────2// Exchanges an OAuth authorization code for a Firebase custom token3// via a server-side Cloud Function, then signs the user into Firebase Auth.4//5// Parameters:6// provider (String) — e.g. 'slack', 'linkedin'7// code (String) — authorization code from OAuth callback8// state (String) — CSRF state parameter for verification9//10// Returns: Boolean (true = success)11//12// SECURITY: The Cloud Function holds client_secret. Never pass it here.1314import 'package:firebase_auth/firebase_auth.dart';15import 'package:cloud_functions/cloud_functions.dart';1617Future<bool> completeCustomOAuth(18 String provider,19 String code,20 String state,21) async {22 if (code.isEmpty) {23 debugPrint('completeCustomOAuth: code is empty — did OAuth callback fire?');24 return false;25 }26 if (state.isEmpty) {27 debugPrint('completeCustomOAuth: state is empty — CSRF check failed.');28 return false;29 }3031 try {32 // Call the appropriate Cloud Function based on provider33 final functionName = '${provider}Callback';34 debugPrint('completeCustomOAuth: calling function $functionName');3536 final callable =37 FirebaseFunctions.instance.httpsCallable(functionName);38 final result = await callable.call({'code': code, 'state': state});3940 final customToken = result.data['customToken'] as String?;41 if (customToken == null || customToken.isEmpty) {42 debugPrint('completeCustomOAuth: Cloud Function returned no token.');43 return false;44 }4546 // Sign into Firebase with the custom token from the server47 final credential =48 await FirebaseAuth.instance.signInWithCustomToken(customToken);49 debugPrint('completeCustomOAuth: signed in uid=${credential.user?.uid}');50 return true;51 } on FirebaseFunctionsException catch (e) {52 debugPrint(53 'completeCustomOAuth FunctionsException: ${e.code} — ${e.message}');54 return false;55 } on FirebaseAuthException catch (e) {56 debugPrint(57 'completeCustomOAuth AuthException: ${e.code} — ${e.message}');58 return false;59 } catch (e) {60 debugPrint('completeCustomOAuth unexpected error: $e');61 return false;62 }63}6465// ─── Security reminder ───────────────────────────────────────────────────────66// DO NOT add client_secret as a parameter to this action.67// client_secret belongs in Firebase Functions environment config only:68// firebase functions:config:set provider.client_secret="your_secret"69// Exposing client_secret in client code allows token forgery attacks.Common mistakes
Why it's a problem: Storing the OAuth client_secret in a Custom Action parameter or hardcoded string
How to avoid: 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.
Why it's a problem: Using the wrong callback URL in the OAuth provider's application settings
How to avoid: 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.
Why it's a problem: Skipping the CSRF state parameter in the custom OAuth flow
How to avoid: 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.
Why it's a problem: Attempting to call Firebase's signInWithCustomToken() from inside a Firebase Cloud Function instead of on the client
How to avoid: 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.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm building a FlutterFlow app with Firebase and need to add OAuth 2.0 login for [provider name, e.g. Slack]. Firebase Auth doesn't support this provider natively. Please write a Firebase Cloud Function (Node.js) that implements the Authorization Code flow: one function that generates the authorization URL, and one that exchanges the authorization code for a Firebase custom token using the Admin SDK. The client_secret should be read from Firebase environment config, never hardcoded. Also write the FlutterFlow Custom Action that calls the callback function and signs the user in with the returned custom token.
Write a FlutterFlow Custom Action called 'completeCustomOAuth' that calls a Firebase Cloud Function named '{provider}Callback' with parameters code (String) and state (String). The function returns a customToken string. The action should call FirebaseAuth.instance.signInWithCustomToken() with the token and return Boolean (true on success). Handle FirebaseFunctionsException and FirebaseAuthException separately with debugPrint. Write only the function body for the FlutterFlow Custom Code editor.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation