SlideShare ist ein Scribd-Unternehmen logo
1 von 69
Downloaden Sie, um offline zu lesen
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

Weitere ähnliche Inhalte

Was ist angesagt?

[140315 박민근] 젠킨스를 이용한 자동빌드 시스템 구축하기(ci)
[140315 박민근] 젠킨스를 이용한 자동빌드 시스템 구축하기(ci)[140315 박민근] 젠킨스를 이용한 자동빌드 시스템 구축하기(ci)
[140315 박민근] 젠킨스를 이용한 자동빌드 시스템 구축하기(ci)
MinGeun Park
 
30分でRHEL6 High Availability Add-Onを超絶的に理解しよう!
30分でRHEL6 High Availability Add-Onを超絶的に理解しよう!30分でRHEL6 High Availability Add-Onを超絶的に理解しよう!
30分でRHEL6 High Availability Add-Onを超絶的に理解しよう!
Etsuji Nakai
 
R2서버정진욱
R2서버정진욱R2서버정진욱
R2서버정진욱
jungjinwouk
 
Monitoring and analytics with was liberty
Monitoring and analytics with was libertyMonitoring and analytics with was liberty
Monitoring and analytics with was liberty
sflynn073
 
1 회사및게임소개자료
1 회사및게임소개자료1 회사및게임소개자료
1 회사및게임소개자료
정의 윤
 
복귀 이벤트를 해도 유저가 돌아오지 않는 이유
복귀 이벤트를 해도 유저가 돌아오지 않는 이유복귀 이벤트를 해도 유저가 돌아오지 않는 이유
복귀 이벤트를 해도 유저가 돌아오지 않는 이유
Chanman Jo
 

Was ist angesagt? (20)

190119 unreal engine c++ 입문 및 팁
190119 unreal engine c++ 입문 및 팁190119 unreal engine c++ 입문 및 팁
190119 unreal engine c++ 입문 및 팁
 
[NDC14] (공개용)게임 QA에 적용할 수 있는 테스팅 기법과 패턴 활용_황우람
[NDC14] (공개용)게임 QA에 적용할 수 있는 테스팅 기법과 패턴 활용_황우람[NDC14] (공개용)게임 QA에 적용할 수 있는 테스팅 기법과 패턴 활용_황우람
[NDC14] (공개용)게임 QA에 적용할 수 있는 테스팅 기법과 패턴 활용_황우람
 
게임회사 실무용어 완전정복! 쿡앱스 용어정리집
게임회사 실무용어 완전정복! 쿡앱스 용어정리집 게임회사 실무용어 완전정복! 쿡앱스 용어정리집
게임회사 실무용어 완전정복! 쿡앱스 용어정리집
 
自動運転サービスの認証認可
自動運転サービスの認証認可自動運転サービスの認証認可
自動運転サービスの認証認可
 
[NDC2017] 딥러닝으로 게임 콘텐츠 제작하기 - VAE를 이용한 콘텐츠 생성 기법 연구 사례
[NDC2017] 딥러닝으로 게임 콘텐츠 제작하기 - VAE를 이용한 콘텐츠 생성 기법 연구 사례[NDC2017] 딥러닝으로 게임 콘텐츠 제작하기 - VAE를 이용한 콘텐츠 생성 기법 연구 사례
[NDC2017] 딥러닝으로 게임 콘텐츠 제작하기 - VAE를 이용한 콘텐츠 생성 기법 연구 사례
 
Packetbeatの基礎から、IoTデバイス異常検知への応用まで
Packetbeatの基礎から、IoTデバイス異常検知への応用までPacketbeatの基礎から、IoTデバイス異常検知への応用まで
Packetbeatの基礎から、IoTデバイス異常検知への応用まで
 
장재화, Replay system, NDC2011
장재화, Replay system, NDC2011장재화, Replay system, NDC2011
장재화, Replay system, NDC2011
 
게임사를 위한 Amazon GameLift 세션 - 이정훈, AWS 솔루션즈 아키텍트
게임사를 위한 Amazon GameLift 세션 - 이정훈, AWS 솔루션즈 아키텍트게임사를 위한 Amazon GameLift 세션 - 이정훈, AWS 솔루션즈 아키텍트
게임사를 위한 Amazon GameLift 세션 - 이정훈, AWS 솔루션즈 아키텍트
 
[140315 박민근] 젠킨스를 이용한 자동빌드 시스템 구축하기(ci)
[140315 박민근] 젠킨스를 이용한 자동빌드 시스템 구축하기(ci)[140315 박민근] 젠킨스를 이용한 자동빌드 시스템 구축하기(ci)
[140315 박민근] 젠킨스를 이용한 자동빌드 시스템 구축하기(ci)
 
IT エンジニアのための 流し読み Windows 10 - Windows 10 サブスクリプションのライセンス認証
IT エンジニアのための 流し読み Windows 10 - Windows 10 サブスクリプションのライセンス認証IT エンジニアのための 流し読み Windows 10 - Windows 10 サブスクリプションのライセンス認証
IT エンジニアのための 流し読み Windows 10 - Windows 10 サブスクリプションのライセンス認証
 
30分でRHEL6 High Availability Add-Onを超絶的に理解しよう!
30分でRHEL6 High Availability Add-Onを超絶的に理解しよう!30分でRHEL6 High Availability Add-Onを超絶的に理解しよう!
30分でRHEL6 High Availability Add-Onを超絶的に理解しよう!
 
언리얼4 플레이어 컨트롤러의 이해.
언리얼4 플레이어 컨트롤러의 이해.언리얼4 플레이어 컨트롤러의 이해.
언리얼4 플레이어 컨트롤러의 이해.
 
클라우드 기반 Unity 게임 서버 구축, 60분이면 충분하다
클라우드 기반 Unity 게임 서버 구축, 60분이면 충분하다클라우드 기반 Unity 게임 서버 구축, 60분이면 충분하다
클라우드 기반 Unity 게임 서버 구축, 60분이면 충분하다
 
R2서버정진욱
R2서버정진욱R2서버정진욱
R2서버정진욱
 
Monitoring and analytics with was liberty
Monitoring and analytics with was libertyMonitoring and analytics with was liberty
Monitoring and analytics with was liberty
 
PlayFab analytics gdc
PlayFab analytics gdcPlayFab analytics gdc
PlayFab analytics gdc
 
1 회사및게임소개자료
1 회사및게임소개자료1 회사및게임소개자료
1 회사및게임소개자료
 
Ndc14 분산 서버 구축의 ABC
Ndc14 분산 서버 구축의 ABCNdc14 분산 서버 구축의 ABC
Ndc14 분산 서버 구축의 ABC
 
복귀 이벤트를 해도 유저가 돌아오지 않는 이유
복귀 이벤트를 해도 유저가 돌아오지 않는 이유복귀 이벤트를 해도 유저가 돌아오지 않는 이유
복귀 이벤트를 해도 유저가 돌아오지 않는 이유
 
Building Instruqt, a scalable learning platform
Building Instruqt, a scalable learning platformBuilding Instruqt, a scalable learning platform
Building Instruqt, a scalable learning platform
 

Ähnlich wie Everything I know about S3 pre-signed URLs

SnapyX
SnapyXSnapyX
SnapyX
ekino
 

Ähnlich wie 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
 

Mehr von Luciano Mammino

Mehr von 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)
 

Kürzlich hochgeladen

+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 

Kürzlich hochgeladen (20)

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
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
[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
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Tech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdfTech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdf
 
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?
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
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...
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 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
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
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
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of Brazil
 
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...
 
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
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 

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