new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Supabase · Storage · all subjects

storage/access-control

72 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

Storage access control uses row-level security

Supabase Storage manages file permissions through row-level security and custom policies to provide fine-grained access control.

InvalidJWT error code (401)

The InvalidJWT error with status code 401 means the provided JWT (JSON Web Token) is invalid. Resolution: The JWT might be expired or malformed. Provide a valid JWT.

InvalidSignature error code (403)

The InvalidSignature error with status code 403 means the signature provided does not match the calculated signature. Resolution: Check that you are providing the correct signature format. Refer to the AWS S3 SignatureV4 documentation for more information.

SignatureDoesNotMatch error code (403)

The SignatureDoesNotMatch error with status code 403 means the request signature does not match the calculated signature. Resolution: Verify that your credentials are correct, including access key ID, secret access key, and region. Refer to S3 Authentication documentation.

AccessDenied error code (403)

The AccessDenied error with status code 403 means access to the specified resource is denied. Resolution: Check that you have the correct RLS policy to allow access to this resource.

S3InvalidAccessKeyId error code (403)

The S3InvalidAccessKeyId error with status code 403 means the provided AWS access key ID is invalid. Resolution: Verify the AWS access key ID provided and ensure it is correct and active.

S3MaximumCredentialsLimit error code (400)

The S3MaximumCredentialsLimit error with status code 400 means the maximum number of credentials has been reached.

Legacy error format (404 not_found)

The legacy error format returns status code 404 with error code 'not_found', indicating that the resource is not found or you don't have the correct permission to access it. Resolution: Add an RLS policy to grant permission to the resource, ensure you include the user Authorization header, or verify the object exists.

Legacy error format (403 unauthorized)

The legacy error format returns status code 403 with error code 'unauthorized', indicating you don't have permission to action this request. Resolution: Add an RLS policy to grant permission or ensure you include the user Authorization header.

Storage RLS policy access denied requirements

When encountering AccessDenied (403) or unauthorized (legacy 403) errors, the solution is to add an RLS policy to grant permission to the resource. This applies to both new and legacy error formats.

Permissions required to copy objects

To copy objects, users need select permission on the source object and insert permission on the destination object. If the user also has update permission, the copy can be performed as an upsert, which will overwrite the destination object if it already exists.

Permissions required to move objects

To move objects, users need select and update permissions on the object.

RLS policy example - select own objects across buckets

Storage RLS policy to allow authenticated users to select their own objects across any buckets: create policy "User can select their own objects (in any buckets)" on storage.objects for select to authenticated using ( owner_id = (select auth.uid()) );

RLS policy example - insert in own folders across buckets

Storage RLS policy to allow authenticated users to insert in their own folders across any buckets: create policy "User can insert in their own folders (in any buckets)" on storage.objects for insert to authenticated with check ( (storage.foldername(name))[1] = (select auth.uid()) );

RLS policy example - update own objects across buckets

Storage RLS policy to allow authenticated users to update their own objects across any buckets: create policy "User can update their own objects (in any buckets)" on storage.objects for update to authenticated using ( owner_id = (select auth.uid()) );

RLS policy for delete permission on storage objects

To delete an object, the user must have the delete permission on the object. Example RLS policy: create policy "User can delete their own objects" on storage.objects for delete TO authenticated USING (owner = (select auth.uid()::text));

RLS delete policy for storage objects

Storage RLS policies support the delete operation to control which users can delete objects. The policy uses the USING clause to specify conditions, such as matching the owner field to the authenticated user's ID.

Add indexes to RLS policy columns

When creating RLS policies against the storage tables, you can add indexes to the interested columns to speed up the lookup and improve policy evaluation performance.

S3 access keys provide full bucket access and bypass RLS

S3 access keys provide full access to all S3 operations across all buckets and bypass RLS policies. They are meant to be used only on the server and must be kept secure.

Session token authentication uses JWT for limited RLS-scoped S3 access

You can authenticate to Supabase S3 with a user JWT token to provide limited access via RLS to all S3 operations. All S3 operations performed with the Session Token are scoped to the authenticated user, and RLS policies on the Storage Schema are respected.

Session token S3 credentials configuration

To authenticate with S3 using a Session Token, use: access_key_id = project_ref, secret_access_key = anonKey (publishableKey is not yet supported), session_token = valid jwt token.

RLS policies are respected with Session Token S3 operations

When using Session Token authentication with S3, all operations are scoped to the authenticated user and RLS policies on the Storage Schema are enforced.

S3 access key example with aws-sdk-js

Example S3 client configuration with S3 access keys: import { S3Client } from '@aws-sdk/client-s3'; const client = new S3Client({ forcePathStyle: true, region: 'project_region', endpoint: 'https://project_ref.storage.supabase.co/storage/v1/s3', credentials: { accessKeyId: 'your_access_key_id', secretAccessKey: 'your_secret_access_key', } })

Session token S3 client example with aws-sdk

Example S3 client configuration with Session Token authentication: import { S3Client } from '@aws-sdk/client-s3' const { data: { session }, } = await supabase.auth.getSession() const client = new S3Client({ forcePathStyle: true, region: 'project_region', endpoint: 'https://project_ref.storage.supabase.co/storage/v1/s3', credentials: { accessKeyId: 'project_ref', secretAccessKey: 'anonKey', sessionToken: session.access_token, }, })

Local development S3 configuration with access keys

On local development, use: region = 'local', endpoint = IP and port e.g. 'http://127.0.0.1:54321/storage/v1/s3'.

Local development S3 configuration with session token

On local development with session token, use: region = 'local', endpoint = IP and port e.g. 'http://127.0.0.1:54321/storage/v1/s3', accessKeyId = 'stub', secretAccessKey = ANON_KEY value from 'supabase status -o env'.

Self-hosted S3 session token uses STORAGE_TENANT_ID as accessKeyId

On self-hosted Supabase, the accessKeyId for session token authentication is the STORAGE_TENANT_ID environment variable defined in the .env file.

AWS credentials file configuration for Supabase Storage

AWS credentials can be configured in ~/.aws/credentials file with profile [supabase] containing: aws_access_key_id, aws_secret_access_key, endpoint_url = https://project_ref.storage.supabase.co/storage/v1/s3, region.

Add storage security rules via Dashboard

To add security rules via the Dashboard: Go to the Storage page in the Dashboard. Click Policies in the sidebar. Click Add Policies in the OBJECTS table to add policies for Files (you can also create policies for Buckets). Choose whether you want the policy to apply to downloads (SELECT), uploads (INSERT), updates (UPDATE), or deletes (DELETE). Give your policy a unique name. Write the policy using SQL.

Create storage policy with SQL

To create a storage policy using SQL: create policy "Public Access" on storage.objects for select using ( bucket_id = 'public' );

Example: Use custom role to access storage bucket

This example shows how to use a custom role to access storage buckets. First, create a JWT token with the manager role: const token = jwt.sign({ role: 'manager', sub: USER_ID }, JWT_SECRET, { expiresIn: '1h' }). Then create a StorageClient with the token: const storage = new StorageClient(PROJECT_URL, { authorization: `Bearer ${token}` }). Finally, use the client to interact with storage: await storage.from('teams').list().

Create a custom role for storage access control

Create a custom role using SQL with the syntax: create role 'manager';. After creating the role, grant it to the authenticator role and grant the anon role to it using: grant manager to authenticator; and grant anon to manager;. This allows the custom role to be used with Storage RLS policies.

Create a storage policy with custom role

Create a policy on storage.objects table that restricts access by custom role. Example: create policy "Manager can view all files in the bucket 'teams'" on storage.objects for select to manager using (bucket_id = 'teams');. This grants full read permissions to all objects in the specified bucket for the manager role.

Test storage policy with JWT token containing custom role

To test a storage policy with a custom role, create a JWT token with the role claim set to the custom role name. Use the jsonwebtoken library to sign the token with your JWT_SECRET from Supabase project settings under API, include the role in the claims (role: 'manager'), and set an expiration. Never expose JWT_SECRET in frontend code or version control. Use this token in an authorization header (Authorization: Bearer {token}) when calling the Storage API.

Storage uses same RLS system as other Supabase services

Supabase Storage uses the same role-based access control system as any other Supabase service, implemented through RLS (Row Level Security). This means custom roles and policies created for storage follow the same patterns as other services.

Can RLS be used for file access control in Supabase Storage

Yes, users can use RLS (Row-Level Security) policies for access control in Supabase Storage. RLS policies are applied to the storage schema tables to enforce access control.

Custom indexes recommended for storage schema performance

While the storage schema should not be modified, users are encouraged to add custom indexes to the storage schema as they can significantly improve the performance of the RLS policies created for enforcing access control.

storage.allow_only_operation() example policy for listing own objects

This example policy allows authenticated users to list only their own objects: create policy "Allow users to list their own objects" on storage.objects for select to authenticated using (storage.allow_only_operation('object.list') and owner_id = (select auth.uid()::text));

storage.allow_only_operation() helper function

The storage.allow_only_operation() function returns true when the current Storage API operation exactly matches the provided operation name. This is useful when a single SQL privilege such as SELECT is used by multiple Storage actions, but you want a policy to apply to only one of them, such as object listing versus object download. Storage normalizes operation names before comparing them, so both 'storage.object.list' and 'object.list' are treated as equivalent. Partial values such as 'object' do not match 'object.list'. If the current operation is not set or the input is empty, the function returns false.

storage.extension() example policy to restrict file uploads

This example policy restricts uploads to only PNG files inside a bucket called cats: create policy "Only allow PNG uploads" on storage.objects for insert to authenticated with check (bucket_id = 'cats' and storage.extension(name) = 'png');

storage.extension() helper function

The storage.extension() function returns the file extension. For example, if a file is stored in public/subfolder/avatar.png, the function returns 'png'. This function is useful in RLS policies to restrict uploads or downloads based on file type.

storage.foldername() helper function

The storage.foldername() function returns an array path containing all subfolders that a file belongs to. For example, if a file is stored in public/subfolder/avatar.png, the function returns ['public', 'subfolder']. This function is useful in RLS policies to match against folder paths.

storage.foldername() example policy for authenticated uploads

This example policy allows authenticated users to upload files to a folder called private: create policy "Allow authenticated uploads" on storage.objects for insert to authenticated with check ((storage.foldername(name))[1] = 'private');

storage.filename() example policy for public downloads

This example policy allows any user to download a file called favicon.ico: create policy "Allow public downloads" on storage.objects for select to public using (storage.filename(name) = 'favicon.ico');

storage.filename() helper function

The storage.filename() function returns the name of a file without any path information. For example, if a file is stored in public/subfolder/avatar.png, the function returns 'avatar.png'. This function is useful in RLS policies to match against specific filenames.

storage.allow_any_operation() example policy for multiple operations

This example policy allows authenticated users to list their own objects and read their own authenticated objects: create policy "Allow users to list and read their own authenticated objects" on storage.objects for select to authenticated using (storage.allow_any_operation(ARRAY['object.list', 'storage.object.get_authenticated']) and owner_id = (select auth.uid()::text));

RLS policies can restrict bucket access by filename, folder, file extension, and operation type

Supabase Storage provides SQL helper functions that enable writing RLS policies to control access based on file characteristics and operations. These include storage.filename() for matching specific filenames, storage.foldername() for matching folder paths, storage.extension() for matching file types, and storage.allow_only_operation() or storage.allow_any_operation() for matching specific Storage API operations. These helper functions can be used with standard RLS policy conditions including owner_id matching and role-based access control.

storage.allow_any_operation() helper function

The storage.allow_any_operation() function returns true when the current Storage API operation exactly matches any operation in the provided array. Use this when the same policy should apply to a small set of Storage actions.

Storage schema and helper functions for policies

Documentation for the storage schema is available in the storage schema design guide. Helper functions are available to simplify crafting policies. The RLS policies required for different operations are documented in the storage API reference.

Storage RLS policies are required by default

By default Supabase Storage does not allow any uploads to buckets without RLS policies. You must create RLS policies on the storage.objects table to selectively allow operations.

RLS policies work with Postgres Row Level Security

Supabase Storage is designed to work with Postgres Row Level Security (RLS). You can use RLS to create Security Access Policies that restrict access based on your business needs.

INSERT permission required for uploads

The only RLS policy required for uploading objects is to grant the INSERT permission to the storage.objects table.

Upsert requires INSERT, SELECT, and UPDATE permissions

To allow overwriting files using the upsert functionality, you must grant INSERT, SELECT, and UPDATE permissions on the storage.objects table.

Operation-aware helpers for different Storage actions

If you need different SELECT policies for different Storage actions such as listing objects versus reading authenticated objects, use the operation-aware helpers storage.allow_only_operation() and storage.allow_any_operation() documented in Storage Helper Functions.

Example: Allow authenticated users to upload to specific bucket

To allow authenticated users to upload assets to a specific bucket, create a policy: create policy "policy_name" on storage.objects for insert to authenticated with check ( bucket_id = 'my_bucket_id' );

Example: Allow uploads to specific folder

To allow authenticated users to upload files to a specific folder called 'private' inside a bucket, use: create policy "Allow authenticated uploads" on storage.objects for insert to authenticated with check ( bucket_id = 'my_bucket_id' and (storage.foldername(name))[1] = 'private' );

Public bucket access requires allow_any_operation filter

To allow anyone to access objects in a bucket via publishable key, use allow_any_operation() filter in the policy. Without this filter, users would be able to list bucket contents. This is not needed for buckets marked as public, which are already publicly accessible.

Example: Allow public access to objects with publishable key

To allow anyone to access objects in the 'avatars' bucket via publishable key: create policy "Avatar images are publicly accessible." on storage.objects for select using (bucket_id = 'avatars' and storage.allow_any_operation(array['object.get_authenticated_info', 'object.get_authenticated']));

Service key bypasses RLS policies

The service key in the Authorization header entirely bypasses RLS policies, granting unrestricted access to all Storage APIs. Service keys should only be used from trusted clients such as your own servers and should not be shared publicly.

Restrict bucket access by user ID using RLS policies

To restrict bucket access by user ID, create an RLS policy on storage.objects that compares the owner_id field with the authenticated user's ID (obtained from auth.uid()). The owner_id is derived from the 'sub' claim in the JWT token of the user who created the resource.

Give your agent this brain