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.
- Set buckets to private (public = false).
- Remove any public listing rules (bucket/object list access).
- Standardize object names to non-guessable identifiers (e.g., UUIDs).
- Implement an API route that validates user ownership of the object path.
- Generate a signed URL with a short expiration (minutes, not days).
- Return only the signed URL to the client (no keys).
- 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
- Client requests /api/invoices/{id}/download.
- Server checks invoice ownership.
- Server signs the storage object path for the invoice PDF.
- Client fetches via signed URL.
Expiring share link
- User clicks βShareβ.
- Server generates a signed URL valid for 10 minutes.
- User shares the URL.
- 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)
- Re-run the verification checks after every migration that touches auth, policies, grants, functions, or storage.
- Audit grants and bucket settings periodically in production (donβt assume they stay correct).
- Monitor for denied access spikes after tightening permissions (often reveals missed app paths).
- Document the βwhyβ and the verification steps so future changes donβt reintroduce exposure.
Rollback plan for Supabase Storage signed URLs (without reopening exposure)
- If something breaks, roll back application behavior first (restore backend endpoint responses) rather than reβgranting direct client access.
- Use logs of denied direct access to identify missed call paths and patch them.
- 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.
- Attempt the direct client call (REST/RPC/Storage) for the protected operation and confirm it fails with a clear denial.
- Call the backend endpoint as an authorized user and confirm it succeeds with minimal data returned.
- Call the backend endpoint as an unauthorized user and confirm it fails without leaking sensitive metadata.
- If you use signed URLs: confirm they are short-lived and that re-use after expiry fails.
- 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
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
Make a bucket private + serve files with signed URLs/templates/storage-safety/make-bucket-private-signed-urls