├── .gitignore ├── .npmignore ├── README.md ├── bin └── cloudfront-reverse-proxy-apigw.ts ├── cdk.json ├── cf_proxy_image.png ├── lib └── cloudfront-reverse-proxy-apigw-stack.ts ├── package.json ├── src ├── backend │ └── lambda │ │ ├── api-auth │ │ └── index.js │ │ └── api │ │ └── index.js └── frontend │ ├── dist │ ├── index.html │ └── index.js │ └── src │ ├── index.html │ ├── index.js │ └── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | bin/*.js 2 | lib/*.js 3 | !jest.config.js 4 | *.d.ts 5 | node_modules 6 | 7 | # CDK asset staging directory 8 | .cdk.staging 9 | cdk.out 10 | 11 | # Parcel default cache directory 12 | .parcel-cache 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudFront reverse proxy API Gateway to prevent CORS 2 | 3 | In this blog we will do a quick recap of CORS and reverse proxies. Then we will show how a reverse proxy can eliminate 4 | CORS, specifically in the context of a SPA hosted on CloudFront with an API Gateway backend. The sample code focuses 5 | on public, authenticated routes (Authorization header) and IAM signed request all being reverse proxied through 6 | CloudFront. Everything is done with the AWS CDK... continue reading the accompanying blog post 7 | here => https://www.rehanvdm.com/serverless/cloudfront-reverse-proxy-api-gateway-to-prevent-cors/index.html 8 | 9 | ![CloudFront reverse proxy API Gateway Custom domain](cf_proxy_image.png "CloudFront reverse proxy API Gateway Custom domain") 10 | 11 | This is a stock standard CDK project using TypeScript. 12 | 13 | ### Prerequisites: 14 | 1. AWS IAM profile setup in `..\.aws\credentials`, with Admin rights. 15 | 3. AWS CDK bootstrap must have been run in the account already. 16 | 17 | ### Up and running 18 | * Run `npm install` 19 | * Change directory to ***/src/frontend/src*** and run `npm install` 20 | * Create an ACM certificate and Hosted zone. These are only needed to use and test the API Gateway Custom domain 21 | * Search and replace `rehan` with your AWS profile name 22 | * Replace the CDK context variables values in the package.json file, both `custDomainCertArn` and `custDomainName` 23 | with the values of resources you created above 24 | 25 | ### Useful commands 26 | * `cdk diff` compare deployed stack with current state 27 | * `cdk deploy` deploy this stack 28 | 29 | ### Other 30 | You can navigate to the frontend under /src/frontend/dist to view the basic HTML page used to make requests. -------------------------------------------------------------------------------- /bin/cloudfront-reverse-proxy-apigw.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import * as cdk from '@aws-cdk/core'; 4 | import { CloudfrontReverseProxyApigwStack } from '../lib/cloudfront-reverse-proxy-apigw-stack'; 5 | 6 | 7 | const app = new cdk.App(); 8 | new CloudfrontReverseProxyApigwStack(app, 'cloudfront-reverse-proxy-apigw'); 9 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node bin/cloudfront-reverse-proxy-apigw.ts", 3 | "context": { 4 | "@aws-cdk/core:enableStackNameDuplicates": "true", 5 | "aws-cdk:enableDiffNoFail": "true", 6 | "@aws-cdk/core:stackRelativeExports": "true" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /cf_proxy_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rehanvdm/cloudfront-reverse-proxy-apigw/fefc0f0f66d1d50cb4ad98f8f37a8247c0437afb/cf_proxy_image.png -------------------------------------------------------------------------------- /lib/cloudfront-reverse-proxy-apigw-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from '@aws-cdk/core'; 2 | import * as cloudfront from '@aws-cdk/aws-cloudfront'; 3 | import * as s3 from '@aws-cdk/aws-s3'; 4 | import * as s3deploy from '@aws-cdk/aws-s3-deployment'; 5 | import * as apigateway from '@aws-cdk/aws-apigateway'; 6 | import * as lambda from '@aws-cdk/aws-lambda'; 7 | import * as cert from '@aws-cdk/aws-certificatemanager'; 8 | 9 | export class CloudfrontReverseProxyApigwStack extends cdk.Stack { 10 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { 11 | super(scope, id, props); 12 | 13 | const allowCors = true; /* Set to false for production, no longer needed */ 14 | 15 | let defaultCorsOptions: apigateway.CorsOptions | undefined = undefined; 16 | if(allowCors) 17 | { 18 | defaultCorsOptions = { 19 | allowOrigins: apigateway.Cors.ALL_ORIGINS, 20 | allowMethods: apigateway.Cors.ALL_METHODS, 21 | allowCredentials: true, 22 | allowHeaders: ["*"], 23 | maxAge: cdk.Duration.days(1) 24 | }; 25 | } 26 | 27 | const name = (resourceName: string) => { 28 | return id + "-" + resourceName; 29 | }; 30 | 31 | const custDomainCertArn: string = this.node.tryGetContext('custDomainCertArn'); 32 | const custDomainName: string = this.node.tryGetContext('custDomainName'); 33 | const customDomainMappingPath = "default"; 34 | const cloudFrontApiGwPath = "cf-apigw"; 35 | const cloudFrontCustDomainPath = "cf-cust-domain"; 36 | const apiStageName = "prod"; 37 | const api = new apigateway.RestApi(this, name("rest-api"), { 38 | restApiName: name("rest-api"), 39 | deployOptions: { 40 | stageName: apiStageName, 41 | } 42 | }); 43 | 44 | const apiLambda = new lambda.Function(this, name("api-lambda"), { 45 | functionName: name("api-lambda"), 46 | code: new lambda.AssetCode('./src/backend/lambda/api'), 47 | handler: 'index.handler', 48 | runtime: lambda.Runtime.NODEJS_12_X, 49 | timeout: cdk.Duration.seconds(25), 50 | }); 51 | const apiLambdaAuth = new lambda.Function(this, name("api-lambda-auth"), { 52 | functionName: name("api-lambda-auth"), 53 | code: new lambda.AssetCode('./src/backend/lambda/api-auth'), 54 | handler: 'index.handler', 55 | runtime: lambda.Runtime.NODEJS_12_X, 56 | timeout: cdk.Duration.seconds(25), 57 | }); 58 | 59 | /* Add default non protected route that will catch all if no path exists. */ 60 | /* ANY /* */ 61 | api.root.addProxy({ 62 | defaultIntegration: new apigateway.LambdaIntegration(apiLambda), 63 | defaultCorsPreflightOptions: defaultCorsOptions, 64 | anyMethod: true, 65 | }); 66 | 67 | 68 | /* --------------------------- Paths used for the CloudFront to APIGW proxy ------------------------------------- */ 69 | /* Creates a top level resource that is required because CloudFront prepends the path with the `pathPattern` when forwarding */ 70 | let cloudFrontApiGwResource = api.root.addResource(cloudFrontApiGwPath, { defaultCorsPreflightOptions: defaultCorsOptions }); 71 | 72 | /* Add specific route for Custom Lambda Token Authorizer which requires the Authorization header */ 73 | /* GET /cf-apigw/auth */ 74 | let cfApiGwAuthResource = cloudFrontApiGwResource.addResource("auth", { defaultCorsPreflightOptions: defaultCorsOptions }); 75 | cfApiGwAuthResource.addMethod("GET", new apigateway.LambdaIntegration(apiLambda), { 76 | authorizationType: apigateway.AuthorizationType.CUSTOM, 77 | authorizer: new apigateway.TokenAuthorizer(this, name("api-apigw-auth"), { handler: apiLambdaAuth }) 78 | }); 79 | 80 | /* Add specific route for IAM Authorization which requires the Authorization header */ 81 | /* GET /cf-apigw/auth-iam */ 82 | let cfApiGwAuthIamResource = cloudFrontApiGwResource.addResource("auth-iam", { defaultCorsPreflightOptions: defaultCorsOptions }); 83 | cfApiGwAuthIamResource.addMethod("GET", new apigateway.LambdaIntegration(apiLambda), { 84 | authorizationType: apigateway.AuthorizationType.IAM, 85 | }); 86 | /* -------------------------------------------------------------------------------------------------------------- */ 87 | 88 | 89 | /* ------------------------ Paths used for the CloudFront to Custom Domain proxy --------------------------------- */ 90 | /* Creates a top level resource that is required because CloudFront prepends the path with the `pathPattern` when forwarding */ 91 | let cloudFrontCustDomainResource = api.root.addResource(cloudFrontCustDomainPath, { defaultCorsPreflightOptions: defaultCorsOptions }); 92 | 93 | /* Add specific route for Custom Lambda Token Authorizer which requires the Authorization header */ 94 | /* GET /cf-cust-domain/auth */ 95 | let cfCustDomainAuthResource = cloudFrontCustDomainResource.addResource("auth", { defaultCorsPreflightOptions: defaultCorsOptions }); 96 | cfCustDomainAuthResource.addMethod("GET", new apigateway.LambdaIntegration(apiLambda), { 97 | authorizationType: apigateway.AuthorizationType.CUSTOM, 98 | authorizer: new apigateway.TokenAuthorizer(this, name("api-cust-domain-auth"), { handler: apiLambdaAuth }) 99 | }); 100 | 101 | /* Add specific route for IAM Authorization which requires the Authorization header */ 102 | /* GET /cf-cust-domain/auth-iam */ 103 | let cfCustDomainAuthIamResource = cloudFrontCustDomainResource.addResource("auth-iam", { defaultCorsPreflightOptions: defaultCorsOptions }); 104 | cfCustDomainAuthIamResource.addMethod("GET", new apigateway.LambdaIntegration(apiLambda), { 105 | authorizationType: apigateway.AuthorizationType.IAM, 106 | }); 107 | /* -------------------------------------------------------------------------------------------------------------- */ 108 | 109 | 110 | let originAccessIdentity = new cloudfront.OriginAccessIdentity(this, 'OAI', { comment: id }); 111 | const siteBucket = new s3.Bucket(this, name('web-bucket'), { 112 | bucketName: name('web-bucket'), 113 | websiteIndexDocument: 'index.html', 114 | websiteErrorDocument: 'error.html', 115 | removalPolicy: cdk.RemovalPolicy.DESTROY 116 | }); 117 | siteBucket.grantRead(originAccessIdentity); 118 | 119 | const distribution = new cloudfront.CloudFrontWebDistribution(this, name("web-dist"), { 120 | comment: name("web-dist"), 121 | originConfigs: [ 122 | { 123 | s3OriginSource: { 124 | s3BucketSource: siteBucket, 125 | originAccessIdentity: originAccessIdentity 126 | }, 127 | behaviors : [ {isDefaultBehavior: true} ], 128 | }, 129 | /* --------------------------------- Reverse proxy path to API GW ------------------------------------------- */ 130 | { 131 | behaviors: [{ 132 | pathPattern: "/"+cloudFrontApiGwPath+"/*", 133 | allowedMethods: cloudfront.CloudFrontAllowedMethods.ALL, 134 | maxTtl: cdk.Duration.seconds(0), 135 | minTtl: cdk.Duration.seconds(0), 136 | defaultTtl: cdk.Duration.seconds(0), 137 | compress: false, 138 | forwardedValues: { 139 | queryString: true, 140 | headers: ["Authorization"] 141 | } 142 | }], 143 | customOriginSource: { 144 | /* domainName: api.url.replace('https://', '') 145 | Won't resolve at runtime, only after the stack is deployed. To get around this use multiple stacks and pass the 146 | output from the api(backend) stack to the frontend stack. Just keeping one now to reduce complexity. */ 147 | 148 | /* Replace manually in the AWS Console after deploy with the API GW domain 149 | example: "xxxxxxxxxx.execute-api.us-east-1.amazonaws.com"*/ 150 | domainName: "repalce.after-deployment-with-apigw-domain.com", 151 | originPath: "/"+apiStageName, 152 | originProtocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY, 153 | } 154 | }, 155 | /* --------------------------- Reverse proxy path to API GW Custom Domain ------------------------------------ */ 156 | { 157 | behaviors: [{ 158 | pathPattern: "/"+cloudFrontCustDomainPath+"/*", 159 | allowedMethods: cloudfront.CloudFrontAllowedMethods.ALL, 160 | maxTtl: cdk.Duration.seconds(0), 161 | minTtl: cdk.Duration.seconds(0), 162 | defaultTtl: cdk.Duration.seconds(0), 163 | compress: false, 164 | forwardedValues: { 165 | queryString: true, 166 | headers: ["Authorization"] 167 | } 168 | }], 169 | customOriginSource: { 170 | domainName: custDomainName, 171 | originPath: "/"+customDomainMappingPath, 172 | originProtocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY, 173 | } 174 | } 175 | ] 176 | }); 177 | 178 | new s3deploy.BucketDeployment(this, name('deploy-with-invalidation'), { 179 | sources: [ s3deploy.Source.asset("./src/frontend/dist") ], 180 | destinationBucket: siteBucket, 181 | distribution, 182 | distributionPaths: ['/*'] 183 | }); 184 | 185 | /* Remember to replace these with your own domain and change the value for custDomainCertArn and custDomainCertArn 186 | values in the package.json that gets passed as context variables */ 187 | const apiGwDomain = new apigateway.DomainName(this, name('cust-domain'), { 188 | domainName: custDomainName, 189 | certificate: cert.Certificate.fromCertificateArn(this, name('cust-domain-cert'), custDomainCertArn), 190 | endpointType: apigateway.EndpointType.EDGE, 191 | securityPolicy: apigateway.SecurityPolicy.TLS_1_2 192 | }); 193 | apiGwDomain.addBasePathMapping(api, { basePath: customDomainMappingPath, stage: api.deploymentStage}); 194 | 195 | /* TODO: After deployment copy the API Gateway domain name from the AWS console of the Custom domain name 196 | * and create a cname record that points to `custDomainName` */ 197 | 198 | new cdk.CfnOutput(this, name("output-apigw-endpoint"), { value: api.url + "/" + apiStageName }); 199 | new cdk.CfnOutput(this, name("output-apigw-custom-domain-endpoint"), { value: apiGwDomain.domainName + "/" + customDomainMappingPath }); 200 | new cdk.CfnOutput(this, name("output-cloudfront-endpoint"), { value: distribution.distributionDomainName }); 201 | } 202 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudfront-reverse-proxy-apigw", 3 | "version": "0.1.0", 4 | "bin": { 5 | "cloudfront-reverse-proxy-apigw": "bin/cloudfront-reverse-proxy-apigw.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "cdk": "cdk", 11 | "build-frontends": "npm --prefix ./src/frontend/src run build", 12 | "deploy": "npm run build-frontends && tsc && cdk deploy --profile rehan --context custDomainCertArn=arn:aws:acm:us-east-1:XXXXXX --context custDomainName=YYYYYY", 13 | "diff": "tsc && cdk diff --profile rehan --context custDomainCertArn=XXXXX --context custDomainName=YYYYYY" 14 | }, 15 | "devDependencies": { 16 | "@aws-cdk/assert": "1.62.0", 17 | "@types/jest": "^26.0.10", 18 | "@types/node": "10.17.27", 19 | "jest": "^26.4.2", 20 | "ts-jest": "^26.2.0", 21 | "aws-cdk": "1.62.0", 22 | "ts-node": "^8.1.0", 23 | "typescript": "~3.9.7" 24 | }, 25 | "dependencies": { 26 | "@aws-cdk/aws-certificatemanager": "1.62.0", 27 | "@aws-cdk/aws-apigateway": "1.62.0", 28 | "@aws-cdk/aws-lambda": "1.62.0", 29 | "@aws-cdk/aws-lambda-event-sources": "1.62.0", 30 | "@aws-cdk/aws-cloudfront": "1.62.0", 31 | "@aws-cdk/aws-s3": "1.62.0", 32 | "@aws-cdk/aws-s3-deployment": "1.62.0", 33 | "@aws-cdk/core": "1.62.0", 34 | "source-map-support": "^0.5.16" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/backend/lambda/api-auth/index.js: -------------------------------------------------------------------------------- 1 | /* Taken as is from: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html */ 2 | 3 | exports.handler = function(event, context, callback) { 4 | var token = event.authorizationToken; 5 | switch (token) { 6 | case 'allow': 7 | callback(null, generatePolicy('user', 'Allow', event.methodArn)); 8 | break; 9 | case 'deny': 10 | callback(null, generatePolicy('user', 'Deny', event.methodArn)); 11 | break; 12 | case 'unauthorized': 13 | callback("Unauthorized"); // Return a 401 Unauthorized response 14 | break; 15 | default: 16 | callback("Error: Invalid token"); // Return a 500 Invalid token response 17 | } 18 | }; 19 | 20 | // Help function to generate an IAM policy 21 | var generatePolicy = function(principalId, effect, resource) { 22 | var authResponse = {}; 23 | 24 | authResponse.principalId = principalId; 25 | if (effect && resource) { 26 | var policyDocument = {}; 27 | policyDocument.Version = '2012-10-17'; 28 | policyDocument.Statement = []; 29 | var statementOne = {}; 30 | statementOne.Action = 'execute-api:Invoke'; 31 | statementOne.Effect = effect; 32 | statementOne.Resource = resource; 33 | policyDocument.Statement[0] = statementOne; 34 | authResponse.policyDocument = policyDocument; 35 | } 36 | 37 | // Optional output with custom properties of the String, Number or Boolean type. 38 | authResponse.context = { 39 | "stringKey": "stringval", 40 | "numberKey": 123, 41 | "booleanKey": true 42 | }; 43 | return authResponse; 44 | } -------------------------------------------------------------------------------- /src/backend/lambda/api/index.js: -------------------------------------------------------------------------------- 1 | exports.handler = async (event, context) => 2 | { 3 | return { 4 | statusCode: 200, 5 | body: JSON.stringify(event), 6 | headers: { 7 | "Content-Type": "application/json", 8 | "Access-Control-Allow-Origin": "*", 9 | "Access-Control-Allow-Methods": "*", 10 | "Access-Control-Allow-Headers": "*" 11 | }, 12 | isBase64Encoded: false 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /src/frontend/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Cloudfront reverse proxy to API GW

6 | 7 | Cloudfront Domain:
8 | Cloudfront API GW Mapping:
9 | Cloudfront Custom Domain Mapping:
10 | 11 |

This section is only required if you are interested in the IAM auth

12 |
13 | 14 |

Signing

15 |
16 |

Using API GW

17 | API GW Domain:
18 | API GW Stage:
19 | 20 |

Using Custom Domain

21 | Custom Domain:
22 | Custom Domain Mapping:
23 | 24 |

? The API GW signing variables will be used for the Cloudfront API GW Mapping and similar logic for the Custom Domain.

25 |
26 | 27 | 28 | IAM accessKeyId:
29 | IAM secretAccessKey:
30 | IAM sessionToken:
31 |

? You can get these values using the CLI command below. Replace 'rehan' with your profile name. Token valid for 12 hours by default.

32 |
33 |         aws --profile rehan sts get-session-token
34 |     
35 |
36 | 37 | 38 |

39 | After filling the parameters above, open the Network tab of the browser and hit Do Requests to verify that NO OPTION 40 | calls where made for the API calls. You can also verify the response to see the event that the Lambda function received. 41 |

42 | 43 | 44 | 45 |
46 | 
47 | 
83 | 
84 | 
85 | 
86 | 
87 | 
88 | 
89 | 
90 | 
91 | 
92 | 
93 | 
94 | 
95 | 
96 | 


--------------------------------------------------------------------------------
/src/frontend/src/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 | 

Cloudfront reverse proxy to API GW

6 | 7 | Cloudfront Domain:
8 | Cloudfront API GW Mapping:
9 | Cloudfront Custom Domain Mapping:
10 | 11 |

This section is only required if you are interested in the IAM auth

12 |
13 | 14 |

Signing

15 |
16 |

Using API GW

17 | API GW Domain:
18 | API GW Stage:
19 | 20 |

Using Custom Domain

21 | Custom Domain:
22 | Custom Domain Mapping:
23 | 24 |

? The API GW signing variables will be used for the Cloudfront API GW Mapping and similar logic for the Custom Domain.

25 |
26 | 27 | 28 | IAM accessKeyId:
29 | IAM secretAccessKey:
30 | IAM sessionToken:
31 |

? You can get these values using the CLI command below. Replace 'rehan' with your profile name. Token valid for 12 hours by default.

32 |
33 |         aws --profile rehan sts get-session-token
34 |     
35 |
36 | 37 | 38 |

39 | After filling the parameters above, open the Network tab of the browser and hit Do Requests to verify that NO OPTION 40 | calls where made for the API calls. You can also verify the response to see the event that the Lambda function received. 41 |

42 | 43 | 44 | 45 |
46 | 
47 | 
83 | 
84 | 
85 | 
86 | 
87 | 
88 | 
89 | 
90 | 
91 | 
92 | 
93 | 
94 | 
95 | 
96 | 


--------------------------------------------------------------------------------
/src/frontend/src/index.js:
--------------------------------------------------------------------------------
  1 | var aws4 = require('aws4');
  2 | 
  3 | module.exports = {
  4 |     doRequests: function(cloudFrontDomain, cloudFrontApiProxyPath, cloudFrontCustDomainProxyPath,
  5 |                          apiGwDomain, apiGwStagePath, apiGwCustDomain, apiGwCustMapping, accessKeyId, secretAccessKey, sessionToken)
  6 |     {
  7 |         const region = "us-east-1";
  8 | 
  9 |         async function request(url, resource, method, body, headers = {})
 10 |         {
 11 |             let requestUrl = `${url}${resource}`;
 12 | 
 13 |             console.log("Fetch options:", {
 14 |                 url: requestUrl,
 15 |                 method: method,
 16 |                 headers: headers,
 17 |                 body: body
 18 |             });
 19 | 
 20 |             let response = await fetch(requestUrl, {
 21 |                 method: method,
 22 |                 headers: headers,
 23 |                 body: body
 24 |             });
 25 | 
 26 |             console.log(response);
 27 |         }
 28 | 
 29 |         /**
 30 |          *
 31 |          * @param url
 32 |          * @param resource
 33 |          * @param method
 34 |          * @param body
 35 |          * @param headers
 36 |          * @param signHost This is the API GW domain name |OR| the Custom Domain
 37 |          * @param signHostPath If signHost is the API GW then this is the Stage. If signHost is a Custom Domain then this is the Mapping path.
 38 |          * @returns {Promise}
 39 |          */
 40 |         async function signedRequest(url, resource, method, body, headers = {}, { signHost, signHostPath })
 41 |         {
 42 |             let requestUrl = `${url}${resource}`;
 43 | 
 44 |             const opts = {
 45 |                 host: signHost,
 46 |                 path: `${signHostPath}${resource}`,
 47 |                 method: method,
 48 |                 headers: headers,
 49 |                 url:`https://${signHost}`,
 50 |                 region: region,
 51 |                 service: "execute-api"
 52 |             };
 53 | 
 54 |             if (body !== null)
 55 |                 opts.body = body;
 56 | 
 57 |             /* Create the signed request object */
 58 |             const signedRequest = aws4.sign(opts, { accessKeyId, secretAccessKey, sessionToken });
 59 | 
 60 |             console.log("Signing options:", signedRequest);
 61 |             console.log("Fetch options:", {
 62 |                 url: requestUrl,
 63 |                 method: method,
 64 |                 headers: signedRequest.headers,
 65 |                 body: body
 66 |             });
 67 | 
 68 |             /* Add the signed headers to the request */
 69 |             let response = await fetch(requestUrl, {
 70 |                 method: method,
 71 |                 headers: signedRequest.headers,
 72 |                 body: body
 73 |             });
 74 | 
 75 |             console.log(response);
 76 |         }
 77 | 
 78 |         let url = `https://${cloudFrontDomain}`;
 79 | 
 80 |         /* Use the proxy path to the API GW */
 81 |         request(url, `/${cloudFrontApiProxyPath}/no-auth`, "GET");
 82 |         request(url, `/${cloudFrontApiProxyPath}/auth`, "GET", null, { "Authorization": "allow" });
 83 |         signedRequest(url, `/${cloudFrontApiProxyPath}/auth-iam`, "GET", null, { },
 84 |                         {
 85 |                             signHost: apiGwDomain,
 86 |                             signHostPath: apiGwStagePath,
 87 |                         });
 88 | 
 89 | 
 90 |         /* Use the proxy path to the the Custom Domain */
 91 |         request(url, `/${cloudFrontCustDomainProxyPath}/no-auth`, "GET");
 92 |         request(url, `/${cloudFrontCustDomainProxyPath}/auth`, "GET", null, { "Authorization": "allow" });
 93 |         signedRequest(url, `/${cloudFrontCustDomainProxyPath}/auth-iam`, "GET", null, { },
 94 |                         {
 95 |                             signHost: apiGwCustDomain,
 96 |                             signHostPath: apiGwCustMapping,
 97 |                         });
 98 |     }
 99 | }
100 | 
101 | 
102 | 
103 | 


--------------------------------------------------------------------------------
/src/frontend/src/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "aws4-browser-example",
 3 |   "version": "1.0.0",
 4 |   "description": "Example project for how to use aws4 in the browser",
 5 |   "main": "index.js",
 6 |   "author": "Michael Hart  (http://github.com/mhart)",
 7 |   "license": "ISC",
 8 |   "scripts": {
 9 |     "build": "browserify index.js -d --s js > ../dist/index.js "
10 |   },
11 |   "dependencies": {
12 |     "aws4": "^1.10.0"
13 |   },
14 |   "devDependencies": {
15 |     "browserify": "^16.5.1"
16 |   }
17 | }
18 | 


--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2018",
 4 |     "module": "commonjs",
 5 |     "lib": ["es2018"],
 6 |     "declaration": true,
 7 |     "strict": true,
 8 |     "noImplicitAny": true,
 9 |     "strictNullChecks": true,
10 |     "noImplicitThis": true,
11 |     "alwaysStrict": true,
12 |     "noUnusedLocals": false,
13 |     "noUnusedParameters": false,
14 |     "noImplicitReturns": true,
15 |     "noFallthroughCasesInSwitch": false,
16 |     "inlineSourceMap": true,
17 |     "inlineSources": true,
18 |     "experimentalDecorators": true,
19 |     "strictPropertyInitialization": false,
20 |     "typeRoots": ["./node_modules/@types"]
21 |   },
22 |   "exclude": ["cdk.out"]
23 | }
24 | 


--------------------------------------------------------------------------------