How to improve government customer experience by building a modern serverless web application in AWS GovCloud (US)

How to improve government customer experience by building a modern serverless web application in AWS GovCloud (US)

Whether filing taxes, applying for student financial aid, seeking healthcare coverage, or receiving disaster support, customers are increasingly using internet-based web applications as the primary method of interaction with the federal government. Improving the customer experience (CX) of web applications provided by the US federal government is an executive priority, and part of the President’s Management Agenda (PMA). For a positive customer experience, web applications need to be highly responsive.

Modern applications built using microservices architectures improve customer experience by dramatically reducing the risk of failures in a web application. With microservices architecture, developers build an application with independent components that run each application process as a service. These services communicate via a well-defined interface using lightweight APIs. Services are built for specific business capabilities and each service performs a single function. Because they run independently, each service can be updated, deployed, and scaled to meet demand for specific functions of an application.

In the blog post, “Architecture framework for transforming federal customer experience and service delivery,” we describe an architecture framework along with a curated set of services from Amazon Web Services (AWS) that agencies can use to build and deploy modern applications using microservices architectures. In this blog post, we present a sample reference architecture of a microservices application built using services selected from the previously described architecture framework. Since several agencies require their workloads to adhere to a Federal Risk and Authorization Management Program (FedRAMP) High Baseline, we provide an example reference architecture and configuration details of a sample application built in the AWS GovCloud (US) Region. Note: to provide a highly responsive and low latency application across the US and other countries, we use Amazon CloudFront deployed in a commercial region in this walkthrough, as Amazon CloudFront is currently not available from the AWS GovCloud (US) Regions.

Serverless web application reference architecture in AWS GovCloud (US)

Figure 1 illustrates the sample reference architecture of a serverless application that meets FedRAMP High Baseline compliance requirements.

Figure 1. Reference architecture for implementing a modern application on AWS GovCloud (US).

The application consists of a single page application (SPA) built using any front-end frameworks or libraries such as Angular or React. The static content of the SPA is hosted in an Amazon Simple Storage Service (Amazon S3) bucket in an AWS account in the AWS GovCloud (US-West) Region. If the application serves users across a widely dispersed geographic region, then using a content delivery network is important to reduce latency of the application for improved CX.

To achieve low latency, use Amazon CloudFront in an AWS account in any commercial region. Amazon Cognito user pools set up authentication for the application. Agencies can either use this option directly, or choose to integrate Amazon Cognito with an external Security Assertion Markup Language (SAML)-based authentication service. The SPA invokes microservices deployed using AWS Lambda and exposed using RESTful endpoints with Amazon API Gateway. The architecture uses Amazon Cognito user pools so only authenticated users can call the endpoints on Amazon API Gateway. Agencies can also choose to use customized authorizers instead of Amazon Cognito user pools for API authentication. AWS Lambda integrates with Amazon DynamoDB for managing data persistence as well as state of the application. AWS WAF, a web application firewall, helps protect the static content as well as APIs against common web exploits and bots.

Modern serverless web application configuration details

In this section, we walk through the necessary steps to set up the above reference architecture in AWS using a simple application. The application takes your first name and last name as inputs and returns a greeting with your full name.

Prerequisites

We assume basic familiarity with the AWS Management Console and serverless service configurations, as well as a basic understanding of building a web service. To complete the configuration, you need the following:

  1. Access to an AWS account in AWS GovCloud (US)
  2. Access to an AWS account in US-East-1

How to configure a modern severless web application in AWS GovCloud (US)

1. In your AWS GovCloud (US) account, create a table in Amazon DynamoDB with the name “HelloWorldDatabase” and Partition Key “ID.”

Figure 2. Create a table in Amazon DynamoDB.

2. Create an AWS Lambda function “HelloWorldFunction” with Python 3.8 runtime using the following code:

 # import the json utility package since we will be working with a JSON object
import json
# import the AWS SDK (for Python the package name is boto3)
import boto3
# import two packages to help us with dates and date formatting
from time import gmtime, strftime

# create a DynamoDB object using the AWS SDK
dynamodb = boto3.resource('dynamodb')
# use the DynamoDB object to select our table
table = dynamodb.Table('HelloWorldDatabase')
# store the current time in a human readable format in a variable
now = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())

# define the handler function that the Lambda service will use as an entry point
def lambda_handler(event, context):
# extract values from the event object we got from the Lambda service and store in a variable
    name = event['firstName'] +' '+ event['lastName']
# write name and time to the DynamoDB table using the object we instantiated and save response in a variable
    response = table.put_item(
        Item={
            'ID': name,
            'LatestGreetingTime':now
            })
# return a properly formatted JSON object
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda, ' + name)
    }

3. Make sure your lambda execution role has access to read and write to the DynamoDB table “HelloWorldDatabase.”

Figure 3. Add DynamoDB access to the Lambda function.

4. Create an Amazon Cognito user pool and add an application to the user pool to enable the hosted web UI. Provide a dummy value for the callback URL. We update this later in the procedure with Amazon CloudFront URLs. Make sure you save the Client ID and User Pool ID for future reference.

Figure 4. Create an Amazon Cognito user pool.

When adding an application, make sure you uncheck the Generate client secret option as shown in Figure 5. Client secrets are not currently supported for use with browser-based applications.

Figure 5. Uncheck the Generate client secret option when adding an application to your Amazon Cognito user pool.

5. Launch the Hosted UI using the “Launch Hosted UI” link in Application Client Settings and create a user called “Test1234” with a password “Test@1234” using the sign-up link.

Figure 6. Launch the Hosted UI from Amazon Cognito user pools.

6. Build a REST API in Amazon API Gateway with a POST method that integrates with the HelloWorldFunction in Lambda. Make sure you enable CORS on the root resource as well as the POST resource.

Figure 7. Build a REST API in Amazon API Gateway.

Note the API Endpoint URL from the Invoke URL Stage Editor for the API in the API Gateway console (listed as the Invoke URL at the top as shown in Figure 8).

Figure 8. API Endpoint URL in API Gateway.

7. Set up an authorizer in API Gateway specifying the Amazon Cognito user pool “HelloWorld” set up earlier in Step 4. Set the token source as Authorization because the solution passes credentials using authorization headers to Amazon Cognito for validation.

Figure 9. Set up an authorizer in API Gateway.

8. In the POST method of the HelloWorld API, change the authorization to the HelloWorld Cognito Authorizer and then deploy the API.

Figure 10. Change the authorization to the HelloWorld Cognito Authorizer and deploy.

9. In AWS WAF, create a web access control list (web ACL) and add the Amazon API Gateway as an associated resource.

Figure 11. Add API Gateway to AWS WAF.

10. Create a function.js file using the javascript below:

// define the callAPI function that takes a first name and last name as parameters
    var callAPI = (firstName,lastName)=>{
        // instantiate a headers object
        var myHeaders = new Headers();
        // Amazon Cognito creates a session which includes the id, access, and refresh tokens of an authenticated user.

       var authenticationData = {
               Username : 'Test1234',
               Password : 'Test@1234',
           };
           var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
           var poolData = { UserPoolId : 'REPLACE WITH USER POOL ID',
               ClientId : 'REPLACE WITH CLIENT ID'
           };
           var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
           var userData = {
               Username : 'Test1234',
               Pool : userPool
           };
           var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
           cognitoUser.authenticateUser(authenticationDetails, {
               onSuccess: function (result) {
                   var accessToken = result.getAccessToken().getJwtToken();
                   var idToken = result.idToken.jwtToken;
                      // add content type header to object
                      myHeaders.append("Content-Type", "application/json");
                      myHeaders.append("Authorization", idToken);
                      console.log('access token + ' + result.getAccessToken().getJwtToken());
       
                      // using built in JSON utility package turn object to string and store in a variable
                      var raw = JSON.stringify({"firstName":firstName,"lastName":lastName});
                      // create a JSON object with parameters for API call and store in a variable
                      var requestOptions = {
                          method: 'POST',
                          headers: myHeaders,
                          body: raw,
                          redirect: 'follow'
                      };
                      // make API call with parameters and use promises to get response
                      fetch("REPLACE WITH YOUR API ENDPOINT URL FROM API GATEWAY", requestOptions)
                      .then(response => response.text())
                      .then(result => alert(JSON.parse(result).body))
                      .catch(error => console.log('error', error));
               },
       
               onFailure: function(err) {
                   alert(err);
               },

            });
    }

Modify the above code as follows:

Replace UserPoolId and ClientId in the above code with values obtained from Step 4.

Replace the API Endpoint URL value with the Invoke URL obtained in Step 6.

11. Create an index.html file with the code below:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Hello World</title>
    <link rel="stylesheet" href="styles.css">
    <script src="amazon-cognito-identity.min.js"></script>
    <script src="function.js"></script>
</head>
<body>
    <form>
        <label>First Name :</label>
        <input type="text" id="fName">
        <label>Last Name :</label>
        <input type="text" id="lName">
        <!-- set button onClick method to call function we defined passing input values as parameters -->
        <button type="button" onclick="callAPI(document.getElementById('fName').value,document.getElementById('lName').value)">Call API</button>
    </form>
</body>
</html>

12. Create a styles.css file with the code below:

body {
        background-color: #232F3E;
        }
    label, button {
        color: #FF9900;
        font-family: Arial, Helvetica, sans-serif;
        font-size: 20px;
        margin-left: 40px;
        }
     input {
        color: #232F3E;
        font-family: Arial, Helvetica, sans-serif;
        font-size: 20px;
        margin-left: 20px;
        }

13. Download and save the amazon-cognito-identity.min.js file from GitHub.

14. Create an Amazon S3 bucket in your AWS GovCloud (US) account. Under Permissions, uncheck Block Public Access.

Figure 12. Uncheck Block Public Access under Bucket Properties.

In addition, make sure that account level Block Public Access is also disabled as shown in Figure 13. Ideally, this operation should be performed in a standalone account that does not have any other buckets other than those meant for hosting static websites.

Figure 13. Uncheck Block Public Access at the account level.

15. Under Properties of the Amazon S3 bucket, enable the Static Website Hosting option and specify index.html as the index document. Make a note of the domain name (<bucket-name>.s3-website-us-gov-west-1.amazonaws.com).

Figure 14. Enable static website hosting for the Amazon S3 bucket.

16. Upload the function.js you created in Step 10, the index.html file you created in Step 11, styles.css file from Step 12, and the amazon-cognito-identity.min.js file you created from Step 13 to the bucket.

Figure 15. Upload files for the web application to the Amazon S3 bucket.

17. In your commercial account, create an Amazon CloudFront distribution. For the origin name, enter the domain name of the Amazon S3 bucket created in Step 14. (Note: you cannot directly use an origin from the listed Amazon S3 buckets). Make a note of the distribution domain name.

18. To make sure that Amazon S3 is not open for direct retrieval of objects, add a custom header called “Referer” with the value “referertest.”

Figure 16. Add a custom header to the distribution.

19. In the AWS GovCloud (US) account, under the Permissions of the bucket, edit the Bucket Policy to specify the custom header from CloudFront.

Figure 17. Edit bucket policy in the AWS GovCloud (US) account.

Enter the following policy and save the changes:

{
    "Version": "2012-10-17",
    "Id": "http referer policy example",
    "Statement": [
        {
            "Sid": "Allow get requests from Cloudfront",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject",
                "s3:GetObjectVersion"
            ],
            "Resource": "arn:aws-us-gov:s3:::<bucket-name>/*",
            "Condition": {
                "StringLike": {
                    "aws:Referer": "referertest"
                }
            }
        }
    ]
}

Make sure the indentation is exactly as shown below; otherwise, you may get an error when saving.

Figure 18. Save bucket policy in the AWS GovCloud (US) account.

20. In the Amazon Cognito user pools app client that you created in Step 4, update the URLs to the CloudFront Distribution domain name from Step 17. Make sure you specify the full path to the index.html file as shown in Figure 19.

Figure 19. Update Amazon Cognito user pools app client settings with CloudFront URL.

Congratulations! You now have a secure working serverless web application in AWS GovCloud (US) that uses CloudFront for static content distribution from the commercial region. Your application consists of an SPA with content in Amazon S3 that invokes an API on Amazon API Gateway integrated with Lambda, and protected by Amazon Cognito.

21. To test the application, launch the hosted UI from Amazon Cognito and enter the credentials as described in Step 5. Amazon Cognito should redirect you to your application on CloudFront. Enter any first name and last name. You should get a response back as “Hello from Lambda, Testing User.”

Figure 20. Testing the application returns the response “Hello from Lambda, Testing User.”

AWS can help government agencies scale rapidly to deliver on CX

In this blog, we presented a sample reference architecture and configuration details of a microservices-based application deployed in AWS GovCloud (US) that leverages Amazon CloudFront from a commercial region. Modern applications built using a microservices architecture can dramatically reduce the risk of failures in a web application for improved customer experience. Amazon CloudFront further enhances this experience by enabling a highly responsive and low latency web application for customers based across the US and other countries. To get started on a proof-of-concept or implementation project using this reference architecture, or to learn more about AWS serverless and managed services, contact your AWS account team or reach out to the AWS Public Sector team for more.

Read more about AWS for government:


to get the latest in AWS tools, solutions, and innovations from the public sector delivered to your inbox, or contact us.

Please take a few minutes to share insights regarding your experience with the AWS Public Sector Blog in this survey, and we’ll use feedback from the survey to create more content aligned with the preferences of our readers.

This content was originally published here.