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

# はじめに

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

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

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

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

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

# 実施

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

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

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

　ここでログイン画面にアクセスしてみると、OpenAMでは初期状態でデータストア認証の画面が出てくるようになってます。普通にIDとパスワードを入れるというものでして、画面例は下図のようになります。
　これから、このパスワードのフィールドへの入力を、QRコード読み込み対応にしてみます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/316192/c3d7eda5-07e2-0374-6bd5-73ae585df3ae.png" width="90%">


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

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

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


### 認証画面の編集

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

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

　修正内容としては、まず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コードを追加します。
　大して特別なことはしていないので、詳細はコード内コメントやコードを直接ご参照ください。

```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>
```

### 結果

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

　利用中の画面は下図のようになります。
　QRコードをかざすと、長いパスワード文字列が読み取られ、パスワードフィールドに自動入力されます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/316192/fcba4471-81e7-0c81-0996-21ff983f7a25.png" width="90%">


# おわりに

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

以上です。