profile.jpg

Aditya Pratama

DevOps | SRE | Cloud Engineer
Return to Blog List

How to Build a Serverless Website with reCAPTCHA on AWS

August 3, 2022

This is based on my experience hosting my personal static site on AWS. I don't want to run the server for just a 'Contact Us' form. Oh, and I also need a Captcha because I don't want a lot of spam coming in. So I used a serverless site architecture and google recaptcha for that.

Serverless_AWS_Lambda.jpg

This blog is part of AWS Projects series

This is based on my experience hosting my personal static site (adityacprtm.com) on AWS. I don't want to run the server for just a 'Contact Us' form. Oh, and I also need a Captcha because I don't want a lot of spam coming in. So I used a serverless site architecture and google recaptcha for that.

update personal site: adityacprtm.dev

Note: This article is a re-upload and a language translation, my original article can be seen in the link here.

How to Build a Serverless Website with reCAPTCHA on AWS

Application Architecture

Application architecture used:

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

Create Buckets on Amazon S3

  1. Open the console or access Amazon S3 here
  2. create a bucket with your domain name, for example example.com
  3. Enough at this point, we will upload the file later

Create SNS Topics and Subscriptions

  1. Open SNS Console or access here
  2. Create a name for the Topic
  3. Create a Subscription, select the ARN Topic according to what was made in point 2
  4. Select email on the protocol type, then create a subscription
  5. Check the registered email and verify the subscription from AWS

Build Serverless Back-end on AWS Lambda

  1. In the Lambda Console, select the create function
  2. Select Author from scratch
  3. Enter the name of the Function and select Node.js for the Runtime
  4. Then select Create function

At this point we will use lambda with JavaScript. Unfortunately we cannot easily load scripts into the AWS Lambda online editor which has external dependencies such as Axios. So we need to create a Node project with package.json like this:

{
   ""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 things easier when there are changes to the SNS ARN Topic and the ecret Key of reCaptcha, change index.js as follows:

'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 creating the project, we need to compress the folder in ZIP format for uploading to Lambda.

While still in the Lambda Console in the previously created Function, upload the ZIP and set the environment variables as follows:

env_vars_serveless.png

Then select Save.

Deploy Restful API Gateway

  1. Open the API Gateway Console or access here
  2. Select Create API and select REST
  3. Select New API and enter the API Name
  4. Select Create API
  5. On the left navigation, select Resources under your API
  6. From the Actions dropdown menu select Create Resource
  7. Enter a Resource Name such as contact or prod then click Create Resource
  8. On the newly created Resource, from the Action dropdown menu select Create Method
  9. Select Post then checkmark
  10. Select Lambda Function for integration type
  11. Select the Region you are using on AWS Lambda
  12. Enter the name of the function that was created before then Save
  13. [Optional CORS] In the newly created resource, from the Action dropdown select Enable CORS then click enable and replacing
  14. On the Actions dropdown menu select Deploy API, enter the Stage Name then select Deploy
  15. Write down and save the Invoke URL which will be used later

Set up a serverless website

Previously, you could use reCAPTCHA by registering a domain name here.

HTML

We are following the Google reCaptcha documentation, which means we have to enter the code below between the <head> and </head> tags.

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

and enter the following code at the bottom of the form before the submit button. Don't forget to enter the siteKey you got after registering the domain on Google reCaptcha.

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

JavaScript

We can use Ajax to perform asynchronous HTTP (Ajax) requests. Enter the Ajax URL with the Invoke URL we got when building the API gateway.

Host Static Website

  1. Back in the S3 Console, select the bucket that was created earlier
  2. Upload your website files into a bucket
  3. When finished, select the Properties tab
  4. Select Static website hosting
  5. Fill in the index and document error
  6. Make sure Block Public access is not checked
  7. Still on the Permissions tab, select Bucket Policy. Enter the following policy document into the policy bucket editor, replacing [YOUR_BUCKET_NAME] with the bucket name that was created.
{
   ""Version"":""2012-10-17"",
   ""Statement"":[
      {
         ""Effect"":""Allow"",
         ""Principal"":""*"",
         ""Action"":""s3:GetObject"",
         ""Resource"":""arn:aws:s3:::[YOUR_BUCKET_NAME]/*""
      }
   ]
}

Configure the Domain on Route 53

  1. On Amazon Route 53 we can register a new domain or make Route 53 a DNS service for an existing domain
  2. I already have my own domain, so I made Amazon Route 53 the DNS Service for my domain
  3. On the Route 53 Console, select Create Hosted Zone.
  4. On the Create Hosted Zone panel, enter the domain name
  5. In the Type, leave the default on the Public Hosted Zone
  6. Select Create.

Request SSL Certificate in AWS Certificate Manager

  1. Open ACM Console or access here
  2. select Request a Certificate
  3. Select Request a public certificate
  4. Add a domain name with your own domain name, for example example.com or *.example.com
  5. We need to validate the certificate request, how to select DNS validation or email validation. Here I use DNS validation because it's faster and easier.
  6. Add a CNAME on Amazon Route 53 or simply click the button below your domain to add it automatically.

Create a CloudFront Web Distribution

  1. Open the CloudFront console or access here
  2. Create a distribution
  3. select Origin Domain Name with the bucket name created earlier.
  4. Select Redirect HTTP to HTTPS
  5. Select Yes in the Restrict Bucket Access section
  6. In the Distribution Settings, use the Custom SSL Certificate and select the certificate that was created in ACM
  7. Scroll and find Default Root Object, enter the default file Index.html
  8. Leave everything else default and create distribution.
  9. Wait until the status is Deployed, it usually takes a few minutes.
  10. Write down the Domain name from cloudfront

Using Custom Domain on Route 53

  1. Return to the Route 53 Console
  2. Select a domain that has been registered
  3. Select Create Record Set
  4. We will create 2 record sets
  5. The first is example.com and the second is www.example.com
  6. Use A record and select the created CloudFront.

That's it, we've built a serverless static website with reCaptcha protection and a notification for the owner or admin if someone sends a message on the form.


Reference:

  1. AWS Documentations

3 Comments