31
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

八方塞がりなので、不本意ながらJavaScriptだけでつよつよパスワード認証を実装する。

Posted at

ご注意
今回はお上のやんごとない事情で、仕方なくJSでパスワード認証を作っていますが、基本的には一般的に使われている認証方法…… 例えば簡単なところでいうとBasic認証であったり、Firebaseを使うほうが良いのは間違いありません。


さて…… 静的なファイルしか置けないサーバーを渡されたのだが……

長くお仕事をしていると、事情都合にがんじがらめになっている要件に出会うことがあります。

  • 静的なファイルしか置けないウェブサーバー
  • サーバー自体の設定は触れない
  • 外部サービスは使ってはいけない
  • でも制限された領域内に情報を置きたい

???「◯◯さん、JSでどうにかできませんか?」

あなたはここで怒ることもできますし、鼻で笑ってそっぽを向くこともできます。
ただ僕は大人になってしまい…… 断るに断れない…… そんな事情も出てくるのです。

実際に作ったサンプル

index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>JSパスワード認証テスト</title>

<script src="/script/core-min.js"></script>
<script src="/script/sha256-min.js"></script>
</head>
<body>

  <form>
    <input type="password" id="pin">
    <input type="button" value="ボタン" id="button">
  </form>

  <script>
    const objPin = document.getElementById('pin');
    const objButton = document.getElementById('button');

    objButton.onclick = () => {
      const hash = CryptoJS.SHA256(objPin.value);

      const getScript = async () => {
        const response = await fetch('/script/' + hash + '/test.js');

        if (response.ok) {
          const getScriptData = await response.text();

          eval(getScriptData);
          testFunction();//test.jsに書かれている testFunction の呼び出し
        } else {
          alert(response.status + ' (' + response.statusText + ')');//エラーの種別を取得して表示
        }
      }
      getScript();

    }

  </script>
</body>
</html>
/script/(hashで生成されたディレクトリ名)/test.js
function testFunction() {
  alert('大成功');
}

前準備

index.html
<script src="/script/core-min.js"></script>
<script src="/script/sha256-min.js"></script>

ここは CryptoJS から引っ張ってきたファイルです。
https://code.google.com/archive/p/crypto-js/

いろいろありますが今回使っているのは core と sha256 です。
このあたりは各ハッシュ関数の特性や要件等を踏まえて選んでください。

今回の肝

objButton.onclick = () => {
  const hash = CryptoJS.SHA256(objPin.value);

  const getScript = async () => {
    const response = await fetch('/script/' + hash + '/test.js');

    /* --- 略 --- */
  }
  getScript();

}
  1. あらかじめパスワードとして設定したい文字列をSHA-256で変換しておいて、その変換した文字列の名前でディレクトリに作成。
  2. その作成したディレクトリにページを表示するのに必要になるJS等を格納
  3. アクセスした人にはinputでパスワードを入力してもらい、そのパスワードをSHA-256で変換させ、そのディレクトリー&ファイルを探させる。
  4. ディレクトリー&ファイルがあれば正しい入力、なければ正しくない入力として処理する。

というものです。

JSでパスワードというと、コードを見られたら終わりじゃないか…… みたいなのは、みなさんぱっと思いつくと思いますが、その問題はなんとか回避できたかなと思っています。

今回の隠れた肝

このawait fetchの外部JSを読み込む方法ですがjQuery時代であればgetScriptで一瞬にして終わります。便利。

ただjQueryじゃないとなると意外とこれが面倒です。

  • XMLHttpRequestは当然煩雑。
  • 今回はファイルの有無をもって判断させるので、いわゆる**普通のFetchの書き方**だと却って面倒。

ということで async/await を組み合わせています。
Fetch APIでデーターを取得しながらPromiseとasyc/awaitを学んだまとめ」がわかりやすかったのでもしよかったらこちらも併せてご覧ください。 🙇🏻‍♂️

補足

少し工夫して頑張りましたが、所詮JSの簡易的なチェック程度です。
この仕組の最後の砦は「面倒さ」のみです。簡単な文字列だと総当りの前にたやすくやられます。短いパスワードのみだとどうしても弱いので、長いパスワードにするか、ID等とセットで入力させ、ストレスなく長い文字列を取得する等の工夫が必要です。

ハッシュ関数はバレてます。
今回SHA-256を使っているというのは読めばすぐに分かってしまいます。そもそもそんなに強度が必要な状況でこういうものを組むということはあまり考えられませんが、どのハッシュ関数を使うのかは要注意です。

不本意ながらevalを使用しています。
外部のJSファイルをテキストで読み込んで、それをJSのコードとして扱うタイミングで使用しています。ユーザーに入力させるものに対してevalを使用しているわけではないので…… と言いつつ不本意です。

サーバーのログは荒れるかもしれません。
めっちゃ404出すような人(パスワードを間違える人)が登場するとびっくりするかもしれません。

当然ですが真に大切で重要な情報をどうこうするには向いてません。

31
35
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
35

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?