Slide 1

Slide 1 text

AWS User Group: Dublin Meetup / 2022-11-17 1

Slide 2

Slide 2 text

META_SLIDE! fth.link/presign loige 2

Slide 3

Slide 3 text

😎 "I have a startup idea..." loige fth.link/presign 3

Slide 4

Slide 4 text

loige meower fth.link/presign 4

Slide 5

Slide 5 text

loige 5

Slide 6

Slide 6 text

loige 6

Slide 7

Slide 7 text

loige 7

Slide 8

Slide 8 text

loige WTF? 8

Slide 9

Slide 9 text

Yes, I still have to implement the profile picture upload feature... 😅 loige 9

Slide 10

Slide 10 text

$ ~ 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

Slide 11

Slide 11 text

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: [email protected] fth.link/careers 11

Slide 12

Slide 12 text

We host a weekly podcast about AWS awsbites.com loige 12

Slide 13

Slide 13 text

🤔 "upload" feature in a web app... loige 13

Slide 14

Slide 14 text

What's an upload, really? loige 14

Slide 15

Slide 15 text

OK, what protocol? loige 15

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

loige Using Lambda Lambda proxy integration* * JSON Based protocol mapping to HTTP (JSON Request / JSON Response) 18

Slide 19

Slide 19 text

{ "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

Slide 20

Slide 20 text

{ "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

Slide 21

Slide 21 text

"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

Slide 22

Slide 22 text

loige 1. Parse request (JSON) 2. Decode body (Base64) 3. Validation / resize 4. ... Lambda proxy integration request /profilepic/upload 22

Slide 23

Slide 23 text

loige 1. Parse request (JSON) 2. Decode body (Base64) 3. Validation / resize 4. ... Lambda proxy integration request /profilepic/upload 😎 23

Slide 24

Slide 24 text

loige Is this a good solution? 🙄 24

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

loige Is this a good solution? 🙄 ... not really! What about supporting big images or even videos? 26

Slide 27

Slide 27 text

loige An alternative approach ✅ Long lived connection ✅ No size limit 27

Slide 28

Slide 28 text

loige 🤔 SERVERS... 28

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

loige Using S3 pre-signed URLs for upload * yeah, this can be a Lambda as well 😇 * 30

Slide 31

Slide 31 text

loige Using S3 pre-signed URLs for upload * yeah, this can be a Lambda as well 😇 * 31

Slide 32

Slide 32 text

loige Using S3 pre-signed URLs for upload * yeah, this can be a Lambda as well 😇 * 32

Slide 33

Slide 33 text

loige Using S3 pre-signed URLs for upload * yeah, this can be a Lambda as well 😇 * 33

Slide 34

Slide 34 text

loige Using S3 pre-signed URLs for upload ✅ * yeah, this can be a Lambda as well 😇 * 34

Slide 35

Slide 35 text

loige ... and we can also use it for downloads! 🤩 35

Slide 36

Slide 36 text

loige Using S3 pre-signed URLs for download 36

Slide 37

Slide 37 text

loige Using S3 pre-signed URLs for download 37

Slide 38

Slide 38 text

loige Using S3 pre-signed URLs for download 38

Slide 39

Slide 39 text

loige Using S3 pre-signed URLs for download 39

Slide 40

Slide 40 text

loige Using S3 pre-signed URLs for download ✅ 40

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

loige 👿 46

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

loige Pre-signed URLs are validated at request time 51

Slide 52

Slide 52 text

loige Creating a pre-signed URL programmatically AWS SDK for JavaScript v3 52

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

loige 📦 Uploading a file using pre-signed URLs 54

Slide 55

Slide 55 text

loige 2 Options: PUT & POST 🤨 55

Slide 56

Slide 56 text

loige PUT Method PUT HTTP/1.1 Host: .s3..amazonaws.com Content-Length: 2097852 ����JFIFHH������"�� ���Dl��FW�'6N�()H�'p��FD3 [...] 56

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

POST / HTTP/1.1 Host: .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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

// you can use `url` and `fields` to generate an HTML form const code = `

Upload an image to S3

${Object.entries(fields).map(([key, value]) => { return `` }).join('\n')}
` 1 2 3 4 5 6 7 8 9 10 loige 63

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

loige Do you need moar examples? 😼 github.com/lmammino/s3-presigned-urls-examples 67

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

Cover photo by on Kelly Sikkema Unsplash fourtheorem.com THANKS! 🙌 fth.link/presign loige It's a wrap! 69