# How to Let Users Upload and Download S3 Files Without Direct Access, Using Pre-Signed URLs

> A note on using pre-signed URLs so users can download from and upload to a private S3 bucket without ever seeing your AWS keys.

- Date: 2022-12-24
- Tags: aws, s3, typescript
- Also on dev.to: [https://dev.to/jimzandueta/how-to-allow-users-to-upload-and-download-data-from-aws-s3-without-direct-access-using-pre-signed-urls-h33](https://dev.to/jimzandueta/how-to-allow-users-to-upload-and-download-data-from-aws-s3-without-direct-access-using-pre-signed-urls-h33)

## Introduction

Amazon Simple Storage Service (S3) is AWS's file storage service. New S3 buckets are private by default, with Block Public Access turned on, and that is how they should stay. But your users still need to download and upload files. Giving them direct access to the bucket would be a security risk, and it would leave you little control over what they can do.

---

## Problem

Direct access to a bucket means handing your AWS access key and secret key to users. Anyone holding those keys can reach the bucket and everything in it. You also cannot limit them to one action, or to a short window of time.

---

## Solution

A pre-signed URL is a link your server signs with its own credentials. It lets a user reach one object in the bucket without ever seeing your keys. Each URL allows one action, such as a download or an upload, and it stops working after the time you set.


To generate pre-signed URLs in TypeScript, you can use the `getSignedUrl` method from the `aws-sdk` package. That is version 2, which AWS no longer supports. In version 3, `getSignedUrl` from `@aws-sdk/s3-request-presigner` does the same job. Here's an example of how to use `getSignedUrl` to generate a pre-signed URL for downloading an S3 object:

```ts
import * as AWS from "aws-sdk";

// Generate a pre-signed URL for an S3 object
async function generatePresignedUrl(
  bucket: string,
  key: string,
  expiration: number
): Promise<string> {
  const s3 = new AWS.S3()
  const params = {
    Bucket: bucket,
    Key: key,
    Expires: expiration,
  }
  const url = await s3.getSignedUrlPromise('getObject', params)
  return url
}

// Example usage
const bucket = 'my-s3-bucket'
const key = 'path/to/my/object.jpg'
const expiration = 60 // URL will expire in 60 seconds
const url = await generatePresignedUrl(bucket, key, expiration)
console.log(url) // Outputs a pre-signed URL that can be used to access the object
```

To generate a pre-signed URL for uploading an S3 object, you can use the `putObject` method in a similar way:

```ts
import * as AWS from "aws-sdk";
import * as express from "express";

// Configure the AWS SDK with your S3 bucket's credentials
AWS.config.update({
  accessKeyId: "ACCESS_KEY_ID",
  secretAccessKey: "SECRET_ACCESS_KEY",
});

const s3 = new AWS.S3();

// Create an Express.js router
const router = express.Router();

// Route for generating a pre-signed URL for uploading a file to S3
router.post("/upload", async (req, res) => {
  try {
    // Check if the file was included in the request
    if (!req.body.file) {
      throw new Error("No file provided");
    }

    // Get the file and other parameters from the request body
    const { file, bucket, key } = req.body;

    // Generate a pre-signed URL for uploading the file to S3
    const params = {
      Bucket: bucket,
      Key: key,
      ContentType: file.type,
      ACL: "private", // Set the ACL to "private"
    };
    const url = s3.getSignedUrl("putObject", params);

    // Send the pre-signed URL back to the client
    res.send({ url });
  } catch (error) {
    // Send an error response if something went wrong
    res.status(500).send({ error: error.message });
  }
});

// Export the router
export default router;
```

On the client, ask your server for a pre-signed URL, then upload the file straight to S3 with it:

```ts
import * as axios from "axios";

// Function to upload a file to S3 using a pre-signed URL
async function uploadFile(url: string, file: File): Promise<void> {
  // Set the headers for the request
  const headers = {
    "Content-Type": file.type,
  };

  // Make the PUT request to the pre-signed URL
  const response = await axios.put(url, file, { headers });

  // Check the status of the response
  console.log(response.status); // Outputs 200 if upload was successful
}

// Example usage
const file = new File(["Hello, world!"], "hello.txt", { type: "text/plain" });
const { data } = await axios.post("<server_url>/upload", {
  file: { type: file.type },
  bucket: "my-s3-bucket",
  key: "uploads/hello.txt",
});
await uploadFile(data.url, file);

```

---

## Conclusion

Pre-signed URLs let users reach data in an S3 bucket without direct access to it. Your server uses the AWS SDK to sign a URL for one object, and the user can use that URL until it expires.

---

### References

- [AWS documentation on pre-signed URLs](https://docs.aws.amazon.com/AmazonS3/latest/dev/PresignedUrlUploadObject.html)
- [AWS SDK for JavaScript documentation on getSignedUrl method](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property)
