AWS Cognito と 認可コードフロー

# Cognitoと認可コードフロー

認可コードフローを実装する機会がありました。そう言えば以前にCognitoをなんとなく触ったなーというのもあり、Cognitoの理解を深めるためにも、Cognitoで認可コードフローを試してみようと思い、その際の完全個人的メモ(途中)です。


https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/what-is-amazon-cognito.html

>Amazon Cognito はウェブアプリとモバイルアプリ用のアイデンティティプラットフォームです。これは、ユーザーディレクトリ、認証サーバー、OAuth 2.0 アクセストークンと AWS 認証情報の承認サービスです。Amazon Cognito を使用すると、組み込みのユーザーディレクトリ、エンタープライズディレクトリ、Google や Facebook などのコンシューマー ID プロバイダーからユーザーを認証および承認できます。

#### OIDC（OpenID Connect） 
- OAuth 2.0プロトコルに基づいた認証レイヤー
- ユーザーの認証情報とID情報を安全に交換するための標準規格
- OIDC IDプロバイダーは、このOpenID Connect規格を使用してユーザーの認証を行い、認証されたユーザー情報をアプリケーションやサービス（クライアント）に提供するシステムやサービス

#### IDプロバイダ（Identity Provider、略してIdP）
- ユーザー認証とユーザーのデジタルアイデンティティ情報を管理するサービスまたはシステム
- IDプロバイダは、ユーザーが誰であるか（身元）を証明し、
- そのユーザーがアクセスを試みているサービスやアプリケーション（サービスプロバイダ）に対して、その証明を提供
- このプロセスは、シングルサインオン（SSO）やフェデレーション認証の一環として頻繁に行われます


#### スコープ
- aws.cognito.signin.user.admin
    - Amazon Cognito ユーザー プールをAPIで操作する

#### エンドポイント
- /oauth2/authorize
    - 認可エンドポイント
    - ホストされたUI or IdPサインインページにリダイレクト
    - https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html

- /login
    - ログインエンドポイント
    - https://docs.aws.amazon.com/cognito/latest/developerguide/hosted-UI-endpoints.html
- /oauth2/token
    - トークンエンドポイント
    - 認可コード付与のリクエストを完了したユーザーがココードを付与しトークン縁dポイントにリクエストすると、ID、アクセス、およびリフレッシュ トークンが返される
    - https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html
- /auth2/userInfo
    - トークンエンドポイントが発行したアクセストークンを渡すと、ユーザー属性を取得できる
    - アクセストークンのスコープにより、userInfoエンドポイントが返すユーザー属性を定義する


# 流れ

①認可リクエスト(/oauth2/authorize)
https://kinoko-test-oauth.auth.ap-northeast-1.amazoncognito.com/oauth2/authorize?client_id=6rroupfknqnntpkoe94n3u0kan&response_type=code&scope=email+openid&redirect_uri=http://localhost:8080


認可リクエストを投げるとCognitoの場合は以下のログインエンドポイント(/login)にリダイレクトされる
https://kinoko-test-oauth.auth.ap-northeast-1.amazoncognito.com/login?client_id=6rroupfknqnntpkoe94n3u0kan&response_type=code&scope=email+openid&redirect_uri=https://localhost:8080

②認可サーバー（CognitoやGoogleなど）にサインイン
コールバックURIにリダイレクトされ、認可コードが発行される
https://localhost:8080/?code=ba359276-39af-4532-8729-43d579b5bca1

③認可コードをつけて、トークンエンドポイントへリクエスト
(/oauth2/token)

④アクセストークンが発行される

⑤リソースサーバーにアクセス

# ユーザープールの作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/8042dfb2-8c19-72d1-6b7f-00bdcebd081c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/ad091079-233e-7cd6-655c-0888adb28089.png)


# 認証済みユーザー

外部プロバイダーとあるのが、Cognitoプール以外（フェデレーテッドアイデンティティプロバイダー）でサインインしたユーザーになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/b916279f-a47d-1bf9-2d45-f06486905197.png)

フェデレーテッドアイデンティティプロバイダーの設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/62198ff9-56cd-cf8f-2bfe-3e1c4cfc62e0.png)

# Cognitoの認証画面

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/367d5539-ff0e-b52c-4a8a-72e7c4a13743.png)


```
{
  "data": {
    "sub": "18b43b25-b462-428d-b1d2-5bd56b897645",
    "email_verified": "true",
    "name": "ki",
    "email": "mashandroom.kinoko@gmail.com",
    "username": "kinoko2"
  }
}

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/9743a04c-8db9-7dc1-3520-ef8d2b6201bf.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/c8b73159-0384-5db2-f6eb-09a1db95dea0.png)



# ID プロバイダーにGoogleを指定してみた

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/26a4b435-63e9-1dc5-1542-a8a71b37fb6d.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/47a29cb9-6fd0-c8f4-995d-a06a23f43e62.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/26cf350c-dacb-3ddd-5b88-96fff4e13cdd.png)

GoogleのOAuth2で指定可能なスコープは以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/2f3bcb79-c8e9-509d-5cc0-d4c54c2aa640.png)



![スクリーンショット 2024-03-16 20.12.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/b8ffaf79-1def-3677-02e3-d2b51605377c.png)

GoogleのクライアントIDとクライアントシークレットを登録することで、CognitoからGoogle OAuthへ紐づけられるイメージですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/21c7ab20-931e-919e-bebe-086eb717aec3.png)

IDプロバイダーにGoogleを追加することで、Google認証を選択できるようになります（ボタンが表示されます）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/dc347652-aa52-b15a-46e5-3883a9a94ba0.png)


以下の通り、SignIn画面にGoogleでのログインが追加されます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/cbca3fac-1ef8-e022-a772-53dcdf777e1e.png)

Continue with Google を押すと以下にリクエストが投げられます。
```
https://kinoko-google-oauth.auth.ap-northeast-1.amazoncognito.com/login?client_id=5k5ihs3ucupfcbh17btapbffp2&response_type=code&scope=email+openid+profile&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth
```

|  |  |
|:--|:--|
|https://kinoko-google-oauth.auth.ap-northeast-1.amazoncognito.com/login   |Cognitoログインエンドポイント   |
|client_id   |クライアントID   |
|response_type   |認可コードフロー   |
|scope   |email+openid+profile   |
|redirect_uri   |認可後のリダイレクト先   |

以下の設定をもとに、上記リクエストURIが生成されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/3110a86d-d020-ea8a-8db4-2a5bd5f8fbf7.png)




### 属性マッピング

Googleの属性をCognitoのどの属性にマッピングするか？を指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/12be6928-f609-3145-dab2-3097694914d4.png)


アクセエストークンを取得後、 `/oauth2/userInfo` にリクエストを投げユーザー情報を取得したものを見ると、マッピングした属性が含まれていることがわかります。

```
{
  "data": {
    "sub": "xxxxx",
    "identities": "[{\"userId\":\"zzzzzz\",\"providerName\":\"Google\",\"providerType\":\"Google\",\"issuer\":null,\"primary\":true,\"dateCreated\":1710586402334}]",
    "email_verified": "false",
    "name": "キノコ",
    "given_name": "キノコ",
    "family_name": "マッシュ&ルーム",
    "email": "mashandroom.kinoko@gmail.com",
    "username": "google_yyyyyyyy"
  }
}
```

# コールバックを受けるバックエンド

①〜④は、Cognitoの設定（図）を参照し、変更してください。

```Node.js
const express = require("express");
const axios = require("axios");
const querystring = require("querystring");

const app = express();
const port = 3000;

const cognitoClientId = ""; // ①
const cognitoClientSecret = ""; // ②
const redirectUri = "http://localhost:3000/auth"; // ③
const cognitoDomain = ""; // ④

app.get("/auth", async (req: any, res: any) => {
  const code = req.query.code;
  console.log(code);

  const authString = `${cognitoClientId}:${cognitoClientSecret}`;
  const authHeader = `Basic ${Buffer.from(authString).toString("base64")}`;

  try {
    const response = await axios.post(
      `https://${cognitoDomain}/oauth2/token`,
      querystring.stringify({
        grant_type: "authorization_code",
        client_id: cognitoClientId,
        redirect_uri: redirectUri,
        code: code,
      }),
      {
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
          Authorization: authHeader,
        },
      }
    );

    const accessToken = response.data.access_token;

    const userInfoResponse = await axios.get(
      `https://${cognitoDomain}/oauth2/userInfo`,
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      }
    );

    res.json({ data: userInfoResponse.data });
  } catch (error) {
    res.send("認証に失敗しました");
  }
});

app.listen(port, () => {
  console.log(`running on http://localhost:${port}.`);
});

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/c93df543-cb1f-26d9-f06b-799e214a8f45.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/8092029b-972e-7fba-ec41-28774c1e77be.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/8e20883c-7f95-8ef7-c268-11e39a221275.png)

# ハマった

設定をしたつもりが、なぜか`redirect_uri_mismatch`が出てしまいました。
Google側の設定(承認済みの JavaScript 生成元,承認済みのリダイレクト URI)やCognitoの設定を何度も見直し、リダイレクトURLも正しくされているのに・・

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/fffba59b-1967-933f-e333-de1ee8b16040.png)

 ↑　に出ている「エラーの詳細」は、以前にクリックしてドキュメントにリンクされるだけだと思っていて、なぜか押さずに数時間経過・・・いよいよわからん！と思って押してみたら、以下の通りでした・・・やっぱメッセージ確認するべきですね。

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/ddbabefe-91e5-af21-26b1-faf3b77fb93a.png)

 ということで、Google側の設定に、 `https://kinoko-google-oauth.auth.ap-northeast-1.amazoncognito.com/oauth2/idpresponse` を追加したところ正しく認証されリダイレクトされました。

 最終的にGoogle側の設定は以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/d4b97e0c-e59c-3920-c9fd-9a1d16f5e503.png)