i-Vinci TechBlog
株式会社i-Vinciの技術ブログ

SSO(シングルサインオン)について理解する OAuth2.0 編

こんにちは。
近頃はめっきり濃厚豚骨魚介つけ麺に夢中な Sasakura です。

シングルサインオンについて共有する動機

プロジェクトにおいて AWS の Amazon Cognito というサービスを使用して認証認可に関する設計・実装のタスクを担当させて頂きました。
Amazon Cognito のベースとなっているのが OAuth2.0 という認可に関する仕組みです。
Amazon Cognito の公式ドキュメント は OAuth2.0 に関してある程度知識がある前提で書かれており、おおまかに理解していれば Amazon Cognito や他の認証系サービスを利用するにあたってキャッチアップが容易になるので、備忘録もかねて共有したいと思います。

シングルサインオンとは

1 度のユーザー認証によって複数のシステム(業務アプリケーションやクラウドサービスなど)の利用が可能になる仕組みのことです。
システムごとに ID、パスワード等で認証を行うよりもユーザー・管理者の負担低減やセキュリティの向上が期待できると言われています。


Log in with Google みたいなやつ
Qiita_login

シングルサインオンによく使われる仕組みをざっくり解説

  • OAuth2.0
    認可のためのフレームワークです。
    ユーザーが所持するアプリケーション内のコンテンツへ、別のアプリケーションによるアクセスを許可するための仕組み。

  • OpenID Connect
    OAuth 2.0 を認証のために拡張させたものが OpenID Connect です。
    ユーザーの ID 情報を連携する仕組みでもあります。

  • SAML
    Security Assertion Markup Language の略称で、異なるインターネットドメイン間でユーザー認証を行うための XML をベースにした標準規格です。
    SAML を利用することで企業の持つアイデンティティ情報、例えば、Active Directory などを利用して、複数のクラウドサービスへのシングルサインオンを実現します。

認証と認可の違い

認証(Authentication)認可(Authorization)
概要本人確認のこと。名乗った人物が本人であるかを検証することです。特定条件下において、対象物(リソース)を利用可能にする(アクセス)権限を与えることです。
具体例ログイン画面で ID・パスワードを使用して本人確認を行うEC サイトで商品の閲覧は誰でもできるけど、購入するためにはログインが必要

OAuth2.0 について解説

具体例

具体的な例に基づいて理解を進めていこうと思います。
あるスケジュール管理アプリがあったとして、そのスケージュール管理アプリに、既に Google カレンダーに登録しているスケジュールを取り込む機能を実装するとします。
取り込むにあたって一番単純な方法は、スケジュール管理アプリにユーザーの Google の ID とパスワードを保存して、アプリがユーザーに代わって Google にログインして、Google カレンダーの情報を取得するという方法です。

Deposit_user_ID_password

問題点

しかし、上記の方法には以下の問題点があります。

  1. スケジュール管理アプリに移譲する権限が大きすぎる
    アプリが Google にログイン出来てしまうと、Google カレンダー以外の様々な機能にアクセス出来てしまいます。
  2. アプリが悪意のある製作者によるものかもしれない
    アプリの製作者に悪意があり、預かった ID とパスワードを利用して不正なアクセスが行われるかもしれません。
  3. アプリが攻撃を受けた場合、預けた ID とパスワードが流出する恐れがある
    アプリの製作者に悪意が無かったとしても、攻撃によって ID とパスワードが盗まれる恐れがあります。

上記の問題点を解決するのが OAuth2.0 という認可の仕組みです。

OAuth2.0 とは

OAuth2.0 の基本仕様である RFC6749 に以下のような記載があります。

OAuth 2.0 は, サードパーティーアプリケーションによるHTTPサービスへの限定的なアクセスを可能にする認可フレームワークである.
引用元:The OAuth 2.0 Authorization Framework

上記の説明を今回の具体例に当てはめると以下のような図になります。

OAuth2.0_overview_diagram

ユーザーは Google Auth という Google の認証サービスでユーザー ID とパスワードを使用して認証を行います。その時に、スケジュール管理アプリが Google Calendar に登録されたスケジュール情報を利用する事に同意します。

ユーザーが同意すると Google Auth は許可証を発行して、スケジュール管理アプリに渡します。スケジュール管理アプリは許可証を持って Google Calendar にアクセスする事で、ユーザーの登録したスケジュール情報を利用することができます。

OAuth2.0 の仕組みを利用すると、ユーザーはスケジュール管理アプリにユーザー ID やパスワードを預ける必要がないため、アプリが攻撃されてパスワードが流出する心配がなくなります。

また、許可証を使用してアクセスできるのは Google Calendar に限られていますし、許可証には有効期限があるため、もしスケジュール管理アプリに悪意のあるプログラムが含まれていても被害を限定することができます。

OAuth2.0 の用語解説

OAuth2.0 を利用する上で押さえておきたい重要なキーワードを解説します。

OAuth_keywords

ロール(登場人物)

OAuth には以下の 4 つのロールが存在します。

  • リソースオーナー
    スケジュール管理アプリのユーザーで、リソースの所有者です。今回の例で、リソースは Google Calendar に登録されたスケジュール情報にあたります。

  • クライアント
    リソースサーバーの情報を利用するアプリケーションです。

  • リソースサーバー
    リソースオーナーの情報を管理するサーバーです。

  • 認可サーバー
    リソースオーナーを認証して、クライアントがリソースサーバーの情報を利用するための許可証(アクセストークン)を発行します。
    また、認証時にクライアントがリソースサーバーの情報を利用する事に同意するかの確認をリソースオーナーに対して行います。

トークン

OAuth ではトークンと呼ばれる文字列を使用して情報がやり取りされます。

  • アクセストークン
    認可サーバーからクライアントに対して発行される許可証です。
    誰がどのリソースに対してどのような操作を行う事が許可されているのか、またその有効期限が含まれます。
    今回の例ではスケジュール管理アプリが Google Calendar に登録されたスケジュール情報を利用する事を許可するという情報を持っています。

  • リフレッシュトークン
    アクセストークンの再発行に使用されます。
    アクセストークンを保持していれば、誰でもリソースサーバーにアクセス出来ます。そのため、アクセストークンの有効期限は比較的短めに設定して、もしアクセストークンが流出した場合でも利用できる時間を限定します。
    リフレッシュトークンにはアクセストークンより長い有効期限を設定して、アクセストークンの有効期限が切れたら、リフレッシュトークンを認可サーバーに送信することでアクセストークンを再取得します。

スコープ

スコープとはリソースオーナーがクライアントに付与する権限の範囲です。

エンドポイント

  • 認可エンドポイント
    認可サーバーが持つエンドポイント。クライアントは認可エンドポイントにアクセスすることによって認可処理を開始することができます。

  • トークンエンドポイント
    認可サーバーが持つエンドポイント。クライアントがアクセストークンを取得するためのエンドポイントです。

  • リダイレクトエンドポイント
    クライアントが持つエンドポイント。認可サーバーによってユーザーの認証処理が完了した後にリダイレクトされるエンドポイントです。

グラントタイプ

グラントは付与を意味していて、リソースオーナーからクライアントに権限を付与する手順にはいくつかパターンがあります。想定するシステム構成に合わせてどのグラントタイプを使用するかを選択します。
以下は基本仕様で定義されている 4 つのグラントタイプです。

  • 認可コードグラント
  • インプリシットグラント
  • クライアントクレデンシャルグラント
  • リソースオーナーパスワードクレデンシャルグラント

実践

ここからこれまで紹介してきた Oauth2.0 の認可処理を、AWS の Cognito と API Gateway を使用して構築してみたいと思います。

使用するグラントタイプ

アクセストークンを取得するためのグラントタイプは、最も代表的な認可コードグラントを使用します。
認可コードグラントのシーケンス図は以下になります。
Authorization_code_grant_sequence_diagram

リソースサーバーと認可サーバーの構築

APIGateway を使用して API を作成する

まずはリソースサーバーとして使用する API を API Gateway 上に作成します。

  1. API Gateway のコンソール画面で「API を作成」をクリックします。
    Create_APIGateway_API

  2. 「REST API」の枠にある「構築」をクリックします。
    APIGateway_construct

  3. 「API 名」に「oauth-test-api」と入力して、「API の作成」をクリックします。
    APIGateway_Select_Protocol

  4. 「アクション」から「リソースの作成」をクリックし、「リソース名」に「test」と入力して「リソースの作成」ボタンをクリックします。
    APIGateway_Resource_create

  5. 「メソッドの作成」から GET メソッドを作成します。
    APIGateway_Method_Create

  6. 「統合タイプ」は「Mock」を選択して「保存」をクリックします。
    APIGateway_save_method

  7. 「アクション」から「API のデプロイ」をクリックします。

  8. 「デプロイされるステージ」に「新しいステージ」を選択し、「ステージ名」に「dev」を入力して「デプロイ」をクリックします。
    APIGateway_Deploy

  9. 「ステージ」画面に「dev」が作成されている事を確認します。
    APIGateway_stage_confirmation

  10. ここで作成した API が呼び出せるか確認するために、ポストマンからリクエストしてみます。/test の URL に GET リクエストを送信するとステータスコード 200 が返ってきていて、API を呼び出せることが確認できました。
    APIGateway_test_call

Cognito ユーザープールを作成する

次に、認可サーバーとして使用する Cognito ユーザープールを作成します。
※言及のない箇所はデフォルトの設定値を使用します

  1. Cognito のコンソール画面を開き、「ユーザープールを作成」ボタンをクリックします。
    Create_Cognito_user_pool

  2. 「サインインエクスペリエンスを設定」の画面では「ユーザー名」を選択して「次へ」をクリックします。
    Cognito_Setup_Sign-in_Experience

  3. 「セキュリティ要件を設定」の画面では「多要素認証」で「MFA なし」を選択、「セルフサービスのアカウントの復旧」はチェックを外して「次へ」をクリックします。
    Cognito_Set_Security_Requirements

  4. 「サインアップエクスペリエンスを設定」の画面はデフォルトのまま「次へ」をクリックします。

  5. 「メッセージ配信を設定」の画面では「Cognito で E メールを送信」をチェックして「次へ」をクリックします。
    Cognito_Setup_Message_Delivery

  6. 「アプリケーションを統合」の画面では以下の設定を行います。

    1. 「ユーザープール名」には「oauth-test-userpool」を入力します。
      Cognito_user_pool_name

    2. 「Cognito のホストされた UI を使用」をチェックします。

    3. 「ドメイン」は「Cognito ドメインを使用する」をチェックして「ドメインプレフィックス」に任意の文字列を入力します。
      Cognito_domain

    4. 「アプリケーションタイプ」は「パブリッククライアント」、「アプリケーションクライアント名」は「oauth-test-app」を入力します。
      Cognito_Application_Client

    5. 「クライアントシークレット」は「クライアントシークレットを生成しない」をチェックし、「許可されているコールバック URL」は「http://localhost/callback」を入力します。
      Cognito_Client_Secret

    6. 「高度なアプリケーションクライアントの設定」の中の「OAuth 2.0 許可タイプ」に「認証コード付与」が設定されている事を確認します。
      これはグラントタイプの「認可コードグラント」を使用するために必要な設定です。
      Cognito_authentication_code_grant

    7. 「次へ」をクリックします。

  7. 「確認および作成」の画面では「ユーザープールを作成」をクリックします。

認可サーバーにリソースサーバーを登録する

先ほど作成した API をリソースサーバーとして、認可サーバーである Cognito ユーザープールに登録します。

  1. 先ほど作成した「oauth-test-userpool」の管理画面を開き、「アプリケーションの統合」タブを開きます。
    Register_Cognito_Resource_Server1

  2. 「リソースサーバーを作成」をクリックします。
    Cognito_Resource_Server_Register2

  3. 「リソースサーバーを作成」の画面で、「リソースサーバー」と「リソースサーバー識別子」に「oauth-test-api」を入力します。
    Cognito_Resource_Server_Register3

  4. 「カスタムスコープ - オプション」の「スコープ名」に「read」、「説明」に「読み取り権限」を入力して、「リソースサーバーを作成」をクリックします。
    Register_Cognito_Resource_Server4

アプリクライアントにカスタムスコープを登録する

  1. 「oauth-test-userpool」の管理画面の「アプリケーションの統合」タブから「oauth-test-app」をクリックします。
    Register_Cognito_Resource_Server5

  2. 「ホストされた UI」の「編集」をクリックします。
    Register_Cognito_Resource_Server6

  3. 「カスタムスコープ」に「oauth-test-api/read」を選択して「変更を保存」をクリックします。
    Cognito_Resource_Server_Registration7

Cognito ユーザーを登録する

リソースオーナーであるユーザーを登録します。

  1. 「oauth-test-userpool」の管理画面の「ユーザー」タブから、「ユーザーを作成」をクリックします。
    Cognito_Create_User1

  2. 「ユーザー名」と「パスワード」に任意の値を設定して「ユーザーを作成」をクリックします。
    Cognito_Create_User2

Cognito ユーザーのパスワード変更

Cognito ユーザーは、登録後に必ずパスワードを変更する必要があります。
作成された Cognito ユーザーを確認すると「確認ステータス」が「パスワードを強制的に変更」になっています。
Create_Cognito_User3

以下のような AWS CLI のコマンドを実行してパスワードの変更を行います。

aws cognito-idp admin-set-user-password
--user-pool-id ap-northeast-1_********** \
--username sasakura \
--password  ******** \
--permanent

コマンドを実行すると「確認ステータス」が「確認済み」になり、ユーザーが使用できるようになりました。
Create_Cognito_User4

オーソライザーを作成する

続いて API の認可処理を行うためのオーソライザーを作成します。
リソースサーバーである API に対してクライアントがリクエストを送信した際に、リクエストに含まれるアクセストークンを解析してアクセス権限の有無を判断するのがオーソライザーの役割です。

  1. 最初に作成した「oauth-test-api」の管理画面を開き、「オーソライザー」タブを開きます。
    「新しいオーソライザーの作成」をクリックします。
    APIGateway_Authorizer_create1

  2. 「名前」に「oauth-test-authorizer」、「タイプ」は「Cognito」をチェック、「Cognito ユーザープール」は先ほど作成した「oauth-test-userpool」を選択し、「トークンのソース」は「Authorization」を入力して「作成」をクリックします。
    APIGateway_Authorizer_create2

API にオーソライザーを設定する

API で認可処理を行えるようにオーソライザーを設定します。

  1. 「oauth-test-api」の管理画面の「リソース」タブを開きます。
    「GET」 → 「メソッドリクエスト」をクリックします。
    APIGateway_Authorizer_setup1

  2. 「認可」に「oauth-test-authorizer」、OAuth スコープに「oauth-test-api/read」を入力します。
    APIGateway_Authorizer_setup2

  3. 「アクション」から「API のデプロイ」をクリックします。
    APIGateway_Authorizer_setup3

  4. 「デプロイされるステージ」を「dev」に設定して「デプロイ」をクリックします。
    APIGateway_Authorizer_setup4

認可処理の検証

ここまでで、リソースサーバーと認可サーバーの構築は完了です。
API にオーソライザーを設定したため、アクセストークンを設定せずにリクエストを行うとリクエストが拒否されるはずです。
再度ポストマンでリクエストを送信してみます。
APIGateway_Verification_of_authorization_process

ステータスコード 401 が返ってきていて、オーソライザーによる認可処理でアクセスが拒否されたことが確認できました。

OAuth2.0 によるリソースサーバーへのアクセス

ここからは認可コードグラントの手順に沿って、アクセストークンの取得とリソースサーバーへのアクセスを行います。
Authorization_code_grant_sequence_diagram.drawio

リソースサーバーにアクセスするために、アクセストークンの取得を行います。

認可エンドポイントへアクセスする

「認可エンドポイント」にアクセスするために、シーケンス図 3 番の「認可リクエスト」を行います。

ブラウザのアドレスバーに以下のような URL を設定してアクセスします。

https://*************.auth.ap-northeast-1.amazoncognito.com/oauth2/authorize?
client_id=**************2sr2rn2la6mib&
response_type=code&
state=xyz&
scope=oauth-test-api/read
redirect_uri=http%3A%2F%2Flocalhost%2Fcallback

Cognito の認可エンドポイントの詳細はリンク先のドキュメントに記載がありますが、各パラメータの簡単な説明は以下になります。

  • _client_id_
    Cognito ユーザープールに登録したクライアントの ID です。

  • _response_type_
    "response_type"に設定した文字列によってグラントタイプが決定します。認可コードグラントを使用する場合"code"を設定します。

  • _state_
    パブリッククライアント(web ブラウザ等がクライアントの場合)が認可コードグラントを使用する場合、クロスサイトリクエストフォージェリという手法を使用した攻撃を防止するためにランダムに生成した文字列を設定します。

  • _scope_
    リソースオーナーがクライアントに対して移譲する権限の範囲を設定します。"oauth-test-api/read"を設定すること API の"/test"にアクセス出来るようになります。

  • _redirect_uri_
    認可サーバーからクライアントにリダイレクトする時(シーケンス図 8 番「認可レスポンス」)の URI です。事前登録したものを設定します。

認証情報を入力する

認可エンドポイントにアクセスすると、以下のようなログイン画面が表示されるのでユーザー名とパスワード入力して、「Sign in」をクリックします。
Access_token_acquisition1

本来であれば「Sign in」をクリックした後で、以下のようなリソースオーナーからクライアントへ権限を移譲することの確認画面(シーケンス図 6 番「権限移譲の確認画面」)が表示されて欲しいのですが、Cognito では表示されません。
※ 以下の画像は Google Auth の認可サーバーによる認証を行ったときのスクリーンショットです
Practice_confirmation_of_authorization_transfer

クライアントへのリダイレクト

認可サーバーによる処理が完了すると、シーケンス図 3 「認可リクエスト」の際に設定した"redirect uri"にリダイレクトされます。
Access_token_acquisition2.p
URL には上記のように"code", "state"のパラメータが設定されており、"code"に設定されている文字列が認可コードです。この認可コードを認可サーバーのトークンエンドポイントに送信することでアクセストークンを取得します。

トークンリクエスト

取得した認可コードをトークンエンドポイントに送信します。

POST
https://******************.auth.ap-northeast-1.amazoncognito.com/oauth2/token?
code=8e0f29bd-d2**************&
grant_type=authorization_code&
client_id=5b2ngtb4*******************&
redirect_uri=http://localhost/callback&
scope=oauth-test-api/read
  • _code_
    先ほど取得した認可コードを設定します。

  • _grant_type_
    認可コードグラントグラントでは"authorization_code"を設定します。

以下のようにアクセストークンを取得することができました。
Get_access_token3

リソースへのアクセス

いよいよシーケンス図 12 「リソースへのアクセス」を行います。
以下のように Authorization ヘッダーにアクセストークンを設定した状態でリクエストを行います。
Access_resource1

ステータスコードが 200 になっており、リクエストが成功したことが確認できました。
再度 Authorization ヘッダーを削除してみるとステータスコードが 401 になり、オーソライザーによってアクセスが拒否されたことが確認できました。
Accessing_resources2

まとめ

OAuth2.0 の仕組みについて具体例を交えてご紹介しました。
今回は OAuth2.0 の代表的なグラントタイプである認可コードグラントの手順をトレースするためにリソースサーバーの設定なども行ったのですが、API Gateway のオーソライザーは他にも色々は方式があります。
API Gateway での REST API へのアクセスの制御と管理
認証認可は独特の概念や用語が多くてとっつきにくいですが、少しでもこれから取り組む方の足がかりになれば幸いです。

参考

雰囲気で OAuth2.0 を使っているエンジニアが OAuth2.0 を整理して、手を動かしながら学べる本[2023 年改訂版]