CognitoユーザープールのOAuthスコープ 5パターン
Cognitoユーザープールのアプリクライアントを設定する上で、標準ですと、以下のOAuthスコープから付与する権限の範囲を指定することができます。
- phone
- profile
- openid
- aws.cognito.signin.user.admin
今回はこれらのOAuthスコープの指定の違いによって、具体的にどのような変化があるかを確認します。
構築する環境
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つのスコープが指定されています。これはアプリクライアントの仕様によるものです。
詳細は以下のページをご確認ください。
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(暗黙の認可)を指定します。
本フローの詳細については、以下のページをご確認ください。
ブラウザスクリプト
JavaScriptを使用して、サインイン後のCognito機能を実装します。
AWS SDK for JavaScript v3を使用してブラウザスクリプトを作成する手順は、以下のページをご確認ください。
本ページでは、ポイントとなる点のみを取り上げます。
アクセストークン・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ファイルを公開します。
詳細は以下のページをご確認ください。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
Cognitoリソース確認
AWS Management Consoleから、Cognitoリソースの作成状況を確認します。
S3バケットにHTMLを設置する
静的ウェブサイトホスティング機能が有効化されたS3バケット(fa-036)を作成しました。
このバケットに以下の4種類のコンテンツを配置します。
- インデックスページ([アプリクライアント番号]-index.html)
- サインイン後ページ(signin.html)
- サインアウト後ページ(signout.html)
- ブラウザスクリプト(main.js)
インデックスページはアプリクライアント毎、つまり5つ用意します。
これはサインイン/サインアウトURLに、アプリクライアントのIDを埋め込むためです。
バケットにコンテンツを配置後のイメージは、以下の通りです。
サインアップ
準備が整いましたので、ホストされたUIにアクセスします。
最初はアプリクライアント1を使用するURLにアクセスします。
https://s3-ap-northeast-1.amazonaws.com/fa-036/01-index.html
するとインデックスページが表示されます。
「Sign In」を押下すると、サインインページが表示されます。
初回に限り、サインアップ処理を必要です。サインアップ処理に関しては、以下のページをご確認ください。
今回は追加のスキーマとして「name」「phone_number」を指定したため、これらの属性も登録します。
今回は以下の通りに設定しました。
- name: awstut
- phone_number: +12125551234
OAuthスコープ検証1:phone, openid
サインアップが完了しましたので、サインインを実行します。
アクセストークンを見ると、スコープが「phone openid」とあります。
IDトークンを見ると、電話番号および電話番号での認証状況を確認することができます。
GetUser APIの実行結果を見ると、「NoetAuthorizedException」とあり、APIを実行するための権限がないことがわかります。
以上より、OAuthスコープに「phone」「openid」を選択した場合、IDトークンから電話番号に関する属性にアクセスできるのに対して、アクセストークンを使用するAPIは実行できないことがわかりました。
OAuthスコープ検証2:email, openid
02-index.htmlからサインインします。
アクセストークンを見ると、スコープが「openid email」とあります。
IDトークンを見ると、メールアドレスおよびメールでの認証状況を確認することができます。
GetUser APIの実行結果を見ると、「NoetAuthorizedException」とあり、APIを実行するための権限がないことがわかります。
以上より、OAuthスコープに「email」「openid」を選択した場合、IDトークンからメールアドレスに関する属性にアクセスできるのに対して、アクセストークンを使用するAPIは実行できないことがわかりました。
OAuthスコープ検証3:profile, openid
03-index.htmlからサインインします。
アクセストークンを見ると、スコープが「openid profile」とあります。
IDトークンを見ると、電話番号、メールアドレスの情報に加え、名前も確認することができます。
GetUser APIの実行結果を見ると、「NoetAuthorizedException」とあり、APIを実行するための権限がないことがわかります。
以上より、OAuthスコープに「profile」「openid」を選択した場合、IDトークンから登録されている全属性にアクセスできるのに対して、アクセストークンを使用するAPIは実行できないことがわかりました。
OAuthスコープ検証4:openid
04-index.htmlからサインインします。
アクセストークンを見ると、スコープが「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からサインインします。
アクセストークンを見ると、スコープが「aws.cognito.signin.user.admin」とあります。
IDトークンを見ると、取得できなかったことがわかります。
GetUser APIは正常に実行できました。登録されている全属性が表示されました。
OAuthスコープに「aws.cognito.signin.user.admin」を選択した場合、IDトークンが取得できないのに対して、アクセストークンを使用するAPIが実行できることがわかりました。
まとめ
OAuthスコープによる違いを確認しました。
今回の検証結果を以下に整理します。
Scopes | ID token | Amazon Cognito user pool API operations | Remarks |
phone, openid | phone_number phone_number_verified | × | |
email, openid | email email_verified | × | |
profile, openid | all user attributes | × | |
openid | all user attributes(※) | × | The ID token is not returned if the openid scope is not requested by the client. |
aws.cognito.signin.user.admin | × | ◯ |