View on GitHub

oauthlambdacognito

How to setup a secure, serverless, single-page webapp using Google OAuth2, Lambda, API Gateway, and Cognito

S3 Lambda Cognito OAuth
In order to secure a single-page webapp hosted in S3 and backed up by Lambda/API Gateway, OAuth2 can be used with Cognito and a Web Identity Federation provider (eg: Google+, Facebook, etc). Steps 1-2 of this how-to will create a sample Lambda function served by API Gateway and secured using AWS-IAM. Steps 3-12 document the steps to allow a user to login to access the secured Lambda/API. If you already have a secured Lambda/API Gateway set up you can skip directly to Step 3 but I recommend reading through the first two Steps to understand how to secure your existing API Gateway resources. This how-to assumes previous experience with Lambda functions and roles.

Step 1: Create a sample Lambda function

This function expects no inputs and returns a simple message. It will be placed behind the API Gateway resource created in Step 2 and secured with AWS-IAM. It will be used for testing Cognito Authentication.
  1. Login to the AWS Lambda console
  2. Click on [Create a Lambda function]
  3. Select the hello-world blueprint for Node.js 4.3
  4. Click on [Next]
  5. For Name enter hello-world
  6. For Description enter A starter AWS Lambda function
  7. For Runtime make sure that Node.js 4.3 is selected
  8. For Code entry type make sure that Edit code inline is selected and enter the following in the code box:
    
    'use strict';
    
    console.log('Loading hello-world function');
    
    exports.handler = (event, context, callback) => {
        callback(null, "Greetings and salutations.");  // Make first contact
    };
        
  9. For Handler make sure index.handler entered
  10. For Role select Choose an existing role
  11. For Existing Role select lambda_basic_execution
  12. Leave the rest of the settings as they are and click on [Next]
  13. Review the settings and click on [Create function]
  14. Click on [Test] and then [Save and test] the result "Greetings and salutations" should be returned
Lambda - helloworld - Blueprint Lambda - genAuthUrl - Blueprint Lambda - genAuthUrl - Blueprint

Step 2: Create sample API Gateway

This API Gateway will be serve sample Lambda function created in Step 1. Once it is secured with AWS-IAM it will not be accessible without Cognito authorization.
  1. Login to the AWS API Gateway console
  2. Click on [Create API]
  3. Select New API
  4. For API Name enter hello-world
  5. For Description enter Sample API Gateway for hello-world lambda
  6. Click on Create API
  7. Pulldown the Action menu and select Create Resource
  8. For Resource Name enter sample which should autopopulate Resource Path with /sample
  9. Make sure the Enable API Gateway CORS box is checked
  10. Click on [Create Resource]
  11. Make sure the newly created /sample resource is highlighted and pull down the Actions menu and select Create Method
  12. From the pulldown menu select GET and click the checkmark
  13. For Integration Type select Lambda Function
  14. For Lambda Region select the region your Lambda function was created in
  15. For Lambda Function enter hello-world
  16. Click on [Save]
  17. Click [OK]
  18. Click on Test and then click on [Test], the Response Body should be "Greetings and salutations."
  19. Click on <- Method Execution to return to the Method page and click on Method Request
  20. Click on the Pencil icon next to Authorization
  21. Select AWS_IAM and click on the checkmark
  22. Click on <- Method Execution
  23. In the Method Request box locate and copy down the ARN: string, this will be used in Step 9.7
  24. In the Resources panel highlight the GET method under the /sample resource
  25. Pull down the Actions menu and select Enable CORS
  26. Click on [Enable CORS and replace existing CORS headers]
  27. Click on [Yes, replace existing values]
  28. In the Resources panel highlight the root / resource
  29. Pull down the Actions menu and select Deploy API
  30. For Deployment Stage select [New Stage]
  31. For Stage Name enter prod
  32. For Stage Description enter production
  33. Click on [Deploy]
  34. This should take you to the prod Stage Editor page and the Invoke URL should be highlighted in blue at the top of the page. However, if you attempt to Invoke this URL you will receive {"message": "Missing Authentication Token"} because we enabled AWS_IAM Authorization
  35. Click on the SDK Generation tab
  36. Pull down the Platform menu and select Javascript
  37. Click on [Generate SDK]
  38. Download and save the generated javascript_{date}.zip file as it will be needed in Step 10.5
S3 - Create API S3 - Create Resource S3 - Create Resource S3 - Create Method S3 - Create GET S3 - Function S3 - Test S3 - Method Request S3 - Authorization S3 - CORS S3 - Deploy S3 - Deploy S3 - Invoke URL S3 - SDK

Step 3: Create S3 bucket to host the single-page webapp

The frontend html and js files will be hosted in S3. A bucket will need to be created and configured for website hosting.
  1. Login to the AWS S3 console
  2. Click on [Create Bucket]
  3. Give the bucket a globally unique name, select a region, and click on [Create]
  4. Select the new bucket, click on [Properties] and click on Permissions
  5. Click on [Add bucket policy] and enter the following: (replace NAMEOFBUCKET below with the name of the bucket you just created)
    
    {
    	"Version": "2012-10-17",
    	"Statement": [
    		{
    			"Sid": "PublicReadGetObject",
    			"Effect": "Allow",
    			"Principal": "*",
    			"Action": "s3:GetObject",
    			"Resource": "arn:aws:s3:::NAMEOFBUCKET/*"
    		}
    	]
    }
      
  6. Click on Static Website Hosting > Enable website hosting
  7. For Index Document enter index.html and click Save
  8. Copy the Endpoint: link provided as we will be using it in the Step 4.8.b and Step 10.3.a
S3 - Create Bucket S3 - Add Policy S3 - Enable Website Hosting S3 - Get Endpoint

Step 4: Create Project and Enable Google+ API in Google Developer Console

A Web Identity Federation provider will be used to handle authentication so that end users can use an account they already have rather than creating a separate one for this page. In this example we will use Google+.
  1. Go to https://console.developers.google.com/
  2. At the top, click on Project pull down and select Create Project
  3. Give the project a meaningful project name and click Create
  4. Click on Library > Social APIs > Google+ API
  5. Click on Enable
  6. Click on [Create credentials]
  7. Select the following:
    1. Which API are you using? Google+ API
    2. Where will you be calling the API from? Web server node.js
    3. What data will you be accessing? User data
    4. Click on [What credentials do I need]
  8. Enter the following:
    1. Name: "Enter a meaningful client name"
    2. Authorized JavaScript origins: "The S3 endpoint provided in Step 3.8 minus the last /" eg: (http://cognito-login-test.s3-website-us-west-2.amazonaws.com)
    3. Authorized redirect URIs: "The full path to your callback page" eg: (http://cognito-login-test.s3-website-us-west-2.amazonaws.com/gapicb/)
    4. Click on [Create client ID]
  9. Select your Email address and type in a "Product name shown to users" and click [Continue]
  10. Download the credentials (these will be needed in the following steps) and click on [Done]
GAPI - Create Project GAPI - New Project GAPI - Enable API GAPI - Enable GAPI - Add Credentials GAPI - Create Client ID GAPI - Download Credentials

Step 5: Create Cognito Identity Pool

Users authenticated with Google+ will assume a Cognito Auth role which allows them access to the API Gateway.
  1. Login to the AWS Cognito console
  2. Click on [Manage Federated Identities]
  3. For Identity Pool Name enter your application name
  4. Expand Authentication Providers and select Google+
  5. Enter the Google Client ID from the credentials downloaded in Step 4.10
  6. Click on [Create Pool]
  7. Cognito will create roles for Auth and Unauth users, click [Allow] (These will be edited in Step 9.11)
  8. You can now select JavaScript from the Platform pulldown and download the latest SDK, however I prefer to include it from sdk.amazonaws.com instead
  9. Click on [Go to dashboard] and Edit Identity pool
  10. Make note of the Identity Pool ID, we will use this in Step 10.3.d
Cognito - Create Pool Cognito - Identity Pool ID

Step 6: Lambda - generateAuthUrl

The authorization URL that end users visit to login is generated via the Google+ API. This API call requires the clientId and clientSecret created in Step 4. This API call could be made client side but in order to keep these values secret we will create a Lambda function to make the call from the "server-side".
  1. Download and save the following zip file: https://github.com/kyle138/oauthlambda-generateAuthUrl/releases/download/1.0.0/oauthlambda-generateAuthUrl.zip
  2. Login to the AWS Lambda console
  3. Click on [Create a Lambda function]
  4. For Select Blueprint click on Blank Function
  5. For Configure Triggers click on [Next]
  6. For Name enter oauth-generateAuthUrl
  7. For Description enter Lambda backend for googleapis oauth2 generateAuthUrl
  8. For RunTime select Node.js 4.3
  9. For Code Entry Type select Upload a .ZIP file
  10. Click on [Upload] and select the oauthlambda-generateAuthUrl.zip file you downloaded in Step 6.1
  11. Create three Environment Variables using the exact KEY names below and substituting the values you downloaded in Step 4.10:
    clientId Enter the Google API Client ID that you downloaded in Step 4.10
    clientSecret Enter the Google API Client Secret that you downloaded in Step 4.10
    redirectUrl Enter the authorized redirect URI that you specified in Step 4.8.c
  12. Leave Handler with the default index.handler
  13. For Role select Choose an existing role
  14. For Existing Role select lambda_basic_execution
  15. Leave Memory and Timeout with their default settings of 128mb and 3sec
  16. For VPC select No VPC
  17. Click on [Next]
  18. Review the settings and click on [Create function]
  19. Click on [Test] and in the results you should see your generated oauth2 google URL
Lambda - genAuthUrl - Blueprint Lambda - Configure Lambda - genAuthUrl - Configure

Step 7: Lambda - generateToken

The code received from the Google+ callback will need to be exchanged for identity tokens. This function also retrieves the email account of the user logging in and checks its domain to see if it is allowed. It returns the tokens and email account in a JSON object.
  1. Download and save the following zip file: https://github.com/kyle138/oauthlambda-generateToken/releases/download/1.0.0/oauthlambda-generateToken.zip
  2. Login to the AWS Lambda console
  3. Click on [Create a Lambda function]
  4. For Select Blueprint click on Blank Function
  5. For Configure Triggers click on [Next]
  6. For Name enter oauth-generateToken
  7. For Description enter 'Lambda backend for googleapis oauth2 getToken'
  8. For RunTime select Node.js 4.3
  9. For Code Entry Type select Upload a .ZIP file
  10. Click on [Upload] and select the oauthlambda-generateToken.zip file you downloaded in Step 7.1
  11. Create three Environment Variables using the exact KEY names below and substituting the values you downloaded in Step 4.10:
    clientId Enter the Google API Client ID that you downloaded in Step 4.10
    clientSecret Enter the Google API Client Secret that you downloaded in Step 4.10
    redirectUrl Enter the authorized redirect URI that you specified in Step 4.8.c
  12. Leave Handler with the default index.handler
  13. For Role select Choose an existing role
  14. For Existing Role select lambda_basic_execution
  15. Leave Memory and Timeout with their default settings of 128mb and 3sec
  16. For VPC select No VPC
  17. Click on [Next]
  18. Review the settings and click on [Create function]
  19. We cannot test this function as it is expecting the code input which we do not have yet
Lambda - Configure Lambda - genAuthUrl - Configure

Step 8: API Gateway - oAuth

This API Gateway will serve the genAuthUrl and genToken Lambda functions created in the two previous sections. These will need to be called before the user is authenticated so these endpoints will not need authorization.
  1. Login to the AWS API Gateway console
  2. Click on Create API
  3. Select New API
  4. For name enter oauthlambda
  5. For description enter 'API for oauth backend functions'
  6. Click on [Create API]
  7. Pull down the Actions menu and select Create Resource
  8. For Resource Name enter generateauthurl, this should automatically populate Resource Path with /generateauthurl
  9. Check the Enable API Gateway CORS box
  10. Click on [Create Resource]
  11. Make sure the newly created /generateauthurl resource is highlighted and pull down the Actions menu and select Create Method
  12. From the pulldown menu select GET and click the checkmark
  13. For Integration Type select Lambda Function
  14. For Lambda Region select the region your Lambda function was created in
  15. For Lambda Function enter oauth-generateAuthUrl
  16. Click on [Save]
  17. Click [OK]
  18. Click on Test and then click on [Test], the Response Body should be the generated Google oauth URL.
  19. In the Resources panel click on the root / resource to select it
  20. Pull down the Actions menu and select Create Resource
  21. For Resource Name enter generatetoken, this should automatically populate resource path with /generatetoken
  22. Check the Enable API Gateway CORS box
  23. Click on [Create Resource]
  24. Make sure the newly created /generatetoken resource is highlighted and pull down the Actions menu and select Create Method
  25. From the pulldown menu select GET and click the checkmark
  26. For Integration Type select Lambda Function
  27. For Lambda Region select the region your Lambda function was created in
  28. For Lambda Function enter oauth-generateToken
  29. Click on [Save]
  30. Click on [OK]
  31. Click on Method Request
  32. Click on URL Query String Parameters
  33. Click on + Add query string
  34. For name enter the word code and click the checkmark
  35. Click on <-Method Execution to return to the Method page
  36. Click on Integration Request
  37. Click on Body Mapping Templates to expand it
  38. For Request Body Passthrough select Never
  39. Click on + Add Mapping Template and type in application/json and click the checkmark
  40. In the Generate Template box enter the following:
    
    { "code": "$input.params('code')" }
        
  41. Click on [Save]
  42. In the Resources panel click on the root / resource to select it
  43. Pull down the Actions menu and select Deploy API
  44. For Deployment Stage select [New Stage]
  45. For Stage Name enter prod
  46. For Stage Description enter Production API Stage
  47. Click on [Deploy]
  48. This should take you to the prod Stage Editor page and the Invoke URL should be highlighted in blue at the top of the page. Copy this URL as you will need it in the Step 10.3
API Gateway - oauth - Create API API Gateway - oauth - Create Resource API Gateway - oauth - Create Resource API Gateway - oauth - Create Method API Gateway - oauth - GET API Gateway - oauth - Function API Gateway - oauth - Test API Gateway - oauth - Create Resource API Gateway - oauth - Function API Gateway - oauth - Query String API Gateway - oauth - Integration Request

Step 9: IAM Role for Cognito Authenticated Users

When a user is authenticated and their Google+ token is exchanged for a Cognito token they are given a temporary IAM SecretAccessKey which assumes the Cognito_PoolNameAuth_Role created earlier. To control what access these authenticated users have a policy has to be created and attached to that role.
  1. Login to the AWS IAM console
  2. Click on Policies
  3. Click on [Create Policy]
  4. Next to Create Your Own Policy click on [Select]
  5. For Policy Name enter Cognito_PoolNameAuth_Policy replacing PoolName with the name you gave your Cognito Identity Pool in Step 5.3
  6. For Description enter 'IAM Policy for authenticated Cognito users.'
  7. Enter the following for Policy Document making sure to replace the Resource value with the ARN string you copied in Step 2.23 :
    
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "execute-api:*"
          ],
          "Resource": [
            "arn:aws:execute-api:us-west-2:304786516436:miuk21dh0g/*/GET/sample"
          ]
        }
      ]
    }
        
  8. Click on [Validate Policy]
  9. If the policy validates click on [Create Policy]
  10. In the left panel click on Roles
  11. Click on the Cognito_PoolNameAuth_Role that was auto-created in Step 5.7
  12. On the Permissions tab click on Attach Policy
  13. Select the Cognito_PoolNameAuth_Policy that you just created in Step 9.9 and click on [Attach Policy]
IAM - Create Policy IAM - Attach Policy

Step 10: Upload Frontend Files to S3

This is where it all comes together. Update the frontend code with information from the previous steps and upload it to S3.
  1. Download and extract the following zip file: https://github.com/kyle138/oauthlambdacognito-example/archive/1.0.2.zip
  2. Go into the newly extracted oauthlambdacognito-example-1.0.2 directory
  3. Rename the config.js.example file to config.js and open the file and edit the following lines:
    1. For "s3-origin": update the value with the S3 endpoint you created in Step 3.8 prepended with 'http://' and appended with a trailing '/'
    2. For "apig-generateAuthUrl": update the value with the API Gateway Invoke URL you created in Step 8.48 appended with '/generateauthurl'
    3. For "apig-generateToken": update the value with the API Gateway Invoke URL you created in Step 8.48 appended with '/generatetoken'
    4. For "IdentityPoolId": update the value with Cognito Identity Pool ID created in Step 5.10
  4. config.js should now look similar to the following, save and close the file:
    
    var configuration =
    {
      "s3Origin": "http://cognito-login-test.s3-website-us-west-2.amazonaws.com/",
      "apigGenerateAuthUrl": "https://x7fogme9hg.execute-api.us-west-2.amazonaws.com/prod/generateauthurl",
      "apigGenerateToken": "https://x7fogme9hg.execute-api.us-west-2.amazonaws.com/prod/generatetoken",
      "IdentityPoolId": "us-west-2:801ee7b2-9c85-4d29-b0cb-ad965887558d",
      "awsRegion": "us-east-1"
    }
        
  5. Open the javascript_{date}.zip file you downloaded in Step 2.38
  6. This zip file should contain an apiGateway-js-sdk/ directory, from within it copy the apigClient.js file and the entire lib/ directory into your oauthlambdacognito-example-1.0.2 directory
  7. Login to the AWS S3 console
  8. Click on the bucket you created in Step 1
  9. Click on [Upload]
  10. Select all of the files and folders within the oauthlambdacognito-example-1.0.2 folder and Drag them into the S3 Upload form, click on [Start Upload]
  11. Visit the S3 endpoint URL created in Step 3.8 in a browser
  12. Click on the Google Login button and test if you can log in
  13. ENJOY
S3 - Upload Files S3 - Uploaded

Credits:

At the time of this project I couldn't find a single walkthrough for using Cognito with a webapp, only for iOS or Android. I found several how-tos describing parts of the answer and have pieced them together to create this document. I have included those sources below.