Skip to content

Instantly share code, notes, and snippets.

@kyptin
Last active May 13, 2016 17:35
Show Gist options
  • Save kyptin/4898cd1068f09b893ddc to your computer and use it in GitHub Desktop.
Save kyptin/4898cd1068f09b893ddc to your computer and use it in GitHub Desktop.
S3 presigned URLs ready for image upload
(ns s3-presigned-url
"You can pre-sign an S3 path to allow users to access it without credentials.
This gives fine-grained access control with expiration to 'anonymous' (from
the perspective of S3) users. It's a convenient mechanism, if you can get it
to work.
There are some relatively well-trod paths for doing pre-signed S3 URLs, e.g.
when giving read-only access via an HTTP GET request. These paths have more
convenient and well-known mechanisms out there.
The code in this namespace is for a less common case: giving users
write access to a particular S3 location, e.g. if they upload their profile
picture. Unfortunately, to do this is a little more involved and requires
making greater use of the AWS Java SDK. I figured I'd publish this work in
case it was useful for others later.
For more information, here are some relevant resources:
- http://stackoverflow.com/a/23094964/202292
- http://www.bennadel.com/blog/2693-uploading-files-to-amazon-s3-using-pre-signed-query-string-authentication-urls.htm
Note: for this code to work, be sure to include the `clj-time` and
`com.amazonaws/aws-java-sdk-s3` libraries in your list of dependencies.
-Jeff Terrell, Ph.D.
January 20, 2015
jeff@altometrics.com
https://github.com/kyptin"
(:require
(clj-time
[core :refer [days from-now]]
[coerce :refer [to-date]]))
(:import
(com.amazonaws.services.s3.model GeneratePresignedUrlRequest)
(com.amazonaws.services.s3 AmazonS3Client)
(com.amazonaws HttpMethod)
(com.amazonaws.auth BasicAWSCredentials)
(com.amazonaws.regions Region Regions)))
(def aws-region (Regions/US_EAST_1))
(def access-key "your-access-key-here")
(def secret-key "your-secret-key-here")
(defn generate-presigned-url
"Create and return a S3 URL (as a string) that has been pre-authorized for a
PUT request with the given content-type. The URL expires in 1 day.
Ex:
(let [bucket \"my-avatars\"
s3-key \"The_Murder_Beard_2000.png\"
content-type \"image/png\"]
(presigned-upload-url bucket s3-key content-type))
Returns:
\"https://my-avatars.s3-us-east-1.amazonaws.com/The_Murder_Beard_2000?AWSAccessKeyId=AKAI...&Expires=1421...&Signature=XD2b...\""
[s3-bucket s3-key content-type]
(let [req (doto (GeneratePresignedUrlRequest. s3-bucket s3-key)
(.setContentType content-type)
(.setExpiration (-> 1 days from-now to-date))
(.setMethod (HttpMethod/PUT)))
creds (BasicAWSCredentials. access-key secret-key)
region (Regions/US_WEST_1)
client (doto (AmazonS3Client. creds)
(.setRegion aws-region))
url (.generatePresignedUrl client req)]
(str url)))
@kyptin
Copy link
Author

kyptin commented May 13, 2016

Note the tradeoffs in doing this approach (client self-uploading of images) versus uploading to a backend server that then stores the image in S3. This approach saves server resources and is perhaps quicker for the client, but it also gives no opportunity to the server to enforce size limits or know when (or even whether) an upload is complete (to generate thumbnails, for example). Some of these things could perhaps be done with AWS Lambda nowadays, but AFAIK the size limit is still not a capability of pre-signed URLs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment