Introduction#
Github shows how to build a simple react app with auth using cognito and text to speech using polly, also update images to s3 and message to a dynamodb table.
- create a react app
- use cognito to create account, confirm, sign in
- auth session and s3 access
- cal api polly (text to speech)
- call api to CRUD DyamoDB table
Architecture#
Create a React App#
create a react app
npx create-react-app my-app
install dependencies
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion react-icons @chakra-ui/icons react-router-dom
install aws sdk client (s3)
npm i @aws-sdk/client-cognito-identity-provider @aws-sdk/client-s3 @aws-sdk/credential-providers @aws-sdk/s3-request-presigner
then run the app
npm start
Basic Router#
const router = createBrowserRouter([{path: '/',element: <LoginForm></LoginForm>},{path: '/session',element: <SessionPage></SessionPage>}])function App() {return (<ChakraProvider><RouterProvider router={router}></RouterProvider></ChakraProvider>)}export default App
Cognito Token and ApiGW Auth#
use cognito client to get token (access token and idtoken)
const cognitoClient = new CognitoIdentityProviderClient({region: config.REGION})// authenticate and get tokenconst response = await cognitoClient.send(new InitiateAuthCommand({AuthFlow: 'USER_PASSWORD_AUTH',AuthParameters: {USERNAME: config.USERNAME,PASSWORD: config.PASSWORD},ClientId: config.CLIENT_ID}))console.log('auth res ', response['AuthenticationResult']['IdToken'])
pass the token into header Authorization to call and API
const callApi = async () => {const { data, status } = await axios.post(config.API_URL_MESSAGE,{ message: 'hello' },{headers: {'Content-Type': 'application/json',Authorization: `Bearer ${response['AuthenticationResult']['IdToken']}`}})console.log(data)console.log(status)}callApi()
call and api endpoint to fetch messages from a dynamodb table using token for auth
const testRequest = async () => {const { data, status } = await axios.get(config.API_URL_TEST, {headers: {Authorization: `Bearer ${response['AuthenticationResult']['IdToken']}`}})console.log(data)console.log(status)}
Cognito Client#
This is a js script to test how Cognito works.
create a cognito client
const cognitoClient = new CognitoIdentityProviderClient({region: config.REGION})
authenticate and get token
const response = await cognitoClient.send(new InitiateAuthCommand({AuthFlow: 'USER_PASSWORD_AUTH',AuthParameters: {USERNAME: config.USERNAME,PASSWORD: config.PASSWORD},ClientId: config.CLIENT_ID}))
exchange token for aws credentials to access s3, this done via s3Client
const credential = fromCognitoIdentityPool({cliengConfig: { region: config.REGION },identityPoolId: config.IDENTITY_POOL_ID,logins: {[config.COGNITO_POOL_ID]: response['AuthenticationResult']['IdToken']}})const retrievs = await credential.call()console.log(retrievs)
given the credentials, we can access S3 data
const s3Client = new S3Client({region: config.REGION,credentials: fromCognitoIdentityPool({cliengConfig: { region: config.REGION },identityPoolId: config.IDENTITY_POOL_ID,logins: {[config.COGNITO_POOL_ID]: response['AuthenticationResult']['IdToken']}})})// send s3 list objects commandconst command = new ListObjectsCommand({Bucket: config.BUCKET,Prefix: 'public/'})try {const result = await s3Client.send(command)console.log(result['Contents'])} catch (error) {console.log(error)}
get image pre-signed url
export const getS3Object = async (idToken, key) => {const s3Client = new S3Client({region: config.REGION,credentials: fromCognitoIdentityPool({clientConfig: { region: config.REGION },identityPoolId: config.IDENTITY_POOL_ID,logins: {[config.COGNITO_POOL_ID]: idToken}})})const command = new GetObjectCommand({Bucket: config.BUCKET,Key: key})const signUrl = await getSignedUrl(s3Client, command)return signUrl}
FrontEnd List of Images#
useEffect to load a list of images or objects
const [images, setImages] = useState([])const [imageUrl, setImageUrl] = useState(null)const getImages = async () => {const items = await listObjects(user.IdToken)if (items) {const keys = items.map(item => item['Key'])setImages(keys)}}useEffect(() => {getImages()}, [])
display a list of images
const ListImages = ({ user, images, setImageUrl }) => {return (<Flexdirection={'column'}width={'100%'}// height={"300px"}overflowY={'auto'}marginTop={'20px'}bg={'orange.100'}>{images.map((image, id) => (<Flexkey={id}width={'100%'}justifyContent={'space-between'}alignItems={'center'}padding={'5px'}backgroundColor={'gray.100'}marginBottom={'5px'}><Text>{image}</Text><ButtoncolorScheme={'teal'}onClick={async () => {const url = await getS3Object(user.IdToken, image)setImageUrl(url)}}>Download</Button></Flex>))}</Flex>)}
load and view an image given the pre-singed url
const ViewImage = ({ imageUrl }) => {return (<Boxbg={'gray.100'}width={'1000px'}height={'500px'}padding={'20px'}display={'flex'}justifyContent={'center'}alignItems={'center'}marginBottom={'20px'}>{imageUrl && <Image src={imageUrl} width="auto" height={'350px'}></Image>}</Box>)}
Upload Image and Progress#
at this moment with AWS SDK JavaScript V3, need to use some modification to check the upload progress
import { Upload } from '@aws-sdk/lib-storage'import { XhrHttpHandler } from '@aws-sdk/xhr-http-handler'
and then the upload progress can be checked as a callback in below code
export const uploadToS3Progress = async (idToken, file, setProgress) => {// s3 clientconst s3Client = new S3Client({region: config.REGION,credentials: fromCognitoIdentityPool({clientConfig: { region: config.REGION },identityPoolId: config.IDENTITY_POOL_ID,logins: {[config.COGNITO_POOL_ID]: idToken}})})const upload = new Upload({client: s3Client,params: {Bucket: config.BUCKET,Key: `public/${file.name}`,Body: file}})upload.on('httpUploadProgress', progress => {console.log(progress.loaded)console.log(progress.total)setProgress((progress.loaded / progress.total) * 100.0)})await upload.done()}
write a processFile function and pass to the upload button in the upload form
const processFile = async (file, setProgress) => {// handler upload fileawait uploadToS3Progress(user.IdToken, file, setProgress)// reload list of imagesawait getImages()}
Troubleshooting#
- the configuration and COGNITO_POOL_ID should look like
"COGNITO_POOL_ID": "cognito-idp.ap-southeast-1.amazonaws.com/ap-southeast-1_xxx"
and the config.js
export const config = {USERNAME: 'xxx@xxx.io',PASSWORD: 'xxx',COGNITO_POOL_ID:'cognito-idp.ap-southeast-1.amazonaws.com/ap-southeast-1_xxx',IDENTITY_POOL_ID: 'ap-southeast-1:xxx',CLIENT_ID: 'xxx',REGION: 'ap-southeast-1',BUCKET: 'xxx'}
-
please ensure deploying the api after changes
-
enable api gateway cords out-of-the-box
-
search cors in cdk
-
to enable cors for proxy lambda when using cognito authorizer user ppool need to add the OPTIONS http method to api gateway per resource
declare const myResource: apigateway.ResourcemyResource.addCorsPreflight({allowOrigins: ['https://amazon.com'],allowMethods: ['GET', 'PUT']})
or
declare const resource: apigateway.Resourceconst subtree = resource.addResource('subtree', {defaultCorsPreflightOptions: {allowOrigins: ['https://amazon.com']}})
- double check the s3 bucket when switching between projects