How To Build a Serverless Web Application On AWS with ‘Contact Us’ reCAPTCHA Form

Aditya Chamim Pratama
5 min readOct 31, 2019
My own Architecture via cloudcraft

This is based on my experience on hosting my personal web in AWS (adityacprtm.com). I don’t want to have servers running just to handle ‘contact us’ form which rarely gets invoked. Oh, and I also need Captcha because we don’t have time in our lives for spam. So I used serverless architecture and google recaptcha for that

Application Architecture

The application architecture uses:

  1. AWS Lambda
  2. Amazon API Gateway
  3. Amazon S3
  4. Amazon SNS
  5. Amazon CloudFront
  6. Amazon Route 53
  7. AWS Certificate Manager
  8. Google reCaptcha

Create a Bucket on S3

  1. Go to S3 console on https://console.aws.amazon.com/s3/
  2. Create a bucket with your domain name like example.com
  3. We will upload the file later

Create a SNS Topic and Subscription

  1. Go to SNS Console on https://console.aws.amazon.com/sns/
  2. Create a Topic name
  3. Create a Subscription, select the Topic ARN that was created in point 2
  4. Select email on protocol type, create subscription
  5. Check your email and verify the subscription from AWS

Build a Serverless Back-end on Lambda

  1. On Lambda Console https://console.aws.amazon.com/lambda/, create function
  2. Keep the default Author from scratch card selected
  3. Enter a Function name and select Node.js for the Runtime
  4. Click on Create function

We will develop lambda with JavaScript. Unfortunately we cannot easily insert scripts into the AWS Lambda online editor which has external dependencies such as Axios. So we need to create a small Node project with the following package.json.

{
"name": "contactForm",
"version": "0.0.1",
"private": true,
"scripts": {},
"dependencies": {
"aws-sdk": "^2.560.0",
"axios": "^0.18.0"
}
}

We will use the Environment variable in lambda to make it easier when there are changes to ARN SNS Topic and reCaptcha Secret Key, so the index.js.

'use strict';
const AWS = require("aws-sdk");
const axios = require('axios');
const completeUrl = "https://www.google.com";
// verify recaptcha url
const reCapUrl = "https://www.google.com/recaptcha/api/siteverify";
const reCaptchaSecret = process.env.RECAPTCHA_SECRET_KEY;
// from Amazon SNS
const snsTopic = process.env.ARN_SNS_TOPIC;
module.exports.handler = async (event, context, callback) => {
console.log("Starting ContactForm Processing for website form.");
// console.log("data event: " + JSON.stringify(event));
// verify the result by POSTing to google backend with secret and frontend recaptcha token as payload
let verifyResult = await axios({
method: 'post',
url: reCapUrl,
params: {
secret: reCaptchaSecret,
response: event.captcha
}
})
// print out the result of that. Its a bit verbose though
// console.log("verify result: " + JSON.stringify(verifyResult.data));
if (verifyResult.data.success) {
let sns = new AWS.SNS();
// The structure of the email
let emailbody = "Someone left a message for you.\n\nName\t\t: " + event.name + "\nEmail\t\t: " + event.email + "\nSubject\t\t: " + event.subject + "\nMessage\t\t: " + event.message + "\n\nThanks!";
let params = {
Message: emailbody,
Subject: "Contact Form: " + event.subject,
TopicArn: snsTopic
};
// we publish the created message to Amazon SNS now…
sns.publish(params, context.done);
// now we return a HTTP 302 together with a URL to redirect the browser to success URL (we put in google.com for simplicty)
callback(null, {
statusCode: 302,
headers: {
Location: completeUrl,
}
});
console.log("End of the ContactForm Process With Success");
} else {
console.log("reCaptcha check failed. Most likely SPAM.");
callback(null, {
statusCode: '500',
body: JSON.stringify({
message: 'Invalid recaptcha'
})
});
}
};

After build the project, we need to ZIP the folder for upload to Lambda.

While still in Lambda Console in Function that was created before, upload the ZIP and define the environment variable with the following.

Save the function.

Deploy a Restful API Gateway

  1. Go to API Gateway Console https://console.aws.amazon.com/apigateway/
  2. Choose Create API and choose REST
  3. Select New API and enter API Name
  4. Click Create API
  5. In the left nav, click on Resources under your API
  6. From the Actions dropdown select Create Resource
  7. Enter the Resource Name like contact or prod then click Create Resource
  8. With the newly created resource selected, from the Action dropdown select Create Method
  9. Select Post then click the checkmark
  10. Select Lambda Function for the integration type
  11. Select the Region you are using for Lambda Region
  12. Enter the name of the function you created before then choose Save
  13. [Optional CORS] With the newly created resource selected, from the Action dropdown select Enable CORS then click enable and replacing
  14. In the Actions drop-down list select Deploy API, enter Stage Name then choose Deploy
  15. Note the Invoke URL

Create own website

Before that, you can use reCAPTCHA by registering your domain here: https://www.google.com/recaptcha/admin

The HTML

You can follow Google documentation for the reCaptcha, which means you should put this:

<script src=’https://www.google.com/recaptcha/api.js'></script>

in the head section of your website. And put this:

<div class=”g-recaptcha” data-sitekey=”xxxxxxxxxxxxxxxxxxx”></div>

somewhere down below your form just before your submit button.

The JavaScript

We can use Ajax to Perform an asynchronous HTTP (Ajax) request. Enter the URL of Ajax with Invoke URL that we get when build an API gateway.

Host a Static Website

  1. Back to S3 console, select the bucket that was made before
  2. Upload your files
  3. click on Properties tab
  4. Select Static website hosting
  5. Fill in index and error document
  6. Make sure Block Public access is unchecked
  7. While still in the Permissions tab, choose Bucket Policy.
    Enter the following policy document into the bucket policy editor replacing [YOUR_BUCKET_NAME] with the name of the bucket you created.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::[YOUR_BUCKET_NAME]/*"
}
]
}

Configure Domain on Route 53

  1. On Amazon Route 53 we can Registering a new domain or making Route 53 as DNS Service for existing domain
  2. I already have my own domain, so I make Amazon Route 53 as DNS Service for my domain
  3. On Route 53 Console https://console.aws.amazon.com/route53/
  4. Choose Create Hosted Zone.
  5. In the Create Hosted Zone pane, enter a domain name
  6. For Type, accept the default value of Public Hosted Zone
  7. Choose Create.

Request SSL Certificate on ACM

  1. Go to ACM Console https://console.aws.amazon.com/acm/
  2. Click on Request a Certificate
  3. Choose Request a public certificate
  4. Add domain name with your custom domain like example.com and *.example.com
  5. We need to validate certificate request, we can choose DNS validation or email validation. In here we use DNS validation because it is faster and easier.
  6. Add CNAME in Amazon Route 53 or just click the button below your domain to automatically add it.

Create CloudFront Web Distribution

  1. Go to https://console.aws.amazon.com/cloudfront/
  2. Create your distribution
  3. Choose Origin Domain Name with the bucket that you created before.
  4. Choose Redirect HTTP to HTTPS
  5. in Restrict Bucket Access, Select Yes
  6. In Distribution Settings, use Custom SSL Certificate and choose certificate you created before on ACM
  7. Scroll and find Default Root Object, enter the default file like Index.html
  8. Leave the rest as is and create your distribution.
  9. wait until the status is deployed. this takes a few minutes.
  10. Note the Domain name of your cloudfront

Use Custom Domain on Route 53

  1. back to Route 53 Console
  2. Select domain that was register before
  3. Select Create Record Set
  4. we will create two record set
  5. First one is example.com and the second one is www.example.com
  6. Use A record and select the CloudFront that you created before

That’s it, we have a serverless static web application with with reCaptcha protection for web forms and notifications for owners or admins.

References:

  1. AWS Documentations

--

--