Flutter + Supabase Auth: Secure by Default or Security Theatre?
Supabase markets itself as the "open-source Firebase alternative." Its Auth module promises OAuth, email magic links, social sign-ins, row-level security, and more—usually wired up in a dozen lines of Dart. But developers still ask us:
"If it's that easy, what's the catch? Are the defaults actually safe, or am I just staging security theatre?"
After integrating Supabase Auth in three production Flutter apps and pen-testing two of them, our verdict is:
- Yes—Supabase is secure by default for most consumer use-cases.
- But—you need half-a-dozen extra steps to guard against token abuse, environment leaks, and broken access controls.
Below is the full breakdown, including what we tighten in every client retainer at THE USEFUL APPS.
Table of Contents
- What Supabase Auth Does Out of the Box
- Five Hidden Risks We Discovered
- Lock-Down Checklist for Production
- OAuth Social Sign-Ins the Right Way
- Row-Level Security Rules — Common Pitfalls
- Secure Storage of JWTs in Flutter
- CI/CD and Secrets Management
- Real-World Benchmarks vs Firebase Auth
- When Supabase Isn't Enough
- Conclusion & Next Steps
1. What Supabase Auth Does Out of the Box
Capability | Default behaviour |
---|---|
Postgres roles & JWT | Generates a short-lived (60 min) access token + refresh token signed with project's jwt_secret . |
Row-Level Security | Enabled, but all tables are public until you add policies. |
OAuth providers | Google, Apple, GitHub, Facebook pre-configured; you bring client IDs. |
Magic links | One-time links via Supabase SMTP relay (disabled in "Free Tier"). |
MFA | Off (as of Supabase 2.36) but TOTP in beta. |
Rate-limiting | 6 requests/sec per IP for /auth/v1/** endpoints. |
Take-away: Great starting posture, except row-level security is effectively open unless you write policies.
2. Five Hidden Risks We Discovered
- Leaky Service Key in Flutter code Many tutorials embed the service-role key (full DB admin privileges) in mobile builds—if reversed, your DB is toast.
- Refresh tokens stored in Shared Preferences
The community package
flutter_secure_storage
defaults to plaintext on some Android OEMs prior to API 23. - No webhook signature verification
If you rely on
user.updated
oruser.deleted
webhooks for internal logic, you must check the HMAC header. - Open social-login redirect URI Misconfigured deep links allow credential phishing ("Sign in with Google" popup to attacker domain).
- Missing Content Security Policy on WebView logins Hybrid apps exposing Supabase's hosted login page can be hit by XSS if CSP headers aren't set.
3. Lock-Down Checklist for Production
Action | Fix |
---|---|
Rotate jwt_secret after staging |
Use Dashboard ➜ Settings ➜ API ➜ "Rotate". |
Store only the anon key on device | Keep service key in Cloud Functions or AWS Lambda. |
Custom RLS policies | Start with auth.uid() = id on user tables. |
Tighten CORS & allowlist-domains |
Prevent rogue web origin token exchange. |
Set up log retention + anomaly alerts | Supabase logs stream to your own Grafana. |
We bake these tasks into our Care Plan; see our pricing page for details.
4. OAuth Social Sign-Ins the Right Way
- Use Apple Sign-In on iOS (App Store Guideline 4.8).
- Always enforce verified email (
identity.email_confirmed_at IS NOT NULL
). - For Google: request "select_account" prompt to avoid silent rogue account linking.
- Store provider ID in a dedicated
profiles
table—don't rely onraw_user_meta_data
.
For more on Apple's requirements, see Apple Dev Doc App Store Review Guidelines §4.8 .
5. Row-Level Security Rules — Common Pitfalls
-- ❌ TOO OPEN: anyone can read others' messages
CREATE POLICY "Read messages" ON messages
FOR SELECT USING (auth.role() = 'authenticated');
-- ✅ Tight: only sender or recipient
CREATE POLICY "Read own thread" ON messages
FOR SELECT USING (
auth.uid() IN (sender_id, recipient_id)
);
Enable ALTER TABLE ... FORCE ROW LEVEL SECURITY;
so future tables inherit your strictness.
6. Secure Storage of JWTs in Flutter
Option | iOS | Android |
---|---|---|
flutter_secure_storage |
Keychain ✔ | Keystore ✔ (API 23+) |
shared_preferences |
Plaintext ❌ | Plaintext ❌ |
Our choice | flutter_appauth + AuthFlow → Short-lived token in memory, refresh in background. |
Tip: Use supabase_flutter
2.0 and pass a custom LocalStorage
implementation that wraps Keychain/Keystore only.
7. CI/CD and Secrets Management
- Store
SUPABASE_URL
,SUPABASE_ANON_KEY
as GitHub Secrets. - Service key lives in AWS Parameter Store, fetched by backend functions.
- Run
supabase db diff
in PRs to CI-check RLS rules. - Add Snyk to scan dependencies for JWT and crypto lib CVEs.
8. Real-World Benchmarks vs Firebase Auth
Test (1 000 logins) | Supabase | Firebase |
---|---|---|
Median sign-in latency | 280 ms | 190 ms |
Cold-start SDK size (Flutter) | 1.6 MB | 2.1 MB |
Pricing (10 000 MAU) | $0 (free tier) | $0 (free tier) |
Enterprise SSO | In beta | GA |
Unless you need phone auth at scale or multi-tenant SAML today, Supabase matches Firebase for most indie + SME apps.
9. When Supabase Isn't Enough
- SOC 2 / HIPAA projects—Supabase compliance roadmap still "coming."
- > 1 000 write QPS — Postgres row-level security can add latency; consider partitioning.
- Offline-first apps — Supabase Sync is alpha; we pair it with Hive or Drift for local cache.
10. Conclusion & Next Steps
Supabase Auth is secure by default—as long as you know what "default" actually covers. Harden the RLS rules, keep service keys off devices, and store tokens in secure enclaves—then you have enterprise-grade auth without Firebase lock-in.
Need help auditing your Supabase Flutter app? Book a free security review or explore how our Growth Plan folds continuous security checks into monthly maintenance.