> ## Documentation Index
> Fetch the complete documentation index at: https://developers.authlete.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Amazon Cognito と最新 OAuth/OIDC 仕様

> Amazon Cognito をユーザーデータベースとして用いて OAuth/OIDC 関連のタスクを Authlete に委譲することで、Cognito を使いつつも同時に Financial-grade API などの最新 OAuth/OIDC 仕様をサポートする方法を説明するチュートリアル。

# はじめに

*"**[Amazon Cognito][COGNITO]** user pools implements ID, access, and refresh tokens as defined by the OpenID Connect (OIDC) open standard"* *(Amazon Cognito は OpenID Connect (OIDC) オープン標準で定められている通りに ID トークン、アクセストークン、レフレッシュトークンを実装しています)* --- "[Using Tokens with UserPools][COGNITO_USING_TOKENS]" より抜粋

しかしながら、Cognito の OIDC 実装は非常に限定的で柔軟性もないため、Cognito の OIDC 実装がシステム要件を満たせないということはよくあります。例えば、Cognito が発行する ID トークンの署名アルゴリズムは `RS256` ですが、セキュリティ上の理由により **[Financial-grade API][FAPI]** (**FAPI**) が同署名アルゴリズムを禁止しているにも関わらず、それを変更する方法がありません。

このチュートリアルでは、Cognito をユーザーデータベースとしてだけ用いて OAuth/OIDC 関連のタスクを **Authlete** に委譲することで、Cognito を使いつつも同時に Financial-grade API などの最新 OAuth/OIDC 仕様 (参考: [Authlete スペックシート][SPEC_SHEET]) をサポートする方法を説明します。

# アーキテクチャ

OAuth 2.0 の文脈では、**アクセストークン** (及び任意で**リフレッシュトークン**) を発行するサーバーを**認可サーバー**と呼びます。一方、OpenID Connect の文脈では、**ID トークン**を発行するサーバーを **OpenID Provider** (**IdP**) と呼びます。OIDC は意図的に OAuth 2.0 上に定義されたため、一つのサーバーが両方の役割を持つことはよくあります。そのため、同じサーバーが文脈によって認可サーバーと呼ばれたり IdP と呼ばれたりします。このチュートリアルではそのようなサーバーを統一的に認可サーバーと呼びます。

OAuth/OIDC で最も一般的なフローである**認可コードフロー** ([RFC 6749 Section 4.1][6749_41]) をサポートするためには、認可サーバーは二つのエンドポイントを実装しなければなりません。それらは**認可エンドポイント** ([RFC 6749 Section 3.1][6749_31])、**トークンエンドポイント** ([RFC 6749 Section 3.2][6749_32]) と呼ばれます。Cognito ユーザープールはこれら二つのエンドポイントの実装を提供しますが、Cognito の OIDC 実装に満足できない場合は、自分でこれらのエンドポイントを実装する必要があります。

次の図は、Cognito と Authlete を併せて使った場合の認可コードフローにおけるコンポーネント間の関係を示しています。

<img alt="Cognito と Authlete による認可コードフロー" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/6e8OPufcWwl4fTPw/deployment-and-operations/integration-with-api-gateways/authorization-code-flow-by-cognito-and-authlete_ja.png?fit=max&auto=format&n=6e8OPufcWwl4fTPw&q=85&s=df623fd0a9ce48012e345d11c6996e51" width="1920" height="1080" data-path="deployment-and-operations/integration-with-api-gateways/authorization-code-flow-by-cognito-and-authlete_ja.png" />

**ユーザー認証は Cognito が行うが OAuth/OIDC 関連のタスクは Authlete に委譲される**というのがこの図の要点です。OAuth 2.0 の中心となる仕様 ([RFC 6749][6749]) が *"The way in which the authorization server authenticates the resource owner (e.g., username and password login, session cookies) is **beyond the scope of this specification**."* *(認可サーバーがリソースオーナーを認証する方法は**この仕様の範囲外**である)* と述べていることを考慮すると、この明確な分離には、ユーザー管理ソリューションが直接 OAuth/OIDC をサポートするアプローチよりも多くの利点があります。

後続のセクションでは、このチュートリアル用に用意された認可サーバーが認可エンドポイントとトークンエンドポイントの実装内で行っていることを詳細に説明します。

## 認可エンドポイント

サンプル認可サーバーの認可エンドポイントは次のことを行います。

1. Web ブラウザ経由でクライアントアプリケーションから**認可リクエスト** ([RFC 6749 Section 4.1.1][6749_411]) を受け取ります。
2. 認可リクエストからクエリーパラメーター群を抜き出します。
3. 抜き出したクエリーパラメーター群を Authlete の [/api/auth/authorization][AUTH_AUTHORIZATION] API に渡します。
4. Authlete API から返された情報を元に**認可ページ**を組み立てます。
5. 認可ページを Web ブラウザにに返します。
6. 認可ページに埋め込んだログインフォームを介して、ユーザーからユーザー名とパスワードを取得します。
7. ユーザーを認証するため、ユーザー名とパスワードを Cognito の [AdminInitiateAuth][ADMIN_INITIATE_AUTH] API に渡します。
8. ユーザー属性を取得するため、ユーザー名を Cognito の [AdminGetUser][ADMIN_GET_USER] API に渡します。
9. ユーザーの一意識別子と属性群を Authlete の [/api/auth/authorization/issue][AUTH_AUTHORIZATION_ISSUE] API に渡します。
10. Authlete API から返された情報を元に**認可レスポンス** ([RFC 6749 Section 4.1.2][6749_412]) を組み立てます。
11. 認可レスポンスを Web ブラウザに返します。

<Note>
  `response_mode=form_post` を使わない限り、認可レスポンスの HTTP ステータスは `302 Found` なので、認可レスポンスは**リダイレクトエンドポイント**へ (そして続いてクライアントアプリケーションへ) 送られます。
</Note>

<Note>
  Cognito の AdminInitiateAuth API はアクセストークン、ID トークン、リフレッシュトークンを発行します。しかし、それらは使われません。代わりに、それらのトークンは Authlete が発行します。
</Note>

## トークンエンドポイント

サンプル認可サーバーのトークンエンドポイントは次のことを行います。

1. クライアントアプリケーションから**トークンリクエスト** ([RFC 6749 Section 4.1.3][6749_413]) を受け取ります。
2. トークンリクエストからフォームパラメーター群を抜き出します。
3. 抜き出したフォームパラメーター群を Authlete の [/api/auth/token][AUTH_TOKEN] API に渡します。
4. Authlete API から返された情報を元に**トークンレスポンス** ([RFC 6749 Section 4.1.4][6749_414]) を組み立てます。
5. トークンレスポンスをクライアントアプリケーションに返します。

# 実装

上記に説明したアーキテクチャは **[django-oauth-server][DJANGO_OAUTH_SERVER]** に実装されています。django-oauth-server は **[Django][DJANGO]** Web フレームワークを用いて Python で書かれたオープンソースの認可サーバーです。このサーバーを実行するには、次の手順を踏んでください。

## Cognito 設定

1. Cognito ユーザープールを作成してください。後ほどテストで使用するので、`email` 属性を含めておいてください。
2. Cognito ユーザープールにクライアントを追加してください。当該クライアントが [Server-Side Authentication Flow][SERVER_SIDE_AUTHENTICATION_FLOW] を使えるように、クライアント設定の「認証フローの設定」で `ALLOW_ADMIN_USER_PASSWORD_AUTH` を有効にしてください。
3. Cognito ユーザープールにユーザーを追加してください。
4. 使用する AWS アカウントに、Cognito の [AdminInitiateAuth][ADMIN_INITIATE_AUTH] API と [AdminGetUser][ADMIN_GET_USER] API を呼ぶのに必要な権限を与えてください。

## 認可サーバー設定

必要な Python ライブラリをインストールしてください。

```
$ pip install authlete           # Python 用 Authlete ライブラリ
$ pip install authlete-django    # Django 用 Authlete ライブラリ
$ pip install boto3              # Python 用 AWS SDK
```

認可サーバー実装のソースコードをダウンロードしてください。

```
$ git clone https://github.com/authlete/django-oauth-server.git
$ cd django-oauth-server
```

Authlete API にアクセスするため、Authlete 設定ファイル (`authlete.ini`) を編集してください。

```
$ vi authlete.ini
```

Django 設定ファイル (`django_oauth_server/settings.py`) を開き、

```
$ vi django_oauth_server/settings.py
```

`backends.CognitoBackend` を `AUTHENTICATION_BACKENDS` に追加してください。Django の認証バックエンドの詳細については、[Customizing authentication in Django][DJANGO_AUTH_CUSTOMIZATION] の [Specifying authentication backends][DJANGO_AUTH_BACKENDS] を参照してください。

```python theme={null}
AUTHENTICATION_BACKENDS = ('backends.CognitoBackend',)
```

また、同ファイル内の `COGNITO_USER_POOL_ID` と `COGNITO_CLIENT_ID` を適切に編集してください。

```python theme={null}
COGNITO_USER_POOL_ID = 'YOUR_COGNITO_USER_POOL_ID'
COGNITO_CLIENT_ID    = 'YOUR_COGNITO_CLIENT_ID'
```

Cognito の [AdminInitiateAuth][ADMIN_INITIATE_AUTH] API と [AdminGetUesr][ADMIN_GET_USER] API の呼び出し方に興味がある場合は、ソースコード `[cognito_backend.py][COGNITO_BACKEND_PY]` を調べてみてください。

## 認可サーバー起動

認可サーバーを起動するには、次のコマンドを入力してください。

```
$ python manage.py runserver
```

"`make run`" でも同じことができます。

```
$ make run
```

認可サーバーは下表にリストされているようなエンドポイントを幾つか公開しています。Authlete の設定 (`authlete.ini`) が正しいかどうかを確認する簡単な方法は、**ディスカバリーエンドポイント** ([http://localhost:8000/.well-known/openid-configuration](http://localhost:8000/.well-known/openid-configuration)) にアクセスし、[OpenID Connect Discovery 1.0][OIDC_DISCOVERY] に準拠する JSON が返ってくるかどうかを確認することです。

| エンドポイント        | URL                                                      |
| :------------- | :------------------------------------------------------- |
| 認可エンドポイント      | `http://localhost:8000/api/authorization`                |
| トークンエンドポイント    | `http://localhost:8000/api/token`                        |
| ディスカバリーエンドポイント | `http://localhost:8000/.well-known/openid-configuration` |

# テスト

全ての準備が整いました。認可コードフローでアクセストークンと ID トークンを取得してみましょう。

## 認可リクエスト

認可コードフローの最初のステップは、認可サーバーの**認可エンドポイント**に Web ブラウザ経由で**認可リクエスト**を送ることです。このチュートリアルでは、認可エンドポイントは django-oauth-server が提供する `http://localhost:8000/api/authorization` です。認可リクエストを表す下記の URL の `CLIENT_ID` と `REDIRECT_URI` を適切に置き換え、Web ブラウザを使ってその URL にアクセスしてください。

```
http://localhost:8000/api/authorization?response_type=code&client_id=CLIENT_ID&scope=openid+email&state=123&nonce=abc&redirect_uri=REDIRECT_URI
```

<Note>
  認可リクエスト内の `CLIENT_ID` は Cognito クライアントのクライアント ID では**ありません**。そうではなく、Authlete が発行したクライアント ID でなければなりません。
</Note>

<Note>
  クライアントのリダイレクトエンドポイントを変更していなければ、`REDIRECT_URI` の値としてデフォルト値の `https://api.authlete.com/api/mock/redirection/SERVICE_API_KEY` を使用することができます。`REDIRECT_URI` の値として用いる際は、デフォルトリダイレクトエンドポイント内の `SERVICE_API_KEY` を適切に置き換えてください。
</Note>

Web ブラウザには認可サーバーが生成した**認可ページ**が表示されます。次のように見えるでしょう。

<img alt="Authorization page in authorization code flow" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/6e8OPufcWwl4fTPw/deployment-and-operations/integration-with-api-gateways/authorization-page-in-authorization-code-flow.png?fit=max&auto=format&n=6e8OPufcWwl4fTPw&q=85&s=a39c42a3ada16b638bf3e36c1b150274" width="775" height="844" data-path="deployment-and-operations/integration-with-api-gateways/authorization-page-in-authorization-code-flow.png" />

ページにはログイン ID とパスワードを入力するフィールドがあります。そこに Cognito ユーザープールに登録したユーザーのユーザー名とパスワードを入力し、Authorize ボタンを押してください。Web ブラウザはあなたのクライアントアプリケーションのリダクレクトエンドポイントにリダイレクトされます。

ブラウザのアドレスバーに表示されているリダイレクトエンドポイントの URL には、次のように `code` レスポンスパラメーターが含まれています。

```
REDIRECT_URI?state=123&code=RwRq2Lp0bJVMiLPKAFz4qB1hxieBD1X5HKuv8EPkJeM
```

`code` レスポンスパラメーターの値は、あなたのクライアントアプリケーションに対して認可サーバーから発行された**認可コード**です。この認可コードはクライアントアプリケーションがトークンリクエストを投げる際に必要となります。

## トークンリクエスト

認可コード取得後、クライアントアプリケーションは認可サーバーの**トークンエンドポイント**に**トークンリクエスト**を投げます。このチュートリアルでは、トークンエンドポイントは django-oauth-server が提供する `http://localhost:8000/api/token` です。

トークンリクエストはシェル端末で `curl` コマンドを用いて投げることができます。下記はトークンリクエストの例です。タイプする前に、`CLIENT_ID`、`REDIRECT_URI`、`CODE` を実際の値で置き換えてください。

```
$ curl http://localhost:8000/api/token -d grant_type=authorization_code -d client_id=CLIENT_ID -d redirect_uri=REDIRECT_URI -d code=CODE
```

| 引数                                 | 説明                                                          |
| :--------------------------------- | :---------------------------------------------------------- |
| `http://localhost:8000/api/token`  | トークンエンドポイントの URL。                                           |
| `-d grant_type=authorization_code` | 認可コードフローであることを示す。                                           |
| `-d client_id=CLIENT_ID`           | クライアント ID を指定する。`CLIENT_ID` を実際のクライアント ID で置き換えること。         |
| `-d redirect_uri=REDIRECT_URI`     | リダイレクト URI を指定する。`REDIRECT_URI` を認可リクエストで用いたものと同じ値で置き換えること。 |
| `-d code=CODE`                     | 認可コードを指定する。`CODE` を実際の認可コードで置き換えること。                        |

<Note>
  認可コードの期限は短く、デフォルト値は 10 分です。認可コードの期限が切れる前にトークンリクエストを投げなければなりません。
</Note>

トークンリクエストが成功すると、トークンエンドポイントは `access_token` と `id_token` を含む JSON を返します。

```json theme={null}
{
  "access_token": "FrGIJQpW51-l5mYJHcqGUNIKGJ1W23fFlW6c9AQEZEc",
  "refresh_token": "jhvKm9-haLQwnIR4CfkL6bfPIBBlqluFeqZKAgdPNjM",
  "scope": "email openid",
  "id_token": "eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6InRha2FAYXV0aGxldGUuY29tIiwiaXNzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsInN1YiI6IjIiLCJhdWQiOlsiNDMyNjM4NTY3MCJdLCJleHAiOjE2MTY0MTI3NDIsImlhdCI6MTYxNjMyNjM0MiwiYXV0aF90aW1lIjoxNjE2MzI2MTAwLCJub25jZSI6ImFiYyIsInNfaGFzaCI6InBtV2tXU0JDTDUxQmZraG43OXhQdUEifQ.7sXy2FcELxHo3LCQkb9teLaUE9jtRxXsa8diJKnkwAo",
  "token_type": "Bearer",
  "expires_in": 86400
}
```

`access_token` の値が発行されたアクセストークンです。同様に、`id_token` の値が発行された ID トークンです。

このチュートリアルで発行された ID トークンのペイロード部は、デコードすると下記のようになります。ペイロード内の `email` の値が Cognito ユーザープール内のユーザーの `email` 属性と一致するかどうかをチェックすることで、認可サーバーと Cognito ユーザープールの通信が成功したかどうかを確認することができます。

```json theme={null}
{
  "email": "taka@authlete.com",
  "iss": "https://example.com",
  "sub": "2",
  "aud": [
    "4326385670"
  ],
  "exp": 1616412742,
  "iat": 1616326342,
  "auth_time": 1616326100,
  "nonce": "abc",
  "s_hash": "pmWkWSBCL51Bfkhn79xPuA"
}
```

# おめでとうございます！

あなたはこのチュートリアルを完了しました。Authlete を使うことで、認可サーバーに Amazon Cognito をユーザーデータベースとして使わせつつ同時に最新の OAuth/OIDC 仕様をサポートさせる方法を学びました。

サポートが必要であれば\*\*[お問い合わせ][CONTACT]\*\*ください。いつでも歓迎します!

[6749]: https://tools.ietf.org/html/rfc6749

[6749_31]: https://tools.ietf.org/html/rfc6749#section-3.1

[6749_32]: https://tools.ietf.org/html/rfc6749#section-3.2

[6749_41]: https://tools.ietf.org/html/rfc6749#section-4.1

[6749_411]: https://tools.ietf.org/html/rfc6749#section-4.1.1

[6749_412]: https://tools.ietf.org/html/rfc6749#section-4.1.2

[6749_413]: https://tools.ietf.org/html/rfc6749#section-4.1.3

[6749_414]: https://tools.ietf.org/html/rfc6749#section-4.1.4

[ADMIN_GET_USER]: https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminGetUser.html

[ADMIN_INITIATE_AUTH]: https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminInitiateAuth.html

[AUTH_AUTHORIZATION]: /api-reference/authorization-endpoint/process-authorization-request

[AUTH_AUTHORIZATION_ISSUE]: /api-reference/authorization-endpoint/issue-authorization-response

[AUTH_TOKEN]: /api-reference/token-endpoint/process-token-request

[COGNITO]: https://aws.amazon.com/cognito/

[COGNITO_BACKEND_PY]: https://github.com/authlete/django-oauth-server/blob/master/backends/cognito_backend.py

[COGNITO_USING_TOKENS]: https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html

[CONTACT]: https://www.authlete.com/ja/contact/

[DJANGO]: https://www.djangoproject.com/

[DJANGO_AUTH_BACKENDS]: https://docs.djangoproject.com/en/3.1/topics/auth/customizing/#specifying-authentication-backends

[DJANGO_AUTH_CUSTOMIZATION]: https://docs.djangoproject.com/en/3.1/topics/auth/customizing/

[DJANGO_OAUTH_SERVER]: https://github.com/authlete/django-oauth-server

[FAPI]: https://fapi.openid.net/

[OIDC_DISCOVERY]: https://openid.net/specs/openid-connect-discovery-1_0.html

[SERVER_SIDE_AUTHENTICATION_FLOW]: https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-server-side-authentication-flow

[SPEC_SHEET]: https://www.authlete.com/ja/legal/spec_sheet/
