Building mobile app that lets users access S3 (read, list, upload)
without shipping credentials
in the app - Overview.
Scope:
- Intro,
- The core problem,
- Four common patterns (+ when to use which),
- Direct S3 with Cognito Identity Pools (STS AssumeRole),
- Pre-signed URLs (server issues short-lived URLs),
- CloudFront in front of S3 (read path),
- twtech API as a proxy (upload and download stream through backend),
- Reference architectures (Serverless minimal: Cognito + S3 ...direct)
- Reference architectures (Control everything: API Gateway + Lambda pre-sign + CloudFront),
- Sample IAM policy (per-user home prefix),
- Sample Bucket CORS (Cross-Origin Resource Sharing) for mobile/web,
Upload strategies (mobile-friendly),
- Server code sketch (Node.js Lambda) to pre-sign multipart parts,
- Download strategies,
Security checklist (non-negotiables),
- Cost & performance tips,
- Sample twtech-S3bucket policy (CloudFront OAC for read; deny direct public),
- Mobile-side implementation,
- Quick Decision guide ,
- What to build for most apps.
Intro:
Building a mobile app that accesses Amazon S3 without hardcoding credentials requires using a temporary security credential system like Amazon Cognito Identity Pools for authentication and authorization.
The core problem
- Goal: Give each signed-in user controlled access to a subset of objects in S3 (e.g., their own “home” prefix) with good performance and low cost.
- Constraints: No embedded AWS keys; least privilege; ability to revoke; work on flaky mobile networks; handle large files.
Four common patterns (when
to use which)
1.
Direct S3 with Cognito Identity Pools (STS
AssumeRole)
- App authenticates with an IdP (Cognito User Pool, Apple/Google/Facebook, OIDC, SAML).
- Cognito Identity Pool exchanges that identity for temporary AWS creds via STS.
- The mobile SDK uses those creds to call S3 directly.
- First-party AWS flow, no backend needed for basic CRUD; great for many small/medium objects; multipart upload built in.
- Fine-grained policy design is on twtech.
- Revoking already-issued creds is non-instant (short TTL helps).
- twtech needs both upload + read + list from the device and want minimal backend.
2.
Pre-signed URLs (server issues short-lived URLs)
- twtech backends (API Gateway + Lambda or any server) checks app authz and returns a pre-signed URL for GET/PUT (or multipart).
- The app talks to S3 using the URL—no AWS creds on device.
- No
AWS creds on device; per-request authorization; very tight TTL and object-specific scope; easy to inject
server-side business rules (quota, file
type checks).
- Pre-signed URLs cannot be revoked until they expire; listing still needs a server API (twtech doesn’t want to pre-sign a list).
- twtech wants maximum control and auditing of each access, or twtech doesn’t want STS creds on devices.
3.
CloudFront in front of S3 (read path)
- Private S3 bucket → CloudFront with Origin Access Control (OAC).
- App fetches via CloudFront using signed URLs/cookies or with twtech token auth at the edge (Lambda@Edge/CloudFront Functions).
- Global cache, great performance and cost for read-heavy workloads; easy key rotation for signed URLs; hides S3.
- Not for uploads (twtech still need pre-signed URLs or STS for PUT).
- Read-heavy media/doc delivery where CDN performance and revocation controls matter.
4.
twtech API as a proxy (upload and download stream
through backend)
- Centralized policy & scanning; simple client; can transform on the fly.
- Backend becomes a bandwidth and cost bottleneck.
- Usually avoid for large files.
- Files are small, or content must be inspected/transformed server-side before reaching S3.
NB:
Most mobile apps mix #2 for uploads (pre-signed multipart) and #3 for downloads (CloudFront).
- If twtech also need client-side listing or rename/move semantics, it will combine with #1 or provide list endpoints in its API.
Reference architectures
A) “Serverless
minimal”: Cognito + S3 (direct)
AuthN:- Cognito User Pool (hosted UI or native SDK).
- Cognito Identity Pool role mapping → IAM roles with conditions restricting to a user prefix.
- App ↔ S3 (SDK; multipart uploads).
- Simple per-user storage, light business logic, low ops.
Sample IAM policy (per-user home prefix)
{ "Version": "2012-10-17", "Statement": [ { "Sid": "ListUserHome", "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": "arn:aws:s3:::twtech-S3bucket", "Condition": { "StringLike": { "s3:prefix": ["home/${cognito-identity.amazonaws.com:sub}/*"] } } }, { "Sid": "twtechCRUDUserHome", "Effect": "Allow", "Action": ["s3:GetObject","s3:PutObject","s3:DeleteObject","s3:AbortMultipartUpload","s3:ListMultipartUploadParts"], "Resource": "arn:aws:s3:::twtech-Sbucket/home/${cognito-identity.amazonaws.com:sub}/*" } ]}# Sample Bucket CORS (Cross-Origin Resource Sharing) for mobile/web
<CORSConfiguration> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedMethod>PUT</AllowedMethod> <AllowedMethod>POST</AllowedMethod> <AllowedHeader>*</AllowedHeader> <ExposeHeader>ETag</ExposeHeader> </CORSRule></CORSConfiguration>B) “Control
everything”: API Gateway + Lambda pre-sign + CloudFront
AuthN:- Cognito User Pool (JWT).
- Lambda validates JWT/entitlements; issues pre-signed URLs for PUT/GET and performs server-side checks (quota, content type, virus scan ticket).
- Clients fetch via CloudFront (private S3 with OAC).
- Use CloudFront signed URLs/cookies or a simple custom header/token validated at the edge.
- Client requests pre-signed multipart URLs from API; then uploads directly to S3.
- twtech wants revocation, cache, analytics, and business rules.
Upload strategies (mobile-friendly)
Single-part PUT: - Simple for small files (<5–10 MB typical).
- Required for large files; enables pause/resume and parallelism.
- Client with STS creds: Use the AWS Mobile SDK multipart APIs.
- Client without creds: Backend generates pre-signed URLs per part; client PUTs each part, then backend (or client) completes the upload.
Server code sketch (Node.js Lambda) to pre-sign multipart parts
import { S3Client, CreateMultipartUploadCommand, UploadPartCommand, \CompleteMultipartUploadCommand } from "@aws-sdk/client-s3";import { getSignedUrl } from "@aws-sdk/s3-request-presigner";const s3 = new S3Client({ region: process.env.AWS_REGION });export async function handler(event) { const { key, parts } = JSON.parse(event.body); // validate: path, size caps, content-type, user ownership const create = await s3.send(new CreateMultipartUploadCommand({ Bucket: process.env.BUCKET, Key: key, ContentType: "application/octet-stream", ACL: undefined // ACLs off (Bucket owner enforced) })); const urls = await Promise.all([...Array(parts)].map((_, idx) => getSignedUrl(s3, new UploadPartCommand({ Bucket: process.env.BUCKET, Key: key, UploadId: create.UploadId, PartNumber: idx + 1 }), { expiresIn: 900 }) // 15 minutes )); return { statusCode: 200, body: JSON.stringify({ uploadId: create.UploadId, urls }) };}Download strategies
Private S3 via CloudFront + OAC:- Best performance. Use signed URLs with short TTL; rotate keys. For per-user access checks, have twtech origin URL include a user-scoped path, or validate JWT at the edge (Lambda@Edge/Functions).
- Great for one-off files; keeps S3 fully private; TTL seconds–minutes.
Security checklist (non-negotiables)
Never embed IAM access keys in the app. - Only STS temp creds or pre-signed URLs.
- enable Object Ownership: Bucket owner enforced (disables ACLs).
Short TTLs:
- STS creds (≤1h typical), pre-signed URLs (≤15m typical), CloudFront signed URLs (minutes–hours).
- Short TTL + key rotation; with CloudFront you can rotate key pairs.
- For pre-signed S3, twtech can’t revoke—expire fast.
- For sensitive data; use per-app CMKs and limit who can decrypt.
- Enforce allowed Content-Type, max size, allowed extensions before issuing upload URLs.
- Trigger Lambda on
ObjectCreated:*(S3 Event) -> scan (e.g., ClamAV layer) -> tag/quarantine; expose only scanned objects to users (tag-based access).
- Separate customer data by account or S3 Access Points; don’t rely solely on prefixes for multi-tenant SaaS with strong isolation—consider per-tenant buckets or access points.
- CloudTrail data events for S3; CloudFront logs; S3 server access logs or CloudWatch access logs; metrics on 4xx/5xx; alarms on unusual activity.
- prefer identity-scoped conditions.
- Tighten as needed; expose
ETagfor optimistic concurrency.
Cost & performance tips
CloudFront on
reads: - offload S3, lower TTFB globally.
- On uploads if users are global and large files are common.
- For media (server-side or via Object Lambda).
- For dynamic transformations (thumbnails, redactions) without making the object public.
- 5–15 MB part size is a good starting point on mobile; retry with exponential backoff.
- Keep small metadata locally; consider an index API rather than listing deep prefixes from the device.
- with owner/userId; use lifecycle to transition/expire.
Sample twtech-S3bucket policy (CloudFront
OAC for read; deny direct public)
{ "Version": "2012-10-17", "Statement": [ { "Sid": "twtechOnlyAllowCloudFrontOriginAccessControl", "Effect": "Allow", "Principal": { "Service": "cloudfront.amazonaws.com" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::twtech-S3bucket/private/*", "Condition": { "StringEquals": { "AWS:SourceArn": "arn:aws:cloudfront::accountID:distribution/E123ABC456" } } }, { "Sid": "twtechDenyNonTLS", "Effect": "Deny", "Principal": "*", "Action": "s3:*", "Resource": ["arn:aws:cloudfront::accountID:distribution/E123ABC456","arn:aws:s3:::twtech-S3bucket/*", \ "arn:aws:s3:::twtech-S3bucket"], "Condition": { "Bool": { "aws:SecureTransport": "false" } } } ]}Mobile-side implementation
Token storage: - Store IdP/Cognito tokens in the OS-secure keystore (Keychain/Keystore); refresh silently.
- Handle transient 5xx and throttling; support pause/resume for uploads.
- Queue + persist multipart state (uploadId, completed parts).
- Capture ETag per part and on final object; use ETag for cache/concurrency.
- Redact in telemetry.
Quick Decision guide
- Need read at scale + revocation? → CloudFront + OAC for reads.
- Need uploads from device with tight server control? → Pre-signed multipart via API.
- Want no backend and okay with IAM complexity? → Cognito Identity Pool + STS direct S3.
- Very strict compliance & scanning? → Proxy uploads through API (or pre-signed + post-ingest scanning).
What to build for most apps
Auth: - Cognito User Pool (federation allowed).
- API Gateway + Lambda to issue pre-signed multipart URLs (15-minute TTL), enforce quotas/types, and return object metadata lists.
- CloudFront (OAC) in front of the S3 bucket’s
private/prefix; use signed cookies for session access or validate a JWT at the edge.
- Private, Object Ownership: Bucket owner enforced, SSE-KMS, lifecycle rules.
- S3 Event → Lambda → malware scan + tag → only tagged “clean” objects are served.
No comments:
Post a Comment