How to Quickly Build and Deploy a Secure VoiceFirst, CloudFirst, Serverless Hybrid App

Matt Pitts
AdonousTech
Published in
10 min readNov 3, 2019

--

We will be using an Infrastructure as Code approach for this tutorial. We will be dealing with 2 regions in our AWS account. I am closest to us-west-2, so the bulk of my infrastructure will be deployed in that region. You could deploy everything in us-east-1, but you will need to modify the instructions slightly. Here are the high level steps you need to take to build this solution:

  1. Create a 3rd Party Identity provider that will allow users to login with an identity stored in another system. For this tutorial, we will use Login With Amazon.
  2. Create an ACM certificate for the Cloudfront Distribution in us-east-1.
  3. Create the Lambda@Edge function for our security headers in us-east-1.
  4. Deploy the back-end infrastructure for the serverless SPA. For this tutorial, our SPA will be using Angular + TypeScript. This and all other resources requiring a specific region will be deployed in us-west-2.
  5. Create the SPA using Angular.
  6. Create an Alexa Skill with dedicated infrastructure that links to the SPA + API Backend
  7. Deploy the Lambda@Edge functions to add a final layer of security

Clone the Repository

The first thing you need to do is clone the repository we will be using for this tutorial.

git clone https://github.com/AdonousTech/voice-first-serverless-hybrid.git

Create a 3rd Party Identity Provider (Login with Amazon)

Our SPA will use a Cognito User Pool to authenticate/authorize users for the SPA. The Alexa Skill will use the same user pool to authenticate skill users and associate them with linked SPA/API. Both the SPA and the Alexa Skill are clients of the user pool. We want to enable a seamless login/sign up experience, so we will use Login with Amazon as a 3rd party identity provider. Next, we will setup LWA.

A. Go to https://developer.amazon.com/loginwithamazon and create a new Security Profile

Initial LWA screen if you have no Security Profiles

B. Add Security Profile Details

Add Security Profile Details

C. Create the Profile

At this point you will have a default LWA Security Profile.You do not need to take any further steps at this point. Click the “Web Settings” tab. We will use the Client Id and Client Secret later, as well as fill in the Allowed Origins and Allowed Return URLs.

The Web Settings tab of the LWA Security Profile

Create the ACM (AWS Certificate Manager) SSL Certificate

Our SPA will be an Angular Application deployed (via AWS CodePipeline) to a S3 bucket setup for static website hosting. The S3 bucket will be locked down, but will allow a CloudFront web distribution to access contents via Origin Access Identity. The CloudFront distribution will use a custom domain and SSL Certificate.

The first resource we need to create is a custom SSL certificate using AWS Certificate Manager. This step assumes you already have a Hosted Zone in Route 53 (with a root domain name). AWS automatically creates a hosted zone in Route 53 when you register a domain name.

ACM SSL Certificate for CloudFront
  1. CD Into the spa-acm-us-east directory
cd spa-acm-us-east

2. Open package.json and add configuration values:

stackName: Add a name for this Cloudformation Stack. Keep this name short as other resource names will be derived from this name.

s3TemplateBucket: The name of the S3 bucket that will hold the template files. If this bucket does not already exist, it will be created at deployment.

s3TempalteBucketLocation: Add the name of the template bucket after the s3:// prefix.

s3TemplateFileUrl: The URL to this template in S3. The template name is east.json, so the value of this property should have the following format: https://s3.amazonaws.com/{s3TemplateBucket}/east.json

domain: The fully qualified subdomain you want to use for the certificate. This subdomain will be the same for the CloudFront Distribution.

profile: The associated AWS CLI profile. The region for this profile should be us-east-1. If you do not use named profiles, please set one up before proceeding.

3. Deploy the CloudFormation Stack

Make sure you are in the root of the project. Run the following command:

npm run create-stack

Go to the CloudFormation console (us-east-1), and you will notice the stack is in the CREATE_IN_PROGRESS Status. If you look to the right, in status reason, you will also see a string “Content of DNS Record is:…”. You will need to use that information to create a new CNAME record in Route 53.

Creation of SSL In Progress Pending DNS Validation

Next, use the DNS record details above to create a CNAME in your Route 53 Hosted Zone.

Create a new CNAME Record

Once you create the new Record Set, ACM will proceed with validation, and the stack status will move to CREATE_COMPLETE status.

Create the Lambda@Edge function for Security Headers

One of the finishing touches for our SPA will be related to security. Next, we will create a Lamda@Edge function that intercepts responses from the CloudFront distribution and modifies the headers before returning the response to the requestor. While we are deploying the function now, we will configure it in the console later.

Lambda@Edge Function Stack
  1. CD into the spa-sec-headers-us-east directory.
cd spa-sec-headers-us-east

2. Open package.json and add configuration values:

generalStackName: The name for this CloudFormation Stack

lambdaSourceCodeBucket: The name of the S3 bucket that will hold the source code for the lambda function. If this bucket does not exist, it will be created.

lambdaSourceCodeBucketArn: The arn of the S3 bucket listed above. The S3 arn format is arn:aws:s3:::bucket-name-here.

lambdaSourceCodeBucketLocation: The location of the bucket above. The format is s3://bucket-name-here.

functionName: Leave this blank. A name for the lambda function will be derived from the stack name. After deploying the stack for the first time, you can come back and update this value.

profile: The associated AWS CLI profile.

3. Deploy the CloudFormation Stack

Make sure you are in the root of the project. Run the following command:

npm run create-stack

Deploy the back-end infrastructure for the serverless SPA

Up next, we will deploy the bulk of the back-end infrastructure for the SPA. At a high level, this includes:

  • A CI/CD Pipeline to build and deploy the SPA
  • A CodeBuild Project for the Pipeline (build provider)
  • A deployment function and cache invalidation function for the pipeline
  • A Cognito User Pool with a Hosted UI
  • A User Pool Client for the SPA
  • A User Pool Client for the Alexa Skill
  • A Federated Identity Pool attached to the SPA with Auth and UnAuth Roles
  • A Login With Amazon Identity Provider for the User Pool
  • A DynamoDB table with On-Demand Pricing
  • Static website bucket with
  • A CloudFront Web Distribution
  • Origin Access Identity for the CloudFront Distribution
  • A Web Application Firewall for the CloudFront Distribution (allows traffic by default)
  • An IPMatch Rule and IPSet for the Web Application Firewall (empty by default)
  • A Route53 record set to add a CNAME record for the CloudFront Distribution
  1. CD into the spa-core-frontend directory
cd spa-core-frontend

2. Open package.json and add configuration values:

s3TemplateBucket: The name of the S3 bucket that will hold the template files. If this bucket does not already exist, it will be created at deployment.

s3TempalteBucketLocation: Add the name of the template bucket after the s3:// prefix.

s3TemplateFileUrl: The URL to this template in S3. The template name is frontend.json, so the value of this property should have the following format: https://s3.amazonaws.com/{s3TemplateBucket}/frontend.json

lwaProviderClientId: The Client ID of the Login With Amazon Security Profile you created earlier

lwaProviderClientSecret: The Client Secret of the Login With Amazon Security Profile you created earlier. Note: Do not push this value to source control. In fact, consider using Secure String Parameters for sensitive values.

hostedZoneId: The id of your existing Route 53 hosted zone.

cloudFrontCNAME: The domain name you used earlier when you created the ACM certificate in us-east-1.

userPoolDomain: The domain prefix for your Cognito User Pool. This should be any value you want, separated by dashes. For example: spa-adonoustech. Try to keep this short, as it is prefixed to additional values specified by AWS. Note: this is not the same as a custom domain for your Cognito User Pool.

profile: The associated AWS CLI profile.

sslCertificateARN: The ARN of the ACM certificate you created earlier.

3. Deploy the stack

Make sure you are in the root of the directory, and run:

npm run create-stack

Create the SPA using Angular

The SPA is a basic Angular application. Rather than setting up the boilerplate for the authentication, a sample is included with the rest of the code for this tutorial.

  1. CD into the spa directory:
cd spa

2. Open environment.ts and add environment variables:

authData.clientId: The Client ID UserPoolClient created earlier. This is under App Clients in the Cognito User Pool console.

authData.AppWebDomain: This is the domain for your user pool that you created earlier.

authData.UserPoolId: The id for the user pool you created earlier.

3. Open package.json and configuration values:

deploymentSourceBucketLocation: Add the name of the codebuild source bucket after the s3:// prefix. The name of this bucket will contain the text bucketcodebuildsource.

4. Open buildspec.yml and add the CodeBuild Output Bucket name

env.variables.S3_BUCKET: Add the name of the CodeBuild Output Bucket. The name of this bucket will contain the text bucketcodebuildoutput

5. Install dependencies

Make sure you are in the root of the project and run:

npm install

6. Update the Login With Amazon Security Profile

Go back to your Login With Amazon security profile and add the following values under the Web Settings tab:

Allowed Origins: Add the domain name of your Cognito User Pool. For example: myspa.auth.us-west-2.amazoncognito.com

Allowed Return URLs: Add the domain name of your Cognito User Pool followed by /oauth2/idpresponse. For example: myspa.auth.us-west-2.amazoncognito.com/oauth2/idpresponse

7. Test the SPA Authentication

At this point you can run the project with ng serve, browse to localhost::4200/login, and test the authentication. After you click on the hyperlink Test Login, you will be redirected to the login page connected to your user pool. You previously configured Login With Amazon as the only identity provider, so you should see the image below:

Login With Amazon IDP on Hosted Auth Page

After clicking on the LoginWithAmazon button and authenticating with your Amazon credentials, you will be redirected back to localhost:4200, and you should notice an auth code in the uri.

Next, open your browser console with F12. You should see a CognitoAuthSession object written to the console. If you expand that object, you will see details of the current authenticated session.

Note: Before deploying to a remote environment, you will need to change the redirect uris from localhost to your actual CloudFront Domain.

8. Deploy the SPA

Make sure you are in the root of the project and run:

npm deploy

This will deploy the SPA to the CodeBuild versioned source bucket, which will start the build process using CodePipeline. When the build is complete, the static assets will be deployed to the S3 Static Website bucket and available via the CloudFront Web Distribution.

Create an Alexa Skill with dedicated infrastructure that links to the SPA/API

Up next, we will create an Alexa Skill which will be linked to our SPA. This effectively creates a hybrid application, one with both a VUI and traditional UI.

  1. Open the linked-alexa-skill project and follow the instructions in the README to deploy the Alexa Skill. You will use these same procedures to redeploy the skill
  2. Link the skill using the ASK CLI:
ask api create-account-linking [-s|--skill-id <skillId>] [-g|--stage <stage>] [-p|--profile <profile>] [--debug]

Authorization URL: Use your Cognito User Pool domain followed by /oauth2/token. For example: https://myspa.auth.us-west-2.amazoncognito.com/oauth2/authorize

Client ID: This is the Client Id for the Alexa Client you created earlier for the user pool. This will be the 2nd App Client listed in your user pool configuration.

Scopes: These are the scopes for the Alexa Client you created earlier for the user pool.

Authorization Grant Type: AUTH_CODE

Access Token URL: Use your Cognito User Pool domain followed by /oauth2/token. For example: https://myspa.auth.us-west-2.amazoncognito.com/oauth2/token

Client Secret: This is the Client Secret for the Alexa Client you created earlier for the user pool. This will be the 2nd App Client listed in your user pool configuration.

Client Authentication Scheme: HTTP_BASIC

Allow users to enable skill without account linking: YES

[OPTIONAL] Configure the Lambda@Edge Functions

For production, you will want to fortify your application. One way to enhance the security of your SPA is to configure the Lambda@Edge function you created earlier. You can follow the steps here to easily configure your Lambda@Edge function.

Conclusion

You should now have a Voice First, Cloud First, Serverless hybrid application. I hope you enjoyed this article and learned something new.

--

--

Matt Pitts
AdonousTech

Ex Amazon Engineer and Founder of @AdonousTech. A technology company focused on #VoiceFirst #Serverless and #Cloud applications for small businesses