Connect FlutterFlow to Auth0 using two separate paths: add the auth0_flutter pub.dev package via a Custom Action for end-user Universal Login (PKCE, public Client ID — no secret required), then route all Management API admin operations through a Firebase Cloud Function that holds your M2M Client Secret securely on the server side.
| Fact | Value |
|---|---|
| Tool | Auth0 |
| Category | Auth & Identity |
| Method | Custom Action (Dart) |
| Difficulty | Intermediate |
| Time required | 45 minutes |
| Last updated | July 2026 |
Two Auth0 jobs in FlutterFlow — and confusing them is the #1 mistake
Auth0 serves two completely different audiences in a FlutterFlow app. The first is your end user: they tap a Login button, the app opens the system browser to Auth0's Universal Login page, they authenticate, and Auth0 redirects back to your app with an access token. This flow uses OAuth2 PKCE with your public Client ID — no secret is involved, so it is safe to run from client-side Dart code. The auth0_flutter package on pub.dev handles the full handshake for you.
The second audience is you, the developer, performing admin operations: searching your user list, blocking accounts, assigning roles, reading logs. These calls target Auth0's Management API at https://YOUR_DOMAIN/api/v2/ and require a Machine-to-Machine access token obtained via the client-credentials grant. That grant requires your M2M Client Secret — a credential that would be trivially extractable from any compiled Flutter app. It must never be placed in a Dart Custom Action or an API Call header in FlutterFlow.
The solution is a Firebase Cloud Function that holds the M2M Client Secret. The Function requests a Management token from Auth0 on each request (or caches one with the ~86,400s expiry in mind), then forwards the admin call to the Management API. FlutterFlow's API Calls panel is configured to point at the Function's HTTPS endpoint, not at Auth0 directly. Auth0's free tier covers up to 7,500 monthly active users — check their current pricing for paid tiers starting around $35/month.
Integration method
End-user login uses the auth0_flutter Dart package configured as a Custom Action — FlutterFlow calls the SDK which opens the system browser for Universal Login using PKCE, returning a session token that is stored in App State. Admin operations (list users, block accounts, assign roles) require a separate Machine-to-Machine token that contains a Client Secret, so those calls are proxied through a Firebase Cloud Function that holds the secret server-side; FlutterFlow API Calls target the Function, never Auth0's Management API directly.
Prerequisites
- An Auth0 account (free tier covers 7,500 monthly active users)
- A FlutterFlow project (any paid plan — Custom Code requires at least the Standard plan)
- A Firebase project with Cloud Functions enabled (Blaze pay-as-you-go plan required for outbound HTTP calls from Functions)
- Basic familiarity with the FlutterFlow Custom Code panel
- Your app's bundle ID / package name (needed for the Auth0 callback URL scheme)
Step-by-step guide
Create Auth0 Applications and note your credentials
Log in to your Auth0 dashboard at manage.auth0.com. You need to create two separate Application entries. First, create a Native Application for end-user login: click Applications in the left sidebar → Applications → Create Application → choose Native → name it something like 'FlutterFlow App'. On the Settings tab, copy the Domain (e.g., your-tenant.us.auth0.com) and the Client ID (starts with letters/numbers — this is PUBLIC, safe in client code). Do not copy the Client Secret for this application; it is not used in PKCE flows. Next, configure the Allowed Callback URLs for this Native Application. The format for Android is: com.yourcompany.yourapp://YOUR_DOMAIN/android/com.yourcompany.yourapp/callback. For iOS it is: com.yourcompany.yourapp://YOUR_DOMAIN/ios/com.yourcompany.yourapp/callback. Replace com.yourcompany.yourapp with your actual bundle ID (found in FlutterFlow under Settings → App Details → Package Name). Add both URLs to the Allowed Callback URLs field and click Save Changes. A mismatch between the scheme in Auth0 and the scheme in your app is the top cause of silent login failures. Second, create a Machine-to-Machine Application for admin operations: go back to Applications → Create Application → Machine to Machine Applications. Authorize it against the Auth0 Management API (select the API from the dropdown, then grant the permissions you need: read:users, update:users, read:logs, etc. — use least privilege). On the Settings tab, copy both the Client ID and the Client Secret. This Client Secret goes into your Firebase Cloud Function only — never into FlutterFlow.
Pro tip: Keep a text file with your Auth0 Domain, the Native App Client ID, the M2M Client ID, and the M2M Client Secret. You will need them in later steps.
Expected result: Two Auth0 Applications created: a Native app with callback URLs configured, and a Machine-to-Machine app with Management API permissions granted.
Add auth0_flutter and write the login Custom Action
In FlutterFlow, open the left navigation and click Custom Code. Click the + Add button and select Action. Name it auth0Login. In the Dependencies field, add auth0_flutter — FlutterFlow will resolve the latest compatible version from pub.dev automatically. You do not need to run flutter pub get; FlutterFlow handles the dependency graph in the background. In the Dart editor for the action, paste the code below. Set the Return Value type to JSON (or define individual output variables for accessToken, idToken, and name — both approaches work). The action calls Auth0(domain, clientId).webAuthentication().login() which opens the system browser (on Android via Custom Tabs, on iOS via SFSafariViewController) and waits for the redirect. On success it returns the Credentials object containing the access token, ID token, and decoded user profile. IMPORTANT: You must also register the callback URL scheme so the OS redirects back to your app. In FlutterFlow, go to Settings & Integrations → General → URL Schemes and add your scheme (com.yourcompany.yourapp). On Android this registers the intent filter; on iOS it registers the CFBundleURLTypes entry. Without this, Universal Login will complete in the browser but never return to your app. After saving the Custom Action, open the Action Flow Editor for your Login button widget, add the auth0Login Custom Action as the first step, then add Set App State actions to store the returned token and user fields. Add a Navigate action to send the user to your home screen on success.
1// custom_action.dart2import 'package:auth0_flutter/auth0_flutter.dart';34Future<dynamic> auth0Login(5 String domain,6 String clientId,7) async {8 try {9 final auth0 = Auth0(domain, clientId);10 final credentials = await auth011 .webAuthentication(scheme: 'com.yourcompany.yourapp')12 .login();1314 return {15 'accessToken': credentials.accessToken,16 'idToken': credentials.idToken,17 'name': credentials.user.name ?? '',18 'email': credentials.user.email ?? '',19 'sub': credentials.user.sub,20 };21 } catch (e) {22 throw Exception('Auth0 login failed: $e');23 }24}Pro tip: Replace 'com.yourcompany.yourapp' in the scheme parameter with your actual bundle ID. Custom Actions do not run in FlutterFlow's web-based Run mode — test this on a real device via the Test Mode device build or a compiled APK.
Expected result: The auth0Login Custom Action appears in the FlutterFlow Custom Code panel. When triggered on a device, it opens the system browser with Auth0's Universal Login page.
Deploy a Firebase Cloud Function for Management API operations
Because your M2M Client Secret cannot be placed in the FlutterFlow app, you need a Firebase Cloud Function that holds it server-side. This Function requests a Management API token from Auth0 using the client-credentials grant, then forwards the admin request. In the Firebase console, go to your project, then click Functions → Get Started (or write in the functions/ directory if you already have Functions configured). In your functions/index.js file, add the code shown below. The Function first calls https://YOUR_DOMAIN/oauth/token with your M2M credentials to get a Management API bearer token, then uses that token to call the Management API endpoint you request. Set your Auth0 credentials as Firebase environment variables (do NOT hardcode them in source). In the Firebase console, go to Functions → Configuration and add: AUTH0_DOMAIN, AUTH0_M2M_CLIENT_ID, AUTH0_M2M_CLIENT_SECRET. These are injected as process.env variables at runtime. Deploy the Function using the Firebase console's deploy button or by running firebase deploy --only functions in your local terminal. After deployment, copy the HTTPS trigger URL (e.g., https://us-central1-yourproject.cloudfunctions.net/auth0Admin). This is the URL you will configure in FlutterFlow's API Calls panel. The Function caches the M2M token in a module-level variable and checks expiry before each request — this avoids a token exchange on every single API call and keeps your Management API rate limit headroom intact. The Auth0 free plan limits Management API calls to approximately 10 requests per second on the /users endpoint; check your plan's current limits.
1// functions/index.js2const functions = require('firebase-functions');3const admin = require('firebase-admin');4const fetch = require('node-fetch');5admin.initializeApp();67const AUTH0_DOMAIN = process.env.AUTH0_DOMAIN;8const CLIENT_ID = process.env.AUTH0_M2M_CLIENT_ID;9const CLIENT_SECRET = process.env.AUTH0_M2M_CLIENT_SECRET;1011let cachedToken = null;12let tokenExpiry = 0;1314async function getManagementToken() {15 if (cachedToken && Date.now() < tokenExpiry) return cachedToken;1617 const res = await fetch(`https://${AUTH0_DOMAIN}/oauth/token`, {18 method: 'POST',19 headers: { 'Content-Type': 'application/json' },20 body: JSON.stringify({21 grant_type: 'client_credentials',22 client_id: CLIENT_ID,23 client_secret: CLIENT_SECRET,24 audience: `https://${AUTH0_DOMAIN}/api/v2/`,25 }),26 });27 const data = await res.json();28 cachedToken = data.access_token;29 tokenExpiry = Date.now() + (data.expires_in - 60) * 1000;30 return cachedToken;31}3233exports.auth0Admin = functions.https.onRequest(async (req, res) => {34 res.set('Access-Control-Allow-Origin', '*');35 if (req.method === 'OPTIONS') { res.status(204).send(''); return; }3637 try {38 const token = await getManagementToken();39 const { action, userId, query } = req.body;4041 let url = `https://${AUTH0_DOMAIN}/api/v2/users`;42 let method = 'GET';43 let body = undefined;4445 if (action === 'search') url += `?q=${encodeURIComponent(query)}&search_engine=v3`;46 if (action === 'block') { url += `/${userId}`; method = 'PATCH'; body = JSON.stringify({ blocked: true }); }47 if (action === 'getUser') url += `/${userId}`;4849 const mgmtRes = await fetch(url, {50 method,51 headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },52 body,53 });54 const data = await mgmtRes.json();55 res.json(data);56 } catch (err) {57 res.status(500).json({ error: err.message });58 }59});Pro tip: Set AUTH0_DOMAIN, AUTH0_M2M_CLIENT_ID, and AUTH0_M2M_CLIENT_SECRET in Firebase Functions Configuration, never in source code. Firebase Functions requires the Blaze (pay-as-you-go) plan for outbound HTTP calls.
Expected result: The auth0Admin Firebase Function is deployed and has an HTTPS trigger URL. Calling it with {"action": "search", "query": "alice"} returns matching users from your Auth0 tenant.
Configure FlutterFlow API Calls for admin operations
With the Firebase Function deployed, you can now wire up FlutterFlow's API Calls panel to reach it. In the left nav, click API Calls → + Add → Create API Group. Name the group Auth0Admin. Set the Base URL to your Firebase Function HTTPS trigger URL (e.g., https://us-central1-yourproject.cloudfunctions.net/auth0Admin). You do not need authentication headers here because the Function itself is the trust boundary. Add individual API Calls inside the group. Click + Add → Create API Call. Name the first one searchUsers. Set the Method to POST. Leave the endpoint path empty (the Function URL is the full path). In the Body tab, set the body type to JSON and define the structure: { "action": "search", "query": "{{ query }}" }. Click the Variables tab and add a variable named query (type String). This becomes a FlutterFlow binding point you can connect to a TextField widget. Add a second call named blockUser with body { "action": "block", "userId": "{{ userId }}" } and a userId variable. In the Response & Test tab for searchUsers, paste a sample Auth0 users array response and click Generate JSON Paths. FlutterFlow creates JSON Path extractors like $.users[*].email, $.users[*].user_id, $.users[*].last_login that you can bind directly to ListView tile widgets. To trigger these calls in your app, open the Action Flow Editor for a search button, add Backend/API Call → searchUsers, pass the text field value as the query variable, then bind the response to a Page State variable. Display results in a ListView with Dynamic Children bound to the response data.
Pro tip: Auth0 user IDs use the format 'auth0|00abc123...' — when you call blockUser, pass the full user_id string from the search response, not just the email.
Expected result: The Auth0Admin API group appears in FlutterFlow with searchUsers and blockUser endpoints. Running a test call from the Response & Test tab returns user data from your Auth0 tenant via the Firebase Function.
Wire logout and add page-level auth guards
Logout in Auth0 Universal Login requires clearing both the app session and the Auth0 browser session. Add another Custom Action named auth0Logout. In the Dart code, call auth0.webAuthentication().logout() which opens the system browser briefly to invalidate the Auth0 session cookie, then returns. After the action completes, add a Set App State action to clear all auth-related App State fields (accessToken, idToken, name, email), then a Navigate action to send the user back to the Login screen. To protect pages that require authentication, use FlutterFlow's Conditional Navigation. On each protected page's initState (or via the page's On Page Load action), add an If/Else action that checks whether your App State accessToken field is empty. If it is empty, navigate to the Login page. This gates every route entry point without duplicating guard logic. For a more polished experience, add a loading state to the Login button that activates while auth0Login is running — use a Boolean App State variable isLoggingIn, set it to true at the start of the action and false at the end (in both success and error paths). Bind the button's Loading property to this boolean so users see a spinner during the browser handoff. If you want to display the RapidDev team's recommendation inline: if this custom action + Firebase Function setup feels complex, RapidDev's team builds FlutterFlow integrations like this every week — book a free scoping call at rapidevelopers.com/contact.
1// auth0_logout.dart2import 'package:auth0_flutter/auth0_flutter.dart';34Future auth0Logout(String domain, String clientId) async {5 final auth0 = Auth0(domain, clientId);6 await auth07 .webAuthentication(scheme: 'com.yourcompany.yourapp')8 .logout();9}Pro tip: Calling logout() without the scheme parameter that matches your registered URL scheme may silently fail on Android — always pass the scheme.
Expected result: Tapping the Logout button clears App State, calls Auth0 to end the browser session, and navigates to the Login screen. Protected pages redirect unauthenticated users to Login automatically.
Common use cases
Mobile SaaS app with secure user login
A FlutterFlow SaaS app uses Auth0 Universal Login so users sign in with email/password or social providers (Google, GitHub). After login, the access token is stored in App State and used to gate feature screens. The auth0_flutter Custom Action handles the entire PKCE flow with zero secret exposure.
Build a login screen that triggers Auth0 Universal Login and, on success, saves the user's name and email to App State, then navigates to a Home page.
Copy this prompt to try it in FlutterFlow
Internal admin dashboard for user management
A company builds a FlutterFlow admin panel where support staff can search users by email, view their last login, and deactivate compromised accounts. All Management API calls go through a Firebase Cloud Function that holds the M2M credentials, so the Dart app never touches Auth0 admin endpoints directly.
Build a user search page that calls a Firebase Function, displays results in a ListView with name, email, and last login, and has a Deactivate button that calls a confirm dialog before firing the block action.
Copy this prompt to try it in FlutterFlow
Role-gated app with post-login role assignment
A multi-tenant FlutterFlow app assigns Auth0 roles (admin, viewer, editor) after signup based on the user's email domain. A Firebase Cloud Function reads the ID token, checks the domain, and calls the Auth0 Management API to assign the correct role; the FlutterFlow app reads the role from the decoded token to show or hide UI sections.
After a user logs in, call a Firebase Function that checks their email domain and assigns an Auth0 role, then use the returned role string in App State to conditionally show the Admin menu.
Copy this prompt to try it in FlutterFlow
Troubleshooting
Login opens the browser but the app never receives the callback — it just stays on the browser screen
Cause: The callback URL scheme registered in Auth0's Allowed Callback URLs does not match the scheme configured in FlutterFlow's URL Schemes setting or in the Dart action's scheme parameter.
Solution: Check three places must match exactly: (1) Auth0 Dashboard → Applications → your Native App → Allowed Callback URLs, (2) FlutterFlow → Settings & Integrations → General → URL Schemes, (3) the scheme string in your auth0_flutter webAuthentication(scheme: ...) call. The format is 'com.yourcompany.yourapp' — no slashes, no https prefix.
Universal Login works in a device build but nothing happens in FlutterFlow's web Run/Test mode
Cause: Auth0 Universal Login requires an OS-level browser redirect with a custom URL scheme. FlutterFlow's web Run mode runs in a browser iframe and cannot handle native URL scheme callbacks.
Solution: This is expected behavior — Custom Actions with native redirects cannot be tested in FlutterFlow's web preview. Use the FlutterFlow Test Mode on a physical device (iOS or Android) or build an APK/IPA for testing.
Firebase Function returns 401 Unauthorized when calling Auth0 Management API
Cause: The M2M token request failed, likely because AUTH0_DOMAIN, AUTH0_M2M_CLIENT_ID, or AUTH0_M2M_CLIENT_SECRET environment variables are not set in Firebase Functions configuration, or the M2M Application is not authorized for the Management API audience.
Solution: In the Firebase Console, go to Functions → Configuration and verify all three environment variables are present. In the Auth0 Dashboard, go to Applications → your M2M Application → APIs and confirm it is authorized against the Auth0 Management API with the permissions you need (read:users, update:users, etc.).
Management API call succeeds but returns no users despite the user existing
Cause: Auth0 Management API free-tier search uses a different search engine; free tenants must explicitly pass search_engine=v3 in the query and use Lucene syntax for field-specific searches.
Solution: Append &search_engine=v3 to your /users query URL in the Firebase Function. Use Lucene syntax for field-specific searches: email:alice@example.com instead of just alice@example.com.
Best practices
- Never place your Auth0 M2M Client Secret in a Dart Custom Action, App Values constant, or API Call header in FlutterFlow — it ships inside the compiled app bundle and is trivially extractable.
- Use the PKCE flow via auth0_flutter for all end-user authentication; the public Client ID is safe client-side because PKCE does not rely on a secret.
- Store the returned access token in App State (in-memory only), not in Firestore or SharedPreferences — tokens are short-lived credentials, not persistent data.
- In your Firebase Cloud Function, cache the M2M access token in a module-level variable and check the expiry before each request to avoid unnecessary token exchanges on every API call.
- Apply least-privilege permissions when authorizing your M2M Application — only grant the Management API scopes you actually use (e.g., read:users, update:users) rather than all permissions.
- Always test auth0_flutter Custom Actions on a real device or device build, not in FlutterFlow's web Run mode, because the OS-level URL scheme redirect cannot complete in a browser iframe.
- Register both Android and iOS callback URL formats in Auth0's Allowed Callback URLs from the start, even if you are only targeting one platform initially — adding them later requires a Settings save that can take a few minutes to propagate.
Alternatives
Firebase Authentication is a FlutterFlow native integration — zero custom code for email/social login, tighter Firestore binding, and no separate proxy layer needed for basic auth.
Choose Okta when your organization already uses Okta Workforce Identity for employees and you need SSO + user lifecycle management via SSWS API; Auth0 is better for customer-facing app login flows.
Frequently asked questions
Is the Auth0 Client ID safe to include in my FlutterFlow app?
Yes — the Client ID for a Native Application using PKCE is a public identifier, not a secret. It tells Auth0 which application is initiating the login, but it cannot be used to obtain tokens on its own without the PKCE code verifier that the SDK generates per-session. Never confuse it with the Client Secret, which is only generated for Machine-to-Machine applications and must stay in your Firebase Cloud Function.
Can I use Auth0 social login (Google, GitHub, Apple) with this setup?
Yes — once Auth0 Universal Login is configured, social providers are enabled in your Auth0 dashboard under Authentication → Social. The auth0_flutter SDK handles the redirect transparently regardless of which provider the user chooses; your FlutterFlow Custom Action does not need to change. Configure each social provider's client ID and secret in Auth0, not in FlutterFlow.
Do auth0_flutter Custom Actions work on web (Flutter Web)?
The auth0_flutter package supports Flutter Web, but Universal Login on web opens a popup or redirect rather than the system browser. The key limitation is that FlutterFlow's in-editor Run/Test mode cannot handle this — you must either deploy your app to a real web host or test via a device build. Web builds work correctly in production at a hosted URL where Auth0 can redirect back to your registered callback.
How do I pass the user's Auth0 ID to my Firestore database after login?
The auth0_flutter credentials object returns user.sub, which is the Auth0 user ID in the format 'auth0|...'. Store this in App State after login, then use it as the document ID or a foreign key when creating Firestore records via FlutterFlow's Firebase integration. This links Auth0 identity to your Firestore data without any additional lookup.
What if I want to refresh the access token automatically?
The auth0_flutter package provides a CredentialsManager that can store and automatically refresh tokens using a refresh token. Configure your Auth0 application to enable refresh tokens (set Refresh Token Rotation in the Auth0 dashboard), then use Auth0(domain, clientId).credentialsManager.credentials() in your Dart action instead of calling webAuthentication().login() every time. This keeps the user logged in across app restarts without re-prompting.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation