オープンソース・ソリューション・テクノロジ株式会社
Open Source Solution Technology Corporation

OpenAMでQRコード認証を実現できるか試してみる

2021-03-04 - 星野 康

1. はじめに

=== この記事は、以前Qiitaに挙げていたものの再掲です ===

 新入社員のアカウントの初期パスワードは、どのように設定・入力してもらうと良いでしょうか。

 初期パスワードを設定する際、誕生日にアルファベットを足したものや社員番号など、類推可能なものにするのは危険です。ランダムな文字列にする場合でも、短いものにしてしまうと、ブルートフォースアタックの脅威にさらされてしまうかもしれません。それでは長いランダム文字列にした場合は…、今度は利用開始の障壁になってしまいそうです。

 解決案として、「長いランダム初期パスワードをQRコード化して送付し、初回ログイン時はQRコードをかざすことで認証してもらう。(その後、強制的にパスワードを変更してもらう)」、というのはどうでしょうか。

 幸い近年、QRコードを扱うJavascriptライブラリを公開してくださってる、素晴らしい方々がいらっしゃいます。弊社も取り扱っております認証基盤OSS「OpenAM」では色々カスタマイズが可能なので、ライブラリを組み込んでQRコード認証の実現を試してみます。

(注: 本記事は、あくまで簡易的に試してみるというものです。実際にご利用される場合は、各自のご責任の下で実施くださいませ。)

2. 実施

2.1 OpenAM のインストールと初期設定

 まず、OSSTech版のOpenAM 13をインストールして初期設定しておきます。ここは今回の本題ではないので、詳細は割愛します。 (OpenAM 13がお手元にない場合でも、読み取ったQRコードの格納先となるテキストフィールドなりをご用意いただければ、QRコード読み取りのテストは可能です。)

 次にですが、スマホでもカメラを起動してQRコードを読めるようにするためには、httpsでのアクセスにする必要があるようです。このため、あらかじめhttps化の対応もしておきます。ここも、詳細は割愛します。

 ここでログイン画面にアクセスしてみると、OpenAMでは初期状態でデータストア認証の画面が出てくるようになってます。普通にIDとパスワードを入れるというものでして、画面例は下図のようになります。  これから、このパスワードのフィールドへの入力を、QRコード読み込み対応にしてみます。

2.2 QRコード読み取りライブラリの取り込み

 QRコード画像を読み取るjavascriptライブラリとして、今回は「jsqrcode」を取り込んでみます。 https://github.com/LazarSoft/jsqrcode (Apache License 2.0)

$ mkdir <OpenAM ウェブアプリのデプロイ先>/openam/XUI/jsqrcode
$ (作成したディレクトリ内に、jsqrcodeのjs群を格納する。)

2.3 認証画面の編集

 認証画面を編集していきます。  データストア認証の認証画面(の一部)は以下の場所にありますので、viでオープンすることにします。

$ vi <OpenAM ウェブアプリのデプロイ先>/openam/XUI/templates/openam/authn/DataStore1.html

 修正内容としては、まずHTMLの要素として以下のコードを追加します。

  <div style="text-align: center;">
    <video id="video_id" width="300" height="300" autoplay="1" playsinline></video>

    <div style="display:none">
      <canvas id="canvas_id"></canvas>
    </div>

    <div id="message_id"></div>
  </div>

 そして、以下のjavascriptコードを追加します。  大して特別なことはしていないので、詳細はコード内コメントやコードを直接ご参照ください。

<script type="text/javascript" src="jsqrcode/grid.js"></script>
同様にjsqrcodeのgitページに記載の通り必要なjsを全部取り込む。)

<script>
  // HTML要素群。この'idToken2'が、読み取った値の格納先
  const const_resultArea = document.getElementById('idToken2');
  const const_video = document.getElementById('video_id');
  const const_canvas = document.getElementById('canvas_id');
  const const_messageDiv = document.getElementById('message_id');

  // 連続失敗カウンタ
  let let_failureCounter = 0;

  // カメラを見つけてストリームをセット
  // (スマホで内向きカメラが選ばれないよう、外向きカメラをideal指定する)
  if (!navigator.mediaDevices.getUserMedia) {
    alert("This browser may not handle HTML5_UserMedia.  Please try other browers.");
  } else {
    navigator.mediaDevices.getUserMedia(
      { video: { facingMode: { ideal: 'environment' } }, audio: false }
    ).then(
      function(stream) { const_video.srcObject = stream; },
      function() { alert("Error... Cannot get video stream..."); }
    );
  }

  // 読み取り結果を処理。
  // (失敗の判定が雑な感じだが、ひとまずお試しとしてこれで動かす。)
  qrcode.callback = function(decodedInformation) {
    if (!decodedInformation ||
        decodedInformation === "error decoding QR Code" ||
        decodedInformation === "Failed to load the image") {
      let_failureCounter++;
      const_messageDiv.textContent = "QR Read : Failed... " + let_failureCounter;
    } else {
      let_failureCounter = 0;
      const_resultArea.value = decodedInformation;
      const_messageDiv.textContent = "QR Read : Succeeded!!";
    }
  };

  // 画像化&QRコード読み取りを1回実施
  function decodeOneTime() {
    const_canvas.setAttribute("width", const_video.videoWidth);
    const_canvas.setAttribute("height", const_video.videoHeight);
    const const_canvas_context = const_canvas.getContext('2d');
    const_canvas_context.drawImage(const_video, 0, 0, const_video.videoWidth, const_video.videoHeight);

    if (const_canvas_context) {
      qrcode.decode(const_canvas.toDataURL('image/png'));
    }
  }

  // 毎秒処理する
  setInterval(decodeOneTime, 1000);
</script>

2.4 結果

 たったこれだけで、QRコード認証の基本的なところはできてしまいました。

 利用中の画面は下図のようになります。  QRコードをかざすと、長いパスワード文字列が読み取られ、パスワードフィールドに自動入力されます。

3. おわりに

 きちんと検証する必要はありますが、QRコード認証を実現できそうな感触を得ることができました。  今回は初期ログインという目的を例示しましたが、他の目的で利用しても良いかもしれません。例えば、多要素認証における簡易的な2段階目認証として使う、とかどうでしょう。

以上です。