はじめに
“Amazon 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 の OIDC 実装は非常に限定的で柔軟性もないため、Cognito の OIDC 実装がシステム要件を満たせないということはよくあります。例えば、Cognito が発行する ID トークンの署名アルゴリズムはRS256 ですが、セキュリティ上の理由により Financial-grade API (FAPI) が同署名アルゴリズムを禁止しているにも関わらず、それを変更する方法がありません。
このチュートリアルでは、Cognito をユーザーデータベースとしてだけ用いて OAuth/OIDC 関連のタスクを Authlete に委譲することで、Cognito を使いつつも同時に Financial-grade API などの最新 OAuth/OIDC 仕様 (参考: Authlete スペックシート) をサポートする方法を説明します。
アーキテクチャ
OAuth 2.0 の文脈では、アクセストークン (及び任意でリフレッシュトークン) を発行するサーバーを認可サーバーと呼びます。一方、OpenID Connect の文脈では、ID トークンを発行するサーバーを OpenID Provider (IdP) と呼びます。OIDC は意図的に OAuth 2.0 上に定義されたため、一つのサーバーが両方の役割を持つことはよくあります。そのため、同じサーバーが文脈によって認可サーバーと呼ばれたり IdP と呼ばれたりします。このチュートリアルではそのようなサーバーを統一的に認可サーバーと呼びます。 OAuth/OIDC で最も一般的なフローである認可コードフロー (RFC 6749 Section 4.1) をサポートするためには、認可サーバーは二つのエンドポイントを実装しなければなりません。それらは認可エンドポイント (RFC 6749 Section 3.1)、トークンエンドポイント (RFC 6749 Section 3.2) と呼ばれます。Cognito ユーザープールはこれら二つのエンドポイントの実装を提供しますが、Cognito の OIDC 実装に満足できない場合は、自分でこれらのエンドポイントを実装する必要があります。 次の図は、Cognito と Authlete を併せて使った場合の認可コードフローにおけるコンポーネント間の関係を示しています。
認可エンドポイント
サンプル認可サーバーの認可エンドポイントは次のことを行います。- Web ブラウザ経由でクライアントアプリケーションから認可リクエスト (RFC 6749 Section 4.1.1) を受け取ります。
- 認可リクエストからクエリーパラメーター群を抜き出します。
- 抜き出したクエリーパラメーター群を Authlete の /api/auth/authorization API に渡します。
- Authlete API から返された情報を元に認可ページを組み立てます。
- 認可ページを Web ブラウザにに返します。
- 認可ページに埋め込んだログインフォームを介して、ユーザーからユーザー名とパスワードを取得します。
- ユーザーを認証するため、ユーザー名とパスワードを Cognito の AdminInitiateAuth API に渡します。
- ユーザー属性を取得するため、ユーザー名を Cognito の AdminGetUser API に渡します。
- ユーザーの一意識別子と属性群を Authlete の /api/auth/authorization/issue API に渡します。
- Authlete API から返された情報を元に認可レスポンス (RFC 6749 Section 4.1.2) を組み立てます。
- 認可レスポンスを Web ブラウザに返します。
response_mode=form_post を使わない限り、認可レスポンスの HTTP ステータスは 302 Found なので、認可レスポンスはリダイレクトエンドポイントへ (そして続いてクライアントアプリケーションへ) 送られます。Cognito の AdminInitiateAuth API はアクセストークン、ID トークン、リフレッシュトークンを発行します。しかし、それらは使われません。代わりに、それらのトークンは Authlete が発行します。
トークンエンドポイント
サンプル認可サーバーのトークンエンドポイントは次のことを行います。- クライアントアプリケーションからトークンリクエスト (RFC 6749 Section 4.1.3) を受け取ります。
- トークンリクエストからフォームパラメーター群を抜き出します。
- 抜き出したフォームパラメーター群を Authlete の /api/auth/token API に渡します。
- Authlete API から返された情報を元にトークンレスポンス (RFC 6749 Section 4.1.4) を組み立てます。
- トークンレスポンスをクライアントアプリケーションに返します。
実装
上記に説明したアーキテクチャは django-oauth-server に実装されています。django-oauth-server は Django Web フレームワークを用いて Python で書かれたオープンソースの認可サーバーです。このサーバーを実行するには、次の手順を踏んでください。Cognito 設定
- Cognito ユーザープールを作成してください。後ほどテストで使用するので、
email属性を含めておいてください。 - Cognito ユーザープールにクライアントを追加してください。当該クライアントが Server-Side Authentication Flow を使えるように、クライアント設定の「認証フローの設定」で
ALLOW_ADMIN_USER_PASSWORD_AUTHを有効にしてください。 - Cognito ユーザープールにユーザーを追加してください。
- 使用する AWS アカウントに、Cognito の AdminInitiateAuth API と AdminGetUser API を呼ぶのに必要な権限を与えてください。
認可サーバー設定
必要な Python ライブラリをインストールしてください。authlete.ini) を編集してください。
django_oauth_server/settings.py) を開き、
backends.CognitoBackend を AUTHENTICATION_BACKENDS に追加してください。Django の認証バックエンドの詳細については、Customizing authentication in Django の Specifying authentication backends を参照してください。
COGNITO_USER_POOL_ID と COGNITO_CLIENT_ID を適切に編集してください。
[cognito_backend.py][COGNITO_BACKEND_PY] を調べてみてください。
認可サーバー起動
認可サーバーを起動するには、次のコマンドを入力してください。make run” でも同じことができます。
authlete.ini) が正しいかどうかを確認する簡単な方法は、ディスカバリーエンドポイント (http://localhost:8000/.well-known/openid-configuration) にアクセスし、OpenID Connect Discovery 1.0 に準拠する 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 にアクセスしてください。
認可リクエスト内の
CLIENT_ID は Cognito クライアントのクライアント ID ではありません。そうではなく、Authlete が発行したクライアント ID でなければなりません。クライアントのリダイレクトエンドポイントを変更していなければ、
REDIRECT_URI の値としてデフォルト値の https://api.authlete.com/api/mock/redirection/SERVICE_API_KEY を使用することができます。REDIRECT_URI の値として用いる際は、デフォルトリダイレクトエンドポイント内の SERVICE_API_KEY を適切に置き換えてください。
code レスポンスパラメーターが含まれています。
code レスポンスパラメーターの値は、あなたのクライアントアプリケーションに対して認可サーバーから発行された認可コードです。この認可コードはクライアントアプリケーションがトークンリクエストを投げる際に必要となります。
トークンリクエスト
認可コード取得後、クライアントアプリケーションは認可サーバーのトークンエンドポイントにトークンリクエストを投げます。このチュートリアルでは、トークンエンドポイントは django-oauth-server が提供するhttp://localhost:8000/api/token です。
トークンリクエストはシェル端末で curl コマンドを用いて投げることができます。下記はトークンリクエストの例です。タイプする前に、CLIENT_ID、REDIRECT_URI、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 を実際の認可コードで置き換えること。 |
認可コードの期限は短く、デフォルト値は 10 分です。認可コードの期限が切れる前にトークンリクエストを投げなければなりません。
access_token と id_token を含む JSON を返します。
access_token の値が発行されたアクセストークンです。同様に、id_token の値が発行された ID トークンです。
このチュートリアルで発行された ID トークンのペイロード部は、デコードすると下記のようになります。ペイロード内の email の値が Cognito ユーザープール内のユーザーの email 属性と一致するかどうかをチェックすることで、認可サーバーと Cognito ユーザープールの通信が成功したかどうかを確認することができます。