CognitoユーザープールのOAuthスコープ 5パターン

CognitoユーザープールのOAuthスコープ 5パターン

Cognitoユーザープールのアプリクライアントを設定する上で、標準ですと、以下のOAuthスコープから付与する権限の範囲を指定することができます。

  • phone
  • email
  • profile
  • openid
  • aws.cognito.signin.user.admin

今回はこれらのOAuthスコープの指定の違いによって、具体的にどのような変化があるかを確認します。

構築する環境

Diagram of 5 Patterns of OAuth scopes for Cognito User Pool

S3バケットを作成し、静的ウェブサイトホスティング機能を有効にします。
内部にHTMLファイルを設置して、コンテンツを用意します。

Cognitoユーザープールを作成します。
加えて5つのアプリクライアントを作成し、それぞれ異なる付与するOAuthスコープを設定します。
具体的には、以下の通りに設定します。

  • クライアント1:phone, openid
  • クライアント2:email, openid
  • クライアント3:profile, openid
  • クライアント4:openid
  • クライアント5:aws.cognito.signin.user.admin

クライアント1〜3は、2つのスコープが指定されています。これはアプリクライアントの仕様によるものです。
詳細は以下のページをご確認ください。

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-app-idp-settings.html

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLに、CloudFormationテンプレートに加え、ブラウザスクリプト等を配置してます。

https://github.com/awstut-an-r/awstut-fa/tree/main/036

ポイント解説

ユーザープールのスキーマ

ユーザープール本体の設定を確認します。
今回のポイントはサインアップ時に登録する属性に関する設定です。

Resources:
  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      AutoVerifiedAttributes:
        - email
      UsernameAttributes:
        - email
      UserPoolName: !Sub "${Prefix}-UserPool"
      Schema:
        - AttributeDataType: String
          Mutable: true
          Name: name
          Required: true
        - AttributeDataType: String
          Mutable: true
          Name: phone_number
          Required: true
Code language: YAML (yaml)

Schemaプロパティでサインアップ時に登録する属性を指定できます。
今回は、サインアップ時に検証用で使用するメールアドレス(email)に加えて、名前(name)と電話番号(phone_number)の登録を必須とします。

ユーザープール アプリクライアントのOAuthスコープ

ユーザープールのアプリクライアントを確認します。

Resources:
  UserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      AllowedOAuthFlowsUserPoolClient: true
      AllowedOAuthFlows:
        - implicit
      AllowedOAuthScopes: !Ref Scopes
      CallbackURLs:
        - !Sub "${BucketWesSiteEndpointUrl}/${SigninHtml}"
      ClientName: !Sub ${Prefix}-UserPoolClient
      ExplicitAuthFlows:
        - ALLOW_REFRESH_TOKEN_AUTH
        - ALLOW_USER_SRP_AUTH
      LogoutURLs:
        - !Sub "${BucketWesSiteEndpointUrl}/${SignoutHtml}"
      SupportedIdentityProviders:
        - COGNITO
      UserPoolId: !Ref UserPool
Code language: YAML (yaml)

AllowedOAuthScopesプロパティで、付与するOAuthスコープを設定できます。
組み込み関数Fn::Refを使用して、アプリクライアントごとにスコープを指定します。

AllowedOAuthFlowsプロパティでOAuthフローを指定できます。
今回はImplicit grant(暗黙の認可)を指定します。
本フローの詳細については、以下のページをご確認ください。

あわせて読みたい
Cognitoユーザープールの認証後にIDプールで権限を与える(Implicit Grant編) 【Cognitoユーザープールの認証後にIDプールで権限を与える】 CognitoユーザープールとIDプールを使用して、サインインユーザーにAWSリソースにアクセスするための権限...

ブラウザスクリプト

JavaScriptを使用して、サインイン後のCognito機能を実装します。
AWS SDK for JavaScript v3を使用してブラウザスクリプトを作成する手順は、以下のページをご確認ください。

あわせて読みたい
ブラウザでAWS SDK for JavaScript v3を扱う 【ブラウザでAWS SDK for JavaScript v3を扱う】 AWSを使用してWebアプリを開発する場合、AWS SDK for JavaScriptの使用がベストプラクティスとなります。同SDKには2つ...

本ページでは、ポイントとなる点のみを取り上げます。

アクセストークン・IDトークン確認

OAuthスコープの指定によって違いが生じるものに、アクセストークン・IDトークンがあります。

const getPayloadFromJWT = (token) => {
  return token.split(".")[1]
};

const params = new URLSearchParams(location.hash.slice(1));
const accessToken = params.get("access_token");
const idToken = params.get("id_token");

const showAccessToken = () => {
  document.getElementById("access-token").innerText = JSON.stringify(
    JSON.parse(atob(getPayloadFromJWT(accessToken))), null , 2);
};

const showIdToken = () => {
  if (idToken !== null) {
    document.getElementById("id-token").innerText = JSON.stringify(
      JSON.parse(atob(getPayloadFromJWT(idToken))), null , 2);
  } else {
    document.getElementById("id-token").innerText = "No ID Token.";
  }
};

showAccessToken();
showIdToken();
Code language: JavaScript (javascript)

今回の構成では、OAuthフローにImplicit grantを指定しているため、URLパラメータとして両トークンが渡されます。
両トークンはJWT(JSON Web Token)です。ペイロードの部分を切り出して、Base64形式の文字列をデコードした上で、中身をHTMLに埋め込みます。

アクセストークンを使用したAPI実行

aws.cognito.signin.user.adminの挙動を確認するために、アクセストークンを使用するAPIを実行します。

import {
  CognitoIdentityProviderClient,
  GetUserCommand
} from "@aws-sdk/client-cognito-identity-provider";

const client = new CognitoIdentityProviderClient({
  region: REGION
});

const showUser = async () => {
  try {
    const response = await client.send(
      new GetUserCommand({
        AccessToken: accessToken
      })
    );
    document.getElementById("get-user").innerText = JSON.stringify(response, null, 2);
  } catch (err) {
    document.getElementById("get-user").innerText = JSON.stringify(err, null, 2);
  }
}

showUser();
Code language: JavaScript (javascript)

今回はアクセストークンを使用するAPIということで、GetUserを実行します。
実行結果をHTMLに埋め込みます。

S3の静的ウェブサイトホスティング機能

静的ウェブサイトホスティング機能で、S3バケットに設置したHTMLファイルを公開します。
詳細は以下のページをご確認ください。

あわせて読みたい
S3静的ウェブサイトホスティング機能でサイトを公開する 【S3静的ウェブサイトホスティング機能でサイトを公開する構成】 S3の静的ウェブサイトホスティング機能を使って、ウェブサイトを公開する方法を確認します。 ウェブサ...

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

Cognitoリソース確認

AWS Management Consoleから、Cognitoリソースの作成状況を確認します。

Cognito User Pool
Cognito User Pool App Client 1
Cognito User Pool App Client 2
Cognito User Pool App Client 4
Cognito User Pool App Client 3
Cognito User Pool App Client 5

S3バケットにHTMLを設置する

静的ウェブサイトホスティング機能が有効化されたS3バケット(fa-036)を作成しました。
このバケットに以下の4種類のコンテンツを配置します。

  1. インデックスページ([アプリクライアント番号]-index.html)
  2. サインイン後ページ(signin.html)
  3. サインアウト後ページ(signout.html)
  4. ブラウザスクリプト(main.js)

インデックスページはアプリクライアント毎、つまり5つ用意します。
これはサインイン/サインアウトURLに、アプリクライアントのIDを埋め込むためです。

バケットにコンテンツを配置後のイメージは、以下の通りです。

Upload contents to S3 bucket

サインアップ

準備が整いましたので、ホストされたUIにアクセスします。
最初はアプリクライアント1を使用するURLにアクセスします。

https://s3-ap-northeast-1.amazonaws.com/fa-036/01-index.html

するとインデックスページが表示されます。

Index Page

「Sign In」を押下すると、サインインページが表示されます。
初回に限り、サインアップ処理を必要です。サインアップ処理に関しては、以下のページをご確認ください。

あわせて読みたい
Cognito ユーザープールでサインインページを作成する 【CognitoユーザープールのホストされたUIでサインインページを作成する】 Cognitoを使用してサインインページを作成します。 CognitoはAWSが提供する認証系サービスで...

今回は追加のスキーマとして「name」「phone_number」を指定したため、これらの属性も登録します。

Sign up page

今回は以下の通りに設定しました。

  • name: awstut
  • phone_number: +12125551234

OAuthスコープ検証1:phone, openid

サインアップが完了しましたので、サインインを実行します。

The Result of OAuth Scope: "phone openid"

アクセストークンを見ると、スコープが「phone openid」とあります。
IDトークンを見ると、電話番号および電話番号での認証状況を確認することができます。
GetUser APIの実行結果を見ると、「NoetAuthorizedException」とあり、APIを実行するための権限がないことがわかります。
以上より、OAuthスコープに「phone」「openid」を選択した場合、IDトークンから電話番号に関する属性にアクセスできるのに対して、アクセストークンを使用するAPIは実行できないことがわかりました。

OAuthスコープ検証2:email, openid

02-index.htmlからサインインします。

The Result of OAuth Scope: "openid email"

アクセストークンを見ると、スコープが「openid email」とあります。
IDトークンを見ると、メールアドレスおよびメールでの認証状況を確認することができます。
GetUser APIの実行結果を見ると、「NoetAuthorizedException」とあり、APIを実行するための権限がないことがわかります。
以上より、OAuthスコープに「email」「openid」を選択した場合、IDトークンからメールアドレスに関する属性にアクセスできるのに対して、アクセストークンを使用するAPIは実行できないことがわかりました。

OAuthスコープ検証3:profile, openid

03-index.htmlからサインインします。

The Result of OAuth Scope: "openid profile"

アクセストークンを見ると、スコープが「openid profile」とあります。
IDトークンを見ると、電話番号、メールアドレスの情報に加え、名前も確認することができます。
GetUser APIの実行結果を見ると、「NoetAuthorizedException」とあり、APIを実行するための権限がないことがわかります。
以上より、OAuthスコープに「profile」「openid」を選択した場合、IDトークンから登録されている全属性にアクセスできるのに対して、アクセストークンを使用するAPIは実行できないことがわかりました。

OAuthスコープ検証4:openid

04-index.htmlからサインインします。

The Result of OAuth Scope: "openid"

アクセストークンを見ると、スコープが「openid」とあります。
IDトークンを見ると、電話番号、メールアドレスの情報に加え、名前も確認することができます。
GetUser APIの実行結果を見ると、「NoetAuthorizedException」とあり、APIを実行するための権限がないことがわかります。
以上より、OAuthスコープに「openid」を選択した場合、IDトークンから登録されている全属性にアクセスできるのに対して、アクセストークンを使用するAPIは実行できないことがわかりました。

なおこの挙動はホストされたUIを使用したことによるものと考えられます。本来の仕様は、以下の通りです。

openid スコープはクライアントが読み取り可能な ID トークン内のすべてのユーザー属性を返します。openid スコープがクライアントによってリクエストされない場合は、ID トークンは返されません。

許可されている OAuth スコープ

OAuthスコープ検証5:aws.cognito.signin.user.admin

05-index.htmlからサインインします。

The Result of OAuth Scope: "aws.cognito.signin.user.admin"

アクセストークンを見ると、スコープが「aws.cognito.signin.user.admin」とあります。
IDトークンを見ると、取得できなかったことがわかります。
GetUser APIは正常に実行できました。登録されている全属性が表示されました。
OAuthスコープに「aws.cognito.signin.user.admin」を選択した場合、IDトークンが取得できないのに対して、アクセストークンを使用するAPIが実行できることがわかりました。

まとめ

OAuthスコープによる違いを確認しました。
今回の検証結果を以下に整理します。

ScopesID tokenAmazon Cognito user pool API operationsRemarks
phone, openidphone_number
phone_number_verified
×
email, openidemail
email_verified
×
profile, openidall user attributes×
openidall user attributes(※)×The ID token is not returned if the openid scope is not requested by the client.
aws.cognito.signin.user.admin×