Play with CORS using AWS Cognito, API Gateway
Tags: nextjs, webdev, react, aws
Published: 2021-09-11 05:33:05

---
canonical_url: "https://www.thangphan.xyz/posts/play-with-cors-using-aws-cognito-api-gateway/"
---
I'm standing in domain A, I'd like to call an API that was hosted in domain B. Normally, I can't do it without setting CORS for the API. In this post, I'm going to do it using API Gateway and AWS Cognito.

### Basic concept
#### API Gateway

I assume I already have had many resources(API), but I didn't set up the access permission yet. In this case, API Gateway is so helpful, I can config the middleware for whatever API I would like to control.

Apart from authorizer, API Gateway also helps us for controlling the resources(API), connecting with other AWS services.

The reason I use API Gateway is that It's so easy to config middleware between API Gateway and AWS Cognito.

#### AWS Cognito

AWS Cognito is a service that helps us for building authentication. We can sign up user, sign in, logout, etc...

If I sign in successfully, I can get 3 types of tokens.

1. access token: the token of my account.
2. id token: use to access other AWS services. I will use it to verify my API Gateway.
3. refresh token: use to re-new session. 

#### Lambda function

In this demo, I'm going to use lambda to fake responses in API. You can imagine it's a function that we can make the response for our API.

### Implementation
I will have 2 patterns with CORS.
1. Access the outside API without credentials.
2. Access the outside API by sending credentials from the client side.

This is my logic.

#### Without credentials
![Without credentials](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubwepp5yi09izqipa8rv.png)

#### Credentials
![Credentials](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wwtowpig9juguq0fy1ny.png)


### Setup
#### Nextjs

Create a new project:
```tsx
npx create-next-app --typescript
```
I give it the name `dev-cors`.

#### AWS
I assume you already had an IAM account in order to use AWS services.

##### Lambda function
I will access to AWS Lambda console environment, and create a new function `dev-cors-function`.

![Dev Cors Function](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jmgdhrznwitwepfldjcu.png)

Edit the response in order to make it looks like a realistic API:

```tsx
const response = {
  statusCode: 200,
  body: {
    userId: 1,
    id: 1,
    title: "delectus aut autem",
    completed: false
  },
};
```
And Hit `Deploy` in order to finish our set up.

##### AWS Cognito
I'm going to the AWS Cognito console environment, and create new user pool with name `dev-cors-cognito`.

![Dev Cors Cognito](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hlipzdeq2cawp3nba8tu.png)

Hit `Add app client...` I need to create app client in order to make request `sign up`, `sign in`, etc...

Give it a name `dev-cors-app-client`.
The important thing here is that I need to uncheck `Generate client secret`, if I don't do this, I'm not able to make a request successful(it was an error of AWS Cognito).

Hit `Create pool` and I finished creating a user pool for our app.

##### API Gateway

I'm going to API Gateway console, and create new API for our demo.

![API Gateway Console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lbg8kar7iwsham84jxek.png)

Hit button `Build` in the `REST API` column.

And give it a name like `dev-cors-api`, then click `Create API`.

Now I have had an API, let's make two resources for our app.
1. without-credentials
2. credentials

![API Gateway](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/edxstegzn4gu9c4n6c2r.png)

> Note: we need to check button `Enable API Gateway CORS` in order to make use our API will be enabled CORS.

In `without-credentials`, let's create a method `GET`, and connect to the lambda function that I just created in the previous step.

![GET Without Credentials](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cr3wqx5cqnrn9x003y16.png)

In order to make a request successful from the client-side, I need to config `Access-Control-Allow-Origin` in my API.

In `GET` method, hit `Integration Response` -> `Method Response`.

![Integration Response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j5xm8ylefj1qy8y43wum.png)

Hit `Add Header`, and add `Access-Control-Allow-Origin`.

Now, I go back to `Integration Response`, I'm able to see
`Access-Control-Allow-Origin`. But I have to change the value to `'*'`.

![Access Control Allow Origin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rv7h0gztk35w5t5v38wc.png)

With `credentials` I do the same way with `without-credentials`, create a method `GET`, but I will create an authorizer for the resource.

Hit `Authorizers` tab, and select the `user pool` that I just created in the previous step.

![Dev Cors Authorizer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8g3tfu9snv3koi99n4lo.png)

Give it the name `dev-cors-authorizer`, and fill out `Authorization` in `Token Source *` input. I will need to send `Authorization: token` from the client in order to verify.

Ok cool! Now, I come back to `credentials` resource, and hit `GET` -> `Method Request` and select `dev-cors-authorizer` for `Authorization`. If it wasn't showed, you maybe need to reload your page.

![Authorization Setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/55bqq7gqvd1bekgtr1kx.png)

Finally, I need to deploy my API. Let's do it:
![Deploy API Gateway](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1vfd4902m4af3ee23vkl.png)

Here is my API:
![API production](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5f2s45pyux8ot56z74p0.png)

### Coding

Let's create a file `.env`, and set up a variable point to our API Gateway:

```tsx
NEXT_PUBLIC_API_GATEWAY='https://i83ybr0e1d.execute-api.us-west-2.amazonaws.com/dev'
```

#### Without Credentials

Let's create a page in nextjs `without-credentials.tsx`

```tsx
import * as React from 'react'

function WithoutCredentials() {
  React.useEffect(() => {
    async function fetchData() {
      const data = await fetch(
        `${process.env.NEXT_PUBLIC_API_GATEWAY}/without-credentials`,
      )
      const resJson = await data.json()

      // do some stuff in here.
    }

    fetchData()
  }, [])
  return (
    <div>
      <h1>Without Credentials</h1>
    </div>
  )
}

export default WithoutCredentials

```

Hit `Network` tab and take a glance at the response:

![Request Successful Without Credentials](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r36nghi18h81mec455gm.png)

You can try to remove `Access-Control-Allow-Origin` in API Gateway, and you will see the CORS error.

![CORS Error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cy9ly2czwmk5dty1r801.png)


#### Credentials

This is the way that I'm going to do:

1. Sign up an account to AWS Cognito.
2. Sign in with the account from step one.
3. Get id token after signed in.
4. Use id token to call to API Gateway.

Let's do it step by step!

First of all, I need to install `amplify` in order to interact with AWS Cognito:

```tsx
npm install aws-amplify
```

In order to connect to AWS Cognito, I also need to config some property in `amplify`:

```tsx
Amplify.configure({
  Auth: {
    region: 'us-west-2.',
    userPoolId: 'us-west-2_ltdxXTVoV',
    userPoolWebClientId: '7n2162uuqkck3jpa4cjv7fnh69',
  },
})
```

And the code for signing up an account to AWS Cognito:

```tsx
const username = 'Username'
const password = 'Password@1

async function signUp() {
    try {
      const {user} = await Auth.signUp({
        username,
        password,
        attributes: {
          email: 'email@gmail.com',
        },
      })
      console.log(user)
    } catch (error) {
      console.log('error signing up:', error)
    }
  }
}

<button onClick={signUp}>Sign Up</button>
```

After signing up, I have to sign in with the `username`, `password` that I just signed up for. But before I do this, I need to go to the AWS Cognito in order to confirm the user.

```tsx
  const [idToken, setIdToken] = React.useState('')
  async function signIn() {
    try {
      const user = await Auth.signIn(username, password)

      console.log('user', user)
      setIdToken(user.signInUserSession.idToken.jwtToken)
    } catch (error) {
      console.log('error signing in', error)
    }
  }
```

Now, I have had the `idToken` that allows me access to API Gateway's resources.

Let's try a request without `idToken`:

```tsx
  async function callAPIGateway() {
    try {
      const data = await fetch(
        `${process.env.NEXT_PUBLIC_API_GATEWAY}/credentials`
      )
    } catch (error) {
      console.log('error calling API gateway', error)
    }
  }
```

I got an CORS error:

![CORS Error Example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gie1nn4llixwmk9auf4y.png)

Let's try to add the `idToken` I have just received from signing in.

```tsx
  async function callAPIGateway() {
    try {
      const data = await fetch(
        `${process.env.NEXT_PUBLIC_API_GATEWAY}/credentials`,
        {
          headers: {
            Authorization: idToken,
          },
        },
      )
    } catch (error) {
      console.log('error calling API gateway', error)
    }
  }
```

No! It doesn't work! Hmm, When I send a request to API that is a different domain and with credentials(Authorization), I have to specific 
`Access-Control-Allow-Origin` in the API. It means I have to change '*' -> 'http://localhost:3000'

Let's change it in API Gateway console.
![LocalHost Origin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yyb5ugskni125jgunqg3.png)

Then re-call the API Gateway.

![Successful Calling](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xol7jj2wevysp37dqmvd.gif)

It worked! 

### Conclusion

We are done with calling API from other domains and have a basic overview about CORS, API Gateway, AWS Cognito.
I hope it was helpful for you. Please feel free to refer to the [source code](https://github.com/thangphan37/dev-cors/blob/main/pages/credentials.tsx).