Mockly

integrations

Next.js backend-only Supabase access

A safe integration pattern where Supabase service_role is only used in server code (API routes or server actions) and the browser never calls tables/RPC/storage directly. This guide focuses on the practical steps, the workflows that matter in production, and the verification checks that prevent false confidence.

What β€œNext.js backend-only Supabase access” gives you

A safe integration pattern where Supabase service_role is only used in server code (API routes or server actions) and the browser never calls tables/RPC/storage directly.

Threat model for Next.js backend-only Supabase access (what it blocks)

  • Direct client access to privileged tables, Storage objects, or RPC functions.
  • Authorization logic living only in the UI (bypass via direct API calls).
  • Accidental secret leakage (service_role) into browser bundles or public env vars.
  • Configuration drift that silently widens access over time (policies, grants, bucket settings).

Prerequisites

  • A Next.js app using the App Router
  • Environment variables configured for server-only secrets (no client exposure)
  • A clear boundary: client β†’ your backend β†’ Supabase

Setup steps for Next.js backend-only Supabase access

Follow these steps in order. If you skip verification, you can end up with a working UI and an exposed backend.

  1. Create a server-only Supabase client module (never imported by client components).
  2. Store service_role key in a server-only env var (do not prefix with NEXT_PUBLIC).
  3. Implement API routes or server actions for each data access operation.
  4. Replace any direct client Supabase queries with calls to your backend endpoints.
  5. Add authorization checks in your backend layer (user/session validation).
  6. Log and rate-limit sensitive operations server-side.
  7. Verify that the browser bundle does not include service_role (search build output).

Implementation notes for Next.js backend-only Supabase access (what to change in your app)

Integrations aren’t just configuration β€” they usually require changing how your app talks to Supabase.

  • Replace direct client Supabase calls with backend endpoints for privileged operations.
  • Keep service_role and other secrets in server-only environment variables (never NEXT_PUBLIC_*).
  • If you return signed URLs, treat them as sensitive: short TTL, no logging, no persistence.
  • Add explicit authorization checks in server code (ownership, membership, tenancy).

Best-fit use cases

  • Serving user-specific data without exposing tables publicly
  • Generating signed URLs for private Storage objects
  • Executing privileged RPC functions safely
  • Running admin-only maintenance tasks

Workflow examples (real sequences you can implement)

Private file download (signed URL)

  1. Client requests a download token from your API route (includes object path).
  2. API route validates the user and checks they own the file.
  3. API route uses service_role to create a signed URL.
  4. Client redirects to the signed URL to download the file.

Privileged RPC execution

  1. Client submits parameters to your API route.
  2. API route validates the request and rate-limits abuse.
  3. API route executes the RPC with service_role.
  4. API route returns sanitized results to the client.

How to verify Next.js backend-only Supabase access (avoid false confidence)

Verification is the difference between β€œwe changed something” and β€œwe reduced exposure.”

  • Direct client access to the protected resource should fail (401/403).
  • Backend endpoint should succeed for authorized users.
  • Secrets (service_role) must not appear in browser bundle, logs, or client env vars.
  • Re-scan after implementation to confirm exposure is gone.

Common pitfalls when implementing Next.js backend-only Supabase access

  • Leaving one direct client code path in place (the exposure still exists).
  • Generating signed URLs client-side (turns private files into shareable public links).
  • Using long-lived signed URLs and accidentally logging or caching them.
  • Revoking grants before the backend endpoint is deployed (causes outages and rollback pressure).
  • Fixing dev but forgetting to replicate changes in staging/prod (drift).

Operational checklist (keep Next.js backend-only Supabase access safe over time)

  1. Re-run the verification checks after every migration that touches auth, policies, grants, functions, or storage.
  2. Audit grants and bucket settings periodically in production (don’t assume they stay correct).
  3. Monitor for denied access spikes after tightening permissions (often reveals missed app paths).
  4. Document the β€œwhy” and the verification steps so future changes don’t reintroduce exposure.

Rollback plan for Next.js backend-only Supabase access (without reopening exposure)

  1. If something breaks, roll back application behavior first (restore backend endpoint responses) rather than re‑granting direct client access.
  2. Use logs of denied direct access to identify missed call paths and patch them.
  3. If you must create a temporary exception, make it narrow and time-limited, then remove it once the backend path is fixed.

Regression tests for Next.js backend-only Supabase access (keep it secure after migrations)

Treat this integration like a contract: it should keep working after schema changes and refactors.

  • Keep a direct access test (REST/RPC/Storage) that should always fail for client credentials.
  • Add a checklist query or scan that flags new public grants, policies, buckets, or EXECUTE permissions.
  • Verify secrets are still server-only (no public env vars, no bundle leakage).
  • Re-run the workflow examples after major changes to confirm the user-facing flow still works securely.

Security invariants for Next.js backend-only Supabase access (non‑negotiables)

If you want this pattern to stay secure over time, a few statements must remain true no matter how your schema evolves.

Use these as invariants in reviews and release checklists:

  • The browser cannot reach privileged data surfaces directly (tables, Storage objects, privileged RPC).
  • Privileged operations go through server code that enforces authorization (and rate limits where abuse is plausible).
  • Secrets used for privileged access remain server-only and are never logged or persisted (especially service_role).
  • After migrations, the same direct-access tests still fail for client credentials; drift is caught early.
  • When you intentionally allow client access, the decision is documented and tested (not accidental).

If any invariant is violated, treat it as a bug: restore the boundary first, then iterate on features.

Minimal test cases for Next.js backend-only Supabase access (fast confidence)

You don’t need a huge test suite to keep this safe. You need a small set of high-signal checks that match attacker behavior.

  1. Attempt the direct client call (REST/RPC/Storage) for the protected operation and confirm it fails with a clear denial.
  2. Call the backend endpoint as an authorized user and confirm it succeeds with minimal data returned.
  3. Call the backend endpoint as an unauthorized user and confirm it fails without leaking sensitive metadata.
  4. If you use signed URLs: confirm they are short-lived and that re-use after expiry fails.
  5. Run one drift check after a migration that touches auth, policies, grants, buckets, or functions.

These checks validate the boundary, not just the UI behavior.

Quick recap: Next.js backend-only Supabase access

If you remember only one thing, remember the boundary: privileged operations must not be reachable directly from the browser.

  • Backend endpoints own privileged reads/writes/downloads.
  • Secrets stay server-only.
  • Direct client access tests fail after the fix.
  • Drift checks run after migrations.

Related glossary terms and templates

  • Glossary: Row Level Security (RLS) β†’ /glossary/row-level-security
  • Glossary: Public Table Exposure β†’ /glossary/public-table-exposure
  • Glossary: Over-permissive RLS Policies β†’ /glossary/over-permissive-rls-policies
  • Glossary: Supabase Storage Bucket Privacy β†’ /glossary/supabase-storage-bucket-privacy
  • Glossary: RPC EXECUTE Grants β†’ /glossary/rpc-execute-grants
  • Glossary: Signed URLs β†’ /glossary/signed-urls
  • Glossary: Service Role Key β†’ /glossary/service-role-key
  • Glossary: Forced RLS (FORCE ROW LEVEL SECURITY) β†’ /glossary/force-row-level-security
  • Glossary: Client Role Grants (anon/authenticated) β†’ /glossary/client-role-grants
  • Glossary: Ownership-bound RLS Policies β†’ /glossary/ownership-bound-rls-policies
  • Glossary: Storage Object Enumeration β†’ /glossary/storage-object-enumeration
  • Glossary: Public RPC Surface Area β†’ /glossary/public-rpc-surface-area
  • Glossary: NEXT_PUBLIC Secret Leakage β†’ /glossary/next-public-secret-leakage
  • Glossary: Broken Object Level Authorization (BOLA) β†’ /glossary/broken-object-level-authorization
  • Glossary: Insecure Direct Object References (IDOR) β†’ /glossary/insecure-direct-object-references
  • Glossary: Tenant ID Trusted from Client β†’ /glossary/tenant-id-trust-in-client
  • Glossary: Missing WITH CHECK Policy β†’ /glossary/missing-with-check-policy
  • Glossary: Broad SELECT for Authenticated Role β†’ /glossary/broad-authenticated-select
  • Glossary: Broad UPDATE for Authenticated Role β†’ /glossary/broad-authenticated-update
  • Glossary: Broad DELETE for Authenticated Role β†’ /glossary/broad-authenticated-delete
  • Glossary: Default Privilege Drift β†’ /glossary/default-privilege-drift
  • Glossary: Schema USAGE Granted to PUBLIC β†’ /glossary/schema-usage-granted-to-public
  • Glossary: Exposed Materialized Views β†’ /glossary/exposed-materialized-views
  • Glossary: Insecure SECURITY DEFINER Functions β†’ /glossary/insecure-security-definer-functions
  • Glossary: Mutable Function search_path β†’ /glossary/mutable-function-search-path
  • Glossary: Trigger Privilege Escalation β†’ /glossary/trigger-privilege-escalation
  • Glossary: Migration Owner Bypass of RLS β†’ /glossary/migration-owner-bypass
  • Glossary: Shadow Table Without RLS β†’ /glossary/shadow-table-without-rls
  • Glossary: Audit Log Table Publicly Readable β†’ /glossary/audit-log-public-readable
  • Glossary: Soft Delete Policy Bypass β†’ /glossary/soft-delete-policy-bypass
  • Glossary: UPSERT Policy Gap β†’ /glossary/upsert-policy-gap
  • Glossary: Cross-Schema Data Exposure β†’ /glossary/cross-schema-exposure
  • Glossary: Unrestricted View Definitions β†’ /glossary/unrestricted-view-definitions
  • Glossary: Leaked JWT Signing Secret β†’ /glossary/leaked-jwt-secret
  • Glossary: Stale JWT Claims β†’ /glossary/stale-jwt-claims
  • Glossary: Auth Role Claim Confusion β†’ /glossary/auth-role-claim-confusion
  • Glossary: Magic Link Open Redirect β†’ /glossary/magic-link-redirect-open-redirect
  • Glossary: Password Reset Token Leakage β†’ /glossary/password-reset-token-leakage
  • Glossary: OAuth Role Mapping Errors β†’ /glossary/oauth-role-mapping-errors
  • Glossary: SSO Group Sync Escalation β†’ /glossary/sso-group-sync-escalation
  • Glossary: Invite Flow Tenant Escalation β†’ /glossary/invite-flow-tenant-escalation
  • Glossary: Membership Race Condition β†’ /glossary/membership-race-condition
  • Glossary: Admin Panel Client-Only Auth β†’ /glossary/admin-panel-client-auth-only
  • Glossary: Database URL Leaked in Client β†’ /glossary/leaked-database-url-in-client
  • Glossary: Secrets in Repository History β†’ /glossary/secrets-in-repo-history
  • Glossary: Environment Parity Security Drift β†’ /glossary/env-parity-security-drift
  • Glossary: Staging Database Public Exposure β†’ /glossary/staging-db-public-exposure
  • Glossary: Test Data Left in Production β†’ /glossary/test-data-in-production
  • Glossary: Unencrypted Sensitive Columns β†’ /glossary/unencrypted-sensitive-columns
  • Glossary: Missing Key Rotation Policy β†’ /glossary/key-rotation-policy-missing
  • Glossary: Service Role Overreach in Cron Jobs β†’ /glossary/service-role-overreach-in-cron
  • Glossary: PII in Error Traces β†’ /glossary/pii-in-error-traces
  • Glossary: PII in Analytics Events β†’ /glossary/pii-in-analytics-events
  • Glossary: Missing Data Retention Policy β†’ /glossary/data-retention-policy-missing
  • Glossary: Incomplete GDPR Delete Flow β†’ /glossary/incomplete-gdpr-delete-flow
  • Glossary: No Exfiltration Anomaly Detection β†’ /glossary/no-anomaly-detection-exfiltration
  • Glossary: Weak Tenant Isolation Tests β†’ /glossary/weak-tenant-isolation-tests
  • Glossary: Policy Drift After Schema Rename β†’ /glossary/policy-drift-after-schema-rename
  • Glossary: Orphaned Policies After Table Rename β†’ /glossary/orphaned-policies-after-table-rename
  • Glossary: Generated Columns Leak Sensitive Data β†’ /glossary/generated-columns-sensitive-leak
  • Glossary: View Without Security Barrier β†’ /glossary/view-without-security-barrier
  • Glossary: Missing Column-Level Redaction β†’ /glossary/column-level-redaction-missing
  • Glossary: Unrestricted PostgREST Origin Proxy β†’ /glossary/unrestricted-postgrest-origin
  • Glossary: CORS Misconfiguration in Edge Functions β†’ /glossary/cors-misconfiguration-edge-functions
  • Glossary: Missing Webhook Signature Validation β†’ /glossary/missing-webhook-signature-validation
  • Glossary: Webhook Replay Attack Risk β†’ /glossary/webhook-replay-attack-risk
  • Glossary: Billing Webhook Idempotency Gap β†’ /glossary/billing-webhook-idempotency-gap
  • Glossary: Insecure Edge Function Authentication β†’ /glossary/insecure-edge-function-auth
  • Glossary: Edge Function Service Role Overuse β†’ /glossary/edge-function-service-role-overuse
  • Glossary: RPC Dynamic SQL Injection β†’ /glossary/rpc-dynamic-sql-injection
  • Glossary: RPC Missing Input Validation β†’ /glossary/rpc-missing-input-validation
  • Glossary: RPC Unbounded Result Sets β†’ /glossary/rpc-unbounded-result-set
  • Glossary: RPC Error Message Data Leak β†’ /glossary/rpc-error-data-leak
  • Glossary: Public Function Source Disclosure β†’ /glossary/public-function-source-disclosure
  • Glossary: Overloaded RPC Signature Miss β†’ /glossary/overloaded-rpc-signature-miss
  • Glossary: Unrestricted Admin Search Endpoint β†’ /glossary/unrestricted-admin-search-endpoint
  • Glossary: Bulk Export Endpoint Overexposure β†’ /glossary/bulk-export-endpoint-overexposure
  • Glossary: CSV Import Trusts Client Columns β†’ /glossary/csv-import-trusts-client-columns
  • Glossary: Row Ownership Transfer Without Recheck β†’ /glossary/row-ownership-transfer-without-recheck
  • Glossary: File Upload MIME Spoofing β†’ /glossary/file-upload-mime-spoofing
  • Glossary: Storage Upload Size Abuse β†’ /glossary/storage-upload-size-abuse
  • Glossary: Missing Malware Scanning on Uploads β†’ /glossary/missing-malware-scanning-uploads
  • Glossary: Public Backup Bucket Leak β†’ /glossary/public-backup-bucket-leak
  • Glossary: Expired Signed URL Caching Leak β†’ /glossary/expired-signed-url-caching-leak
  • Glossary: Object Path Predictability Risk β†’ /glossary/object-path-predictability-risk
  • Glossary: Storage Lifecycle Policy Missing β†’ /glossary/storage-lifecycle-policy-missing
  • Glossary: Bucket LIST Permission Too Broad β†’ /glossary/bucket-list-permission-too-broad
  • Glossary: Realtime Channel Authorization Gap β†’ /glossary/realtime-channel-authorization-gap
  • Glossary: Realtime Presence Data Leak β†’ /glossary/realtime-presence-data-leak
  • Glossary: Publication Includes Sensitive Tables β†’ /glossary/publication-includes-sensitive-tables
  • Glossary: Replication Role Overgrant β†’ /glossary/replication-role-overgrant
  • Glossary: Unbounded Pagination Enumeration β†’ /glossary/unbounded-pagination-enumeration
  • Glossary: Guessable Primary Keys β†’ /glossary/guessable-primary-keys
  • Glossary: Missing Rate Limits on Write Paths β†’ /glossary/rate-limit-missing-on-write-paths
  • Glossary: Missing CAPTCHA on Sensitive Flows β†’ /glossary/missing-captcha-sensitive-flows
  • Glossary: Insecure Feature Flag Disclosure β†’ /glossary/insecure-feature-flag-disclosure
  • Glossary: No Two-Person Review for Privilege Changes β†’ /glossary/no-two-person-review-privilege-changes
  • Glossary: Dependency Drift Misses Security Updates β†’ /glossary/dependency-drift-security-updates-missed
  • Glossary: Private Key Material in Logs β†’ /glossary/private-key-material-in-logs
  • Glossary: API Cache Leaks Private Data β†’ /glossary/api-cache-private-data-leak
  • Glossary: Data API Public Schema Exposure β†’ /glossary/data-api-public-schema-exposure
  • Glossary: Data API Custom Schema Misconfiguration β†’ /glossary/data-api-custom-schema-misconfiguration
  • Glossary: pg_graphql Extension Exposure β†’ /glossary/pg-graphql-extension-exposure
  • Glossary: Realtime Public Channel Mode β†’ /glossary/realtime-public-channel-mode
  • Glossary: Realtime Topic Policy Mismatch β†’ /glossary/realtime-topic-policy-mismatch
  • Glossary: Realtime Broadcast Overexposure β†’ /glossary/realtime-broadcast-overexposure
  • Glossary: Edge Function JWT Verification Gap β†’ /glossary/edge-function-jwt-verification-gap
  • Glossary: Service Role Authorization Header Override β†’ /glossary/service-role-authorization-header-override
  • Glossary: Publishable vs Secret Key Scope Confusion β†’ /glossary/publishable-secret-key-scope-confusion
  • Glossary: Missing Network Restrictions β†’ /glossary/missing-network-restrictions
  • Glossary: IPv6 Allowlist Gap β†’ /glossary/ipv6-allowlist-gap
  • Glossary: Default Function EXECUTE to PUBLIC β†’ /glossary/default-function-execute-to-public
  • Glossary: Untrusted Language Function Risk β†’ /glossary/untrusted-language-function-risk
  • Glossary: Storage Authenticated Endpoint Overtrust β†’ /glossary/storage-authenticated-endpoint-overtrust
  • Template: Lock down a public table (backend-only access) β†’ /templates/access-control/lock-down-public-table
  • Template: Make a bucket private + serve files with signed URLs β†’ /templates/storage-safety/make-bucket-private-signed-urls
  • Template: Lock down RPC: revoke EXECUTE from public roles β†’ /templates/rpc-functions/lock-down-rpc-execute
  • Template: Remove over-permissive RLS policies (adopt deny-by-default) β†’ /templates/access-control/remove-over-permissive-policies

FAQ

Is this pattern compatible with RLS?

Yes. Backend-only access works with any RLS posture. Many teams use RLS as a safety gate while keeping most access logic in server endpoints.

What’s the main failure mode of this integration?

Accidentally leaking secrets to the browser (service_role) or leaving a direct client path in place. Verify by testing direct access and auditing your build output.

How do I know this isn’t overkill?

If the resource is sensitive (user data, billing, exports, private files), backend-only access is usually the simplest path to predictable security.

Next step

If you want to confirm which surfaces in your project need this integration, run a scan in Mockly and follow the linked remediation steps.

Explore related pages

parent

Integrations

/integrations

sibling

Astro API Rate Protection Supabase security integration

/integrations/astro-api-rate-protection-supabase-security

sibling

Astro Auth Context Validation Supabase security integration

/integrations/astro-auth-context-validation-supabase-security

cross

Lock down a public table (backend-only access)

/templates/access-control/lock-down-public-table

cross

Make a bucket private + serve files with signed URLs

/templates/storage-safety/make-bucket-private-signed-urls

cross

Pricing

/pricing