RevComm Tech Blog

コミュニケーションを再発明し 人が人を想う社会を創る

Cognito user pool で OpenID Connect を利用した外部 ID Provider によるサインインを実現する

この記事は、RevComm Advent Calender 21 日目の記事です。

RevComm の宇佐美です。認証基盤開発チームで開発および Project Manager を担当しています。

OpenID Connect (OIDC) を利用して Cognito user pool と外部の Identity Provider (IdP) の連携を行う方法について調べる機会があったので、まとめてみました。

IdP には Azure AD(Microsoft が提供するクラウドベースの ID およびアクセス管理サービス)を使います。一部 Azure AD 特有の要件がありますが、基本的には OIDC での連携はほぼ同じ流れでできると思います。

この記事について

この記事のゴール

Cognito user pool と外部 IdP を OIDC で連携して、IdP へのサインインで Cognito user pool の既存のユーザーとして Cognito から各種 token を払い出す方法をまとめます。

前提知識

  • OAuth 2.0, OIDC の概要
  • Cognito の基本的な仕様
  • Azure AD の設定方法
  • AWS CLI の一般的な知識

注意事項

この記事で記載している ID 連携の手順はあくまで動作するために必要最低限のものであり、本番環境での運用を想定したものではありません。

実務では、CSRF や Open redirect などの OAuth 2.0 / OIDC における既知の脆弱性に対して対策を行う *1 ことが必要で、Authorization code flow における PKCE (Proof Key for Code Exchange) などのベストプラクティスに対応することも推奨されます。

本文

外部 IdP での ID 連携を行うことの利点

これから詳細に説明していくとおり、連携形式を問わず IdP との ID 連携を行うためにはそれなりの手間がかかりますが、連携を行うことによるメリットはどこにあるのでしょうか。

ID 連携を行う大きな利点としては以下のような点が挙げられます。

ユーザー側の利点

  • ユーザー情報の統一管理
    • ユーザーを IdP で一元的に管理できるため、複数のサービスごとに ID やパスワード、MFA デバイスなどの認証情報を管理する必要がなくなります。IdP でサインイン状態が継続していればそれぞれのサービスに都度サインインする必要がないので、UX も向上します。
  • セキュリティ向上
    • 認証情報をサービスごとに管理していると、どうしてもパスワードが使いまわされたり、サービス提供者側のセキュリティインシデントなどによる漏洩が起きたりするリスクがあります。IdP でのサインインに統一しておけば、これらのリスクを軽減することができます。
  • 認証手段
    • IdP 側で MFA や FIDO などの認証手段を提供している場合、サインインしたい対象のサービスが対応していなくてもこれらを利用することができます。

アプリケーション提供側の利点

  • 認証情報管理の委譲
    • アプリケーション提供者としても、パスワードなどの認証情報を管理することは極力避けたいものです *2。データベースに保管する際の不可逆なハッシュ化やログのマスキングなどの基本的な対策に加え、アプリケーション内で認証情報を扱うあらゆるところで特別な配慮が必要になります。IdP に認証情報の管理を委譲してしまえば、これらのうち一部はアプリケーション内で管理する必要がなくなります。

Cognito user poolの概要

Cognito は AWS が提供するウェブおよびモバイルアプリの認証、承認、およびユーザー管理サービスです。

Cognito user pool は、Cognito 内でユーザーを管理するためのディレクトリに相当するもので、ユーザーのサインアップおよびサインイン、IdP 経由でのサインイン、簡易的な Web UI (Hosted UI) などの機能を提供しています。

Cognito user pool にユーザー ID とパスワードでサインインすると、各種 token が払い出されます。

Cognito には Identity pool というものもあって紛らわしいですが、これは本記事で実現しようとする IdP 経由での user pool へのサインインとは異なる機能なので注意が必要です。

OpenID Connect を使った ID 連携の概要

OpenID Connect (OIDC) は、OAuth 2.0 をベースとして構築された認証プロトコルです。つまり OIDC は OAuth 2.0 の拡張として位置づけられますが、OAuth 2.0 が認可 (authorization) のプロトコルであるのに対して、OIDC は認証 (authentication) のためのプロトコルです。

平たく言うと、OAuth 2.0 はあるユーザーがどのリソースにアクセスできるかを制御するための仕様であるのに対し、OIDC ではこれに加えてそのユーザーが誰であるかという認証情報の提供についても規定しています。

そのため、今回のように既存の Web アプリケーションにおけるユーザー管理機能と統合して、ID 連携を行うことができます。具体的には、以下のような流れで認証を行うことになります。

  1. Web アプリケーションから IdP に認証リクエスト
  2. ブラウザ上で認証画面にリダイレクト
  3. ユーザーが IdP の認証情報を入力
  4. IdP で認証成功
  5. token 払い出し
  6. ID token をデコードしてユーザー情報を取得
  7. ユーザー情報を使って Web アプリケーションにサインイン

この流れは非常にシンプルに書いたもので、実際にはセキュリティ的な対応など途中でさまざまな処理が入りますが、大枠としてはこのような流れになります。

Azure AD App のセットアップ

それでは実際に Azure AD を IdP として、Cognito user pool との連携を行う流れを見ていきます(Microsoft の公式ドキュメント)。 なお、各サービスの画面キャプチャーは 記事作成時点の 2022 年 12 月のものです。

事前に必要なものは以下のリソースです。

  • Cognito user pool
  • User pool の App Client
  • User pool のユーザー
  • Azure AD のテナント(ディレクトリ)

まずは Azure の Portal にサインインして、ホーム画面から以下のように進みます。

  1. Manage Azure Active Directory
  2. App registrations
  3. + New registration

App 登録画面

ここでは App の Name など最低限の情報のみ入力します。 Redirect URI は認証リクエストの redirect_uri として指定できる URI です。一旦 http://localhost だけで問題ありません。

Register を押して App が作成されたら、その App を選択して詳細画面を見てみましょう。 ここで必要となるのは Application (client) ID なので、値をコピーしておきます。

作成した App

画面上部の Endpoints を選択すると、OIDC や SAML での連携に必要となる各種エンドポイントをまとめて確認できます。この中の OpenID Connect metadata document に、OIDC で利用するエンドポイントの情報があります。

次に、Client secret を作成します。 左ペインから Certificates & secrets を選択して、+ New client secret から作成します。

作成された Client secret のうち、value を使用するので忘れずにコピーしておきましょう。一度ページ遷移すると、この後は value が表示されませんので注意です。

次に API 権限の設定を行います。

同じく左ペインから API permissions を選択し、+ Add a permission から権限追加ができます。Microsoft Graph の Delegated permissions から、以下の項目にチェックを入れます。

  • email
  • openid
  • User.Read

これで Azure AD 側での準備はひとまず終わりです。

Cognito user pool のセットアップ

ここからは Cognito 側の設定に移ります。

まず、Cognito user pool を作成します(AWS の公式ドキュメント)。設定は一般的なもので問題ありませんが、Cognito user pool sign-in options は Email を指定し、 Required attributes に name を追加しておきます。

User pool ができたら、Azure AD に存在するユーザーの Email でユーザーを作成しておきます。

次に App integrations から App client を作成します。

Hosted UI を有効化し、App client の App type は Public client にしておきます。

Allowed callback URLs には http://localhost を設定します。これは Cognito での認証リクエストが成功した後、Authorization code を受け取るエンドポイントになります。

Authentication flows の USER_PASSWORD_AUTH も有効化して、OpenID Connect scopes には Email OpenID Profile を設定します。

App client ができたら、作成したユーザーで Hosted UI からサインインができることを確認しておきましょう。サインインが成功するとパスワード変更を促されるので、ここで新しいパスワードを設定します。

Hosted UI を起動してサインイン

パスワード変更が終わると、Callback URL に指定した localhost にリダイレクトされるはずです。

あわせて AWS CLI からも認証できることを確認します。

aws cognito-idp initiate-auth \
    --auth-flow USER_PASSWORD_AUTH \
    --auth-parameters USERNAME=<your-user-name>,PASSWORD=<your-password> \            
    --client-id <app-client-id>

client-id に指定するのは、上記で作成した App client の Client ID です。

うまくいけば、レスポンスの AuthenticationResult の中に AccessTokenIdTokenRefreshToken が入っているのが確認できるはずです。

これで、Cognito の Email とパスワードでサインインできることを確認できました。

Azure AD と Cognito user poolの連携

ここからは用意した Cognito user pool のユーザーで Azure AD 経由のサインインを試してみます。ゴールは、Cognito の Email とパスワードを使用せずに Azure AD の認証を行うことで Cognito から取得したのと同じ token が得られることです。

まず、Cognito user pool の Sign-in experience タブで Federated identity provider sign-in から Add identity provider を選択します。

Identity provider の追加

Identity provider の選択肢は OpenID Connect (OIDC) を選び、Set up OpenID Connect federation with this user pool の各項目にはそれぞれ以下の値を入れます。

  • Provider name
    • 名称。Hosted UI のボタンに表示されます。
  • Client ID
    • Azure AD で作成した App の Client ID
  • Client secret
    • Azure AD で作成した App の Client secret (value)
  • Authorized scopes
    • email profile openid
  • Identifiers
    • 一意の ID(任意)
  • Issuer URL
    • Azure AD で作成した App の OpenID Connect metadata document リンク先 issuer の値
  • Map attributes between your OpenID Connect provider and your user pool
    • name: name
    • email: preferred_username

これで Cognito user poolでの IdP 作成は完了です。 次に、この IdP を App client から利用できるようにするため、App integration タブから作成した App client を選択します。

Hosted UI の Edit を押下し、Identity providers の項目に先ほど作成した Identity Provider を追加します。

作成した IdP にチェックを追加

Save してから再度 Hosted UI を開いてみると、追加した Identity provider のボタンが表示されているはずです。これで Cognito と Azure AD の OIDC 連携はとりあえずできている状態になります。

Sign in with your corporate ID に追加した IdP が表示される

ここまで来たら、一旦 Azure AD のポータルに戻って App の設定を追加します。 左ペインから Authentication を選択して、Redirect URIs に以下の URL を追加します。

https://<your-cognito-domain>/oauth2/idpresponse

ドメインは Hosted UI のドメインと同一です。 このエンドポイントでは以下のようなことが行われます *3

  1. IdP での認証が終わった後に Authorization code を受け取る
  2. IdP に token request を送信
  3. 受け取った Access token で IdP の Userinfo エンドポイントにリクエスト
  4. Userinfo のレスポンスを元に Cognito user pool のユーザーにマッピング

あらためて Hosted UI を開き、IdP 経由でサインインしてみましょう。うまくいけば、http://localhost/?code=<cognito-authorization-code> のようにリダイレクトされて、Cognito の Authorization code が取得できるはずです。

この Code を使って、Cognito に以下の token request を送ると Cognito 発行の token が得られます *4

curl --location --request POST 'https://<your-cognito-domain>/oauth2/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'code=<cognito-authorization-code> \
--data-urlencode 'redirect_uri=http://localhost \
--data-urlencode 'client_id=<cognito-client-id>' \
--data-urlencode 'grant_type=authorization_code'
--data-urlencode 'scope=openid email profile'

得られた token のうち、id_tokenjwt.io などでデコードしてみましょう。 Cognito の通常の Claim に加えて、identities という Claim に IdP の情報が入っているのがわかります。

Azure AD でのシングルサインオン

これで OIDC での ID 連携は完成、と言いたいところですが、前段で Cognito の Authorization code が適切に取得できている場合、User pool に新しいユーザーが作成されているはずです。

Confirmation status が External provider となっていて、外部 IdP によって作成されたユーザーということがわかります。

同じメールアドレスで新しいユーザーができてしまう

この状態だと、既存のユーザーと IdP 経由で作成されたユーザーが同じ Email アドレスなのに別ユーザーと認識されてしまいます。これでは、この記事のゴールだったはずの「既存の Cognito user pool のユーザーとして Azure AD ユーザーでサインインする」という点が実現できていません。

既存ユーザーとして IdP でのサインインを行うためには、AWS CLI で admin-link-provider-for-user という API を使います *5

事前に Azure AD から ID token を取得しておく必要がありますので、ドキュメントに沿って取得しておきましょう

ID token を取得できたらこれをデコードします。External provider で作成されたユーザーは一度削除してから、以下を実行します。

aws cognito-idp admin-link-provider-for-user \
--user-pool-id <your-user-pool-id> \
--destination-user ProviderName=Cognito,ProviderAttributeValue=<cognito-user-name> \
--source-user ProviderName=<idp-name>,ProviderAttributeName=Cognito_Subject,ProviderAttributeValue=<user-sub-for-idp>
  • cognito-user-name
    • Cognito での User name
  • idp-name
    • Cognito の Identity provider として登録している IdP の name
  • user-sub-for-idp
    • IdP でのユーザーの識別子(ID token の sub)

紐付けが成功すると、Cognito 内で User pool のユーザーと IdP のユーザーが同一だと認識してくれるので、IdP 経由のサインインで既存ユーザーとしての token を払い出してくれます。

なお、一度紐付けされたユーザーを再度紐付けしようとすると、すでに同一のユーザーとしてみなされているため、InvalidParameterException が発生します。紐付けを解除するためには、admin-disable-user を使います *6

紐付けができたら再度 Hosted UI を開いて、IdP 経由でサインインしてみましょう。 Redirect 先で取得できる Code を使って、Cognito に token request を送り、token を取得します。

取得した ID token をデコードすると、email や sub などの値が Cognito の既存ユーザーと同じであることが確認できます。

また、紐付け前に IdP サインインした時とは異なり、User pool に新しいユーザーが作成されていないはずです。紐付けを行ったことにより、IdP のサインインでも既存のユーザーとしてのサインインとしてみなされているためです。

あとはこの token をアプリケーション側の認証方式に応じて Authorization header などに使って、User pool のユーザーとして各種リソースにアクセスすることができます。

User pool には先ほどと違って新しいユーザーは作成されておらず、ユーザーの詳細を確認すると User attributes の identities に IdP と連携した情報が記録されているのがわかります。

Cognito 既存ユーザーの User attributes

以上で、Cognito の既存ユーザーとして Azure AD 経由での Cognito サインインができたことになります。

まとめ

Cognito も Azure AD もそれぞれドキュメントは充実していて、よく読みながら手順通り進めていけば ID 連携を行うことは可能です。

一方で、OIDC で両者を連携する手順をウォークスルーした資料はあまりなく、最初にやや苦労する点がありました。特に、Cognito の idpresponse エンドポイントでは裏側で何が起きているのかわかりづらく、AWS Support に相談して回答を得られたことが助けとなりました。

本記事が、今後同じような開発を行う方にとって有益であれば幸いです。

おわりに

RevComm では OAuth 2.0 や OIDC などの技術を活用した外部連携や認証基盤開発を行っています。興味がある方は、下記から採用情報をチェックしてみてください!

www.revcomm.co.jp