How to Quickly Build and Deploy a Secure VoiceFirst, CloudFirst, Serverless Hybrid App
--
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:
- 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.
- Create an ACM certificate for the Cloudfront Distribution in us-east-1.
- Create the Lambda@Edge function for our security headers in us-east-1.
- 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.
- Create the SPA using Angular.
- Create an Alexa Skill with dedicated infrastructure that links to the SPA + API Backend
- 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
B. 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.
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.
- 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.
Next, use the DNS record details above to create a CNAME in your Route 53 Hosted Zone.
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.
- 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
- 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.
- 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:
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.
- 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
- 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.