[{"data":1,"prerenderedAt":180},["ShallowReactive",2],{"blog-blueprints/s3-uploads":3},{"id":4,"title":5,"body":6,"category":160,"date":161,"dateModified":161,"description":162,"draft":163,"extension":164,"faq":165,"featured":163,"headerVariant":166,"image":165,"keywords":165,"meta":167,"navigation":168,"ogDescription":169,"ogTitle":165,"path":170,"readTime":171,"schemaOrg":172,"schemaType":173,"seo":174,"sitemap":175,"stem":176,"tags":177,"twitterCard":178,"__hash__":179},"blog/blog/blueprints/s3-uploads.md","S3 Secure Uploads Guide",{"type":7,"value":8,"toc":149},"minimark",[9,20,24,30,35,50,54,63,67,76,85,89,94,97,100,103,106,109,112,123,137],[10,11,12],"blueprint-summary",{},[13,14,15,19],"p",{},[16,17,18],"strong",{},"To secure S3 uploads,"," you need to: (1) use presigned URLs for direct client uploads instead of exposing AWS credentials, (2) validate file types and sizes server-side before generating upload URLs, (3) generate unique object keys server-side to prevent path traversal and overwrites, (4) configure bucket policies to block public access unless explicitly needed, and (5) set short expiry times on presigned URLs. This blueprint prevents credential exposure and upload abuse.",[21,22],"blueprint-meta",{"time":23},"1-2 hours",[25,26,27],"tldr",{},[13,28,29],{},"Never expose AWS credentials to the client. Use presigned URLs for direct uploads, validate file types by content (not extension), generate unique keys to prevent overwrites, and configure bucket policies to block public access unless explicitly needed.",[31,32,34],"h2",{"id":33},"presigned-url-generation-aws-s3","Presigned URL Generation AWS S3",[36,37,39],"code-block",{"label":38},"app/api/upload/route.ts",[40,41,46],"pre",{"className":42,"code":44,"language":45},[43],"language-text","import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner'\nimport { auth } from '@/lib/auth'\nimport { nanoid } from 'nanoid'\n\nconst s3 = new S3Client({ region: process.env.AWS_REGION })\n\nconst ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp']\nconst MAX_SIZE = 5 * 1024 * 1024  // 5MB\n\nexport async function POST(req: Request) {\n  const session = await auth()\n  if (!session?.user) {\n    return Response.json({ error: 'Unauthorized' }, { status: 401 })\n  }\n\n  const { contentType, size } = await req.json()\n\n  // Validate content type\n  if (!ALLOWED_TYPES.includes(contentType)) {\n    return Response.json({ error: 'Invalid file type' }, { status: 400 })\n  }\n\n  // Validate size\n  if (size > MAX_SIZE) {\n    return Response.json({ error: 'File too large' }, { status: 400 })\n  }\n\n  // Generate unique key (prevents overwrites and path traversal)\n  const key = `uploads/${session.user.id}/${nanoid()}`\n\n  const command = new PutObjectCommand({\n    Bucket: process.env.S3_BUCKET!,\n    Key: key,\n    ContentType: contentType,\n    ContentLength: size,\n  })\n\n  const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 300 })\n\n  return Response.json({ uploadUrl, key })\n}\n","text",[47,48,44],"code",{"__ignoreMap":49},"",[31,51,53],{"id":52},"client-side-upload","Client-Side Upload",[36,55,57],{"label":56},"components/FileUpload.tsx",[40,58,61],{"className":59,"code":60,"language":45},[43],"async function uploadFile(file: File) {\n  // Get presigned URL from your API\n  const res = await fetch('/api/upload', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({\n      contentType: file.type,\n      size: file.size,\n    }),\n  })\n\n  if (!res.ok) {\n    throw new Error('Failed to get upload URL')\n  }\n\n  const { uploadUrl, key } = await res.json()\n\n  // Upload directly to S3\n  const uploadRes = await fetch(uploadUrl, {\n    method: 'PUT',\n    headers: { 'Content-Type': file.type },\n    body: file,\n  })\n\n  if (!uploadRes.ok) {\n    throw new Error('Upload failed')\n  }\n\n  return key\n}\n",[47,62,60],{"__ignoreMap":49},[31,64,66],{"id":65},"bucket-policy-aws-s3","Bucket Policy AWS S3",[36,68,70],{"label":69},"S3 Bucket Policy",[40,71,74],{"className":72,"code":73,"language":45},[43],"{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"DenyPublicAccess\",\n      \"Effect\": \"Deny\",\n      \"Principal\": \"*\",\n      \"Action\": \"s3:GetObject\",\n      \"Resource\": \"arn:aws:s3:::your-bucket/*\",\n      \"Condition\": {\n        \"StringNotEquals\": {\n          \"aws:PrincipalAccount\": \"your-account-id\"\n        }\n      }\n    }\n  ]\n}\n\n// For public read access (if needed):\n// Use CloudFront with signed URLs instead of making bucket public\n",[47,75,73],{"__ignoreMap":49},[77,78,79],"warning-box",{},[13,80,81,84],{},[16,82,83],{},"Never trust client-provided filenames."," Generate unique keys server-side to prevent path traversal attacks and accidental overwrites.",[31,86,88],{"id":87},"security-checklist","Security Checklist",[90,91,93],"h4",{"id":92},"pre-launch-checklist","Pre-Launch Checklist",[13,95,96],{},"AWS credentials never exposed to client",[13,98,99],{},"File types validated server-side",[13,101,102],{},"File size limited",[13,104,105],{},"Unique keys generated server-side",[13,107,108],{},"Bucket public access blocked",[13,110,111],{},"Presigned URLs have short expiry",[113,114,115,120],"stack-comparison",{},[116,117,119],"h3",{"id":118},"related-integration-stacks","Related Integration Stacks",[13,121,122],{},"Cloudflare R2 Alternative\nSupabase Storage Integration\nFirebase Storage Patterns",[124,125,126,132],"related-articles",{},[127,128],"related-card",{"description":129,"href":130,"title":131},"Deep dive","/blog/how-to/validate-user-input","File Upload Security",[127,133],{"description":134,"href":135,"title":136},"R2 alternative","/blog/blueprints/cloudflare-workers","Cloudflare Workers",[138,139,142,146],"cta-box",{"href":140,"label":141},"/","Start Free Scan",[31,143,145],{"id":144},"check-your-upload-security","Check Your Upload Security",[13,147,148],{},"Scan for file upload vulnerabilities.",{"title":49,"searchDepth":150,"depth":150,"links":151},2,[152,153,154,155,159],{"id":33,"depth":150,"text":34},{"id":52,"depth":150,"text":53},{"id":65,"depth":150,"text":66},{"id":87,"depth":150,"text":88,"children":156},[157],{"id":118,"depth":158,"text":119},3,{"id":144,"depth":150,"text":145},"blueprints","2026-02-11","Security guide for AWS S3 file uploads. Use presigned URLs, validate file types server-side, configure bucket policies, prevent path traversal, and secure your uploads.",false,"md",null,"purple",{},true,"Secure S3 file upload implementation patterns.","/blog/blueprints/s3-uploads","11 min read","[object Object]","Article",{"title":5,"description":162},{"loc":170},"blog/blueprints/s3-uploads",[],"summary_large_image","2uaMMMihkdPxN4SJMnKdSF2uYNVybA07fu2vTrbIm6w",1775843932047]