AWS & Typescript Masterclass - 10. Using AWS inside a React project with Amplify

September 17th, 2022

82-93

 


(82) Section intro

  1. introduce AWS Amplify in the browser

  2. Get temporary credentials in the Browser

  3. CORS issues

  4. App will be approaching it's final form


(83) Setup and Amplify install

npm i aws-amplify @aws-amplify/auth

npm i aws-sdk


(84) Cognito login from React code

 

model.ts

import {CognitoUser} from '@aws-aplify/auth'

 

export interface User {

userName: string

cognitoUser: CognitoUser

}

 

service/AuthService

import {Auth} from "aws-amplify";

import Amplify from "aws-amplify";

import {CognitoUser} from "@aws-amplify/auth";

import {config} from "./config";

import * as AWS from "aws-sdk";

import { Credentials } from 'aws-sdk/lib/credentials'

 

Amplify.configure({

Auth: {

mandatorySignIn: false,

region: config.REGION,

userPoolId: config.USER_POOL_ID,

userPoolWebClientId: config.APP_CLIENT_ID,

identityPoolId: config.IDENTITY_POOL_ID,

authenticationFlowType: 'USER_PASSWORD_AUTH'

}

})

 

export class AuthService {

public async login(userName: string, password: string): Promise<User|undefined>{

try {

const user = await Auth.signIn(userName, password) as CognitoUser;

return {

cognitoUser: user,

userName: user.getUsername(),

}

catch (error) {

return undefined

}

}

 

public async getUserAttributes(user: User):Promise<UserAttribute[]>{

const result: UserAttribute[] = [];

const attributes = await Auth.userAttributes(user)

result.push(...attributes)

return result

}

}


(86) Photo bucket name and bucket CORS

buckets need globally unique name

use stack-id

const shortStackId = Fn.select(2, Fn.split('/', this.stackId))

suffix = Fn.select(4, Fn.split('-', shortStackId))

 

this.spacesPhotosBucket = new Bucket(this, 'spaces-photos', {

bucketName: 'spaces-photos-' + this.suffix,

cors: [{

allowedMethods: [

HttpMethods.HEAD,

HttpMethods.GET,

HttpMethods.PUT,

],

allowedOrigins: ['*'],

allowedHeaders: ['*'],

}]

})

new CfnOutput(this, 'spaces-photos-bucket-name', {

value: this.spacesPhotosBucket.bucketName

})


(87) Passing the bucket ARN to Auth

this.adminRole.addToPolicy(new PolicyStatement({

effect: Effect.ALLOW,

actions: [

's3:PutObject',

's3:PutObjectAcl',    // publicly accessible objects

],

resources: [this.photoBucketArn]

}))


(88) Lambda CORS

ref: CORS in 100 Seconds - YouTube

result.headers = {

'Content-Type': 'application/json',

'Access-Control-Allow-Origin': '*',

'Access-Control-Allow-Methods': '*',

}

 


(90) AWS credentials in the browser

public async getAWSTemporaryCreds(user: CognitoUser){

const cognitoIdentityPool = `cognito-idp.${config.REGION}.amazonaws.com/${config.USER_POOL_ID}`;

const creds = new AWS.CognitoIdentityCredentials({

IdentityPoolId: config.IDENTITY_POOL_ID,

Logins: {

[cognitoIdentityPool]: user.getSignInUserSession()!.getIdToken().getJwtToken()

}

}, {

region: config.REGION

});

AWS.config.credentials = creds;

await this.refreshCredentials();

return creds

}

 

private async refreshCredentials(): Promise<void>{

return new Promise((resolve, reject)=>{

(AWS.config.credentials as Credentials).refresh(err =>{

if (err) {

reject(err)

} else {

resolve()

}

})

})

}

?? do we really need to set it globally on AWS.config.credentials?

  • probably yes, if we want calls to work

  • otherwise we'll probably need to pass credentials around everywhere in our UI components

?? do we really need to refresh the credentials for it to work?

 


(91) Uploading public files

import { S3, config as S3Config}

S3Config.update({region: appConfig.REGION})

 

const s3Client = new S3({region: appConfig.REGION})

const uploadResult = await new S3({region: appConfig.REGION}).upload({

Bucket: appConfig.SPACES_PHOTOS_BUCKET,

Key: generateRandomFilename(),

Body: file,

ACL: 'public-read'

}).promise

return uploadResult.Location

> upload Key should be random

 

the creation of the lib needs to happen lazy

if it is created  before AWS credentials are set globally, it will never get the credentials

new S3({region: appConfig.REGION})

"Maybe I'll do this with a lazy singleton later"

?? what is a lazy singleton - react feature? typescript feature? or custom-built Singleton pattern?

private getS3Client():S3 {

if(!this.s3Client) {

this.client = new S3({region: appConfig.REGION})

}

return this.s3Client

}

 


(92) Creating spaces

const requestOptions: RequestInit = {

method: 'POST',

body: JSON.stringify(data),

}

const result = await fetch(requestUrl, requestOptions)

const json = await result.json()


(93) Getting spaces

const result = await fetch(requestUrl, {method: 'GET'})

const json = await result.json()