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

2021-03-14Amazon,AWS

はじめに

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

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

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

改めて 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 の後方にはクエリ文字列形式で Authentication code と呼ばれるものを付与 (ex: https://hoge.com/callback.html&code=xxxx
  6. "AppServer" は受け取った Authentication code を使って Cognito にリクエスト
  7. ID Token を取得
  8. JavaScript では、ページの URL からフラグメントを切り出し、(5)で受け取った ID Token を抜き出すロジックが書かれている。
  9. "AppServer" は HTML/JavaScript/ ID Token を返却。

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

次回は実際にサンプルプログラムを作って動作を確認していこうと思います。

「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" は "AppServer" に問い合わせを行う際に、"ID Token" も一緒に送信する
  2. "AppServer" は "Cognito" から「公開鍵」を "Download"(事前あるいは初回だけ"Download"で OK)
  3. "AppServer" は "ID Token" の電子署名が改ざんされていないかを「公開鍵」を使ってチェック

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

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

ひとこと

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

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

2021-03-14Amazon,AWS