Amazon Cognito による認証

2020年4月11日

はじめに

Amazon Cognito を用いると、ユーザー認証画面を作ることができる。

ユーザープールと ID プール

Cognito ではユーザープールと ID プールというものを管理する。ユーザープールはユーザーを管理したり認証を行ったりするものである。ID プールは、Cognito のユーザープールだけでなく Google など一般的な認証サービスを提供するもの (認証プロバイダー) の認証情報に基づいて権限を与える。ID プールでは認証が通った時のロールを指定できるので、認証が通った時にだけ AWS SDK に必要なサービスへの権限を与えるということができる。

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

ここではユーザーは管理側で作成するものとし、ユーザーには e-mail アドレスなどを要求しないものとする。

  • AWS マネジメントコンソールで Amazon Cognito を開いて、[ユーザープールの管理] を選択。
  • [ユーザープールを作成する] を選択。
  • プール名を設定。[デフォルトを確認する] を選択。
  • 左の [属性] で "どの標準属性が必要ですか?" で "email" のチェックを外す。「次のステップ] を選択。
  • [ポリシー] の "ユーザーに自己サインアップを許可しますか?" で "管理者のみにユーザーの作成を許可する" を選択。[次のステップ
  • [確認] で [プールの作成] を選択。プール ID をメモしておく。

※ちなみに、属性で email を有効にして自己サインアップを許可すると、e-mail アドレスを用いてユーザー自身でユーザー登録を行うことができる。

  • 左の [全体設定]-[アプリクライアント] で [アプリクライアントの追加] を選択。アプリクライアント名を設定。"クライアントシークレットを作成" のチェックを外す。[アプリクライアントの作成] を選択。アプリクライアント ID が表示されるので、メモする。
  • [アプリの統合]-[アプリクライアントの設定] で "有効な ID プロバイダ" の "Cognito User Pool" にチェックを入れる。
    • "コールバック URL"、"サインアウト URL" を HTTPS で設定する。テスト用の "http://localhost" は例外的に受け付ける。
    • "OAuth 2.0" の "許可されている OAuth フロー" の "Implicit grant" および "許可されている OAuth スコープ" の "opneid" にチェックを入れる。[変更の保存]。
  • [アプリの統合]-[ドメイン名] で "Amazon Cognito ドメイン" の "ドメインのプレフィックス" を設定する。[使用可能かチェック] で問題なければ [変更の保存]。ドメインのプレフィックスをメモしておく。

ここではローカルの http サーバーでテストすることにする。"コールバック URL" と "サインアウト URL" を "http://localhost:8000" と設定する。Python でサーバーを立てる (コマンドプロンプトで実行)。

>python -m http.server

ブラウザで "http://localhost:8000" にアクセスしてディレクトリの内容が表示されれば OK。

ログイン画面へのアクセス

ログイン画面にアクセスできるか確認する。ログイン画面の URL は次のようになる。

https://(プレフィックス).auth.(リージョン).amazoncognito.com/login?response_type=token&client_id=(アプリクライアント ID)&redirect_uri=(コールバック URL)

東京リージョンの場合はリージョンのところを "ap-northeast-1" とする。

コールバック URL は [アプリクライアントの設定] で設定したものと同じものである。とりあえずログイン画面が出てくれば OK。

  • [全体設定] - [ユーザーとグループ] でユーザーを作成する。[ユーザーの作成] を選び、ユーザー名を設定。"この新規ユーザーに招待を送信しますか?"、"電話番号を検証済みにしますか?”、"E メールを検証済みにしますか?" のチェックを外す。仮パスワードを設定して [ユーザーの作成]。
  • ログイン画面にアクセスし、今作ったユーザーでログインしてみる。パスワードを変えろと出てくるので、新しいパスワードを設定する (同じパスワードでも OK)。
  • コールバック URL に移動するが、URL を見ると情報がくっついているのが確認できる。

ログアウトは次の URL にアクセスする (リージョンが東京の場合)。

https://(プレフィックス).auth.(リージョン).amazoncognito.com/logout?client_id=(アプリクライアント ID)&logout_uri=(コールバック URL)

ID プールの作成

  • AWS マネジメントコンソールで Amazon Cognito を開いて、[ID プールの管理] を選択。
  • ID プール名を入れる。[認証プロバイダー] の “Cognito” タブでユーザープール ID とアプリクライアント ID を設定できるので、そこに作成したユーザープールの情報を設定する。[プールの作成]。
  • ID プールで使うロールについての画面になるので、"詳細を表示" でロール名を確認する。[許可]。
  • "Amazon Cognito での作業開始" と出るので、"プラットフォーム" に "JavaScript" を選択し、"AWS 認証情報の取得" のコードをメモする。
// Amazon Cognito 認証情報プロバイダーを初期化します
AWS.config.region = '(リージョン)';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: '(ID プール ID)',
});

ロールの設定

IAM を開き、ID プールの作成時に作成された "認証されたロール" に必要な権限を設定する。たとえば、認証されたら S3 のデータが見えるようにするなど。

認証情報による権限の取得

Web ページにおいて、AWS SDK for JavaScript について認証が通った場合の権限を取得するには、以下ようにする。

Cognit のログイン画面で認証を通過し、"コールバック URL" に設定したページに移動すると、URL の後ろに "#..." の形でパラメータ (ID トークン) がくっついている。それを URLSearchParams で取り出して AWS SDK に渡す。

const idToken = (() => {
  const params = new URLSearchParams(location.hash.slice(1));
  return params.get("id_token");
})();

// Initialize the Amazon Cognito credentials provider
AWS.config.region = "(リージョン)";
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: "(ID プール ID)",
    Logins: {
      ["cognito-idp.(リージョン).amazonaws.com/(ユーザープール ID)']: idToken,
    }
});

これにより "認証されたロール" が適用されるので、たとえば S3 へのアクセス権を与えているなら、S3 の API が使えるようになる。

ログイン画面の URL は長くて使いにくいが、直接 Web ページにアクセスしたときに、認証トークンがなかったらログイン画面にリダイレクトするなどすればよい。

if (idToken === "") {
    location.href = "(ログイン画面の URL)";
}

認証の成否

認証の成否は、アクセスキー ID などが取得できているかどうかで確認できる。

AWS.config.credentials.get(function() {
  const secretAccessKey = AWS.config.credentials.secretAccessKey;
  if (secretAccessKey === undefined) {
    location.href = "(ログイン画面の URL)";
  }
});

ユーザー名の取得

ID トークンは JWT (JSON Web Token) であり、Base64 の文字列が '.' で区切られている。これをデコードすればユーザー名を取り出せる。

const username = (() => {
  const tokens = idToken.split('.');
  const obj = JSON.parse(atob(tokens[1]));
  return obj['cognito:username'];
})();

ログインスクリプト

以上の情報を以下のようなスクリプトにまとめておくとよい。

login.js

const awsRegion = 'ap-northeast-1';
const bucketName = '(バケット名)';
const domainPrefix = bucketName;
const cognitoUserPoolId = '(ユーザープール ID)';
const cognitoAppClientId = '(アプリクライアント ID)';
const cognitoIdentityPoolId = '(ID プール ID)';

//const thisUrl = "http://localhost:8000/index.html"; // for test
const thisUrl = `https://${bucketName}.s3-${awsRegion}.amazonaws.com/index.html`;

const cognitoDomainUrl = `https://${domainPrefix}.auth.${awsRegion}.amazoncognito.com`;
const cognitoLoginUrl = `${cognitoDomainUrl}/login?response_type=token&client_id=${cognitoAppClientId}&redirect_uri=${thisUrl}`;
const cognitoLogoutUrl = `https://${domainPrefix}.auth.${awsRegion}.amazoncognito.com/logout?client_id=${cognitoAppClientId}&logout_uri=${thisUrl}`;

const logout = () => {
  location.href = cognitoLogoutUrl;
}

const idToken = (() => {
  const params = new URLSearchParams(location.hash.slice(1));
  return params.get("id_token");
})();

let username = '';

if (idToken === null) {
  location.href = cognitoLoginUrl;
}

// Initialize the Amazon Cognito credentials provider
AWS.config.region = awsRegion;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: cognitoIdentityPoolId,
    Logins: {
      [`cognito-idp.${awsRegion}.amazonaws.com/${cognitoUserPoolId}`]: idToken,
    }
});

AWS.config.credentials.get(function() {
  const secretAccessKey = AWS.config.credentials.secretAccessKey;
  if (secretAccessKey === undefined) {
    location.href = cognitoLoginUrl;
  }
});

if (idToken !== null) {
  username = (() => {
    const tokens = idToken.split('.');
    const obj = JSON.parse(atob(tokens[1]));
    return obj['cognito:username'];
  })();
}

最初の定数を環境に合わせて設定する。本番の Web ページは S3 のバケットに置き、バケット名とドメインプレフィックスは同じとしている。テスト環境では thisUrl を入れ替える (テストではローカル Web サーバーの index.html としている。Cognito ユーザープールのコールバック URL、サインアウト URL をこれに合わせる)。Web ページにおいてこのスクリプトを AWS SDK の読み込みのあとに読み込む。

<script src="https://sdk.amazonaws.com/js/aws-sdk-2.651.0.min.js"></script>
<script src="login.js"></script>

グループの利用

ユーザープールではグループを作成でき、個別にロールを設定できる。グループにユーザーを追加しておけば、認証時にグループ名を得られる。

  groups = (() => {
    const tokens = idToken.split('.');
    const obj = JSON.parse(atob(tokens[1]));
    return obj['cognito:groups'];
  })();

グループ名は配列で得られる。