Authentication & Authorization
For web and mobile applications uploading resumes directly to S3, use AWS Cognito Identity Pools (Federated Identities) to issue short-lived, scoped AWS credentials to the client. This avoids exposing long-lived AWS access keys in your frontend code.
How It Works
The Cognito Identity Pool acts as a credential broker. Your frontend requests temporary AWS
credentials from the pool, which issues them via an IAM role — scoped to only s3:PutObject
on the RAISE bucket. The credentials expire automatically (typically after 1 hour).
Two modes are supported:
| Mode | Who | How |
|---|---|---|
| Unauthenticated | Anonymous users (no login required) | Identity Pool issues credentials for the Unauthenticated IAM Role |
| Authenticated | Logged-in users (Cognito User Pool, Google, etc.) | Frontend passes an ID Token; Identity Pool validates it and issues credentials for the Authenticated IAM Role |
IAM Policy for S3 Uploads
Both the Unauthenticated and Authenticated IAM Roles need a policy granting upload access to
the RAISE S3 bucket. Replace YOUR_BUCKET_NAME with the value from the CDK stack output.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts"
],
"Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*"
}
]
}
Do not grant s3:GetObject, s3:ListBucket, or s3:DeleteObject. The client only needs
to upload — Lambda handles the rest. Least-privilege is critical when issuing credentials to
browser clients.
Unauthenticated Access
Use this for applications where users upload without logging in.
- React
- Angular
- Vue
import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity";
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity";
import { S3Client } from "@aws-sdk/client-s3";
const REGION = "us-east-1";
const IDENTITY_POOL_ID = "us-east-1:YOUR_IDENTITY_POOL_ID";
export function createS3Client(): S3Client {
return new S3Client({
region: REGION,
credentials: fromCognitoIdentityPool({
client: new CognitoIdentityClient({ region: REGION }),
identityPoolId: IDENTITY_POOL_ID,
}),
});
}
import { Injectable } from "@angular/core";
import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity";
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity";
import { S3Client } from "@aws-sdk/client-s3";
@Injectable({ providedIn: "root" })
export class AwsCredentialsService {
private readonly region = "us-east-1";
private readonly identityPoolId = "us-east-1:YOUR_IDENTITY_POOL_ID";
createS3Client(): S3Client {
return new S3Client({
region: this.region,
credentials: fromCognitoIdentityPool({
client: new CognitoIdentityClient({ region: this.region }),
identityPoolId: this.identityPoolId,
}),
});
}
}
// composables/useAwsCredentials.ts
import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity";
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity";
import { S3Client } from "@aws-sdk/client-s3";
const REGION = "us-east-1";
const IDENTITY_POOL_ID = "us-east-1:YOUR_IDENTITY_POOL_ID";
export function useAwsCredentials() {
function createS3Client(): S3Client {
return new S3Client({
region: REGION,
credentials: fromCognitoIdentityPool({
client: new CognitoIdentityClient({ region: REGION }),
identityPoolId: IDENTITY_POOL_ID,
}),
});
}
return { createS3Client };
}
Authenticated Access
Use this when users log in via Cognito User Pools or a third-party provider (Google, Facebook, etc.).
Pass the ID Token from the login provider to the Identity Pool via the logins map.
- React
- Angular
- Vue
import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity";
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity";
import { S3Client } from "@aws-sdk/client-s3";
const REGION = "us-east-1";
const IDENTITY_POOL_ID = "us-east-1:YOUR_IDENTITY_POOL_ID";
const USER_POOL_ID = "us-east-1_YOUR_USER_POOL_ID";
export function createAuthenticatedS3Client(idToken: string): S3Client {
return new S3Client({
region: REGION,
credentials: fromCognitoIdentityPool({
client: new CognitoIdentityClient({ region: REGION }),
identityPoolId: IDENTITY_POOL_ID,
logins: {
[`cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}`]: idToken,
},
}),
});
}
import { Injectable } from "@angular/core";
import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity";
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity";
import { S3Client } from "@aws-sdk/client-s3";
@Injectable({ providedIn: "root" })
export class AwsCredentialsService {
private readonly region = "us-east-1";
private readonly identityPoolId = "us-east-1:YOUR_IDENTITY_POOL_ID";
private readonly userPoolId = "us-east-1_YOUR_USER_POOL_ID";
createAuthenticatedS3Client(idToken: string): S3Client {
return new S3Client({
region: this.region,
credentials: fromCognitoIdentityPool({
client: new CognitoIdentityClient({ region: this.region }),
identityPoolId: this.identityPoolId,
logins: {
[`cognito-idp.${this.region}.amazonaws.com/${this.userPoolId}`]: idToken,
},
}),
});
}
}
// composables/useAwsCredentials.ts
import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity";
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity";
import { S3Client } from "@aws-sdk/client-s3";
const REGION = "us-east-1";
const IDENTITY_POOL_ID = "us-east-1:YOUR_IDENTITY_POOL_ID";
const USER_POOL_ID = "us-east-1_YOUR_USER_POOL_ID";
export function useAwsCredentials() {
function createAuthenticatedS3Client(idToken: string): S3Client {
return new S3Client({
region: REGION,
credentials: fromCognitoIdentityPool({
client: new CognitoIdentityClient({ region: REGION }),
identityPoolId: IDENTITY_POOL_ID,
logins: {
[`cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}`]: idToken,
},
}),
});
}
return { createAuthenticatedS3Client };
}
Benefits of This Approach
- No backend proxy — Resumes upload directly from the browser to S3, reducing server load and complexity.
- Scalability — S3 handles upload scaling directly; no bottleneck through your application servers.
- Security — Temporary credentials expire automatically. IAM roles enforce least-privilege — clients can only
PutObject, nothing else. - Flexibility — The same pattern works for anonymous uploads and authenticated uploads with minimal code change.