2024 06 29 aws cdk cors issue resolution for react app integration - bizkite-co/handterm GitHub Wiki
original file name: Codeium Chat - L57tJaNSXrp3xm1KvrH5r6Yxu9Asy3Tu.md summary: The user shared a snippet of their AWS CDK code for setting up authentication with Cognito, API Gateway, and Lambda functions. They were facing a CORS error when trying to sign up from their React app hosted on GitHub Pages.
I suggested modifying the AWS CDK template to enable CORS on the API Gateway by setting the defaultCorsPreflightOptions
property with appropriate configuration for allowed origins, methods, and headers. I also provided guidance on allowing calls from multiple origins during development and production.
The user encountered an error due to incorrect usage of the corsEnabled
method, which I clarified as not being a valid method in AWS CDK for defining CORS configurations. I corrected the method by directly specifying the allowed origins without using corsEnabled
.
Overall, the conversation revolved around resolving CORS issues in the AWS CDK template for the user's React app interacting with the deployed AWS resources. date: 2024-06-29T13:42
Note: This is purely the output of the chat conversation and does not contain any raw data, codebase snippets, etc. used to generate the output.
User at 1:42 pm, Jun 29, 2024
I have this AWS CDK that's already deployed and working:
// cdk/lib/cdk-stack.ts
import {
aws_cognito as cognito,
aws_s3 as s3,
aws_lambda as lambda,
aws_iam as iam,
aws_apigateway as apigateway,
} from "aws-cdk-lib";
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
const lambdaRuntime = lambda.Runtime.NODEJS_16_X;
export class HandTermCdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Cognito User Pool
const userPool = new cognito.UserPool(this, 'HandTermUserPool', {
userPoolName: 'HandTermUserPool',
selfSignUpEnabled: true,
userVerification: {
emailSubject: 'Verify your email for our app!',
emailBody: 'Hello {username}, Thanks for signing up to our app! Your verification code is {####}',
emailStyle: cognito.VerificationEmailStyle.CODE,
},
signInAliases: {
email: true
},
passwordPolicy: {
minLength: 8,
requireUppercase: true,
requireLowercase: true,
requireDigits: true,
requireSymbols: true,
},
autoVerify: { email: true }
});
// Assuming lambdaAtEdge is your authentication Lambda function
const api = new apigateway.RestApi(this, 'HandTermApi', {
restApiName: 'HandTerm Service',
description: 'This service serves authentication requests.',
});
// Assuming `api` is your RestApi object and `userPool` is your Cognito User Pool
const cognitoAuthorizer = new apigateway.CognitoUserPoolsAuthorizer(this, 'CognitoAuthorizer', {
cognitoUserPools: [userPool],
identitySource: 'method.request.header.Authorization',
authorizerName: 'CognitoAuthorizer'
});
// Cognito User Pool Client
const userPoolClient = userPool.addClient('AppClient', {
authFlows: {
userSrp: true,
},
generateSecret: false,
// Add your API Gateway endpoint URL to the list of callback URLs
});
// Cognito Identity Pool
const identityPool = new cognito.CfnIdentityPool(this, 'HandTermIdentityPool', {
identityPoolName: 'HandTermIdentityPool',
allowUnauthenticatedIdentities: false,
cognitoIdentityProviders: [{
clientId: userPoolClient.userPoolClientId,
providerName: userPool.userPoolProviderName,
}],
});
// S3 Bucket for User Logs
const logsBucket = new s3.Bucket(this, 'HandTermHistoryBucket', {
bucketName: 'handterm-history',
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
encryption: s3.BucketEncryption.S3_MANAGED,
});
// Lambda Execution Role
const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', {
assumedBy: new iam.CompositePrincipal(
new iam.ServicePrincipal('lambda.amazonaws.com'),
new iam.ServicePrincipal('edgelambda.amazonaws.com')
),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3FullAccess'),
],
});
const tokenLambdaRole = new iam.Role(this, 'TokenHandlerLambdaRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), // Basic Lambda execution role
// Add additional policies as needed
],
});
const tokenHandlerLambda = new lambda.Function(this, 'TokenHandlerFunction', {
runtime: lambda.Runtime.NODEJS_16_X,
handler: 'tokenHandler.handler',
role: tokenLambdaRole,
code: lambda.Code.fromAsset('lambda/tokenHandler'),
environment: {
COGNITO_APP_CLIENT_ID: userPoolClient.userPoolClientId,
}
});
const tokenHandlerIntegration = new apigateway.LambdaIntegration(tokenHandlerLambda);
const authCallbackResource = api.root.addResource('auth');
// Use the authorizer for your endpoint
authCallbackResource.addMethod('GET', tokenHandlerIntegration, {
authorizer: cognitoAuthorizer,
authorizationType: apigateway.AuthorizationType.COGNITO,
});
const signUpLambda = new lambda.Function(this, 'SignUpFunction', {
runtime: lambdaRuntime,
handler: 'signUp.handler',
role: lambdaExecutionRole,
code: lambda.Code.fromAsset('lambda/authentication'),
environment: {
COGNITO_APP_CLIENT_ID: userPoolClient.userPoolClientId,
}
});
const signUpIntegration = new apigateway.LambdaIntegration(signUpLambda);
const signUpResource = api.root.addResource('signup');
signUpResource.addMethod('POST', signUpIntegration);
// Outputs
new cdk.CfnOutput(this, 'UserPoolId', { value: userPool.userPoolId });
new cdk.CfnOutput(this, 'UserPoolClientId', { value: userPoolClient.userPoolClientId });
new cdk.CfnOutput(this, 'IdentityPoolId', { value: identityPool.ref });
new cdk.CfnOutput(this, 'BucketName', { value: logsBucket.bucketName });
new cdk.CfnOutput(this, 'ApiEndpoint', { value: api.url });
}
}
const app = new cdk.App();
new HandTermCdkStack(app, 'HandTermCdkStack');
I am connecting to it from my React app hosted on GitHub Pages.
When I try to signUp
from within here:
import { useState, useEffect } from 'react';
import axios from 'axios';
const API_URL = import.meta.env.VITE_API_URL;
export const useAuth = () => {
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
useEffect(() => {
// Optionally check session validity on hook mount
}, []);
const checkSession = async () => {
try {
// This could be a call to a `/session` endpoint that verifies the session
await axios.get(`${API_URL}/session`);
setIsLoggedIn(true);
} catch (error) {
console.error('Session check failed:', error);
setIsLoggedIn(false);
}
};
const signUp = async (username: string, password: string, email: string) => {
try {
await axios.post(`${API_URL}/signUp`, { username, password, email });
// Handle post-signup logic (e.g., auto-login or redirect to login page)
} catch (error) {
console.error('Signup failed:', error);
throw error;
}
};
const getCurrentUser = async () => {
try {
const response = await axios.get(`${API_URL}/currentUser`);
return response.data; // Contains username, attributes, etc.
} catch (error) {
console.error('Error fetching current user:', error);
return null;
}
};
const login = async (username: string, password: string) => {
try {
const response = await axios.post(`${API_URL}/login`, { username, password });
// The API should set an HttpOnly cookie directly, no need to handle tokens here
setIsLoggedIn(true);
return response.data; // Return session data if needed
} catch (error) {
console.error('Login failed:', error);
throw error;
}
};
const logout = async () => {
try {
await axios.get(`${API_URL}/logout`);
setIsLoggedIn(false);
} catch (error) {
console.error('Logout failed:', error);
throw error;
}
};
const refreshSession = async () => {
try {
// This endpoint should refresh the session and set a new HttpOnly cookie
await axios.post(`${API_URL}/refresh-session`);
setIsLoggedIn(true);
} catch (error) {
console.error('Session refresh failed:', error);
setIsLoggedIn(false);
throw error;
}
};
return { isLoggedIn, login, logout, signUp, refreshSession, getCurrentUser, checkSession };
};
I get a CORS error:
Access to XMLHttpRequest at 'https://u4ceo7qshi.execute-api.us-east-1.amazonaws.com/prod//signUp' from origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
How can I fix that in the CDK template code?
Codeium at 1:42 pm, Jun 29, 2024
To resolve the CORS error in your AWS CDK template, you need to enable CORS on your API Gateway. This allows requests from your React application hosted on GitHub Pages (or any other domain you wish to allow) to interact with your API. Here's how you can modify your HandTermApi
setup to include CORS settings:
const api = new apigateway.RestApi(this, 'HandTermApi', {
restApiName: 'HandTerm Service',
description: 'This service serves authentication requests.',
// Add default CORS options here
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS, // Adjust this to be more restrictive based on your needs
allowMethods: apigateway.Cors.ALL_METHODS, // You can customize this to only include methods you need, e.g., ['OPTIONS', 'GET', 'POST']
allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key'], // Add any other headers your API requires
}
});
This modification adds a defaultCorsPreflightOptions
property to your API Gateway definition, specifying which origins, methods, and headers are allowed by your API. This is crucial for web applications that make cross-origin requests to your API, which is exactly what's happening when your React app hosted on GitHub Pages (or locally during development) tries to communicate with your API deployed on AWS.
-
allowOrigins
: Specifies which origins are allowed to make requests to your API. Usingapigateway.Cors.ALL_ORIGINS
allows requests from any domain. For production, it's recommended to specify the exact origins you want to allow (e.g.,['https://yourgithubpage.github.io']
) for better security. -
allowMethods
: Specifies which HTTP methods are allowed.apigateway.Cors.ALL_METHODS
allows all methods, but you can restrict this to just the methods your API uses. -
allowHeaders
: Specifies which headers can be included in the requests. This should include any headers your API expects or requires.
After applying these changes, redeploy your CDK stack to AWS. This should resolve the CORS error by properly configuring your API Gateway to accept requests from your React application's origin.
User at 1:46 pm, Jun 29, 2024
In production, all the calls will come from https://handterm.com
, but when I am testing I would also like to make calls from my local machine.
How do I do that?
Codeium at 1:46 pm, Jun 29, 2024
To allow calls from both your production domain (https://handterm.com
) and your local machine during development, you can specify both origins in the allowOrigins
array of your CORS configuration for the API Gateway. Here's how you can adjust the defaultCorsPreflightOptions
in your HandTermApi
setup within your CDK template:
const api = new apigateway.RestApi(this, 'HandTermApi', {
restApiName: 'HandTerm Service',
description: 'This service serves authentication requests.',
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.corsEnabled(['https://handterm.com', 'http://localhost:3000']), // Adjust the localhost port as necessary
allowMethods: apigateway.Cors.ALL_METHODS,
allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Requested-With'], // Include any other headers your API expects
}
});
In this configuration, allowOrigins
is set to an array containing both https://handterm.com
and http://localhost:3000
(adjust the port 3000
if your local development server runs on a different port). This setup allows your API to accept requests from your production domain and your local development environment.
-
Security: While allowing
http://localhost
is common for development purposes, ensure that your production environment is as restrictive as necessary. You might consider using environment-specific configurations to enforce stricter CORS policies in production. - Deployment: Remember to redeploy your CDK stack after making these changes to apply the updated CORS policy to your API Gateway.
- Testing: After redeployment, verify both from your production domain and your local development setup to ensure that the CORS policy is correctly applied and that requests are successful from both origins.
This approach provides a flexible way to work with your API from different environments without encountering CORS issues.
User at 1:48 pm, Jun 29, 2024
I am getting this error:
Property 'corsEnabled' does not exist on type 'typeof Cors'.ts(2339)
⚠ Error (TS2339) |
Codeium at 1:48 pm, Jun 29, 2024
The error you're encountering suggests that there's a mistake in the usage of the AWS CDK's API Gateway CORS configuration method. The corsEnabled
method I previously mentioned does not exist. My apologies for the confusion. The correct way to configure CORS in your API Gateway using AWS CDK is to directly set the allowOrigins
array in the defaultCorsPreflightOptions
without using a method like corsEnabled
. Here's how you can correctly do it:
const api = new apigateway.RestApi(this, 'HandTermApi', {
restApiName: 'HandTerm Service',
description: 'This service serves authentication requests.',
defaultCorsPreflightOptions: {
allowOrigins: ['https://handterm.com', 'http://localhost:3000'], // Include both your production and local origins
allowMethods: apigateway.Cors.ALL_METHODS,
allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Requested-With'],
}
});
In this corrected version:
- The
allowOrigins
property directly accepts an array of strings specifying the allowed origins, addressing the error you encountered. - Make sure to replace
'http://localhost:3000'
with the actual port number you use for your local development if it's different.
This configuration allows your API Gateway to accept CORS requests from both your production domain and your local development environment. After making these changes, redeploy your CDK stack to apply the new CORS policy.