Mockly

integrations

Supabase Storage signed URLs

A secure file-delivery integration that keeps buckets private while enabling downloads through time-limited signed URLs generated server-side. This guide focuses on the practical steps, the workflows that matter in production, and the verification checks that prevent false confidence.

What β€œSupabase Storage signed URLs” gives you

A secure file-delivery integration that keeps buckets private while enabling downloads through time-limited signed URLs generated server-side.

Threat model for Supabase Storage signed URLs (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

  • Private Storage buckets (public = false)
  • A backend endpoint that can use service_role
  • A file ownership model (who is allowed to download what)

Setup steps for Supabase Storage signed URLs

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

  1. Set buckets to private (public = false).
  2. Remove any public listing rules (bucket/object list access).
  3. Standardize object names to non-guessable identifiers (e.g., UUIDs).
  4. Implement an API route that validates user ownership of the object path.
  5. Generate a signed URL with a short expiration (minutes, not days).
  6. Return only the signed URL to the client (no keys).
  7. Add monitoring for repeated signing attempts (abuse patterns).

Implementation notes for Supabase Storage signed URLs (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

  • User profile photo downloads
  • Private invoices and receipts
  • Customer documents
  • One-time share links with expiration

Workflow examples (real sequences you can implement)

Invoice download

  1. Client requests /api/invoices/{id}/download.
  2. Server checks invoice ownership.
  3. Server signs the storage object path for the invoice PDF.
  4. Client fetches via signed URL.

Expiring share link

  1. User clicks β€œShare”.
  2. Server generates a signed URL valid for 10 minutes.
  3. User shares the URL.
  4. After expiry, the link no longer works.

How to verify Supabase Storage signed URLs (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 Supabase Storage signed URLs

  • 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 Supabase Storage signed URLs 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 Supabase Storage signed URLs (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 Supabase Storage signed URLs (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 Supabase Storage signed URLs (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 Supabase Storage signed URLs (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: Supabase Storage signed URLs

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