SlideShare a Scribd company logo
1 of 69
Download to read offline
AWS User Group: Dublin Meetup / 2022-11-17
1
META_SLIDE!
fth.link/presign
loige 2
😎"I have a startup idea..."
loige
fth.link/presign
3
loige
meower
fth.link/presign
4
loige 5
loige 6
loige 7
loige
WTF?
8
Yes, I still have to implement the
profile picture upload feature... 😅
loige 9
$ ~ whoami
👋I'm Luciano ( 🍕🍝 )
Senior Architect @ fourTheorem (Dublin )
nodejsdp.link
📔Co-Author of Node.js Design Patterns 👉
Let's connect!
(blog)
(twitter)
(twitch)
(github)
loige.co
@loige
loige
lmammino 10
Always re-imagining
We are a pioneering technology consultancy
focused on AWS and serverless
| |
Accelerated Serverless AI as a Service Platform Modernisation
loige
✉Reach out to us at
😇We are always looking for talent:
hello@fourTheorem.com
fth.link/careers
11
We host a weekly podcast about AWS
awsbites.com
loige 12
🤔
"upload" feature
in a web app...
loige 13
What's an upload, really?
loige 14
OK, what protocol?
loige 15
loige
Structure of an HTTP request
POST /profilepic/upload HTTP/1.1
Host: api.meower.com
Content-Type: text/plain
Content-Length: 9
Some data
Method
Path Version
Headers
Body
16
loige
What if it's a binary (like a picture)?
PUT /profilepic/upload HTTP/1.1
Host: api.meower.com
Content-Type: image/jpeg
Content-Length: 2097852
����JFIFHH������"��
���Dl��FW�'6N�()H�'p��FD3 [...]
read 2097852
bytes
17
loige
Using Lambda
Lambda proxy integration*
* JSON Based protocol mapping to HTTP
(JSON Request / JSON Response)
18
{
"resource": "/profilepic/upload",
"path": "/profilepic/upload",
"httpMethod": "POST",
"headers": {
"Content-Type": "text/plain",
"Content-Length": "9",
"Host": "api.meower.com",
},
"body": "Some data"
}
1
2
3
4
5
6
7
8
9
10
11
"resource": "/profilepic/upload",
"path": "/profilepic/upload",
{
1
2
3
"httpMethod": "POST",
4
"headers": {
5
"Content-Type": "text/plain",
6
"Content-Length": "9",
7
"Host": "api.meower.com",
8
},
9
"body": "Some data"
10
}
11
"httpMethod": "POST",
{
1
"resource": "/profilepic/upload",
2
"path": "/profilepic/upload",
3
4
"headers": {
5
"Content-Type": "text/plain",
6
"Content-Length": "9",
7
"Host": "api.meower.com",
8
},
9
"body": "Some data"
10
}
11
"headers": {
"Content-Type": "text/plain",
"Content-Length": "9",
"Host": "api.meower.com",
},
{
1
"resource": "/profilepic/upload",
2
"path": "/profilepic/upload",
3
"httpMethod": "POST",
4
5
6
7
8
9
"body": "Some data"
10
}
11
"body": "Some data"
{
1
"resource": "/profilepic/upload",
2
"path": "/profilepic/upload",
3
"httpMethod": "POST",
4
"headers": {
5
"Content-Type": "text/plain",
6
"Content-Length": "9",
7
"Host": "api.meower.com",
8
},
9
10
}
11
{
"resource": "/profilepic/upload",
"path": "/profilepic/upload",
"httpMethod": "POST",
"headers": {
"Content-Type": "text/plain",
"Content-Length": "9",
"Host": "api.meower.com",
},
"body": "Some data"
}
1
2
3
4
5
6
7
8
9
10
11
loige
Lambda proxy integration (request)
19
{
"resource": "/profilepic/upload",
"path": "/profilepic/upload",
"httpMethod": "PUT",
"headers": {
"Content-Type": "image/jpeg",
"Content-Length": "2097852",
"Host": "api.meower.com",
},
"body": "????????"
}
1
2
3
4
5
6
7
8
9
10
11
"Content-Type": "image/jpeg",
"Content-Length": "2097852",
{
1
"resource": "/profilepic/upload",
2
"path": "/profilepic/upload",
3
"httpMethod": "PUT",
4
"headers": {
5
6
7
"Host": "api.meower.com",
8
},
9
"body": "????????"
10
}
11
"body": "????????"
{
1
"resource": "/profilepic/upload",
2
"path": "/profilepic/upload",
3
"httpMethod": "PUT",
4
"headers": {
5
"Content-Type": "image/jpeg",
6
"Content-Length": "2097852",
7
"Host": "api.meower.com",
8
},
9
10
}
11
{
"resource": "/profilepic/upload",
"path": "/profilepic/upload",
"httpMethod": "PUT",
"headers": {
"Content-Type": "image/jpeg",
"Content-Length": "2097852",
"Host": "api.meower.com",
},
"body": "????????"
}
1
2
3
4
5
6
7
8
9
10
11
loige
Lambda proxy integration (request - picture)
20
"isBase64Encoded": true,
"body": "/9j/4AAQSkZJRgABAQEASABIAAD/2w[...]"
{
1
"resource": "/profilepic/upload",
2
"path": "/profilepic/upload",
3
"httpMethod": "PUT",
4
"headers": {
5
"Content-Type": "image/jpeg",
6
"Content-Length": "2097852",
7
"Host": "api.meower.com",
8
},
9
10
11
}
12
{
"resource": "/profilepic/upload",
"path": "/profilepic/upload",
"httpMethod": "PUT",
"headers": {
"Content-Type": "image/jpeg",
"Content-Length": "2097852",
"Host": "api.meower.com",
},
"isBase64Encoded": true,
"body": "/9j/4AAQSkZJRgABAQEASABIAAD/2w[...]"
}
1
2
3
4
5
6
7
8
9
10
11
12
loige
Lambda proxy integration (request - picture)
21
loige
1. Parse request (JSON)
2. Decode body (Base64)
3. Validation / resize
4. ...
Lambda proxy integration request
/profilepic/upload
22
loige
1. Parse request (JSON)
2. Decode body (Base64)
3. Validation / resize
4. ...
Lambda proxy integration request
/profilepic/upload
😎 23
loige
Is this a good solution? 🙄
24
loige
Limitations...
API Gateway requests timeout: 30 sec
API Gateway payload: max 10 MB
Lambda timeout: max 15 mins
Lambda payload size: max 6 MB
Upload: 6 MB / 30 sec
25
loige
Is this a good solution? 🙄
... not really!
What about supporting big images or even videos?
26
loige
An alternative approach
✅ Long lived connection
✅ No size limit
27
loige
🤔SERVERS...
28
loige
S3 pre-signed URLs 😱
An S3 built-in feature
to authorize operations (download, upload, etc) on a
bucket / object
using time-limited authenticated URLs
29
loige
Using S3 pre-signed URLs for upload
* yeah, this can be a Lambda as well 😇
*
30
loige
Using S3 pre-signed URLs for upload
* yeah, this can be a Lambda as well 😇
*
31
loige
Using S3 pre-signed URLs for upload
* yeah, this can be a Lambda as well 😇
*
32
loige
Using S3 pre-signed URLs for upload
* yeah, this can be a Lambda as well 😇
*
33
loige
Using S3 pre-signed URLs for upload
✅
* yeah, this can be a Lambda as well 😇
*
34
loige
... and we can also use it for downloads! 🤩
35
loige
Using S3 pre-signed URLs for download
36
loige
Using S3 pre-signed URLs for download
37
loige
Using S3 pre-signed URLs for download
38
loige
Using S3 pre-signed URLs for download
39
loige
Using S3 pre-signed URLs for download
✅
40
loige
⚠VERY important details!
I lied to you a little in those diagrams... 🤥
It's a decent mental model, but it's not accurate 😅
The server never really talks with S3!
The server actually creates the signed URL by itself!
We will see later what's the security model around this idea!
41
loige
Is this a good solution? 🙄
✅It's a managed feature (a.k.a. no servers to manage)
✅We can upload and download arbitrarily big files with no practical limits*
✅Reasonably simple and secure
👍Seems good to me!
* objects in S3 are "limited" to 5TB (when using multi-part upload), 5 GB otherwise.
42
loige
Generating our first pre-signed URL
$ aws s3 presign 
s3://finance-department-bucket/2022/tax-certificate.pdf
https://s3.amazonaws.com/finance-department-bucket/2022/tax-
certificate.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-
Credential=AKIA3SGQVQG7FGA6KKA6%2F20221104%2Fus-east-
1%2Fs3%2Faws4_request&X-Amz-Date=20221104T140227Z&X-Amz-
Expires=3600&X-Amz-SignedHeaders=host&X-Amz-
Signature=b228dbec8c1008c80c162e1210e4503dceead1e4d4751b4d9787
314fd6da4d55
Whoever has this URL can
download the tax certificate!
43
loige
What's in a pre-signed URL
https://s3.amazonaws.com/finance-department-bucket/2022/tax-
certificate.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-
Credential=AKIA3SGQVQG7FGA6KKA6%2F20221104%2Fus-east-
1%2Fs3%2Faws4_request&X-Amz-Date=20221104T140227Z&X-Amz-
Expires=3600&X-Amz-SignedHeaders=host&X-Amz-
Signature=b228dbec8c1008c80c162e1210e4503dceead1e4d4751b4d9787
314fd6da4d55
44
loige
What's in a pre-signed URL
https://s3.amazonaws.com
/finance-department-bucket
/2022/tax-certificate.pdf
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=AKIA3SGQXQG7XXXYKKA6%2F20221104...
&X-Amz-Date=20221104T140227Z
&X-Amz-Expires=3600
&X-Amz-SignedHeaders=host
&X-Amz-Signature=b228dbec8c1008c80c162e1210e4503dceead1e4d4...
What if I change this to
/passwords.txt?
45
loige
👿46
loige
Pre-signed URLs validation
https://s3.amazonaws.com
/finance-department-bucket
/2022/tax-certificate.pdf
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=AKIA3SGQXQG7XXXYKKA6%2F20221104...
&X-Amz-Date=20221104T140227Z
&X-Amz-Expires=3600
&X-Amz-SignedHeaders=host
&X-Amz-Signature=b228dbec8c1008c80c162e1210e4503dceead1e4d4...
47
loige
🤓
Once a pre-signed URL is generated you
cannot edit it without breaking it
Photo by on
CHUTTERSNAP Unsplash
⚠Also note that you can use a pre-signed URL as many
times as you want until it expires
48
loige
🔐Permissions
Anyone with valid credentials can create a pre-signed URL (client side)
valid credentials = Role, User, or Security Token
The generated URL inherits the permissions of the credentials used to generate it
This means you can generate pre-signed URLs for things you don't have access to 😅
49
loige
$ aws s3 presign s3://ireland/i-love-you
https://ireland.s3.eu-west-1.amazonaws.com/i-love-you?X-Amz-
Algorithm=AWS4-HMAC-SHA256&X-Amz-
Credential=AKIA3ABCVQG7FGA6KKA6%2F20221115%2Feu-west-
1%2Fs3%2Faws4_request&X-Amz-Date=20221115T182036Z&X-Amz-
Expires=3600&X-Amz-SignedHeaders=host&X-Amz-
Signature=75749c92d94d03e411e7bbf64419f2af09301d1791b0df54c639
137c715f7888
😱
I swear I don't even know if this
bucket exists or who owns it!
50
loige
Pre-signed URLs are validated at request time
51
loige
Creating a pre-signed URL
programmatically
AWS SDK for JavaScript v3
52
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const s3Client = new S3Client()
const command = new GetObjectCommand({
Bucket: "some-bucket",
Key: "some-object"
})
const preSignedUrl = await getSignedUrl(s3Client, command, {
expiresIn: 3600
})
console.log(preSignedUrl)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
1
2
3
const s3Client = new S3Client()
4
5
const command = new GetObjectCommand({
6
Bucket: "some-bucket",
7
Key: "some-object"
8
})
9
10
const preSignedUrl = await getSignedUrl(s3Client, command, {
11
expiresIn: 3600
12
})
13
14
console.log(preSignedUrl)
15
const s3Client = new S3Client()
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
1
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
2
3
4
5
const command = new GetObjectCommand({
6
Bucket: "some-bucket",
7
Key: "some-object"
8
})
9
10
const preSignedUrl = await getSignedUrl(s3Client, command, {
11
expiresIn: 3600
12
})
13
14
console.log(preSignedUrl)
15
const command = new GetObjectCommand({
Bucket: "some-bucket",
Key: "some-object"
})
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
1
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
2
3
const s3Client = new S3Client()
4
5
6
7
8
9
10
const preSignedUrl = await getSignedUrl(s3Client, command, {
11
expiresIn: 3600
12
})
13
14
console.log(preSignedUrl)
15
const preSignedUrl = await getSignedUrl(s3Client, command, {
expiresIn: 3600
})
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
1
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
2
3
const s3Client = new S3Client()
4
5
const command = new GetObjectCommand({
6
Bucket: "some-bucket",
7
Key: "some-object"
8
})
9
10
11
12
13
14
console.log(preSignedUrl)
15 console.log(preSignedUrl)
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
1
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
2
3
const s3Client = new S3Client()
4
5
const command = new GetObjectCommand({
6
Bucket: "some-bucket",
7
Key: "some-object"
8
})
9
10
const preSignedUrl = await getSignedUrl(s3Client, command, {
11
expiresIn: 3600
12
})
13
14
15
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const s3Client = new S3Client()
const command = new GetObjectCommand({
Bucket: "some-bucket",
Key: "some-object"
})
const preSignedUrl = await getSignedUrl(s3Client, command, {
expiresIn: 3600
})
console.log(preSignedUrl)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
loige 53
loige
📦
Uploading a file
using pre-signed URLs
54
loige
2 Options: PUT & POST 🤨
55
loige
PUT Method
PUT <preSignedURL> HTTP/1.1
Host: <bucket>.s3.<region>.amazonaws.com
Content-Length: 2097852
����JFIFHH������"��
���Dl��FW�'6N�()H�'p��FD3 [...]
56
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const s3Client = new S3Client()
const command = new PutObjectCommand({
Bucket: "some-bucket",
Key: "some-object"
})
const preSignedUrl = await getSignedUrl(s3Client, command, {
expiresIn: 3600
})
console.log(preSignedUrl)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
loige
Only difference with the
previous example
57
loige
PUT Method - Limitations
You cannot set a limit on the upload size (max of 5 GB)! *
You can limit the Content-Type but you can specify exactly one
* Unless you know the exact size in advance
58
loige
POST method
It uses the multipart/form-data encoding (form upload)
Gives more freedom to the client to shape the request (Content-Type, file name, etc)
It uses a policy mechanism to define the "rules" of what can be uploaded
E.g. you can limit the supported mime types and provide a maximum file size
You can use it to upload from a web form and even configure the redirect URL
It's not really a URL but more of a pre-signed form!
59
POST / HTTP/1.1
Host: <bucket>.s3.amazonaws.com
Content-Type: multipart/form-data; boundary=9431149156168
Content-Length: 2097852
--9431149156168
Content-Disposition: form-data; name="key"
picture.jpg
--9431149156168
Content-Disposition: form-data; name="X-Amz-Credential"
AKIA3SGABCDXXXA6KKA6/20221115/eu-west-1/s3/aws4_request
--9431149156168
Content-Disposition: form-data; name="Policy"
eyJleHBpcmF0aW9uIjoiMjAyMi0xMS0xNVQyMDo0NjozN1oiLCJjb25kaXRpb25zIjpbWyJj[...]
--9431149156168
Content-Disposition: form-data; name="X-Amz-Signature"
2c1da0001dfec7caea1c9fb80c7bc8847f515a9e4483d2942464f48d2f827de7
--9431149156168
Content-Disposition: form-data; name="file"; filename="MyFilename.jpg"
Content-Type: image/jpeg
����JFIFHH������"��
���Dl��FW�'6N�()H�'p��FD3[...]
--9431149156168--
loige
60
loige
POST method Policy
A JSON object (Base64 encoded) that defines the upload rules (conditions) and the
expiration date
This is what gets signed: you cannot alter the policy without breaking the signature
{
"expiration": "2022-11-15T20:46:37Z",
"conditions": [
["content-length-range", 0, 5242880],
["starts-with", "$Content-Type", "image/"],
{"bucket": "somebucket"},
{"X-Amz-Algorithm": "AWS4-HMAC-SHA256"},
{"X-Amz-Credential": "AKIA3SGABCDXXXA6KKA6/20221115/eu-west-1/s3/aws4_request"},
{"X-Amz-Date": "20221115T194637Z"},
{"key": "picture.jpg"}
]
}
61
import { S3Client } from '@aws-sdk/client-s3'
import { createPresignedPost } from '@aws-sdk/s3-presigned-post'
const { BUCKET_NAME, OBJECT_KEY } = process.env
const s3Client = new S3Client()
const { url, fields } = await createPresignedPost(s3Client, {
Bucket: 'somebucket',
Key: 'someobject',
Conditions: [
['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max
],
Fields: {
success_action_status: '201',
'Content-Type': 'image/png'
},
Expires: 3600
})
console.log({ url, fields })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { createPresignedPost } from '@aws-sdk/s3-presigned-post'
import { S3Client } from '@aws-sdk/client-s3'
1
2
3
const { BUCKET_NAME, OBJECT_KEY } = process.env
4
const s3Client = new S3Client()
5
6
const { url, fields } = await createPresignedPost(s3Client, {
7
Bucket: 'somebucket',
8
Key: 'someobject',
9
Conditions: [
10
['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max
11
],
12
Fields: {
13
success_action_status: '201',
14
'Content-Type': 'image/png'
15
},
16
Expires: 3600
17
})
18
19
console.log({ url, fields })
20
const { url, fields } = await createPresignedPost(s3Client, {
})
import { S3Client } from '@aws-sdk/client-s3'
1
import { createPresignedPost } from '@aws-sdk/s3-presigned-post'
2
3
const { BUCKET_NAME, OBJECT_KEY } = process.env
4
const s3Client = new S3Client()
5
6
7
Bucket: 'somebucket',
8
Key: 'someobject',
9
Conditions: [
10
['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max
11
],
12
Fields: {
13
success_action_status: '201',
14
'Content-Type': 'image/png'
15
},
16
Expires: 3600
17
18
19
console.log({ url, fields })
20
Bucket: 'somebucket',
import { S3Client } from '@aws-sdk/client-s3'
1
import { createPresignedPost } from '@aws-sdk/s3-presigned-post'
2
3
const { BUCKET_NAME, OBJECT_KEY } = process.env
4
const s3Client = new S3Client()
5
6
const { url, fields } = await createPresignedPost(s3Client, {
7
8
Key: 'someobject',
9
Conditions: [
10
['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max
11
],
12
Fields: {
13
success_action_status: '201',
14
'Content-Type': 'image/png'
15
},
16
Expires: 3600
17
})
18
19
console.log({ url, fields })
20
Key: 'someobject',
import { S3Client } from '@aws-sdk/client-s3'
1
import { createPresignedPost } from '@aws-sdk/s3-presigned-post'
2
3
const { BUCKET_NAME, OBJECT_KEY } = process.env
4
const s3Client = new S3Client()
5
6
const { url, fields } = await createPresignedPost(s3Client, {
7
Bucket: 'somebucket',
8
9
Conditions: [
10
['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max
11
],
12
Fields: {
13
success_action_status: '201',
14
'Content-Type': 'image/png'
15
},
16
Expires: 3600
17
})
18
19
console.log({ url, fields })
20
Conditions: [
['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max
],
import { S3Client } from '@aws-sdk/client-s3'
1
import { createPresignedPost } from '@aws-sdk/s3-presigned-post'
2
3
const { BUCKET_NAME, OBJECT_KEY } = process.env
4
const s3Client = new S3Client()
5
6
const { url, fields } = await createPresignedPost(s3Client, {
7
Bucket: 'somebucket',
8
Key: 'someobject',
9
10
11
12
Fields: {
13
success_action_status: '201',
14
'Content-Type': 'image/png'
15
},
16
Expires: 3600
17
})
18
19
console.log({ url, fields })
20
Fields: {
success_action_status: '201',
'Content-Type': 'image/png'
},
import { S3Client } from '@aws-sdk/client-s3'
1
import { createPresignedPost } from '@aws-sdk/s3-presigned-post'
2
3
const { BUCKET_NAME, OBJECT_KEY } = process.env
4
const s3Client = new S3Client()
5
6
const { url, fields } = await createPresignedPost(s3Client, {
7
Bucket: 'somebucket',
8
Key: 'someobject',
9
Conditions: [
10
['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max
11
],
12
13
14
15
16
Expires: 3600
17
})
18
19
console.log({ url, fields })
20
Expires: 3600
import { S3Client } from '@aws-sdk/client-s3'
1
import { createPresignedPost } from '@aws-sdk/s3-presigned-post'
2
3
const { BUCKET_NAME, OBJECT_KEY } = process.env
4
const s3Client = new S3Client()
5
6
const { url, fields } = await createPresignedPost(s3Client, {
7
Bucket: 'somebucket',
8
Key: 'someobject',
9
Conditions: [
10
['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max
11
],
12
Fields: {
13
success_action_status: '201',
14
'Content-Type': 'image/png'
15
},
16
17
})
18
19
console.log({ url, fields })
20 console.log({ url, fields })
import { S3Client } from '@aws-sdk/client-s3'
1
import { createPresignedPost } from '@aws-sdk/s3-presigned-post'
2
3
const { BUCKET_NAME, OBJECT_KEY } = process.env
4
const s3Client = new S3Client()
5
6
const { url, fields } = await createPresignedPost(s3Client, {
7
Bucket: 'somebucket',
8
Key: 'someobject',
9
Conditions: [
10
['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max
11
],
12
Fields: {
13
success_action_status: '201',
14
'Content-Type': 'image/png'
15
},
16
Expires: 3600
17
})
18
19
20
import { S3Client } from '@aws-sdk/client-s3'
import { createPresignedPost } from '@aws-sdk/s3-presigned-post'
const { BUCKET_NAME, OBJECT_KEY } = process.env
const s3Client = new S3Client()
const { url, fields } = await createPresignedPost(s3Client, {
Bucket: 'somebucket',
Key: 'someobject',
Conditions: [
['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max
],
Fields: {
success_action_status: '201',
'Content-Type': 'image/png'
},
Expires: 3600
})
console.log({ url, fields })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
loige 62
// you can use `url` and `fields` to generate an HTML form
const code = `<h1>Upload an image to S3</h1>
<form action="${url}" method="post" enctype="multipart/form-data">
${Object.entries(fields).map(([key, value]) => {
return `<input type="hidden" name="${key}" value="${value.replace(/"/g, '&quot;')}">`
}).join('n')}
<div><input type="file" name="file" accept="image/png"></div>
<div><input type="submit" value="Upload"></div>
</form>`
1
2
3
4
5
6
7
8
9
10
loige 63
loige
Limitations and quirks
It supports only 1 file (cannot upload multiple files in one go)
The file field must be the last entry in the form
(S3 will ignore every other field after the file)
From the browser (AJAX) you need to enable CORS on the bucket
64
loige
Should I use PUT or POST? 🧐
PUT is simpler but definitely more limited
POST is slightly more complicated (and less adopted) but it's more flexible
You should probably put some time into learning POST and use that!
65
loige
Pre-signed URLs for other operations
S3 pre-signed URLs are not limited to GET, PUT or POST operations
You can literally create pre-signed URLs for any command
(DeleteObject, ListBuckets, MultiPartUpload, etc...)
66
loige
Do you need moar examples? 😼
github.com/lmammino/s3-presigned-urls-examples
67
loige
... In summary
S3 pre-signed URLs are a great way to authorise operations on S3
They are generally used to implement upload/download features
The signature is created client-side so you can sign anything. Access is validated at
request time
This is not the only solution, you can also use the JavaScript SDK from the frontend
and get limited credentials from Cognito (Amplify makes that process simpler)
For upload you can use PUT and POST, but POST is much more flexible
💬PS: Meower.com doesn't really exist... but... do you want to invest?! It's a great
idea, trust me!
68
Cover photo by on
Kelly Sikkema Unsplash
fourtheorem.com
THANKS! 🙌
fth.link/presign
loige
It's a wrap!
69

More Related Content

What's hot

인프런 - 스타트업 인프랩 시작 사례
인프런 - 스타트업 인프랩 시작 사례인프런 - 스타트업 인프랩 시작 사례
인프런 - 스타트업 인프랩 시작 사례Hyung Lee
 
게임사를 위한 Amazon GameLift 세션 - 이정훈, AWS 솔루션즈 아키텍트
게임사를 위한 Amazon GameLift 세션 - 이정훈, AWS 솔루션즈 아키텍트게임사를 위한 Amazon GameLift 세션 - 이정훈, AWS 솔루션즈 아키텍트
게임사를 위한 Amazon GameLift 세션 - 이정훈, AWS 솔루션즈 아키텍트Amazon Web Services Korea
 
【Unite Tokyo 2018】その最適化、本当に最適ですか!? ~正しい最適化を行うためのテクニック~
【Unite Tokyo 2018】その最適化、本当に最適ですか!? ~正しい最適化を行うためのテクニック~【Unite Tokyo 2018】その最適化、本当に最適ですか!? ~正しい最適化を行うためのテクニック~
【Unite Tokyo 2018】その最適化、本当に最適ですか!? ~正しい最適化を行うためのテクニック~Unity Technologies Japan K.K.
 
마비노기듀얼 이야기-넥슨 김동건
마비노기듀얼 이야기-넥슨 김동건마비노기듀얼 이야기-넥슨 김동건
마비노기듀얼 이야기-넥슨 김동건강 민우
 
쿠키런 1년, 서버개발 분투기
쿠키런 1년, 서버개발 분투기쿠키런 1년, 서버개발 분투기
쿠키런 1년, 서버개발 분투기Brian Hong
 
1인개발자가되기전알아야할것들
1인개발자가되기전알아야할것들1인개발자가되기전알아야할것들
1인개발자가되기전알아야할것들Jinsub Jung
 
「宴」実装時に得られたUnityプログラムノウハウ
「宴」実装時に得られたUnityプログラムノウハウ「宴」実装時に得られたUnityプログラムノウハウ
「宴」実装時に得られたUnityプログラムノウハウRyohei Tokimura
 
UI아트 작업자를 위한 언리얼엔진4 UMG #1
UI아트 작업자를 위한 언리얼엔진4 UMG #1UI아트 작업자를 위한 언리얼엔진4 UMG #1
UI아트 작업자를 위한 언리얼엔진4 UMG #1Hong-Gi Joe
 
C++20에서 리플렉션 기능 구현
C++20에서 리플렉션 기능 구현C++20에서 리플렉션 기능 구현
C++20에서 리플렉션 기능 구현Bongseok Cho
 
AWS IoTにおけるデバイスへの認証情報のプロビジョニング
AWS IoTにおけるデバイスへの認証情報のプロビジョニングAWS IoTにおけるデバイスへの認証情報のプロビジョニング
AWS IoTにおけるデバイスへの認証情報のプロビジョニングAmazon Web Services Japan
 
[NDC18] 만들고 붓고 부수고 - 〈야생의 땅: 듀랑고〉 서버 관리 배포 이야기
[NDC18] 만들고 붓고 부수고 - 〈야생의 땅: 듀랑고〉 서버 관리 배포 이야기[NDC18] 만들고 붓고 부수고 - 〈야생의 땅: 듀랑고〉 서버 관리 배포 이야기
[NDC18] 만들고 붓고 부수고 - 〈야생의 땅: 듀랑고〉 서버 관리 배포 이야기Chanwoong Kim
 
GFX Part 7 - Introduction to Rendering Targets in OpenGL ES
GFX Part 7 - Introduction to Rendering Targets in OpenGL ESGFX Part 7 - Introduction to Rendering Targets in OpenGL ES
GFX Part 7 - Introduction to Rendering Targets in OpenGL ESPrabindh Sundareson
 
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)Heungsub Lee
 
엄재민 Nhn과제 신규 게임 컨셉 제안서
엄재민 Nhn과제 신규 게임 컨셉 제안서엄재민 Nhn과제 신규 게임 컨셉 제안서
엄재민 Nhn과제 신규 게임 컨셉 제안서재민 엄
 
[야생의 땅: 듀랑고] 서버 아키텍처 Vol. 2 (자막)
[야생의 땅: 듀랑고] 서버 아키텍처 Vol. 2 (자막)[야생의 땅: 듀랑고] 서버 아키텍처 Vol. 2 (자막)
[야생의 땅: 듀랑고] 서버 아키텍처 Vol. 2 (자막)Heungsub Lee
 
Spring bootでweb バリデート編
Spring bootでweb バリデート編Spring bootでweb バリデート編
Spring bootでweb バリデート編なべ
 
Git을 조금 더 알아보자!
Git을 조금 더 알아보자!Git을 조금 더 알아보자!
Git을 조금 더 알아보자!Young Kim
 
Amazon SageMaker Studio Lab紹介
Amazon SageMaker Studio Lab紹介Amazon SageMaker Studio Lab紹介
Amazon SageMaker Studio Lab紹介西岡 賢一郎
 
AWS IoT 기반 사물 인터넷 아키텍처 구현하기 - AWS Summit Seoul 2017
AWS IoT 기반 사물 인터넷 아키텍처 구현하기 - AWS Summit Seoul 2017AWS IoT 기반 사물 인터넷 아키텍처 구현하기 - AWS Summit Seoul 2017
AWS IoT 기반 사물 인터넷 아키텍처 구현하기 - AWS Summit Seoul 2017Amazon Web Services Korea
 

What's hot (20)

인프런 - 스타트업 인프랩 시작 사례
인프런 - 스타트업 인프랩 시작 사례인프런 - 스타트업 인프랩 시작 사례
인프런 - 스타트업 인프랩 시작 사례
 
게임사를 위한 Amazon GameLift 세션 - 이정훈, AWS 솔루션즈 아키텍트
게임사를 위한 Amazon GameLift 세션 - 이정훈, AWS 솔루션즈 아키텍트게임사를 위한 Amazon GameLift 세션 - 이정훈, AWS 솔루션즈 아키텍트
게임사를 위한 Amazon GameLift 세션 - 이정훈, AWS 솔루션즈 아키텍트
 
【Unite Tokyo 2018】その最適化、本当に最適ですか!? ~正しい最適化を行うためのテクニック~
【Unite Tokyo 2018】その最適化、本当に最適ですか!? ~正しい最適化を行うためのテクニック~【Unite Tokyo 2018】その最適化、本当に最適ですか!? ~正しい最適化を行うためのテクニック~
【Unite Tokyo 2018】その最適化、本当に最適ですか!? ~正しい最適化を行うためのテクニック~
 
2015/04/01 AWS Blackbelt EC2
2015/04/01 AWS Blackbelt EC22015/04/01 AWS Blackbelt EC2
2015/04/01 AWS Blackbelt EC2
 
마비노기듀얼 이야기-넥슨 김동건
마비노기듀얼 이야기-넥슨 김동건마비노기듀얼 이야기-넥슨 김동건
마비노기듀얼 이야기-넥슨 김동건
 
쿠키런 1년, 서버개발 분투기
쿠키런 1년, 서버개발 분투기쿠키런 1년, 서버개발 분투기
쿠키런 1년, 서버개발 분투기
 
1인개발자가되기전알아야할것들
1인개발자가되기전알아야할것들1인개발자가되기전알아야할것들
1인개발자가되기전알아야할것들
 
「宴」実装時に得られたUnityプログラムノウハウ
「宴」実装時に得られたUnityプログラムノウハウ「宴」実装時に得られたUnityプログラムノウハウ
「宴」実装時に得られたUnityプログラムノウハウ
 
UI아트 작업자를 위한 언리얼엔진4 UMG #1
UI아트 작업자를 위한 언리얼엔진4 UMG #1UI아트 작업자를 위한 언리얼엔진4 UMG #1
UI아트 작업자를 위한 언리얼엔진4 UMG #1
 
C++20에서 리플렉션 기능 구현
C++20에서 리플렉션 기능 구현C++20에서 리플렉션 기능 구현
C++20에서 리플렉션 기능 구현
 
AWS IoTにおけるデバイスへの認証情報のプロビジョニング
AWS IoTにおけるデバイスへの認証情報のプロビジョニングAWS IoTにおけるデバイスへの認証情報のプロビジョニング
AWS IoTにおけるデバイスへの認証情報のプロビジョニング
 
[NDC18] 만들고 붓고 부수고 - 〈야생의 땅: 듀랑고〉 서버 관리 배포 이야기
[NDC18] 만들고 붓고 부수고 - 〈야생의 땅: 듀랑고〉 서버 관리 배포 이야기[NDC18] 만들고 붓고 부수고 - 〈야생의 땅: 듀랑고〉 서버 관리 배포 이야기
[NDC18] 만들고 붓고 부수고 - 〈야생의 땅: 듀랑고〉 서버 관리 배포 이야기
 
GFX Part 7 - Introduction to Rendering Targets in OpenGL ES
GFX Part 7 - Introduction to Rendering Targets in OpenGL ESGFX Part 7 - Introduction to Rendering Targets in OpenGL ES
GFX Part 7 - Introduction to Rendering Targets in OpenGL ES
 
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
 
엄재민 Nhn과제 신규 게임 컨셉 제안서
엄재민 Nhn과제 신규 게임 컨셉 제안서엄재민 Nhn과제 신규 게임 컨셉 제안서
엄재민 Nhn과제 신규 게임 컨셉 제안서
 
[야생의 땅: 듀랑고] 서버 아키텍처 Vol. 2 (자막)
[야생의 땅: 듀랑고] 서버 아키텍처 Vol. 2 (자막)[야생의 땅: 듀랑고] 서버 아키텍처 Vol. 2 (자막)
[야생의 땅: 듀랑고] 서버 아키텍처 Vol. 2 (자막)
 
Spring bootでweb バリデート編
Spring bootでweb バリデート編Spring bootでweb バリデート編
Spring bootでweb バリデート編
 
Git을 조금 더 알아보자!
Git을 조금 더 알아보자!Git을 조금 더 알아보자!
Git을 조금 더 알아보자!
 
Amazon SageMaker Studio Lab紹介
Amazon SageMaker Studio Lab紹介Amazon SageMaker Studio Lab紹介
Amazon SageMaker Studio Lab紹介
 
AWS IoT 기반 사물 인터넷 아키텍처 구현하기 - AWS Summit Seoul 2017
AWS IoT 기반 사물 인터넷 아키텍처 구현하기 - AWS Summit Seoul 2017AWS IoT 기반 사물 인터넷 아키텍처 구현하기 - AWS Summit Seoul 2017
AWS IoT 기반 사물 인터넷 아키텍처 구현하기 - AWS Summit Seoul 2017
 

Similar to Everything I know about S3 pre-signed URLs

Building a dev pipeline using GitHub Actions, Node.js, and AWS ECS Fargate
Building a dev pipeline using GitHub Actions, Node.js, and AWS ECS FargateBuilding a dev pipeline using GitHub Actions, Node.js, and AWS ECS Fargate
Building a dev pipeline using GitHub Actions, Node.js, and AWS ECS Fargatedatree
 
Continuous Deployment @ AWS Re:Invent
Continuous Deployment @ AWS Re:InventContinuous Deployment @ AWS Re:Invent
Continuous Deployment @ AWS Re:InventJohn Schneider
 
Continuous Integration and Deployment Best Practices on AWS (ARC307) | AWS re...
Continuous Integration and Deployment Best Practices on AWS (ARC307) | AWS re...Continuous Integration and Deployment Best Practices on AWS (ARC307) | AWS re...
Continuous Integration and Deployment Best Practices on AWS (ARC307) | AWS re...Amazon Web Services
 
There is something about serverless
There is something about serverlessThere is something about serverless
There is something about serverlessgjdevos
 
Serverless in production, an experience report (FullStack 2018)
Serverless in production, an experience report (FullStack 2018)Serverless in production, an experience report (FullStack 2018)
Serverless in production, an experience report (FullStack 2018)Yan Cui
 
DevSecCon Singapore 2018 - Remove developers’ shameful secrets or simply rem...
DevSecCon Singapore 2018 -  Remove developers’ shameful secrets or simply rem...DevSecCon Singapore 2018 -  Remove developers’ shameful secrets or simply rem...
DevSecCon Singapore 2018 - Remove developers’ shameful secrets or simply rem...DevSecCon
 
Serverless for High Performance Computing
Serverless for High Performance ComputingServerless for High Performance Computing
Serverless for High Performance ComputingLuciano Mammino
 
SnapyX
SnapyXSnapyX
SnapyXekino
 
Adopt openjdk and how it impacts you in 2020
Adopt openjdk and how it impacts you in 2020Adopt openjdk and how it impacts you in 2020
Adopt openjdk and how it impacts you in 2020George Adams
 
Gojko's 5 rules for super responsive Serverless applications
Gojko's 5 rules for super responsive Serverless applicationsGojko's 5 rules for super responsive Serverless applications
Gojko's 5 rules for super responsive Serverless applicationsDaniel Zivkovic
 
Serverless in Production, an experience report (AWS UG South Wales)
Serverless in Production, an experience report (AWS UG South Wales)Serverless in Production, an experience report (AWS UG South Wales)
Serverless in Production, an experience report (AWS UG South Wales)Yan Cui
 
DevSecCon SG 2018 Fabian Presentation Slides
DevSecCon SG 2018 Fabian Presentation SlidesDevSecCon SG 2018 Fabian Presentation Slides
DevSecCon SG 2018 Fabian Presentation SlidesFab L
 
Lessons learned from a large scale OSGi web app
Lessons learned from a large scale OSGi web appLessons learned from a large scale OSGi web app
Lessons learned from a large scale OSGi web appPaul Bakker
 
IDEALIZE 2023 - NodeJS & Firebase Session
IDEALIZE 2023 - NodeJS & Firebase SessionIDEALIZE 2023 - NodeJS & Firebase Session
IDEALIZE 2023 - NodeJS & Firebase SessionBrion Mario
 
Serverless for High Performance Computing
Serverless for High Performance ComputingServerless for High Performance Computing
Serverless for High Performance ComputingLuciano Mammino
 
Serverless in production, an experience report
Serverless in production, an experience reportServerless in production, an experience report
Serverless in production, an experience reportYan Cui
 

Similar to Everything I know about S3 pre-signed URLs (20)

Building a dev pipeline using GitHub Actions, Node.js, and AWS ECS Fargate
Building a dev pipeline using GitHub Actions, Node.js, and AWS ECS FargateBuilding a dev pipeline using GitHub Actions, Node.js, and AWS ECS Fargate
Building a dev pipeline using GitHub Actions, Node.js, and AWS ECS Fargate
 
Continuous Deployment @ AWS Re:Invent
Continuous Deployment @ AWS Re:InventContinuous Deployment @ AWS Re:Invent
Continuous Deployment @ AWS Re:Invent
 
Continuous Integration and Deployment Best Practices on AWS (ARC307) | AWS re...
Continuous Integration and Deployment Best Practices on AWS (ARC307) | AWS re...Continuous Integration and Deployment Best Practices on AWS (ARC307) | AWS re...
Continuous Integration and Deployment Best Practices on AWS (ARC307) | AWS re...
 
Supercharge your app with Cloud Functions for Firebase
Supercharge your app with Cloud Functions for FirebaseSupercharge your app with Cloud Functions for Firebase
Supercharge your app with Cloud Functions for Firebase
 
There is something about serverless
There is something about serverlessThere is something about serverless
There is something about serverless
 
Serverless in production, an experience report (FullStack 2018)
Serverless in production, an experience report (FullStack 2018)Serverless in production, an experience report (FullStack 2018)
Serverless in production, an experience report (FullStack 2018)
 
DevSecCon Singapore 2018 - Remove developers’ shameful secrets or simply rem...
DevSecCon Singapore 2018 -  Remove developers’ shameful secrets or simply rem...DevSecCon Singapore 2018 -  Remove developers’ shameful secrets or simply rem...
DevSecCon Singapore 2018 - Remove developers’ shameful secrets or simply rem...
 
Serverless for High Performance Computing
Serverless for High Performance ComputingServerless for High Performance Computing
Serverless for High Performance Computing
 
SnapyX
SnapyXSnapyX
SnapyX
 
SnapyX - ParisJS
SnapyX - ParisJSSnapyX - ParisJS
SnapyX - ParisJS
 
[AWS Builders] Effective AWS Glue
[AWS Builders] Effective AWS Glue[AWS Builders] Effective AWS Glue
[AWS Builders] Effective AWS Glue
 
Adopt openjdk and how it impacts you in 2020
Adopt openjdk and how it impacts you in 2020Adopt openjdk and how it impacts you in 2020
Adopt openjdk and how it impacts you in 2020
 
Gojko's 5 rules for super responsive Serverless applications
Gojko's 5 rules for super responsive Serverless applicationsGojko's 5 rules for super responsive Serverless applications
Gojko's 5 rules for super responsive Serverless applications
 
Serverless in Production, an experience report (AWS UG South Wales)
Serverless in Production, an experience report (AWS UG South Wales)Serverless in Production, an experience report (AWS UG South Wales)
Serverless in Production, an experience report (AWS UG South Wales)
 
DevSecCon SG 2018 Fabian Presentation Slides
DevSecCon SG 2018 Fabian Presentation SlidesDevSecCon SG 2018 Fabian Presentation Slides
DevSecCon SG 2018 Fabian Presentation Slides
 
Lessons learned from a large scale OSGi web app
Lessons learned from a large scale OSGi web appLessons learned from a large scale OSGi web app
Lessons learned from a large scale OSGi web app
 
IDEALIZE 2023 - NodeJS & Firebase Session
IDEALIZE 2023 - NodeJS & Firebase SessionIDEALIZE 2023 - NodeJS & Firebase Session
IDEALIZE 2023 - NodeJS & Firebase Session
 
Function as a Service
Function as a ServiceFunction as a Service
Function as a Service
 
Serverless for High Performance Computing
Serverless for High Performance ComputingServerless for High Performance Computing
Serverless for High Performance Computing
 
Serverless in production, an experience report
Serverless in production, an experience reportServerless in production, an experience report
Serverless in production, an experience report
 

More from Luciano Mammino

Did you know JavaScript has iterators? DublinJS
Did you know JavaScript has iterators? DublinJSDid you know JavaScript has iterators? DublinJS
Did you know JavaScript has iterators? DublinJSLuciano Mammino
 
What I learned by solving 50 Advent of Code challenges in Rust - RustNation U...
What I learned by solving 50 Advent of Code challenges in Rust - RustNation U...What I learned by solving 50 Advent of Code challenges in Rust - RustNation U...
What I learned by solving 50 Advent of Code challenges in Rust - RustNation U...Luciano Mammino
 
Building an invite-only microsite with Next.js & Airtable - ReactJS Milano
Building an invite-only microsite with Next.js & Airtable - ReactJS MilanoBuilding an invite-only microsite with Next.js & Airtable - ReactJS Milano
Building an invite-only microsite with Next.js & Airtable - ReactJS MilanoLuciano Mammino
 
From Node.js to Design Patterns - BuildPiper
From Node.js to Design Patterns - BuildPiperFrom Node.js to Design Patterns - BuildPiper
From Node.js to Design Patterns - BuildPiperLuciano Mammino
 
Let's build a 0-cost invite-only website with Next.js and Airtable!
Let's build a 0-cost invite-only website with Next.js and Airtable!Let's build a 0-cost invite-only website with Next.js and Airtable!
Let's build a 0-cost invite-only website with Next.js and Airtable!Luciano Mammino
 
JavaScript Iteration Protocols - Workshop NodeConf EU 2022
JavaScript Iteration Protocols - Workshop NodeConf EU 2022JavaScript Iteration Protocols - Workshop NodeConf EU 2022
JavaScript Iteration Protocols - Workshop NodeConf EU 2022Luciano Mammino
 
Building an invite-only microsite with Next.js & Airtable
Building an invite-only microsite with Next.js & AirtableBuilding an invite-only microsite with Next.js & Airtable
Building an invite-only microsite with Next.js & AirtableLuciano Mammino
 
Let's take the monolith to the cloud 🚀
Let's take the monolith to the cloud 🚀Let's take the monolith to the cloud 🚀
Let's take the monolith to the cloud 🚀Luciano Mammino
 
A look inside the European Covid Green Certificate - Rust Dublin
A look inside the European Covid Green Certificate - Rust DublinA look inside the European Covid Green Certificate - Rust Dublin
A look inside the European Covid Green Certificate - Rust DublinLuciano Mammino
 
Node.js: scalability tips - Azure Dev Community Vijayawada
Node.js: scalability tips - Azure Dev Community VijayawadaNode.js: scalability tips - Azure Dev Community Vijayawada
Node.js: scalability tips - Azure Dev Community VijayawadaLuciano Mammino
 
A look inside the European Covid Green Certificate (Codemotion 2021)
A look inside the European Covid Green Certificate (Codemotion 2021)A look inside the European Covid Green Certificate (Codemotion 2021)
A look inside the European Covid Green Certificate (Codemotion 2021)Luciano Mammino
 
AWS Observability Made Simple
AWS Observability Made SimpleAWS Observability Made Simple
AWS Observability Made SimpleLuciano Mammino
 
Semplificare l'observability per progetti Serverless
Semplificare l'observability per progetti ServerlessSemplificare l'observability per progetti Serverless
Semplificare l'observability per progetti ServerlessLuciano Mammino
 
Finding a lost song with Node.js and async iterators - NodeConf Remote 2021
Finding a lost song with Node.js and async iterators - NodeConf Remote 2021Finding a lost song with Node.js and async iterators - NodeConf Remote 2021
Finding a lost song with Node.js and async iterators - NodeConf Remote 2021Luciano Mammino
 
Finding a lost song with Node.js and async iterators - EnterJS 2021
Finding a lost song with Node.js and async iterators - EnterJS 2021Finding a lost song with Node.js and async iterators - EnterJS 2021
Finding a lost song with Node.js and async iterators - EnterJS 2021Luciano Mammino
 
How to send gzipped requests with boto3
How to send gzipped requests with boto3How to send gzipped requests with boto3
How to send gzipped requests with boto3Luciano Mammino
 
Finding a lost song with Node.js and async iterators
Finding a lost song with Node.js and async iteratorsFinding a lost song with Node.js and async iterators
Finding a lost song with Node.js and async iteratorsLuciano Mammino
 
AWS Observability (without the Pain)
AWS Observability (without the Pain)AWS Observability (without the Pain)
AWS Observability (without the Pain)Luciano Mammino
 

More from Luciano Mammino (20)

Did you know JavaScript has iterators? DublinJS
Did you know JavaScript has iterators? DublinJSDid you know JavaScript has iterators? DublinJS
Did you know JavaScript has iterators? DublinJS
 
What I learned by solving 50 Advent of Code challenges in Rust - RustNation U...
What I learned by solving 50 Advent of Code challenges in Rust - RustNation U...What I learned by solving 50 Advent of Code challenges in Rust - RustNation U...
What I learned by solving 50 Advent of Code challenges in Rust - RustNation U...
 
Building an invite-only microsite with Next.js & Airtable - ReactJS Milano
Building an invite-only microsite with Next.js & Airtable - ReactJS MilanoBuilding an invite-only microsite with Next.js & Airtable - ReactJS Milano
Building an invite-only microsite with Next.js & Airtable - ReactJS Milano
 
From Node.js to Design Patterns - BuildPiper
From Node.js to Design Patterns - BuildPiperFrom Node.js to Design Patterns - BuildPiper
From Node.js to Design Patterns - BuildPiper
 
Let's build a 0-cost invite-only website with Next.js and Airtable!
Let's build a 0-cost invite-only website with Next.js and Airtable!Let's build a 0-cost invite-only website with Next.js and Airtable!
Let's build a 0-cost invite-only website with Next.js and Airtable!
 
JavaScript Iteration Protocols - Workshop NodeConf EU 2022
JavaScript Iteration Protocols - Workshop NodeConf EU 2022JavaScript Iteration Protocols - Workshop NodeConf EU 2022
JavaScript Iteration Protocols - Workshop NodeConf EU 2022
 
Building an invite-only microsite with Next.js & Airtable
Building an invite-only microsite with Next.js & AirtableBuilding an invite-only microsite with Next.js & Airtable
Building an invite-only microsite with Next.js & Airtable
 
Let's take the monolith to the cloud 🚀
Let's take the monolith to the cloud 🚀Let's take the monolith to the cloud 🚀
Let's take the monolith to the cloud 🚀
 
A look inside the European Covid Green Certificate - Rust Dublin
A look inside the European Covid Green Certificate - Rust DublinA look inside the European Covid Green Certificate - Rust Dublin
A look inside the European Covid Green Certificate - Rust Dublin
 
Monoliths to the cloud!
Monoliths to the cloud!Monoliths to the cloud!
Monoliths to the cloud!
 
The senior dev
The senior devThe senior dev
The senior dev
 
Node.js: scalability tips - Azure Dev Community Vijayawada
Node.js: scalability tips - Azure Dev Community VijayawadaNode.js: scalability tips - Azure Dev Community Vijayawada
Node.js: scalability tips - Azure Dev Community Vijayawada
 
A look inside the European Covid Green Certificate (Codemotion 2021)
A look inside the European Covid Green Certificate (Codemotion 2021)A look inside the European Covid Green Certificate (Codemotion 2021)
A look inside the European Covid Green Certificate (Codemotion 2021)
 
AWS Observability Made Simple
AWS Observability Made SimpleAWS Observability Made Simple
AWS Observability Made Simple
 
Semplificare l'observability per progetti Serverless
Semplificare l'observability per progetti ServerlessSemplificare l'observability per progetti Serverless
Semplificare l'observability per progetti Serverless
 
Finding a lost song with Node.js and async iterators - NodeConf Remote 2021
Finding a lost song with Node.js and async iterators - NodeConf Remote 2021Finding a lost song with Node.js and async iterators - NodeConf Remote 2021
Finding a lost song with Node.js and async iterators - NodeConf Remote 2021
 
Finding a lost song with Node.js and async iterators - EnterJS 2021
Finding a lost song with Node.js and async iterators - EnterJS 2021Finding a lost song with Node.js and async iterators - EnterJS 2021
Finding a lost song with Node.js and async iterators - EnterJS 2021
 
How to send gzipped requests with boto3
How to send gzipped requests with boto3How to send gzipped requests with boto3
How to send gzipped requests with boto3
 
Finding a lost song with Node.js and async iterators
Finding a lost song with Node.js and async iteratorsFinding a lost song with Node.js and async iterators
Finding a lost song with Node.js and async iterators
 
AWS Observability (without the Pain)
AWS Observability (without the Pain)AWS Observability (without the Pain)
AWS Observability (without the Pain)
 

Recently uploaded

2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Igalia
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024The Digital Insurer
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CVKhem
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityPrincipled Technologies
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?Igalia
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessPixlogix Infotech
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Enterprise Knowledge
 

Recently uploaded (20)

2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your Business
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 

Everything I know about S3 pre-signed URLs

  • 1. AWS User Group: Dublin Meetup / 2022-11-17 1
  • 3. 😎"I have a startup idea..." loige fth.link/presign 3
  • 9. Yes, I still have to implement the profile picture upload feature... 😅 loige 9
  • 10. $ ~ whoami 👋I'm Luciano ( 🍕🍝 ) Senior Architect @ fourTheorem (Dublin ) nodejsdp.link 📔Co-Author of Node.js Design Patterns 👉 Let's connect! (blog) (twitter) (twitch) (github) loige.co @loige loige lmammino 10
  • 11. Always re-imagining We are a pioneering technology consultancy focused on AWS and serverless | | Accelerated Serverless AI as a Service Platform Modernisation loige ✉Reach out to us at 😇We are always looking for talent: hello@fourTheorem.com fth.link/careers 11
  • 12. We host a weekly podcast about AWS awsbites.com loige 12
  • 13. 🤔 "upload" feature in a web app... loige 13
  • 14. What's an upload, really? loige 14
  • 16. loige Structure of an HTTP request POST /profilepic/upload HTTP/1.1 Host: api.meower.com Content-Type: text/plain Content-Length: 9 Some data Method Path Version Headers Body 16
  • 17. loige What if it's a binary (like a picture)? PUT /profilepic/upload HTTP/1.1 Host: api.meower.com Content-Type: image/jpeg Content-Length: 2097852 ����JFIFHH������"�� ���Dl��FW�'6N�()H�'p��FD3 [...] read 2097852 bytes 17
  • 18. loige Using Lambda Lambda proxy integration* * JSON Based protocol mapping to HTTP (JSON Request / JSON Response) 18
  • 19. { "resource": "/profilepic/upload", "path": "/profilepic/upload", "httpMethod": "POST", "headers": { "Content-Type": "text/plain", "Content-Length": "9", "Host": "api.meower.com", }, "body": "Some data" } 1 2 3 4 5 6 7 8 9 10 11 "resource": "/profilepic/upload", "path": "/profilepic/upload", { 1 2 3 "httpMethod": "POST", 4 "headers": { 5 "Content-Type": "text/plain", 6 "Content-Length": "9", 7 "Host": "api.meower.com", 8 }, 9 "body": "Some data" 10 } 11 "httpMethod": "POST", { 1 "resource": "/profilepic/upload", 2 "path": "/profilepic/upload", 3 4 "headers": { 5 "Content-Type": "text/plain", 6 "Content-Length": "9", 7 "Host": "api.meower.com", 8 }, 9 "body": "Some data" 10 } 11 "headers": { "Content-Type": "text/plain", "Content-Length": "9", "Host": "api.meower.com", }, { 1 "resource": "/profilepic/upload", 2 "path": "/profilepic/upload", 3 "httpMethod": "POST", 4 5 6 7 8 9 "body": "Some data" 10 } 11 "body": "Some data" { 1 "resource": "/profilepic/upload", 2 "path": "/profilepic/upload", 3 "httpMethod": "POST", 4 "headers": { 5 "Content-Type": "text/plain", 6 "Content-Length": "9", 7 "Host": "api.meower.com", 8 }, 9 10 } 11 { "resource": "/profilepic/upload", "path": "/profilepic/upload", "httpMethod": "POST", "headers": { "Content-Type": "text/plain", "Content-Length": "9", "Host": "api.meower.com", }, "body": "Some data" } 1 2 3 4 5 6 7 8 9 10 11 loige Lambda proxy integration (request) 19
  • 20. { "resource": "/profilepic/upload", "path": "/profilepic/upload", "httpMethod": "PUT", "headers": { "Content-Type": "image/jpeg", "Content-Length": "2097852", "Host": "api.meower.com", }, "body": "????????" } 1 2 3 4 5 6 7 8 9 10 11 "Content-Type": "image/jpeg", "Content-Length": "2097852", { 1 "resource": "/profilepic/upload", 2 "path": "/profilepic/upload", 3 "httpMethod": "PUT", 4 "headers": { 5 6 7 "Host": "api.meower.com", 8 }, 9 "body": "????????" 10 } 11 "body": "????????" { 1 "resource": "/profilepic/upload", 2 "path": "/profilepic/upload", 3 "httpMethod": "PUT", 4 "headers": { 5 "Content-Type": "image/jpeg", 6 "Content-Length": "2097852", 7 "Host": "api.meower.com", 8 }, 9 10 } 11 { "resource": "/profilepic/upload", "path": "/profilepic/upload", "httpMethod": "PUT", "headers": { "Content-Type": "image/jpeg", "Content-Length": "2097852", "Host": "api.meower.com", }, "body": "????????" } 1 2 3 4 5 6 7 8 9 10 11 loige Lambda proxy integration (request - picture) 20
  • 21. "isBase64Encoded": true, "body": "/9j/4AAQSkZJRgABAQEASABIAAD/2w[...]" { 1 "resource": "/profilepic/upload", 2 "path": "/profilepic/upload", 3 "httpMethod": "PUT", 4 "headers": { 5 "Content-Type": "image/jpeg", 6 "Content-Length": "2097852", 7 "Host": "api.meower.com", 8 }, 9 10 11 } 12 { "resource": "/profilepic/upload", "path": "/profilepic/upload", "httpMethod": "PUT", "headers": { "Content-Type": "image/jpeg", "Content-Length": "2097852", "Host": "api.meower.com", }, "isBase64Encoded": true, "body": "/9j/4AAQSkZJRgABAQEASABIAAD/2w[...]" } 1 2 3 4 5 6 7 8 9 10 11 12 loige Lambda proxy integration (request - picture) 21
  • 22. loige 1. Parse request (JSON) 2. Decode body (Base64) 3. Validation / resize 4. ... Lambda proxy integration request /profilepic/upload 22
  • 23. loige 1. Parse request (JSON) 2. Decode body (Base64) 3. Validation / resize 4. ... Lambda proxy integration request /profilepic/upload 😎 23
  • 24. loige Is this a good solution? 🙄 24
  • 25. loige Limitations... API Gateway requests timeout: 30 sec API Gateway payload: max 10 MB Lambda timeout: max 15 mins Lambda payload size: max 6 MB Upload: 6 MB / 30 sec 25
  • 26. loige Is this a good solution? 🙄 ... not really! What about supporting big images or even videos? 26
  • 27. loige An alternative approach ✅ Long lived connection ✅ No size limit 27
  • 29. loige S3 pre-signed URLs 😱 An S3 built-in feature to authorize operations (download, upload, etc) on a bucket / object using time-limited authenticated URLs 29
  • 30. loige Using S3 pre-signed URLs for upload * yeah, this can be a Lambda as well 😇 * 30
  • 31. loige Using S3 pre-signed URLs for upload * yeah, this can be a Lambda as well 😇 * 31
  • 32. loige Using S3 pre-signed URLs for upload * yeah, this can be a Lambda as well 😇 * 32
  • 33. loige Using S3 pre-signed URLs for upload * yeah, this can be a Lambda as well 😇 * 33
  • 34. loige Using S3 pre-signed URLs for upload ✅ * yeah, this can be a Lambda as well 😇 * 34
  • 35. loige ... and we can also use it for downloads! 🤩 35
  • 36. loige Using S3 pre-signed URLs for download 36
  • 37. loige Using S3 pre-signed URLs for download 37
  • 38. loige Using S3 pre-signed URLs for download 38
  • 39. loige Using S3 pre-signed URLs for download 39
  • 40. loige Using S3 pre-signed URLs for download ✅ 40
  • 41. loige ⚠VERY important details! I lied to you a little in those diagrams... 🤥 It's a decent mental model, but it's not accurate 😅 The server never really talks with S3! The server actually creates the signed URL by itself! We will see later what's the security model around this idea! 41
  • 42. loige Is this a good solution? 🙄 ✅It's a managed feature (a.k.a. no servers to manage) ✅We can upload and download arbitrarily big files with no practical limits* ✅Reasonably simple and secure 👍Seems good to me! * objects in S3 are "limited" to 5TB (when using multi-part upload), 5 GB otherwise. 42
  • 43. loige Generating our first pre-signed URL $ aws s3 presign s3://finance-department-bucket/2022/tax-certificate.pdf https://s3.amazonaws.com/finance-department-bucket/2022/tax- certificate.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz- Credential=AKIA3SGQVQG7FGA6KKA6%2F20221104%2Fus-east- 1%2Fs3%2Faws4_request&X-Amz-Date=20221104T140227Z&X-Amz- Expires=3600&X-Amz-SignedHeaders=host&X-Amz- Signature=b228dbec8c1008c80c162e1210e4503dceead1e4d4751b4d9787 314fd6da4d55 Whoever has this URL can download the tax certificate! 43
  • 44. loige What's in a pre-signed URL https://s3.amazonaws.com/finance-department-bucket/2022/tax- certificate.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz- Credential=AKIA3SGQVQG7FGA6KKA6%2F20221104%2Fus-east- 1%2Fs3%2Faws4_request&X-Amz-Date=20221104T140227Z&X-Amz- Expires=3600&X-Amz-SignedHeaders=host&X-Amz- Signature=b228dbec8c1008c80c162e1210e4503dceead1e4d4751b4d9787 314fd6da4d55 44
  • 45. loige What's in a pre-signed URL https://s3.amazonaws.com /finance-department-bucket /2022/tax-certificate.pdf ?X-Amz-Algorithm=AWS4-HMAC-SHA256 &X-Amz-Credential=AKIA3SGQXQG7XXXYKKA6%2F20221104... &X-Amz-Date=20221104T140227Z &X-Amz-Expires=3600 &X-Amz-SignedHeaders=host &X-Amz-Signature=b228dbec8c1008c80c162e1210e4503dceead1e4d4... What if I change this to /passwords.txt? 45
  • 48. loige 🤓 Once a pre-signed URL is generated you cannot edit it without breaking it Photo by on CHUTTERSNAP Unsplash ⚠Also note that you can use a pre-signed URL as many times as you want until it expires 48
  • 49. loige 🔐Permissions Anyone with valid credentials can create a pre-signed URL (client side) valid credentials = Role, User, or Security Token The generated URL inherits the permissions of the credentials used to generate it This means you can generate pre-signed URLs for things you don't have access to 😅 49
  • 50. loige $ aws s3 presign s3://ireland/i-love-you https://ireland.s3.eu-west-1.amazonaws.com/i-love-you?X-Amz- Algorithm=AWS4-HMAC-SHA256&X-Amz- Credential=AKIA3ABCVQG7FGA6KKA6%2F20221115%2Feu-west- 1%2Fs3%2Faws4_request&X-Amz-Date=20221115T182036Z&X-Amz- Expires=3600&X-Amz-SignedHeaders=host&X-Amz- Signature=75749c92d94d03e411e7bbf64419f2af09301d1791b0df54c639 137c715f7888 😱 I swear I don't even know if this bucket exists or who owns it! 50
  • 51. loige Pre-signed URLs are validated at request time 51
  • 52. loige Creating a pre-signed URL programmatically AWS SDK for JavaScript v3 52
  • 53. import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3' import { getSignedUrl } from '@aws-sdk/s3-request-presigner' const s3Client = new S3Client() const command = new GetObjectCommand({ Bucket: "some-bucket", Key: "some-object" }) const preSignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 }) console.log(preSignedUrl) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3' import { getSignedUrl } from '@aws-sdk/s3-request-presigner' 1 2 3 const s3Client = new S3Client() 4 5 const command = new GetObjectCommand({ 6 Bucket: "some-bucket", 7 Key: "some-object" 8 }) 9 10 const preSignedUrl = await getSignedUrl(s3Client, command, { 11 expiresIn: 3600 12 }) 13 14 console.log(preSignedUrl) 15 const s3Client = new S3Client() import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3' 1 import { getSignedUrl } from '@aws-sdk/s3-request-presigner' 2 3 4 5 const command = new GetObjectCommand({ 6 Bucket: "some-bucket", 7 Key: "some-object" 8 }) 9 10 const preSignedUrl = await getSignedUrl(s3Client, command, { 11 expiresIn: 3600 12 }) 13 14 console.log(preSignedUrl) 15 const command = new GetObjectCommand({ Bucket: "some-bucket", Key: "some-object" }) import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3' 1 import { getSignedUrl } from '@aws-sdk/s3-request-presigner' 2 3 const s3Client = new S3Client() 4 5 6 7 8 9 10 const preSignedUrl = await getSignedUrl(s3Client, command, { 11 expiresIn: 3600 12 }) 13 14 console.log(preSignedUrl) 15 const preSignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 }) import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3' 1 import { getSignedUrl } from '@aws-sdk/s3-request-presigner' 2 3 const s3Client = new S3Client() 4 5 const command = new GetObjectCommand({ 6 Bucket: "some-bucket", 7 Key: "some-object" 8 }) 9 10 11 12 13 14 console.log(preSignedUrl) 15 console.log(preSignedUrl) import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3' 1 import { getSignedUrl } from '@aws-sdk/s3-request-presigner' 2 3 const s3Client = new S3Client() 4 5 const command = new GetObjectCommand({ 6 Bucket: "some-bucket", 7 Key: "some-object" 8 }) 9 10 const preSignedUrl = await getSignedUrl(s3Client, command, { 11 expiresIn: 3600 12 }) 13 14 15 import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3' import { getSignedUrl } from '@aws-sdk/s3-request-presigner' const s3Client = new S3Client() const command = new GetObjectCommand({ Bucket: "some-bucket", Key: "some-object" }) const preSignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 }) console.log(preSignedUrl) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 loige 53
  • 55. loige 2 Options: PUT & POST 🤨 55
  • 56. loige PUT Method PUT <preSignedURL> HTTP/1.1 Host: <bucket>.s3.<region>.amazonaws.com Content-Length: 2097852 ����JFIFHH������"�� ���Dl��FW�'6N�()H�'p��FD3 [...] 56
  • 57. import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3' import { getSignedUrl } from '@aws-sdk/s3-request-presigner' const s3Client = new S3Client() const command = new PutObjectCommand({ Bucket: "some-bucket", Key: "some-object" }) const preSignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 }) console.log(preSignedUrl) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 loige Only difference with the previous example 57
  • 58. loige PUT Method - Limitations You cannot set a limit on the upload size (max of 5 GB)! * You can limit the Content-Type but you can specify exactly one * Unless you know the exact size in advance 58
  • 59. loige POST method It uses the multipart/form-data encoding (form upload) Gives more freedom to the client to shape the request (Content-Type, file name, etc) It uses a policy mechanism to define the "rules" of what can be uploaded E.g. you can limit the supported mime types and provide a maximum file size You can use it to upload from a web form and even configure the redirect URL It's not really a URL but more of a pre-signed form! 59
  • 60. POST / HTTP/1.1 Host: <bucket>.s3.amazonaws.com Content-Type: multipart/form-data; boundary=9431149156168 Content-Length: 2097852 --9431149156168 Content-Disposition: form-data; name="key" picture.jpg --9431149156168 Content-Disposition: form-data; name="X-Amz-Credential" AKIA3SGABCDXXXA6KKA6/20221115/eu-west-1/s3/aws4_request --9431149156168 Content-Disposition: form-data; name="Policy" eyJleHBpcmF0aW9uIjoiMjAyMi0xMS0xNVQyMDo0NjozN1oiLCJjb25kaXRpb25zIjpbWyJj[...] --9431149156168 Content-Disposition: form-data; name="X-Amz-Signature" 2c1da0001dfec7caea1c9fb80c7bc8847f515a9e4483d2942464f48d2f827de7 --9431149156168 Content-Disposition: form-data; name="file"; filename="MyFilename.jpg" Content-Type: image/jpeg ����JFIFHH������"�� ���Dl��FW�'6N�()H�'p��FD3[...] --9431149156168-- loige 60
  • 61. loige POST method Policy A JSON object (Base64 encoded) that defines the upload rules (conditions) and the expiration date This is what gets signed: you cannot alter the policy without breaking the signature { "expiration": "2022-11-15T20:46:37Z", "conditions": [ ["content-length-range", 0, 5242880], ["starts-with", "$Content-Type", "image/"], {"bucket": "somebucket"}, {"X-Amz-Algorithm": "AWS4-HMAC-SHA256"}, {"X-Amz-Credential": "AKIA3SGABCDXXXA6KKA6/20221115/eu-west-1/s3/aws4_request"}, {"X-Amz-Date": "20221115T194637Z"}, {"key": "picture.jpg"} ] } 61
  • 62. import { S3Client } from '@aws-sdk/client-s3' import { createPresignedPost } from '@aws-sdk/s3-presigned-post' const { BUCKET_NAME, OBJECT_KEY } = process.env const s3Client = new S3Client() const { url, fields } = await createPresignedPost(s3Client, { Bucket: 'somebucket', Key: 'someobject', Conditions: [ ['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max ], Fields: { success_action_status: '201', 'Content-Type': 'image/png' }, Expires: 3600 }) console.log({ url, fields }) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { createPresignedPost } from '@aws-sdk/s3-presigned-post' import { S3Client } from '@aws-sdk/client-s3' 1 2 3 const { BUCKET_NAME, OBJECT_KEY } = process.env 4 const s3Client = new S3Client() 5 6 const { url, fields } = await createPresignedPost(s3Client, { 7 Bucket: 'somebucket', 8 Key: 'someobject', 9 Conditions: [ 10 ['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max 11 ], 12 Fields: { 13 success_action_status: '201', 14 'Content-Type': 'image/png' 15 }, 16 Expires: 3600 17 }) 18 19 console.log({ url, fields }) 20 const { url, fields } = await createPresignedPost(s3Client, { }) import { S3Client } from '@aws-sdk/client-s3' 1 import { createPresignedPost } from '@aws-sdk/s3-presigned-post' 2 3 const { BUCKET_NAME, OBJECT_KEY } = process.env 4 const s3Client = new S3Client() 5 6 7 Bucket: 'somebucket', 8 Key: 'someobject', 9 Conditions: [ 10 ['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max 11 ], 12 Fields: { 13 success_action_status: '201', 14 'Content-Type': 'image/png' 15 }, 16 Expires: 3600 17 18 19 console.log({ url, fields }) 20 Bucket: 'somebucket', import { S3Client } from '@aws-sdk/client-s3' 1 import { createPresignedPost } from '@aws-sdk/s3-presigned-post' 2 3 const { BUCKET_NAME, OBJECT_KEY } = process.env 4 const s3Client = new S3Client() 5 6 const { url, fields } = await createPresignedPost(s3Client, { 7 8 Key: 'someobject', 9 Conditions: [ 10 ['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max 11 ], 12 Fields: { 13 success_action_status: '201', 14 'Content-Type': 'image/png' 15 }, 16 Expires: 3600 17 }) 18 19 console.log({ url, fields }) 20 Key: 'someobject', import { S3Client } from '@aws-sdk/client-s3' 1 import { createPresignedPost } from '@aws-sdk/s3-presigned-post' 2 3 const { BUCKET_NAME, OBJECT_KEY } = process.env 4 const s3Client = new S3Client() 5 6 const { url, fields } = await createPresignedPost(s3Client, { 7 Bucket: 'somebucket', 8 9 Conditions: [ 10 ['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max 11 ], 12 Fields: { 13 success_action_status: '201', 14 'Content-Type': 'image/png' 15 }, 16 Expires: 3600 17 }) 18 19 console.log({ url, fields }) 20 Conditions: [ ['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max ], import { S3Client } from '@aws-sdk/client-s3' 1 import { createPresignedPost } from '@aws-sdk/s3-presigned-post' 2 3 const { BUCKET_NAME, OBJECT_KEY } = process.env 4 const s3Client = new S3Client() 5 6 const { url, fields } = await createPresignedPost(s3Client, { 7 Bucket: 'somebucket', 8 Key: 'someobject', 9 10 11 12 Fields: { 13 success_action_status: '201', 14 'Content-Type': 'image/png' 15 }, 16 Expires: 3600 17 }) 18 19 console.log({ url, fields }) 20 Fields: { success_action_status: '201', 'Content-Type': 'image/png' }, import { S3Client } from '@aws-sdk/client-s3' 1 import { createPresignedPost } from '@aws-sdk/s3-presigned-post' 2 3 const { BUCKET_NAME, OBJECT_KEY } = process.env 4 const s3Client = new S3Client() 5 6 const { url, fields } = await createPresignedPost(s3Client, { 7 Bucket: 'somebucket', 8 Key: 'someobject', 9 Conditions: [ 10 ['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max 11 ], 12 13 14 15 16 Expires: 3600 17 }) 18 19 console.log({ url, fields }) 20 Expires: 3600 import { S3Client } from '@aws-sdk/client-s3' 1 import { createPresignedPost } from '@aws-sdk/s3-presigned-post' 2 3 const { BUCKET_NAME, OBJECT_KEY } = process.env 4 const s3Client = new S3Client() 5 6 const { url, fields } = await createPresignedPost(s3Client, { 7 Bucket: 'somebucket', 8 Key: 'someobject', 9 Conditions: [ 10 ['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max 11 ], 12 Fields: { 13 success_action_status: '201', 14 'Content-Type': 'image/png' 15 }, 16 17 }) 18 19 console.log({ url, fields }) 20 console.log({ url, fields }) import { S3Client } from '@aws-sdk/client-s3' 1 import { createPresignedPost } from '@aws-sdk/s3-presigned-post' 2 3 const { BUCKET_NAME, OBJECT_KEY } = process.env 4 const s3Client = new S3Client() 5 6 const { url, fields } = await createPresignedPost(s3Client, { 7 Bucket: 'somebucket', 8 Key: 'someobject', 9 Conditions: [ 10 ['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max 11 ], 12 Fields: { 13 success_action_status: '201', 14 'Content-Type': 'image/png' 15 }, 16 Expires: 3600 17 }) 18 19 20 import { S3Client } from '@aws-sdk/client-s3' import { createPresignedPost } from '@aws-sdk/s3-presigned-post' const { BUCKET_NAME, OBJECT_KEY } = process.env const s3Client = new S3Client() const { url, fields } = await createPresignedPost(s3Client, { Bucket: 'somebucket', Key: 'someobject', Conditions: [ ['content-length-range', 0, 5 * 1024 * 1024] // 5 MB max ], Fields: { success_action_status: '201', 'Content-Type': 'image/png' }, Expires: 3600 }) console.log({ url, fields }) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 loige 62
  • 63. // you can use `url` and `fields` to generate an HTML form const code = `<h1>Upload an image to S3</h1> <form action="${url}" method="post" enctype="multipart/form-data"> ${Object.entries(fields).map(([key, value]) => { return `<input type="hidden" name="${key}" value="${value.replace(/"/g, '&quot;')}">` }).join('n')} <div><input type="file" name="file" accept="image/png"></div> <div><input type="submit" value="Upload"></div> </form>` 1 2 3 4 5 6 7 8 9 10 loige 63
  • 64. loige Limitations and quirks It supports only 1 file (cannot upload multiple files in one go) The file field must be the last entry in the form (S3 will ignore every other field after the file) From the browser (AJAX) you need to enable CORS on the bucket 64
  • 65. loige Should I use PUT or POST? 🧐 PUT is simpler but definitely more limited POST is slightly more complicated (and less adopted) but it's more flexible You should probably put some time into learning POST and use that! 65
  • 66. loige Pre-signed URLs for other operations S3 pre-signed URLs are not limited to GET, PUT or POST operations You can literally create pre-signed URLs for any command (DeleteObject, ListBuckets, MultiPartUpload, etc...) 66
  • 67. loige Do you need moar examples? 😼 github.com/lmammino/s3-presigned-urls-examples 67
  • 68. loige ... In summary S3 pre-signed URLs are a great way to authorise operations on S3 They are generally used to implement upload/download features The signature is created client-side so you can sign anything. Access is validated at request time This is not the only solution, you can also use the JavaScript SDK from the frontend and get limited credentials from Cognito (Amplify makes that process simpler) For upload you can use PUT and POST, but POST is much more flexible 💬PS: Meower.com doesn't really exist... but... do you want to invest?! It's a great idea, trust me! 68
  • 69. Cover photo by on Kelly Sikkema Unsplash fourtheorem.com THANKS! 🙌 fth.link/presign loige It's a wrap! 69