既存のWebアプリで「Amazon Cognito」認証を利用する(その1:処理フローの整理)

2019-05-23Amazon, AWS

はじめに

以前、 「Amazon Cognito」を使ってログイン機能を作成する(SNS認証あり) | ゲンゾウ用ポストイット で「Amazon Cognito」のユーザ認証機能を利用してログインページを構築しました。

しかし、やりたかったことは既存のWebアプリケーションのログイン機能を「Cognito」を利用した仕組みに置き直すということだったため、完全ではありませんでした。

「Cognito」のユーザ認証が完了した後、既存のアプリケーションでそれを引き継ぐための仕組みを構築すべく、追加で調査をしました。
(今回は整理にとどめて次回のエントリでサンプルコードの実装・公開をしたいと思います。)

当エントリの前提条件

  • "Amazon Cognito" のユーザプールの構築が完了している
  • "Amazon Cognito" のログインページからログインし、コールバックページが表示できる

上記は 「Amazon Cognito」を使ってログイン機能を作成する(SNS認証あり) | ゲンゾウ用ポストイット で行った内容になります。

改めてOAuth2またはOpenID Connectの処理フローについて

「Congito」を使ったユーザ認証は「OpenID Connect」という方式になります。「OAuth2」方式をベースとした後発の認証方式です。したがって「OAuth2」の理解を深めることが「OpenID Connect」の理解を深めることにつながると思いました。

僕が説明するより以下の記事を読むほうが圧倒的にわかりやすいです。

とはいえ、自分の頭の中を整理するため、自分なりにまとめてみます。

めっちゃ汚いですが、手書きの処理フローになります。(一部字が潰れたり上塗りされていたりしていますが(汗))

処理フローを箇条書きで文字起こし

登場人物は3つ、 "Browser" / "AppServer" / "Cognito" です。("Browser"は人の形で表現しています。)

  1. "Browser" は "AppServer" にアクセス
  2. "AppServer" はログイン済みかを判定します。未ログイン状態の場合には "Cognito" に "redirect"("redirect"のURLに対して事前に「Cognito」から払い出された「アプリケーションID」などを付与)
  3. "Cognito" は "login page" を返却
  4. "Browser" は "login" を行う
  5. "Cognito" は受け取った認証情報が正しければ "redirect" 。 "redirect" のURLの後方にはフラグメント形式で 「ID Token」 と呼ばれるものを付与 (ex: https://hoge.com/callback.html#id_token=abcde
  6. "AppServer" はHTMLとJavaScriptを返却。JavaScriptでは、ページのURLからフラグメントを切り出し、(5)で受け取った 「ID Token」 を抜き出すロジックが書かれている。

取得できた 「ID Token」 ですが、「SPA」の場合は "Broser" の 「LocalStorage」 機能を使って保存しておき、以降の通信時に「HTTP Header」に付与して "AppServer" と通信を行うことで認証済みであることを証明します。

今回は「SPA」なんていうおしゃれなアプリケーションではなく、既存の紙芝居式Webアプリケーションなので「LocalStorage」の利用は考えていません。

「ID Token」ってなんだろう?

認証済みとなったタイミングで "Cognito" に発行された 「ID Token」 とはなんでしょうか?
特徴を挙げてみます。

  • 「JSON Web Token (JWT)」(ジョットと読む) というデータフォーマットで構成
  • URL-safe(URLとして利用出来る文字だけ構成される)な文字列で構成されているため、URLにクエリ文字列(?〜)やフラグメント(#〜)として付与することができる
  • 「ID Token」は以下の情報を ドット( . )区切り で連結したものとなっていまる
    1. 電子署名メタ情報
    2. 認証されたユーザーの情報
      • name
      • email
      • phone_number など
    3. 電子署名

JWT authentication concept - Stack Overflow から、JWTのサンプルを拝借してきました。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9.0K8TL1YS0XKnEIfI3lYs-bu2vbWHSNZsVJkN1mXtgWg

ドット( . ) で分割してみます。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9
0K8TL1YS0XKnEIfI3lYs-bu2vbWHSNZsVJkN1mXtgWg

1つ目の文字列が先に上げた「電子署名メタ情報」、
2つ目の文字列が「認証されたユーザーの情報」、
3つ目の文字列が「電子署名」です。

1つ目と2つ目は Base64 でエンコードされているため、デコードできます。デコードすると以下のような情報が取り出せます。

$ echo `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9` | base64 -d
{"typ":"JWT","alg":"HS256"}

$ echo 'eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9' | base64 -d
{"username":"1","id":3,"iat":1465032622,"exp":1465050622}

「電子署名メタ情報」には大した情報はありませんが、「認証されたユーザーの情報」には有益な情報が格納されています。
「Cognito」の場合はメールアドレスやユーザーIDが格納されています。
これを利用することで「誰が」アクセスして来たのかがわかります。

「認証されたユーザーの情報」が書き換えれる恐れはないの?

この疑問に答えるには、電子署名の仕組みについてを理解する必要があります。以下のサイトが参考になりました。

JWT の「電子署名」は、「電子署名メタ情報」と「認証されたユーザーの情報」に対して「Cognito」の 秘密鍵 を使って暗号化されています。
秘密鍵 は公開されていませんので、「電子署名」を生成できるのは「Cognito」だけになります。

「Cognito」は 公開鍵 は公開されているため自由にダウンロードして利用することができます。
公開鍵 を使って「電子署名」を復号化し、「電子署名メタ情報」と「認証されたユーザーの情報」と突き合わせすれば、改ざんされていないかがわかります。

【注意】正確には「秘密鍵」で暗号化したものを「公開鍵」で復号化することはできないそうですが多くの場合は説明を省略するためにこのように説明しているそうです

「ID Token」受け取り後の処理フロー

「ID Token」受け取り後の処理フローはどうなるでしょう?

  1. "Broswer" は "ID Token" を "AppServer" に送信
  2. "AppServer" は "Cognito" から「公開鍵」を "Download"(事前あるいは初回だけ"Download"でOK)
  3. "AppServer" は "ID Token" の電子署名が改ざんされていないかを「公開鍵」を使ってチェック

「認証されたユーザーの情報」に含まれているToken有効期限情報のチェックも行っておく必要があると思います。

あとはこのような仕組みとなるようにWebアプリケーションを変更すればよいはずです。

ひとこと

構築したい処理フローを整理したので、続きは次回に。
このサンプルアプリを作って、動作を確認してみます。

「認証」という非常に重要な機能となりますが、僕の理解が正しいのかが自分でも不安があります。
誤りがあればご指摘いただけると幸いです。

2019-05-23Amazon, AWS