Skip to main content

Day 12: Deploy your frontend (with authentication)

ยท 48 min read
Norah Klintberg Sakal
AI Consultant & Developer

Deploy your frontend (with authentication)

What you'll learn

How to build a secure frontend with Vite, React and Cognito authentication, then deploy it to S3 + CloudFront

Protect your app from unauthorized useโ€‹

Days 9-11: You built the infrastructure

Today: We build the protected frontend

Here's the critical security issue:

When you deploy your AI calling agent:

https://app.yourdomain.com
โ†’ Publicly accessible
โ†’ Anyone can use it
โ†’ Could rack up huge OpenAI/Twilio bills

This is not acceptable.

What you need:

โœ… Login page (only authorized users)
โœ… Simple authentication (email + password)
โœ… Protected routes (must be logged in)

Solution: Cognito User Poll + Amplify UI

Think of Cognito like a private party at your pool house (the AI container in your backyard):

Without auth:

  • You announce a party at your pool house
  • Anyone from the city can walk in
  • Strangers eat your food, use your pool, run up your utilities
  • Chaos (and a massive bill)

With auth:

  • Guests need an invitation (account)
  • They show their invite at the front'yard house (ALB)
  • Only invited guests reach the pool house
  • You control the guest list

Cognito User Pool = your guest list

  • Who's invited to the party
  • What their names/emails are

Amplify UI = checking invites

  • Handles "show me your invitation" flow
  • Turns away uninvited guests before they reach the pool house

Think of Cognito as your guest list for the pool party. It decides who gets an invitation to your backyard: Think of Cognito as your guest list for the pool party. It decides who gets an invitation to your backyard

Think of Cognito as your guest list for the pool party. It decides who gets an invitation to your backyard

By the end of today, you'll have:

โœ… Cognito User Pool (manages users)
โœ… Vite + React frontend
โœ… AWS Amplify UI (pre-built login component)
โœ… Protected app (must login first)
โœ… Deployed to S3 + CloudFront
โœ… Custom domain with HTTPS

Let's build your protected frontend ๐ŸŽจ๐Ÿ”

What you'll build todayโ€‹

A complete authenticated frontend:

ComponentTechnologyPurpose
Frontend FrameworkVite + ReactFast, modern build tool
AuthenticationAWS CognitoUser management
UI ComponentsAWS Amplify UIPre-built login/signup
HostingS3 + CloudFrontStatic site hosting + CDN
DomainRoute 53app.yourdomain.com
SSLACMHTTPS

User flow:

What you'll learnโ€‹

  • How to set up Cognito User Pool
  • How to integrate Amplify UI for auth
  • How to protect React routes
  • How to deploy to S3 as static site
  • How to add CloudFront for CDN
  • How to configure custom domain for frontend
This advent calendar is completely free.

But if you want:

โœ… Complete codebase (one clean repo)
โœ… Complete walkthroughs
โœ… Support when stuck
โœ… Production templates
โœ… Advanced features

Join the waitlist for the full course (launching February 2026):

Building something with AI calling? Let's chat about your use case!
Schedule a free call โ†— - no pitch, just two builders talking.

Time requiredโ€‹

45 minutes

  • Cognito setup: 10 min
  • Frontend build: 20 min
  • Deploy: 15 min

Prerequisitesโ€‹

โœ… Completed Day 3 (VPC) โ†—
โœ… Completed Day 4 (Subnets) โ†—
โœ… Completed Day 5 (NAT Gateway) โ†—
โœ… Completed Day 6 (Route Tables) โ†—
โœ… Completed Day 7 (Security Groups) โ†—
โœ… Completed Day 8 (prove it works) โ†—
โœ… Completed Day 9 (Application Load Balancer) โ†—
โœ… Completed Day 10 (Custom Domain) โ†—
โœ… Completed Day 11 (SSL Certificate) โ†—
โœ… Node.js installed (v18+)
โœ… Access to AWS Console

Understanding the auth architecture (3-minute primer)โ€‹

What is AWS Cognito?โ€‹

Cognito = Managed authentication service from AWS

It handles:
โœ… User sign up
โœ… Email verification
โœ… Password management
โœ… Login/logout
โœ… JWT token
โœ… Multi-factor auth (optional)
โœ… Social login (Google, Facebook, etc.)

You don't build from scratch. Cognito does it all.

Cognito = Managed authentication service from AWS: Cognito = Managed authentication service from AWS

Cognito = Managed authentication service from AWS

What is AWS Amplify UI?โ€‹

Amplify UI = Pre-built React components for authentication

Instead of building:

  • Login form
  • Signup form
  • Forgot password form
  • Email verification
  • Password reset

You get:

import { Authenticator } from '@aws-amplify/ui-react'

<Authenticator>
{({ signOut, user }) => (
<YourApp user={user} signOut={signOut} />
)}
</Authenticator>

Done. That's it.

Why this architecture?โ€‹

S3 + CloudFront for frontend:
โœ… Cheap (~$0.50/month for low traffic)
โœ… Fast (CloudFront CDN)
โœ… Scaled automatically
โœ… No server management
โœ… HTTPS included

Cognito for auth:
โœ… No backend auth logic needed
โœ… Secure by default
โœ… Scales to millions of users
โœ… Free tier: 10,000 MAUs (monthly active users)

This is the modern way to build frontends.

Step 1: Create Cognito User Poolโ€‹

Let's start by setting up authentication.

Open the AWS Console โ†—

In the search bar at the top, type cognito and click Cognito from the dropdown:

In the search bar at the top, type cognito and click Cognito from the dropdown

In the search bar at the top, type cognito and click Cognito from the dropdown

Click the left menu button and then click User pools in the left menu:

Click the left menu button and then click User pools

Click the left menu button and then click User pools in the left menu

Click Create user pool:

Click Create user pool

Click Create user pool

Select Single-page application (SPA):

Select Single-page application (SPA)

Select Single-page application (SPA)

Name your user pool app in the text field Name your application:

Name your user pool app ai-caller-app-client in the text field Name your application

Name your user pool app 'ai-caller-app-client' in the text field Name your application

Scroll down to Configure options and check Email as Options for sign-in identifiers:

Scroll down to Configure options and check Email as Options for sign-in identifiers

Scroll down to Configure options and check Email as Options for sign-in identifiers

Why email:

  • Users already have emails
  • Don't need to remember usernames
  • Email verification is built in

Enable self-registration:

Enable self-registration

Enable self-registration

Why self-registration?

For now: Enable this so you can create your own account and test.

Later: Uncheck it to control who gets access (invite-only).

Scroll down, skip Return URL, click Create user directory

Scroll down, skip Return URL, click Create user directory

Scroll down, skip Return URL, click Create user directory

โœ… You should see "Your application and user pool have been successfully created"

Skip the Quick setup guide

AWS shows a "Quick setup guide" with sample code using react-oidc-context

Ignore it. We're using Amplify UI instead (simpler, less code).

Scroll all the way down and click Go to overview:

Scroll all the way down and click Go to overview

Scroll all the way down and click Go to overview

This will take you to the overview of your new Cognito user pool with your user pool name and ID:

This will take you to the overview of your new Cognito user pool

This will take you to the overview of your new Cognito user pool

Let's change the automatically generated user pool name.

Click Rename:

Click Rename

Click Rename

Name your user pool and click Save changes:

Name your user pool ai-caller-user-pool and click Save changes

Name your user pool 'ai-caller-user-pool' and click Save changes

โœ… You should see "User pool name successfully updated":

You should see User pool name successfully updated

You should see "User pool name successfully updated"

Step 1.2: Save your Cognito IDsโ€‹

You'll need these for your frontend:

  • user pool ID
  • app client ID
  • region

Click the copy icon to copy your user pool name:

Click the copy icon to copy your user pool name

Click the copy icon to copy your user pool name

Save this user pool name. You'll need it in the next step.

Note the region. E.g. us-east-1 save this as well.

Click App clients in the left menu:

Click App clients in the left menu

Click App clients in the left menu

Click on your app client:

Click on your app client

Click on your app client

Click the copy icon to copy your Client ID:

Click the copy icon to copy your Client ID

Click the copy icon to copy your Client ID

Save these somewhere safe! You'll use them in your React app.

Step 2: Create Vite + React frontendโ€‹

Now let's build the frontend.

Open your terminal and run this to create a new Vite project:

Your terminal
npm create vite@latest ai-caller-frontend -- --template react

Select No when asked if use rolldown-vite:

Select No when asked if use rolldown-vite

Select No when asked if use rolldown-vite

Select Yes when asked to install npm and start now:

Select Yes when asked if to install npm and start now

Select Yes when asked to install npm and start now

โœ… You should see a started dev server and localhost address once installed:

You should see a localhost address once installed

You should see a localhost address once installed

What just happened?

You started a local dev server.

Deep dive

That localhost:5173is your app running on your machine:

  • Vite = build tool (fast, modern)
  • Dev server = live preview while you code
  • Hot reload = changes appear instantly (no refresh needed)

Think of it as a mini web server on your laptop. Only you can see it (for now).

Later: We'll deploy to S3 + CloudFront so the work can see it too.

Visit the localhost URL, e.g., http://localhost:5173/:

Visit the localhost address you see, e.g., http://localhost:5173/

Visit the localhost URL, e.g., http://localhost:5173/

โœ… You should see: Spinning React logo and a count button.

Step 2.1 Install Amplifyโ€‹

Open a new terminal window in addition to the running Vite app, and run:

Your new terminal window
npm install aws-amplify @aws-amplify/ui-react
Make sure you're in ai-caller-frontend

Make sure you're in ai-caller-frontend root when installing Amplify

Run this to check:

Your terminal
pwd

โœ… You should see the path /ai-caller-frontend

Step: 2.2: Configure Amplifyโ€‹

Create a new file called inside the src folder:

Your folder system
.
โ”œโ”€โ”€ eslint.config.js
โ”œโ”€โ”€ index.html
โ”œโ”€โ”€ package-lock.json
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ public
โ”‚ย ย  โ””โ”€โ”€ vite.svg
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ src
โ”‚ย ย  โ”œโ”€โ”€ App.css
โ”‚ย ย  โ”œโ”€โ”€ App.jsx
โ”‚ย ย  โ”œโ”€โ”€ assets
โ”‚ย ย  โ”œโ”€โ”€ aws-exports.js <--- Create this new file
โ”‚ย ย  โ”œโ”€โ”€ index.css
โ”‚ย ย  โ””โ”€โ”€ main.jsx
โ””โ”€โ”€ vite.config.js

Add this to src/aws-exports.js:

src/aws-exports.js
// src/aws-exports.js
const awsconfig = {
aws_project_region: 'us-east-1', // Your region
aws_cognito_region: 'us-east-1', // Your region
aws_user_pools_id: 'us-east-1_aBcDeFgHi', // YOUR USER POOL ID
aws_user_pools_web_client_id: '1a2b3c4d5e6f7g8h9i0j1k2l3m', // YOUR CLIENT ID
};

export default awsconfig;

โš ๏ธ Replace with your values:

  • aws_user_pools_id: Your User Pool ID
  • aws_user_pools_web_client_id: Your Client ID
  • Change the region if not in us-east-1
Didn't save your user pool is and client id?
Here's how to find them again

Open the AWS Console โ†—

In the search bar at the top, type cognito and click Cognito from the dropdown:

In the search bar at the top, type cognito and click Cognito from the dropdown

In the search bar at the top, type cognito and click Cognito from the dropdown


You'll see your user pool listed, click on it:

You will see your user pool, click on it

You'll see your user pool, click on it


Click the copy icon to copy your user pool name:

Click the copy icon to copy your user pool name

Click the copy icon to copy your user pool name


Save this user pool name.

Note the region. E.g. us-east-1 save this as well.

Click App clients in the left menu:

Click App clients in the left menu

Click App clients in the left menu


Click on your app client:

Click on your app client

Click on your app client


Click the copy icon to copy your Client ID:

Click the copy icon to copy your Client ID

Click the copy icon to copy your Client ID


Save these somewhere safe and add them to src/aws-exports.js

Step 2.2: Install MUIโ€‹

Let's install Material UI.

Run this in your terminal to install Material UI:

Your terminal
npm install @mui/material @emotion/react @emotion/styled

Step 2.3: Create protected app componentโ€‹

Open the file src/App.jsx in your editor.

.
โ”œโ”€โ”€ eslint.config.js
โ”œโ”€โ”€ index.html
โ”œโ”€โ”€ package-lock.json
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ public
โ”‚ย ย  โ””โ”€โ”€ vite.svg
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ src
โ”‚ย ย  โ”œโ”€โ”€ App.css
โ”‚ย ย  โ”œโ”€โ”€ App.jsx <--- Open this file
โ”‚ย ย  โ”œโ”€โ”€ assets
โ”‚ย ย  โ”œโ”€โ”€ aws-exports.js
โ”‚ย ย  โ”œโ”€โ”€ index.css
โ”‚ย ย  โ””โ”€โ”€ main.jsx
โ””โ”€โ”€ vite.config.js

Replace the src/App.jsx code with this snippet:

src/App.jsx
// src/App.jsx
import { Amplify } from 'aws-amplify';
import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
import {
Box,
Button,
Card,
CardContent,
Container,
TextField,
Typography,
AppBar,
Toolbar
} from '@mui/material';
import awsconfig from './aws-exports';

Amplify.configure(awsconfig);

function App() {
return (
<Box
sx={{
minHeight: "100vh",
display: "flex",
flexDirection: "column",
justifyContent: "center", // vertical
alignItems: "center", // horizontal (optional)
}}
>
<Authenticator loginMechanisms={['email']}>
{({ signOut, user }) => (
<>
<AppBar position="static" sx={{ bgcolor: '#667eea' }}>
<Toolbar sx={{ justifyContent: 'space-between' }}>
<Typography variant="h6" sx={{ fontWeight: 600 }}>
๐Ÿค– AI Caller
</Typography>
<Button
color="inherit"
onClick={signOut}
sx={{ fontWeight: 600 }}
>
Sign Out
</Button>
</Toolbar>
</AppBar>

<Container maxWidth="sm" sx={{ py: 4 }}>
<Card sx={{ mb: 3, borderRadius: 2 }}>
<CardContent>
<Typography variant="h5" sx={{ fontWeight: 600, mb: 1 }}>
Welcome, {user.signInDetails.loginId}! ๐Ÿ‘‹
</Typography>
<Typography color="text.secondary">
Your AI calling agent is ready.
</Typography>
</CardContent>
</Card>

<Card sx={{ mb: 3, borderRadius: 2 }}>
<CardContent>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>
Make a Call
</Typography>
<TextField
fullWidth
label="Phone number"
placeholder="+1234567890"
type="tel"
sx={{ mb: 2 }}
/>
<Button
variant="contained"
fullWidth
size="large"
sx={{
bgcolor: '#667eea',
fontWeight: 600,
'&:hover': { bgcolor: '#5568d3' }
}}
>
๐Ÿ“ž Start AI Call
</Button>
</CardContent>
</Card>

<Card sx={{ borderRadius: 2 }}>
<CardContent>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 1 }}>
Call Status
</Typography>
<Typography sx={{ color: '#10b981', fontWeight: 600 }}>
Ready to call
</Typography>
</CardContent>
</Card>
</Container>
</>
)}
</Authenticator>
</Box>
);
}

export default App;

Head back to your browser with http://localhost:5173 and you should now see a login screen:

Head back to your browser with localhost:5173 and you should now see a login screen

Head back to your browser with localhost:5173 and you should now see a login screen

Step 2.4: Center login screenโ€‹

Let's center the login screen.

Open the file src/index.css in your editor:

.
โ”œโ”€โ”€ eslint.config.js
โ”œโ”€โ”€ index.html
โ”œโ”€โ”€ package-lock.json
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ public
โ”‚ย ย  โ””โ”€โ”€ vite.svg
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ src
โ”‚ย ย  โ”œโ”€โ”€ App.css
โ”‚ย ย  โ”œโ”€โ”€ App.jsx
โ”‚ย ย  โ”œโ”€โ”€ assets
โ”‚ย ย  โ”œโ”€โ”€ aws-exports.js
โ”‚ย ย  โ”œโ”€โ”€ index.css <--- Open this file
โ”‚ย ย  โ””โ”€โ”€ main.jsx
โ””โ”€โ”€ vite.config.js

Add this snippet to the end of the src/index.css file:

src/index.css
/* Rest of your code */

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: 'Roboto', sans-serif;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}

Head back to http://localhost:5173:

Head back to http://localhost:5173. You should now see the login screen centered

Head back to http://localhost:5173. You should now see the login screen centered

โœ… You should now see the login screen centered.

Try signing up:

  1. Click "Create Account"
  2. Enter email + password
  3. Check email for verification code
  4. Enter code
  5. Login

Check email for verification code

Check email for verification code

You should see your protected app! ๐ŸŽ‰

You should see your protected app! ๐ŸŽ‰

You should see your protected app! ๐ŸŽ‰

What just happened?

You created a real user in Cognito.

When you signed up:

  1. Amplify UI sent your email + password to Cognito
  2. Cognito created a user in your User pool
  3. Cognito emailed you a verification code
  4. You verified โ†’ Cognito marked your account as confirmed
  5. Cognito returned a JWT token to your browser
  6. Amplify stored the token and showed the protected app

No backend code. No database. Cognito handles it all.

Step 2.5: Check new userโ€‹

Head back to the AWS Console โ†—

In the AWS Console search bar at the top, type cognito and click Cognito from the dropdown menu:

In the search bar at the top, type cognito and click Cognito from the dropdown

In the search bar at the top, type cognito and click Cognito from the dropdown

Click the left menu button and then click User pools in the left menu:

Click the left menu button and then click User pools

Click the left menu button and then click User pools in the left menu

You'll see your user pool listed, click on it:

You will see your user pool, click on it

You'll see your user pool, click on it

Click Users in the left menu:

Click Users in the left menu

Click Users in the left menu

โœ… You should see the user you just created:

You should see the user you just created

You should see the user you just created

Step 3: Build for productionโ€‹

Run this in your terminal:

Your terminal
npm run build
Make sure you're in ai-caller-frontend

Make sure you're in ai-caller-frontend root when running npm run build

Run this to check:

Your terminal
pwd

โœ… You should see the path /ai-caller-frontend

Run npm run build in your terminal

Run npm run build in your terminal

This creates a dist/ folder with optimized production files.

Open your folder system, you should see the new folder dist:

Your folder system
.
โ”œโ”€โ”€ dist <--- New folder
โ”‚ย ย  โ”œโ”€โ”€ assets
โ”‚ย ย  โ”œโ”€โ”€ index.html
โ”‚ย ย  โ””โ”€โ”€ vite.svg
โ”œโ”€โ”€ eslint.config.js
โ”œโ”€โ”€ index.html
โ”œโ”€โ”€ package-lock.json
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ public
โ”‚ย ย  โ””โ”€โ”€ vite.svg
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ src
โ”‚ย ย  โ”œโ”€โ”€ App.css
โ”‚ย ย  โ”œโ”€โ”€ App.jsx
โ”‚ย ย  โ”œโ”€โ”€ assets
โ”‚ย ย  โ”œโ”€โ”€ aws-exports.js
โ”‚ย ย  โ”œโ”€โ”€ index.css
โ”‚ย ย  โ””โ”€โ”€ main.jsx
โ””โ”€โ”€ vite.config.js

Step 4: Deploy to S3โ€‹

Now let's host the frontend on S3.

Open the AWS Console โ†—

In the search bar at the top, type s3 and click S3 from the dropdown:

In the search bar at the top, type s3 and click S3 from the dropdown

In the search bar at the top, type s3 and click S3 from the dropdown

Click Create bucket:

Click Create bucket

Click Create bucket

Your unique bucket name

S3 bucket names must be globally unique - no two buckets in the world can have the same name.

I've generated a unique ID for you:

This ID is saved in your browser, so it stays the same throughout this tutorial.

Use these bucket settings:

FieldValue
Bucket typeGeneral purpose
Bucket name (already unique to you!)
Object ownershipACLs disabled
Block all public accessUNCHECK (we need public access for website)
Acknowledgeโœ… Check the warning box

Uncheck Block all public access and check the warning box (we need public access for website):

Uncheck Block all public access and check the warning box (we need public access for website)

Uncheck Block all public access and check the warning box (we need public access for website)

Scroll all the way down and click Create bucket:

Scroll all the way down and click Create bucket

Scroll all the way down and click Create bucket

โœ… You should see "Successfully created bucket":

You should see Successfully created bucket

You should see "Successfully created bucket"

Step 4.1: Enable static website hostingโ€‹

Click on your bucket

Click on your bucket

Click on your bucket

Click Properties tab:

Click Properties tab

Click Properties tab

Scroll down to Static website hosting and click Edit:

Scroll down to Static website hosting and click Edit

Scroll down to Static website hosting and click Edit

Click Enable static website hosting and add the following settings:

Click Enable static website hosting and add the following settings

Click Enable static website hosting and add the following settings

Add these settings to enable static website hosting:

FieldValue
Static website hostingEnable
Hosting typeHost a static website
Index document
Error document

Scroll down and click Save changes:

Scroll down and click Save changes

Scroll down and click Save changes

โœ… You should see "Successfully edited static website hosting":

You should see Successfully edited static website hosting

You should see "Successfully edited static website hosting"

Scroll down to Static website hosting and copy the website endpoint (looks like http://ai-caller-frontend-xxxxx.s3-website-us-east-1.amazonaws.com)

Scroll down to Static website hosting and copy the website endpoint

Scroll down to Static website hosting and copy the website endpoint

Step 4.2: Add bucket policyโ€‹

Scroll back upp and click the Permissions tab:

Scroll back upp and click the Permissions tab

Scroll back upp and click the Permissions tab

Scroll down to Bucket policy and click Edit:

Scroll down to Bucket policy and click Edit

Scroll down to Bucket policy and click Edit

Add this policy:

{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Sid": "PublicReadGetObject",
          "Effect": "Allow",
          "Principal": "*",
          "Action": "s3:GetObject",
          "Resource": "arn:aws:s3:::{BUCKET_NAME}/*"
      }
  ]
}

Add this policy

Add this policy

Scroll all the way down and click Save changes:

Scroll all the way down and click Save changes

Scroll all the way down and click Save changes

โœ… You should see Successfully edited bucket policy:

You should see Successfully edited bucket policy

You should see Successfully edited bucket policy

Step 4.3 Upload filesโ€‹

Scroll back upp and click the Objects tab:

Scroll back upp and click the Objects tab

Scroll back upp and click the Objects tab

Click Upload:

Click Upload

Click Upload

Click Add files:

Click Add files

Click Add files

Select all files from dist/ folder and click Open:

Select all files from dist/ folder

Select all files from dist/ folder and click Open

Important: Upload the contents of dist/

Important: Upload the contents of dist/, not the dist/ folder itself.

You should upload:

  • index.html
  • assets/ folder
  • vite.svg

โœ… You should see the selected files:

You should see the selected files

You should see the selected files

Click Add folder:

Click Add folder

Click Add folder

Select the assets folder and click Upload: Select the assets folder and click Upload

Select the assets folder and click Upload

Click Upload when asked if upload files from the folder assets: Click Upload when asked if upload files from Assets

Click Upload when asked if upload files from Assets

You should see the selected files and the files in the assets folder:

You should see the selected files and the files in the assets folder

You should see the selected files and the files in the assets folder

Scroll down and click Upload:

Scroll down and click Upload

Scroll down and click Upload

โœ… You should see "Upload succeeded":

You should see Upload succeeded

You should see "Upload succeeded"

Step 4.4: Test S3 websiteโ€‹

Visit your S3 website endpoint: http://ai-caller-frontend-xxxxx.s3-website-us-east-1.amazonaws.com

โœ… You should see your login page:

You should see your login page

You should see your login page

Step 5: Add CloudFront CDNโ€‹

Why add CloudFront?

S3 websites are HTTP only, so you'll see "Not secure" when visiting your S3 website endpoint:

S3 websites are HTTP only, so youll see Not secure when visiting your S3 website endpoint

S3 websites are HTTP only, so you'll see "Not secure" when visiting your S3 website endpoint

Let's add Cloudfront for HTTPS + CDN.

In the search bar at the top, type cloudfront and click CloudFront from the dropdown menu:

In the search bar at the top, type cloudfront and click CloudFront from the dropdown

In the search bar at the top, type cloudfront and click CloudFront from the dropdown

Click Create distribution:

Click Create distribution

Click Create distribution

Step 5.1: Choose flat-rate pricingโ€‹

AWS now offers flat-rate pricing plans for Cloudfront and there's a free tier.

You'll see a popup introducing flat-rate plans, click Create flat-rate distribution:

Youll see a popup introducing flat-rate plans, click Create flat-rate distribution

You'll see a popup introducing flat-rate plans, click Create flat-rate distribution

Select the Free flat-rate plan ($0/month):

Select the Free flat-rate plan

Select the Free flat-rate plan

Why flat-rate free plan?

The free plan includes everything you need at $0/month.

โœ… 1M requests / 100GB per month - plenty for learning projects
โœ… Global CDN - fast loading worldwide
โœ… Free TLS certificate - automatic HTTPS
โœ… DDoS protection - attacks don't count against your quota
โœ… AWS WAF - web application firewall included
โœ… No overage charges - if you exceed limits, you get throttled (not billed)

This is perfect for learning. No surprise bills.

Scroll down and click Next:

Scroll down and click Next

Scroll down and click Next

Step 5.2: Configure distributionโ€‹

Add these distribution settings:

FieldValue
Distribution name
DescriptionLeave empty (optional)
Distribution typeSingle website or app โœ…

Name your distribution and select Single website or app:

Name your distribution and select Single website or app

Name your distribution and select Single website or app

Domain:

Enter your app. subdomain in the Route 53 managed domain field:

FieldValue
Route 53 managed domain

Click Next:

Click Next

Click Next

Step 5.3: Origin settingsโ€‹

Select Amazon S3 as Origin type:

Select Amazon S3 as Origin type

Select Amazon S3 as Origin type

Add these origin settings:

FieldValue
Origin domainPaste your S3 website endpoint: (NOT the bucket name dropdown!)
ProtocolHTTP only
NameKeep default

Paste your S3 website endpoint:

(NOT the bucket name dropdown!):

Paste your S3 website endpoint (NOT the bucket name dropdown!)

Paste your S3 website endpoint (NOT the bucket name dropdown!) and click Next

Important: Use website endpoint

Don't select the bucket from the dropdown (that's for S3 API access).

Do paste the website endpoint:

{BUCKET_NAME}.s3-website-us-east-1.amazonaws.com

This ensures proper routing for React Router.

Default origin settings:
Keep defaults (these are good for SPAs).

Default cache behavior:
Keep defaults (these are good for SPAs).

Click Next:

Click Next

Click Next

Step 5.4: Enable securityโ€‹

The free plan includes WAF (Web Application Firewall) at no extra cost.

WAF settings:

FieldValue
Use monitor modeโŒ Leave unchecked
What's included for free?

You free plan automatically protects against:

โœ… Common web vulnerabilities (SQL injection, XSS)
โœ… Malicious actors probing for weaknesses
โœ… Known bad IP addresses (AWS threat intelligence)

Monitor mode just logs threats without blocking them, we want actual protection, so leave it unchecked.

Layer 7 DDoS protection

You'll see "Protection against Layer 7 DDoS attacks" grayed out, that's a Business plan feature ($200/month).

Don't worry! The Free plan still includes basic DDoS protection at the network level.

For a learning project, that's plenty.

Leave "Use monitor mode" unchecked and click Next:

Leave Use monitor mode unchecked and click Next

Leave "Use monitor mode" unchecked and click Next

Step 5.5: TLC certificateโ€‹

Remember the SSL certificate you created on Day 11? It's already here!

CloudFront automatically found your certificate from AWS Certificate Manager:

CloudFront automatically found your certificate from AWS Certificate Manager

CloudFront automatically found your certificate from AWS Certificate Manager

Select your certificate:

FieldValue
Available certificatesSelect your certificate (e.g., ai-caller.yourdomain.com)

You'll see the Certificate details:

  • ARN: your certificate's unique identifier
  • Covered domains: ai-caller.yourdomain.com and the wildcard *.yourdomain.com
  • Source: Amazon
Wildcard paid off

This is why we created a wildcard certificate (*.yourdomain.com) on Day 11 โ†—.

It covers app.yourdomain.com and any other subdomain you might add later.
Without having to request a new certificate.

Click Next:

Click Next

Click Next

Step 5.6: Review and createโ€‹

Scroll down and review your settings:

Scroll down and review your settings

Scroll down and review your settings

Scroll all the way down and click Create distribution:

Scroll all the way down and click Create distribution

Scroll all the way down and click Create distribution

โœ… You should see "Successfully created new distribution":

You should see Successfully created new distribution

You should see "Successfully created new distribution"

Wait 5-10 minutes for CloudFront to deploy:

Wait 5-10 minutes for CloudFront to deploy

Wait 5-10 minutes for CloudFront to deploy

Click distributions:

Once you see deploying shift to enabled CloudFront is successfully deployed

Once you see deploying shift to enabled CloudFront is successfully deployed

โœ… Once you see deploying shift to enabled CloudFront is successfully deployed.

Step 6: Add custom domainโ€‹

In the search bar at the top, type route 53 and click Route 53 from the dropdown menu:

In the search bar at the top, type route 53 and click Route 53 from the dropdown menu

In the search bar at the top, type route 53 and click Route 53 from the dropdown menu

Click Hosted zones in the left menu:

Click Hosted zones in the left menu

Click Hosted zones in the left menu

Click your domain then click View details:

Click your domain then click View details

Click your domain then click View details

Click Create record:

Click Create record

Click Create record

Add these values:

FieldValue
Record name
Record typeA
Aliasโœ… ON
Route traffic toAlias to CloudFront distribution
DistributionSelect your CloudFront distribution

Check Alias and select CloudFront distribution in the dropdown:

Check Alias and select CloudFront distribution in the dropdown

Check Alias and select CloudFront distribution in the dropdown

Select your CloudFront distribution from the dropdown:

Select your CloudFront distribution from the dropdown

Select your CloudFront distribution from the dropdown

Click Create records:

Click Create records

Click Create records

โœ… You should see "Successfully created record":

You should see Successfully created record

You should see "Successfully created record"

Step 7: Test your protected appโ€‹

Visit:

https://app.yourdomain.com

Your protected app is live:

Your protected app is live

Your protected app is live

You should see:
โœ… HTTPS (green padlock ๐Ÿ”’ depending on browser)
โœ… Login page (Amplify UI)
โœ… Fast loading (CloudFront CDN)

You should see the login screen

You should see the login screen

Try the full flow:

  1. Click "Create Account"
  2. Enter email + password (min 8 chars, uppercase, number, special char)
  3. Check email for verification code
  4. Enter code
  5. Login
  6. See your protected app ๐ŸŽ‰
How to delete your test user

If you tried to use the same email as when you used the localhost:5173 you'll see the error message saying "User already exists":

If you tried to use the same email as when you used the localhost:5173 youll see the error message saying User already exists

If you tried to use the same email as when you used the localhost:5173 you'll see the error message saying "User already exists"

Here's how to delete the test user before testing the full flow:

Head back to AWS Console โ†—

In the AWS Console search bar at the top, type cognito and click Cognito from the dropdown menu:

In the search bar at the top, type cognito and click Cognito from the dropdown

In the search bar at the top, type cognito and click Cognito from the dropdown

Click the left menu button and then click User pools in the left menu:

Click the left menu button and then click User pools

Click the left menu button and then click User pools in the left menu

You'll see your user pool listed, click on it:

You will see your user pool, click on it

You'll see your user pool, click on it

Click Users in the left menu:

Click Users in the left menu

Click Users in the left menu

Click on the user and click Delete user:

Click on the user and click Delete user

Click on the user and click Delete user

Click Disable user access:

Click Disable user

Click Disable user access

Click Delete:

Click Delete

Click Delete

โœ… You should see "User successfully deleted":

You should see User successfully deleted

You should see "User successfully deleted"

Today's winโ€‹

If you completed all steps:
โœ… Created Cognito User Pool for auth
โœ… Built Vite + React frontend
โœ… Integrated Amplify UI for login/signup
โœ… Protected app with authentication
โœ… Deployed to S3 for static hosting
โœ… Added CloudFront for HTTPS + CDN
โœ… Configured custom domain
โœ… Tested full signup/login flow

You now have a secure frontend.

Before:

  • Anyone could access your API endpoint
  • Infinite OpenAI/Twilio bills possible

After:

  • Must login to access app
  • Only authorized users
  • Protected from unauthorized use โœ…

Tomorrow, we deploy the actual AI containers.

Understanding what you builtโ€‹

The auth flow:

Security layers:

  1. HTTPS encryption
  2. Cognito authentication
  3. JWT tokens
  4. Email verification
  5. Strong password policy
  6. Protected routes in React

This is production-grade auth ๐Ÿ”

Costsโ€‹

Cognito:

  • Free tier: 10,000 MAUs (monthly active users)
  • After free tier: $0.015 per MAU (as of Jan 2026)
  • For development/small apps: free

S3:

  • Storage: $0,023 per GB/month
  • Requests: $0.0004 per 1,000 GET requests
  • For a small frontend: ~$0.10/month

CloudFront:

  • First 1 TB: effectively $0 (free), then $0.085 per GB beyond that
  • 10 million requests: effectively $0 (free), then $0.0075 per 10,000 requests
  • For low traffic: typically around ~$0-1/month

Total for this setup: ~$1-2/month

Common mistakes (and how to avoid them)โ€‹

โŒ Mistake #1: Wrong Cognito IDsโ€‹

Result: Auth doesn't work, blank page
Fix: Double-check User Pool ID and Client ID in aws-exports.js

โŒ Mistake #2: Client secret generatedโ€‹

Result: Auth fails with "client secret required"
Fix: Edit app client โ†’ Regenerate without client secret

โŒ Mistake #3: Using S3 bucket name instead of website endpoint in CloudFrontโ€‹

Result: React routing breaks, 404 errors
Fix: Use S3 website endpoint, not bucket name

โŒ Mistake #4: Forgetting to make S3 bucket publicโ€‹

Result: CloudFront return 403 errors
Fix: Add bucket policy allowing public GetObject

โŒ Mistake #5: Wrong S3 origin URL in CloudFrontโ€‹

Result: 404 Not Found with "NoSuchBucket" error
Fix: Make sure you're using the S3 website endpoint (your-bucket-name.s3-website-us-east-1.amazonaws.com), not the S3 API endpoint.
Go to S3 โ†’ Your bucket โ†’ Properties โ†’ Static website hosting and copy the exact URL.
Watch for typos or duplicated bucket names.

โŒ Mistake #6: Testing before DNS propagatesโ€‹

Result: 504 Gateway Timeout error
Fix: Wait 5-10 minutes after creating the Route 53 A record before testing your custom domain. CloudFront needs time to deploy and DNS needs time to propagate.

Troubleshootingโ€‹

Login page shows but signup fails

Check:

  1. Cognito User Pool allows self-registration
  2. Password meets requirements (8+ chars, uppercase, number, special char)
  3. Email is valid
  4. Check browser console for errors
Email verification code never arrives

Possible causes:

  1. Check spam folder
  2. Cognito free tier limit (50 emails/day)
  3. Email configuration issue

Workaround:

  • Go to Cognito โ†’ Users โ†’ Find user โ†’ Confirm account manually
CloudFront shows "No 'Access-Control-Allow-Origin' header"

This is normal for static sites.

If you add API calls later:

  • Configure CORS on your API
  • Add proper headers in backend
App shows blank page after login

Check:

  1. Browser console for errors
  2. Amplify configuration is correct
  3. User is actually logged in (check Amplify.Auth.currentAuthenticatedUser())
CloudFront shows "404 NoSuchBucket" error

Your CloudFront origin is pointing to a bucket that doesn't exist.

Common causes:

  1. Typo in the S3 website endpoint
  2. Bucket name got duplicated during setup (e.g., my-bucket-my-bucket-xyz instead of my-bucket-xyz)
  3. Copied the S3 bucket ARN or API endpoint instead of the website endpoint
  4. Selected bucket from dropdown instead of pasting website endpoint manually

Fix:

  1. Go to S3 โ†’ Your bucket โ†’ Properties โ†’ Static website hosting
  2. Copy the Bucket website endpoint (looks like your-bucket-name.s3-website-us-east-1.amazonaws.com)
  3. Go to CloudFront โ†’ Your distribution โ†’ Origins tab
  4. Click Edit and paste the correct S3 website endpoint
  5. Save and wait for CloudFront to redeploy (5-10 min)

Remember: The website endpoint format is:

your-bucket-name.s3-website-REGION.amazonaws.com

Copy your unique endpoint:

Not the S3 API format:

your-bucket-name.s3.amazonaws.com  โŒ
CloudFront shows "504 Gateway Timeout"

This usually means DNS hasn't propagated yet.

Fix:

  1. Wait 5-10 minutes after creating your A record
  2. Check CloudFront distribution status is "Enabled" (not "Deploying")
  3. Try the CloudFront domain directly (e.g., d1234abcdef8.cloudfront.net) to confirm CloudFront itself works

If it persists:

  • Verify the origin domain in CloudFront points to a valid S3 website endpoint
  • Check S3 static website hosting is enabled

Tomorrows previewโ€‹

Today: You build a protected frontend

Tomorrow (Day 13): We deploy the AI containers! The big day!

What we'll do:

  1. Create ECR repository (store Docker images)
  2. Build Docker image from Day 1-2 code
  3. Push image to ECR
  4. Create ECS Cluster
  5. Create Fargate Task Definition
  6. Create ECS Service
  7. Deploy containers to private subnets
  8. Register with Target Group
  9. Make your first AI call! ๐Ÿ“ž๐Ÿค–

After Day 13:

  • Everything connects
  • ALB โ†’ Fargate โ†’ OpenAI โ†’ Twilio
  • Real AI phone calls work
  • Your system is live

This is the day it all comes together.

What we learned todayโ€‹

1. How Cognito user pools workโ€‹

Manages authentication service for user management

Think of Cognito as your guest list for the pool party. It decides who gets an invitation to your backyard pool party: Think of Cognito as your guest list for the pool party. It decides who gets an invitation to your backyard

Think of Cognito as your guest list for the pool party. It decides who gets an invitation to your backyard pool party

2. AWS Amplify UI componentsโ€‹

Pre-built React components for auth flows

3. S3 static website hostingโ€‹

Cheap, scalable hosting for frontends

4. CloudFront CDNโ€‹

Global content delivery network for speed + HTTPS

5. Protecting apps from unauthorized useโ€‹

Why authentication is critical for APIs with costs

The frontend is deployedโ€‹

Days 1-2: Local development (your laptop) โœ…
Day 3: VPC (your territory) โœ…
Day 4: Subnets (front yards vs back yards) โœ…
Day 5: NAT Gateway (back gate) โœ…
Day 6: Route Tables (the roads) โœ…
Day 7: Security Groups (the bouncers) โœ…
Day 8: Test Your Network (validation) โœ…
Day 9: Application Load Balancer (front door) โœ…
Day 10: Custom Domain (real URLs) โœ…
Day 11: SSL Certificate (HTTPS) โœ…
Day 12: Deploy Frontend (with auth) โ† YOU ARE HERE โœ…
Day 13: Deploy AI Containers โ† TOMORROW!
Days 14-17: ECS Configuration & Testing
Days 18-24: Features & Polish

12 days done! 12 to go!

Tomorrow is a big day: Container deployment

Share your progressโ€‹

Protected frontend deployed? Share it!

Twitter/X:

"Day 12: Deployed my protected frontend! Built with Vite + React, secured with Cognito auth + Amplify UI, hosted on S3 + CloudFront. Only authorized users can access my AI calling agent. Following @norahsakal's advent calendar ๐ŸŽ„"

LinkedIn:

"Day 12 of building AI calling agents: Deployed a secure frontend with AWS Cognito authentication. Users must sign up/login before accessing the app, critical for preventing unauthorized API use. Used Vite, React, Amplify UI, S3, and CloudFront. Production-ready security!"

Tag me! I want to celebrate your progress! ๐ŸŽ‰

This advent calendar is completely free.

But if you want:

โœ… Complete codebase (one clean repo)
โœ… Complete walkthroughs
โœ… Support when stuck
โœ… Production templates
โœ… Advanced features

Join the waitlist for the full course (launching February 2026):

Building something with AI calling?

Let's chat about your use case!
Schedule a free call โ†— - no pitch, just two builders talking.

Tomorrow: Day 13 - Deploy AI Containers ๐Ÿš€๐Ÿ“ž

See you then!

โ€” Norah