How I Would Fix exposed API keys and missing auth in a Supabase and Edge Functions client portal Using Launch Ready.
The symptom is usually blunt: a client portal works in the browser, but the Supabase anon key, service role key, or Edge Function URL is sitting in the...
How I Would Fix exposed API keys and missing auth in a Supabase and Edge Functions client portal Using Launch Ready
The symptom is usually blunt: a client portal works in the browser, but the Supabase anon key, service role key, or Edge Function URL is sitting in the frontend bundle, network tab, or public repo. At the same time, sensitive portal actions are callable with no real auth check, so anyone who finds the endpoint can read or change data.
The most likely root cause is that the app was built fast with a direct-to-database pattern and never got a proper auth boundary. The first thing I would inspect is the browser bundle and the Edge Functions entry points, because that tells me whether the problem is just exposure or actual unauthorized access.
Triage in the First Hour
1. Check whether any secrets are already public.
- Inspect GitHub, Vercel, Netlify, Cloudflare Pages, and local `.env` history.
- Search for `SUPABASE_SERVICE_ROLE_KEY`, `SUPABASE_ANON_KEY`, JWT secrets, SMTP credentials, and webhook tokens.
- If a service role key is exposed anywhere public, assume it is compromised.
2. Inspect the deployed frontend bundle.
- Open DevTools and search loaded JS for hardcoded keys, project refs, function URLs, or admin endpoints.
- Check source maps if they are enabled.
- Confirm whether sensitive logic is happening client-side instead of behind an authenticated server boundary.
3. Review Supabase Auth settings.
- Verify sign-in methods, session expiry, password reset flow, MFA options, and redirect URLs.
- Check whether row level security is enabled on every table that stores client data.
- Confirm whether policies are missing, too broad, or bypassed with service role access.
4. Review Edge Functions logs.
- Look for requests without valid JWT claims.
- Check for functions that trust query params like `user_id`, `role`, or `org_id`.
- Identify any function returning customer data without verifying ownership.
5. Review database policies and grants.
- Inspect tables used by the portal: messages, files, invoices, tasks, documents, notes.
- Confirm RLS is on and policies match portal roles.
- Look for `auth.uid()` checks that are missing or incorrectly written.
6. Check deploy settings and environment variables.
- Verify secrets are stored only in platform env vars and not in client-side build vars.
- Confirm preview deployments do not point at production secrets unless intended.
- Review Cloudflare caching rules if authenticated pages are being cached publicly.
7. Decide if you need an emergency rotate now.
- If a service role key or admin token was exposed publicly, rotate it before anything else.
- If customer data could have been accessed without auth, pause risky endpoints until access control is fixed.
supabase functions logs <function-name> --project-ref <project-ref> supabase db diff supabase gen types typescript --project-id <project-ref> > supabase.types.ts
Root Causes
1. Secret was shipped into the frontend bundle.
- Common when using `VITE_`, `NEXT_PUBLIC_`, or similar public env prefixes for values that should stay private.
- Confirm by searching built assets for secret strings or matching variable names.
2. Edge Functions trust caller input instead of verified identity.
- A function may accept `user_id` from the request body and use it directly to fetch records.
- Confirm by checking whether the function validates Supabase JWT claims before touching data.
3. RLS is disabled or incomplete on portal tables.
- The app may depend on frontend logic to hide records instead of database enforcement.
- Confirm by checking each table with a direct SQL review and testing access from an unauthenticated session.
4. Service role key used in too many places.
- This key bypasses RLS entirely and should almost never appear outside trusted server code.
- Confirm by tracing every place it is imported and checking whether any client-accessible code path can reach it.
5. Misconfigured storage or signed URL flow.
- File buckets may be public when they should be private, or signed URLs may last too long.
- Confirm by trying to fetch file links after logout and checking bucket policy plus expiration time.
6. Caching layer serving private content publicly.
- Cloudflare or app caching can accidentally cache authenticated responses if headers are wrong.
- Confirm by checking response headers like `Cache-Control`, `Vary`, and any CDN rules around portal routes.
The Fix Plan
My rule here is simple: stop exposure first, then restore access control at the lowest safe layer possible. For a client portal on Supabase and Edge Functions, that means I would fix auth in three places: frontend session handling, function-level verification, and database-level policy enforcement.
1. Rotate exposed credentials immediately.
- Rotate any leaked service role keys, JWT secrets if needed, API tokens, SMTP passwords, webhook secrets, and storage credentials.
- Invalidate old sessions if there is evidence of unauthorized access.
2. Move all privileged logic out of the browser.
- Any action that writes data across tenants should run in an Edge Function or trusted backend only.
- The browser should send only user intent plus an authenticated session token.
3. Enforce authentication at every Edge Function entry point.
- Verify the Supabase JWT before processing anything sensitive.
- Reject requests without a valid bearer token with a 401 response before reading data or calling other services.
4. Enforce authorization separately from authentication.
- Authentication says who they are; authorization says what they can do inside this portal tenant/org/client account.
- Check ownership using claims plus database lookups where needed. Do not trust client-supplied IDs alone.
5. Turn on RLS everywhere sensitive data lives.
- Add explicit policies per table for read/write access based on authenticated user identity and tenant membership.
- Remove broad policies like "allow all authenticated users" unless they are truly safe.
6. Replace public file access with controlled delivery where needed.
- Keep private documents in private buckets where possible.
. Generate short-lived signed URLs for downloads instead of permanent links.
7. Harden Cloudflare and deployment settings after auth is fixed. . Disable caching on authenticated routes unless you have explicit per-user cache keys set up correctly . Ensure SSL is enforced end to end . Lock redirects so login flows cannot be hijacked by loose wildcard rules
8. Add least-privilege environment separation. . Production secrets only in production . Preview deployments use preview data where possible . Admin credentials never enter client-side build variables
9. Remove debug surfaces before redeploying . Strip console logs containing tokens . Disable source maps in production if they expose internal structure unnecessarily . Remove any temporary test endpoints used during development
10. Re-test with one known-good user and one blocked user before opening access back up . I want proof that legitimate users can still work . I also want proof that unauthenticated requests fail cleanly
| Layer | What I enforce | Why it matters | | --- | --- | --- | | Frontend | No secret keys in bundle | Stops easy leakage | | Edge Functions | JWT verification on every request | Blocks unauthenticated calls | | Database | RLS on all sensitive tables | Prevents direct data exposure | | Storage | Private buckets plus signed URLs | Protects files | | CDN | No cache for private routes | Avoids cross-user leaks |
Regression Tests Before Redeploy
I would not ship this fix until I had evidence-based checks passing in staging with production-like data shape but sanitized records.
- Auth required tests
-. Unauthenticated request to each protected Edge Function returns 401 -. Invalid token returns 401 -. Expired token returns 401 -. Valid token for wrong tenant returns 403
- Data isolation tests
-. User A cannot read User B records through SQL queries exposed via app flows -. User A cannot fetch another tenant's files through old URLs -. Admin-only actions fail for normal users even if they modify request payloads
- Secret exposure tests
-. Search built assets for service role key patterns returns nothing -. No secret appears in page source or network responses -. Source maps do not expose sensitive constants
- Functional acceptance criteria
-. Portal login still works within 2 steps on desktop and mobile -. Core actions complete under p95 latency of 500 ms for auth checks plus function calls where practical -. No broken redirects after login/logout across main browsers
- Security QA checks
-. Test at least 10 negative cases across missing token, malformed token, expired token, wrong org ID, tampered body fields -. Verify rate limiting on login and sensitive endpoints to reduce brute-force risk -. Review logs to confirm no secrets are written to observability tools
A good acceptance bar here is boring: zero unauthorized reads found in manual testing across five critical flows and zero secret strings found in production bundles after build.
Prevention
If I were keeping this from happening again, I would treat it as both a security problem and a product quality problem. Most auth leaks survive because teams optimize for shipping speed without a review gate on sensitive changes.
- Put auth checks in code review criteria
-. Any new Edge Function must show JWT validation plus authorization logic before merge -. Any new table must include RLS policy review before deploy
- Add security-focused tests to CI
-. Run negative auth tests on every pull request -. Fail builds if secret scanning detects leaked keys
- Use separate environments cleanly
-. Development keys never go into production builds -. Preview deployments use restricted test projects whenever possible
- Log safely
-. Never log raw tokens, passwords, reset links, or full request bodies containing PII -. Redact identifiers in error reporting tools
- Tighten UX around permission boundaries
-. Show clear "access denied" states instead of silent failures -. Make session expiry obvious so users do not keep retrying broken actions
- Watch performance while securing it
-. Auth middleware should stay lightweight so you do not create slow portals that encourage unsafe workarounds -. Keep p95 checks fast enough that login feels responsive under normal load
When to Use Launch Ready
Launch Ready fits when you already have a working portal but need it made safe enough to go live without creating support chaos or data risk. deployment cleanup, environment variables, secrets handling, uptime monitoring, and a handover checklist so you know exactly what changed.
I would recommend Launch Ready if: -. You have exposed API keys or suspect them but need fast containment plus redeploy support -. Your Supabase app works locally but production auth is weak or inconsistent -. You need one senior engineer to stabilize launch rather than spread this across multiple freelancers
What you should prepare: -. Repo access plus deployment platform access such as Vercel, Netlify, or Cloudflare Pages/Workers settings where relevant -. Supabase project access with database, auth, storage, and functions permissions -. A list of protected portal flows: login, profile, billing, documents, messages, admin actions -. Any current incident notes: what leaked, when you noticed it, and whether customers were affected -. Brand domain registrar access if DNS changes are part of launch cleanup
If you want me to take this from risky prototype to production-safe quickly, I would start with Launch Ready first because it stops the bleeding before we talk about redesigns or growth work.
Delivery Map
References
- https://roadmap.sh/api-security-best-practices
- https://roadmap.sh/cyber-security
- https://roadmap.sh/code-review-best-practices
- https://supabase.com/docs/guides/auth
- https://supabase.com/docs/guides/database/postgres/row-level-security
---
Take the next step
If this is a problem in your product right now, here is what to do next:
- [Use the free Cyprian tools](/tools) - estimate cost, score app risk, check launch readiness, or pick the right service sprint.
- [Book a discovery call](/contact) - I will tell you honestly whether you need a sprint or if you can DIY the next step.
*Written by Cyprian Tinashe Aarons - senior full-stack and AI engineer helping founders rescue, launch, automate, and scale AI-built products.*
Cyprian Tinashe Aarons — Senior Full Stack & AI Engineer
Cyprian helps founders rescue, secure, deploy, and automate AI-built apps with production-grade engineering, launch systems, and AI integration.