AWS Cognito で Client Credentials Grant をやってみた

# はじめに

Amazon Cognito は、固有の User API を使った認証に加えて、OAuth 2.0 認証フレームワークをサポートしています。OAuth 2.0 は、いくつかの認証フローが RFC で定義されています。例えば、次のようなものがあります。

- Authorization code grant : ユーザー認証で利用される基本的な方式
- Implicit grant : 「Authorization code grant」が利用できない特別な利用が有る場合にのみ利用する方式
- Client credentials grant : システム間の連携で利用される方式

詳細は次の英語 Blog で説明されています。
https://aws.amazon.com/jp/blogs/mobile/understanding-amazon-cognito-user-pool-oauth-2-0-grants/



この記事では、「Client credentials grant」を利用する方法を検証してみる内容です。「Client credentials grant」を利用するシチュエーションは次を想像しています。

- システム間の認証で利用する。具体的には、**「AWS 外」**のシステムから、AWS 上の API Gateway を呼びだす。
- この時に特定のシステムのみアクセスを許可したい。

**「AWS 外」**と強調しているのは、AWS 内のアクセスでは、あえて「Client credentials grant」を利用しなくても良い場合があります。例えば、EC2 から API Gateway にアクセスしたいときに、API Gateway の認証方式で「AWS IAM」を選択可能です。これによって、特定の IAM Role を持っている EC2 インスタンスに限定できるので、こちらの方が最適な場合が考えられます。



ということで、「Client credentials grant」の検証を進めてみましょう！



# Cognito : User Pool を作成

まず Cognito の User Pool を作成します。既に User Pool を持っている場合は、この章は無視して頂いて大丈夫です。

![image-20230604211122338.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/d75395ae-9a34-91e9-2489-f8db56152d86.png)



「Client credentials grant」にはあまり関係がないですが、ユーザー属性として Email を選択します。

![image-20230604211243353.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/23e6e2a9-ed4f-aee2-9601-2089f6a11fa3.png)



Password Policy は Default のままです。

![image-20230604211321756.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/92acb6d3-d455-59d8-b4ff-4c2fc525a695.png)



使わないですが、MFA は Optional にしてみます。

![image-20230604211445404.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/c2c92c47-80d3-943f-39d3-dbff35446f11.png)



このあたりも Default のままです。

![image-20230604211411015.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/f3a85a65-0448-a341-3e9b-8a8d5d4b5572.png)



このあたりも Default のままです。

![image-20230604212021485.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/79c04520-0a1a-95ba-145f-2e2a1c47b812.png)



適当に指定します。

![image-20230604212124835.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/65473303-fb3b-da6e-b828-4e8a669e0fa5.png)



Userpool 名を入力します。

![image-20230604212317920.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/023121e6-b890-879c-ada6-a7561847c7ba.png)



Hosted UI の利用を ON にします。

```
client-credentials-grant
```

![image-20230604212407982.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/febf3750-ea2d-1dff-11a1-327cb55fb008.png)



画面上、App Client を作成しないといけないです。後で削除するので、適当な名前を入力します。

![image-20230604213404238.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/10933c41-9afc-9a56-18a3-2aa5b184217c.png)



Create を押します。

![image-20230604213713195.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/b9550cee-3f89-b37a-b738-d0ef6223a1ec.png)



User Pool が作成されました。

![image-20230604213738044.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/b3fbde23-9010-3201-b2e0-961c3cea654c.png)



# Cognito : Resource Server を作成

次に、Cognito User Pool 上で Resource Server の定義が必要です。

作成した User Pool を選択します。

![image-20230604214506405.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/5ccfe840-aaf6-ec54-7e43-43daf6ad3c0d.png)



App integration のタブを選択します。

![image-20230604214555071.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/5c8fd886-42b0-4118-ce5e-158f3c29e661.png)



Create resource server のボタンを押します。

![image-20230605004503230.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/3b83bdc5-7d78-fcc1-caaf-cb75952db887.png)



この Resource Server の名前は任意の好きなもので大丈夫です。Scope も指定します。

```
client-credentials-grant-resource-server
```

![image-20230605004808096.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/3a16bf55-e775-518b-5999-22df7bc7041d.png)



# Cognito : App Client を作成

次に、Cognito 上で App Client を作成します。App integration のタブを選択します。

![image-20230604214555071.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/a0968294-5b3d-0fe7-eaa7-b47b1048778f.png)



以前作成した Dummy の Application Client を削除します。

![image-20230604214638904.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/a53874c6-7a12-f99c-5608-92c48bcda1b8.png)



その後、Create app client を押します。

![image-20230604215136564.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/4aecfa5b-7cd1-8ef8-309f-4b79b4d882bc.png)



Confidential client を生成します。

```
client-credentials-grant-test01
```

![image-20230604215407543.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/e1f703be-7bf1-a239-edc6-aff0cd834906.png)



このあたりはデフォルトです。

![image-20230604221346436.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/56066bb1-1fa5-92ec-5547-bf93e1a89b78.png)



使わないですが、callback url を適当にします。

![image-20230604221501072.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/b8500db5-0910-c526-4695-fcc78dc86148.png)



ポイントがここです。**Client credentials を選択し、Custom scppes も指定します。**

![image-20230605004950367.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/b9372a7d-25ce-a7a2-c97d-e78a717d0c3a.png)



Create を押します。

![image-20230604221618700.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/45b13c9d-a87e-d36b-33ec-6c00b490fb7b.png)



生成されました。Client ID と Client Secret を後で使うため、メモっておきます。

![image-20230605005100985.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/9d07bedb-202c-0e42-d4cd-52159cc2672e.png)



# API Gateway と Lambda に紐づけ

「Client credentials grant」の動作確認をするために、API Gateway と Lambda を準備します。REST API のものを作成します。

![image-20230605005753443.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/75d5a625-582f-85d6-cd5c-180496707565.png)



適当にリソースを作成して、Lambda 関数を紐づけます。

![image-20230605010011030.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/73b886f0-b29b-1f1d-b8f6-8960c013661d.png)



API Gateway に、Cognito のオーソライザーを紐づけるために、Authorizers のメニューから作成します。

```
Authorization
```

![image-20230605010157899.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/18082cc0-77b5-63be-14b2-d1e1b5d5de7d.png)



Authorizer が作成されました。

![image-20230605010220424.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/4935d57e-4325-7a6a-e008-cb61f0fcff29.png)



対象のリソースに、Authorizer を紐づけます。

![image-20230605010309299.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/527e8169-3aaa-4c2a-f4a9-fb643a66a154.png)





Authorization と OAuth Scopes をします。OAuth Scopes は、Cognito 上で定義したものを指定します。

```
client-credentials-grant-resource-server/test
```

![image-20230605010529549.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/ab6a8f96-b58c-3f95-9687-246991fa7d1b.png)



Deploy を押します。

![image-20230605010619880.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/47d89109-4414-d475-8208-b0b03fe4fd18.png)

Deploy を押します。

![image-20230605010938190.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/5d58f5a8-060a-2751-4bab-bce80767e2d7.png)



# 動作確認

実際に、「Client credentials grant」で Token を取得して、API Gateway にアクセスできるか試してみましょう。

まず、Token が無い状態で API Gateway にアクセスしてみると、想定通りエラーになります。

```
> curl -X POST https://emtlsn2nuc.execute-api.ap-northeast-1.amazonaws.com/prod/hello
{"message":"Unauthorized"}
```



Token を入手するための Cognito の Domain URL を調べます。

```
https://client-credentials-grant.auth.ap-northeast-1.amazoncognito.com
```

![image-20230605012025322.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/004d71cd-6c24-a128-17a6-ea5d602750d5.png)



Token を取得するために、curl で Cognito のエンドポイントにアクセスします。APP_CLIENT_ID や CLIENT_SECRET, TOKEN_END_POINT は環境に合わせて書き換えてください。

- Cognito 上で生成した Client ID と Client Secret を使って、Base64 でエンコードする
- エンコードで得られた文字列をリクエストヘッダー `Authorization` に入れる

```
# 事前準備
APP_CLIENT_ID=himitsu1
CLIENT_SECRET=himitsu2
TOKEN_END_POINT=https://client-credentials-grant.auth.ap-northeast-1.amazoncognito.com
CODE=`echo -n ${APP_CLIENT_ID}:${CLIENT_SECRET} | base64 -w 0`

# トークン取得
curl -X POST ${TOKEN_END_POINT}/oauth2/token \
-H "Authorization: Basic $CODE" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" | jq .
```



実行例 : 次のように Access Token が取得できます。この Access Token を使って API Gateway にアクセスができます。

```
{
  "access_token": "eyJraWQiOiJsK25WZTd.....省略......CmA",
  "expires_in": 3600,
  "token_type": "Bearer"
}
```



access_token を変数に格納します。

```
access_token=$(curl -X POST ${TOKEN_END_POINT}/oauth2/token \
-H "Authorization: Basic $CODE" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
--data-urlencode 'scope=client-credentials-grant-resource-server/test' | jq -r .access_token)
```



確認です。

```
echo $access_token
```



次のように curl で API Gateway にアクセスします。リクエストヘッダーに、access_token を指定します。

```
curl -X POST \
-H "Authorization:$access_token" \
https://emtlsn2nuc.execute-api.ap-northeast-1.amazonaws.com/prod/hello
```



以下のように、Lambda の実行結果が返ってくれば OK です！

```
$ curl -X POST \
> -H "Authorization:$access_token" \
> https://emtlsn2nuc.execute-api.ap-northeast-1.amazonaws.com/prod/hello
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}
```



# 検証を通じてわかったこと

- Cogitno で 「Client credentials grant」でアクセスが可能
- Cognito のマネージメントコンソールにある App Client の画面で「Client credentials grant」周りの設定が可能



# 参考 URL

https://qiita.com/poruruba/items/3681f1e1f8e72fcaadfa

https://zenn.dev/noid11/articles/d36dccd74601ed83d83b