GitHub this note shows how to build and deploy a simple nextjs web app on amplify.
Let create a new nextjs project
npx create-next-app@latest
Install depdencies for cognito and s3 client
npm i @aws-sdk/client-s3 @aws-sdk/credential-providers
Create .env.local to store enviroment variables locally
IDENTITY_POOL_ID=
REGION=
S3_REGION=
BUCKET=
Let create a cdk project and the create a cognito userpool
export class CognitoAuthorizer extends Stack {
public readonly userPool: string;
public readonly identityPool: aws_cognito.CfnIdentityPool;
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
const pool = new aws_cognito.UserPool(this, "UserPoolForWebEntest", {
userPoolName: "UserPoolForWebEntest",
selfSignUpEnabled: true,
signInAliases: {
email: true,
},
autoVerify: {
email: true,
},
removalPolicy: RemovalPolicy.DESTROY,
});
const client = pool.addClient("WebClient", {
authFlows: {
userPassword: true,
adminUserPassword: true,
custom: true,
userSrp: true,
},
userPoolClientName: "WebClient",
});
const cognitoIdentityPool = new aws_cognito.CfnIdentityPool(
this,
"IdentityPoolForWebEntest",
{
allowUnauthenticatedIdentities: true,
identityPoolName: "IdentityPoolForWebEntest",
cognitoIdentityProviders: [
{
clientId: client.userPoolClientId,
providerName: pool.userPoolProviderName,
},
],
// supportedLoginProviders: [],
}
);
this.userPool = pool.userPoolArn;
this.identityPool = cognitoIdentityPool;
}
}
Let create an identity pool and attach
interface CognitoAuthProps extends StackProps {
cognitoIdentityPool: aws_cognito.CfnIdentityPool;
}
export class CognitoAuthRole extends Stack {
constructor(scope: Construct, id: string, props: CognitoAuthProps) {
super(scope, id, props);
const role = new aws_iam.Role(this, "RoleForAuthenticatedUserWebEntest", {
assumedBy: new aws_iam.FederatedPrincipal(
"cognito-identity.amazonaws.com",
{
StringEquals: {
"cognito-identity.amazonaws.com:aud": props.cognitoIdentityPool.ref,
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated",
},
},
"sts:AssumeRoleWithWebIdentity"
),
roleName: "RoleForAuthenticatedUserWebEntest",
});
role.addToPolicy(
new aws_iam.PolicyStatement({
effect: aws_iam.Effect.ALLOW,
actions: ["s3:PutObject", "s3:GetObject"],
resources: ["arn:aws:s3:::BUCKET_NAME", "arn:aws:s3:::BUCKET_NAME/*"],
})
);
// unauth role
const unauthRole = new aws_iam.Role(
this,
"RoleForUnAuthenticatedUserWebEntest",
{
assumedBy: new aws_iam.FederatedPrincipal(
"cognito-identity.amazonaws.com",
{
StringEquals: {
"cognito-identity.amazonaws.com:aud":
props.cognitoIdentityPool.ref,
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "unauthenticated",
},
},
"sts:AssumeRoleWithWebIdentity"
),
roleName: "RoleForUnAuthenticatedUserWebEntest",
}
);
unauthRole.addToPolicy(
new aws_iam.PolicyStatement({
effect: aws_iam.Effect.ALLOW,
actions: ["s3:PutObject", "s3:GetObject"],
resources: ["arn:aws:s3:::BUCKET_NAME", "arn:aws:s3:::BUCKET_NAME/*"],
})
);
const attach = new aws_cognito.CfnIdentityPoolRoleAttachment(
this,
"RoleAttachmentForWebEntest",
{
identityPoolId: props.cognitoIdentityPool.ref,
roles: {
authenticated: role.roleArn,
unauthenticated: unauthRole.roleArn,
},
}
);
}
}
Let create a simple upload form
"use client";
import { useState } from "react";
import handleForm from "./action";
const FormPage = () => {
const [status, setStatus] = useState<string | null>(null);
const [message, setMessage] = useState<string | null>(null);
const submit = async (data: FormData) => {
try {
const res = await handleForm(data);
setMessage(res.message);
setStatus("success");
} catch (error) {
console.log(error);
}
};
return (
<div className="dark:bg-slate-800 min-h-screen">
<div className="mx-auto max-w-4xl px-5">
{status ? (
<div className="dark:text-white">Submitted {message}</div>
) : (
<form action={submit}>
<div className="grid gap-6 mb-6 grid-cols-1">
<div>
<label
htmlFor="first_name"
className="block mb-2 text-sm font-medium dark:text-white"
>
First Name
</label>
<input
id="first_name"
type="text"
placeholder="haimtran"
name="username"
className="text-sm rounded-sm block w-full p-2.5 "
></input>
</div>
<div>
<label
htmlFor="email"
className="block mb-2 text-sm font-medium dark:text-white"
>
Email
</label>
<input
id="email"
type="text"
placeholder="hai@entest.io"
name="email"
className="text-sm rounded-sm w-full p-2.5 "
></input>
</div>
<div>
<label
htmlFor="upload"
className="block mb-2 text-sm font-medium dark:text-white"
>
Upload File
</label>
<input
id="upload"
type="file"
name="upload"
className="text-sm rounded-sm w-full p-2.5 cursor-pointer dark:bg-white"
></input>
</div>
<div>
<button className="bg-orange-400 px-10 py-3 rounded-sm">
Submit
</button>
</div>
</div>
</form>
)}
</div>
</div>
);
};
Let create a server function to handle the upload file
"use server";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { fromCognitoIdentityPool } from "@aws-sdk/credential-providers";
import { writeFile } from "fs/promises";
import { join } from "path";
const s3Client = new S3Client({
region: process.env.S3_REGION,
credentials: fromCognitoIdentityPool({
clientConfig: { region: process.env.REGION },
identityPoolId: process.env.IDENTITY_POOL_ID as string,
}),
});
const handleForm = async (data: FormData) => {
const file: File | null = data.get("upload") as unknown as File;
if (!file) {
throw new Error("No file uploaded");
}
// file buffer
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
// write to local
const path = join("./", file.name);
await writeFile(path, buffer);
console.log(`open ${path} to see the upload file`);
// write to s3
await s3Client.send(
new PutObjectCommand({
Bucket: process.env.BUCKET,
Key: `web-entest/${file.name}`,
Body: buffer,
})
);
return {
status: "OK",
message: `${file.name} '${process.env.BUCKET as string}`,
};
};
export default handleForm;
Please ensure to
Here is content of the build.yaml for amplify build
version: 1
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
- echo "IDENTITY_POOL_ID=$IDENTITY_POOL_ID" >> .env
- echo "REGION=$REGION" >> .env
- echo "S3_REGION=$S3_REGION" >> .env
- echo "BUCKET=$BUCKET" >> .env
- npm run build
artifacts:
baseDirectory: .next
files:
- "**/*"