How to Show Some Files from a Private S3 Bucket — While Keeping Others Hidden

When building apps that store files like images, PDFs, or videos using Amazon S3, the best practice is to keep your S3 bucket private. But what if you want to:

Hide sensitive files
Show only allowed files to specific users
Still serve public assets like profile pictures or logos?

Good news: you can do all this securely using pre-signed URLs and smart folder organization.

Let’s walk through how.

 Why You Shouldn’t Make Your S3 Bucket Public

While you can make your bucket public, it’s strongly discouraged:

  •  Anyone can access your files

  •  Search engines might index them

  • No access control

Instead, keep the bucket private and generate secure, temporary URLs when users need to access private files.

Basic Setup

Here’s a typical setup using AWS S3 with a backend and a frontend app:

  • S3 Bucket: private by default

  • Backend (e.g., Strapi or Node.js): handles file uploads and generates access links

  • Frontend (e.g., React Native app): fetches files only for authorized users

Organize Your S3 Bucket for Access Control

Use prefixes (folders) to separate file types and control access:

<pre>s3://my-app-bucket/

├── public/
│ └── avatars/
├── clients/
│ ├── 001/
│ │ └── journal.pdf
│ │ └── mood-chart.png
│ └── 002/
│ └── mood-chart.png
├── internal/
│ └── admin-reports/
</pre>

 

 Explanation:

  • /public/ → Used for public files like profile pictures.

  • /clients/001/ → Private files for user ID 001.

  • /internal/ → Admin-only files (e.g., internal reports).

This structure makes it easy to control access based on folder paths.

How to Show Private Files Securely: Pre-Signed URLs

A pre-signed URL is a secure link to a private S3 file that:

  • Expires after a short time

  • Can’t be reused or bookmarked

  • Is safe to send to the frontend

Backend Code: Generate Pre-Signed URL (Node.js)

 
const AWS = require('aws-sdk');
// Load AWS SDK
// Configure AWS S3 with your credentials
const s3 = new AWS.S3({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_ACCESS_SECRET,
region: process.env.AWS_REGION,
});

// Function to generate a pre-signed URL

const getPresignedUrl = (key) => {
const params = {
Bucket: process.env.AWS_BUCKET_NAME,
// Your S3 bucket name
Key: key,
// File path in the bucket
Expires: 300,
// Expiry time (300 seconds = 5 minutes)
};
return
s3.getSignedUrl('getObject', params);
};

 

Explanation:

  • getSignedUrl() creates a temporary access link.

  • The Expires value defines how long the link will work (e.g., 5 minutes).

  • This URL can be used to fetch the file securely without making the bucket public.

Add This to Your Strapi Controller

 
module.exports = {
async getFile(ctx) {
const { key } = ctx.query;
// Extract file key from query (e.g., clients/001/journal.pdf)
// Make sure the user has permission to access the file
if (!ctx.state.user || !key.includes(`clients/${ctx.state.user.id}/`))
{
return ctx.unauthorized('Access denied.');
}
// Generate secure, time-limited link
const url = getPresignedUrl(key);
ctx.send({ url });
},
};
 

Explanation:

  • ctx.state.user contains the authenticated user.

  • The if check ensures users can only request their own files.

  • If allowed, the signed URL is generated and returned.

React Native Frontend Code

 
// Call your backend to get the signed URL
const response = await fetch(`${API_URL}/api/get-file?key=clients/001/journal.pdf`);
const { url } = await response.json();
// Use the URL to open or display the file
Linking.openURL(url);
// Opens in browser

// OR
<Image source={{ uri: url }} />
// Displays image in the app

 

Explanation:

  • You request the file from your backend, not directly from S3.

  • You then use the returned URL temporarily — it expires automatically.

  • This keeps things secure while offering users access when needed.

For Public Files: Set Bucket Policy

You may have files (like logos or banners) that are okay to show publicly. In that case:

  1. Place them in a /public/ folder.

  2. Add this policy to your S3 bucket:

{
"Sid": "PublicReadGetObject",
"Effect": "Allow",

"Principal": "*",

"Action": "s3:GetObject",

"Resource": "arn:aws:s3:::my-app-bucket/public/*"
}
 

Explanation:

  • This allows anyone to access files under /public/

  • The rest of the bucket stays private

  • Great for assets like default avatars or app logos

Best Practices at a Glance

StrategyWhy It’s Good
Keep your bucket privatePrevent accidental leaks
Use folder prefixes (/public/, /clients/)Simplifies access control
Use pre-signed URLsSecure, short-lived file access
Validate user access before signingPrevents sharing someone else’s data
Set URL expiry (3–5 mins)Minimizes the risk of misuse

Final Thoughts

You don’t have to make your S3 bucket public or complex. Instead:

  • Use folder structure to segment access

  • Use pre-signed URLs for sensitive content

  • Use bucket policies for safe public content

This approach keeps your app secure, organized, and user-friendly — exactly what you want in a modern mobile or web app.

Related reads:

External resources:

Leave a Comment

Your email address will not be published. Required fields are marked *