On occasion, you might want to let someone be able to download a file that you have stored in your private S3 bucket. However, since you don't want to provide that person with perpetual access, you add an expiration and, so, the URL becomes invalid after a preset time-to-live (TTL). If this is all you require, then a presigned url will suffice.
If, instead, you are perhaps more paranoid and wish to further limit the access someone can have to your private bucket, then a different solution is required. In this post, I cover a few ways you can create a download link that can only be used once.
Suppose we provided the link https://made-up.lnk/download?token=abc
to a user so that he can use it to download some file,
say some-file.pdf
.
How do we prevent this link from being used multiple times?
The basic idea here is that we simply store the token
(e.g. "abc"), file location
, and a number of times used
field (initialized to 0
) in a database table.
Then, when the user does use the above link, our server would receive the request.
You then query the database table using the provided token and
In the overview above, we were only explaining how a single-use download link might work under the assumption that the single-use download link exists and have made its way to the intended user. To properly understand the whole process, we need to explain the creation phase as well. As such, let us provide a more concrete example starting from the creation phase.
In the creation phase, we have
https://made-up.lnk/get-link?file=file.pdf
).abc
) and create a new record in DynamoDB with the token and file location.https://made-up.lnk/download?token=abc
) to the API Gateway, which then forwards the token to the user.Note: Although this diagram looks identical to the one in the previous section, the numbering is different.
Then, when the user does want to use the download link you provided:
https://made-up.lnk/download?token=abc
).While the solution above does work, it only works for files smaller than a certain size, namely, 6MB
, the maximum size that a lambda function's return message can be.
If the your files are 6MB
or larger, then modifications and/or trade-offs have to be made.
If you truly require the download link be single-use and your files are potentially 6MB
or larger,
then the only options I can see are server-based, either an EC2 instance or cluster (e.g. EKS and ECS).
In such a case, the process is essentially the same.
The only difference is that requests are made directly to your server(s) rather than to API Gateway as a proxy to lambda.
If you wish to avoid servers or clusters, and wish to use only serverless technologies,
then I know no way of creating a single-use download link for files 6MB or larger
.
However, serverless remains possible if we qualify "single-use" with "approximately."
The solution in this more lenient scenario is to make use of S3 presigned URLs and redirects.
During the creation of an S3 presigned URL, it is possible to specify a TTL (time-to-live), a time interval after which the presigned URL becomes invalid and unusable. By setting an extremely short TTL (but one that's still larger than the latency involved in returning the presigned URL to the user) and coupling it with a redirect, it is possible to make the URL act as if it's single-use. (The hope is that the TTL is too short for the user to use a 2nd time before the link expires.)
When using lambda and API Gateway, to return a redirect, your lambda should return a JSON object in the following format.
{
"isBase64Encoded": false,
"statusCode": 302,
"headers": {
"Location": `${the presigned URL}`,
"Access-Control-Allow-Origin": "*"
},
"body": {}
}
The status code 302
is what tells your browser that it should "redirect" itself to a link specified in the headers under "Location."