パスワード強度のお手軽表示!zxcvbn-tsをReactに導入してみる

投稿日
パスワード強度のお手軽表示!zxcvbn-tsをReactに導入してみる

パスワードの強度を計測してくれる zxcvbn-ts



Reactのアプリケーションに試しに入れてみたため、実装方法を記載していきます!




先に使った感想を記載しておきます

  • 実装めっちゃ簡単。お手軽にパスワードの強度評価してくれる(無料です ☺️)
  • TypeScriptの補完しっかり効きます!

検証した環境

1 @zxcvbn-ts/core 3.0.3
2 @zxcvbn-ts/language-common 3.0.3
3 @zxcvbn-ts/language-ja 3.0.1
4 react 18.2.0
5 next 13.4.4

zxcvbn-tsの導入

公式サイトにexampleがしっかり書いてあるのでそちらを参考に。




まずパッケージをインストール

yarn add @zxcvbn-ts/core @zxcvbn-ts/language-common @zxcvbn-ts/language-ja

指定可能な言語はGitHubのページに記載されています

zxcvbn/packages/languages at master · zxcvbn-ts/zxcvbn




公式のコードを参考にconsole.logを使って表示出来る事を確認します

'use client'
 
import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core'
import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common'
import * as zxcvbnJaPackage from '@zxcvbn-ts/language-ja'
 
const SamplePage = () => {
  const password = 'somePassword'
  const options = {
    translations: zxcvbnJaPackage.translations,
    graphs: zxcvbnCommonPackage.adjacencyGraphs,
    dictionary: {
      ...zxcvbnCommonPackage.dictionary,
      ...zxcvbnJaPackage.dictionary,
    },
  }
 
  zxcvbnOptions.setOptions(options)
  const result = zxcvbn(password)
  console.log('result: ', result)
 
  // zxcvbnを試したいため画面には何も表示しない
  return <div />
}
 
export default SamplePage

↑はNext.jsのApp Routerを使ったコードになっています。

状況に合わせて読み替えて下さいmm





console.logの中を見てみると


日本語のfeedbackが入ってる…!


その他にも表示に役立ちそうな値が色々入っていそうです。



zxcvbn-tsの返り値の確認

zxcvbnが返す値の詳細は公式のOutputという項目にしっかり記載されています。

重要そうなものを3つピックアップします。

score

パスワードの安全性を計る指標。


0 から 4 の値が返ってくる5段階評価で、0だと安全性が低く4だと安全性が高くなります




公式の記載内容をDeepLで翻訳すると以下のようになります。日本語が・・

  • 0・・推測可能すぎる : 危険なパスワード
  • 1・・非常に推測しやすい : オンライン攻撃からの保護
  • 2・・ある程度推測できる : スロットルのないオンライン攻撃からの保護
  • 3・・安全無比 : オフライン・スローハッシュ・シナリオからの適度な保護
  • 4・・推し量れない : オフライン・スローハッシュ・シナリオからの強力な保護


crackTimesSeconds

秒で数えた際の推定クラック時間、以下のような値が返ってきます。

{
    "onlineThrottling100PerHour": 18734400,
    "onlineNoThrottling10PerSecond": 52040,
    "offlineSlowHashing1e4PerSecond": 52.04,
    "offlineFastHashing1e10PerSecond": 0.00005204
}

再度DeepL翻訳。下2つは何言ってるか分からない・・😂

  • onlineThrottling100PerHour・・パスワード認証の試行を制限するサービスへのオンライン攻撃
  • onlineNoThrottling10PerSecond・・レートを制限しないサービスへのオンライン攻撃
  • offlineSlowHashing1e4PerSecond・・オフライン攻撃。複数の攻撃者を想定、ユーザー固有の適切なソルティングと低速のハッシュ関数が必要。bcrypt、scrypt、PBKDF2のような、適度な仕事率を持つハッシュ関数。
  • offlineFastHashing1e10PerSecond・・ユーザー固有のソルティングを用いたオフライン攻撃。SHA-1、SHA-256またはMD5のような高速ハッシュ関数。幅広い範囲10億から1兆までの妥当な数コア数とマシンによる。10B/秒を目安に。


feedback

入力されたパスワードに対してのフィードバック。

画面上に表示出来るような文字列を返してくれます。



password の場合(scoreは0)

feedback: {
  warning: '一般的に使用されるパスワードです',
  suggestions: [ '一般的ではない単語を増やしてください' ]
}

somePassword の場合(scoreは1)

feedback: {
  warning: 'よく使われるパスワードに似ています',
  suggestions: [ '一般的ではない単語を増やしてください', '最初の文字以外も大文字にしてください' ]
}

c2DL6J8b の場合(scoreは2)

feedback: {
  warning: null,
  suggestions: [ '一般的ではない単語を増やしてください' ] 
}


試してみる

パスワード生成ツールを使っていくつか試してみました

パスワードスコアofflineSlowHashing1e4PerSecondメモ
a00.0012
password00.0003
pV8k11.0001ランダム4桁(記号なし)
kF4J321100.0001ランダム6桁(記号なし)
c2DL6J8b210000.0001ランダム8桁(記号なし)
t9GeU.x_210000.0001ランダム8桁(記号入)
PYAWykeD7C31000000.0001ランダム10桁(記号なし)
38Kj(i7cZ!31000000.0001ランダム10桁(記号入)
VY&TbWU#xMD34100000000.0001ランダム12桁(記号入)
RbHMsVkiVt3KHjsj41000000000000ランダム16桁(記号なし)

12桁までくるとかなり強いんですねーー!


また、この秒数によると桁数が増えれば増えるほど、

記号を含めなくても強くなるみたいです。

Reactで強度を表示する

では本題のReactのコードです!



以下のような環境での実装になります

  • App Routerを用いたNext.js
  • cssはtailwindを使用
'use client'
 
import { useEffect, useState } from 'react'
 
import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core'
import { ZxcvbnResult } from '@zxcvbn-ts/core/src/types'
import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common'
import * as zxcvbnJaPackage from '@zxcvbn-ts/language-ja'
 
const SamplePage = () => {
  // 入力されたパスワードの値
  const [password, setPassword] = useState<string | undefined>(undefined)
  // zxcvbnの結果
  const [result, setResult] = useState<ZxcvbnResult | undefined>(undefined)
 
  // zxcvbnの設定
  const options = {
    translations: zxcvbnJaPackage.translations,
    graphs: zxcvbnCommonPackage.adjacencyGraphs,
    dictionary: {
      ...zxcvbnCommonPackage.dictionary,
      ...zxcvbnJaPackage.dictionary,
    },
  }
  zxcvbnOptions.setOptions(options)
 
  useEffect(() => {
    // パスワードがない場合はzxcvbnの結果をリセットする
    if (!password) {
      setResult(undefined)
      return
    }
 
    // 入力されたパスワードを用いてzxcvbnの結果を取得、useStateに格納する
    setResult(zxcvbn(password))
  }, [password, result])
 
  return (
    <div className="w-[300px]">
      <div className="grid gap-[20px]">
        <label htmlFor="email">
          <div>email</div>
          <input
            type="text"
            id="email"
            className="h-[30px] w-full rounded-[4px] border border-[#909090]"
          />
        </label>
        <label htmlFor="password">
          <div>password</div>
          <input
            type="text"
            id="password"
            className="h-[30px] w-full rounded-[4px] border border-[#909090]"
            onChange={e => setPassword(e.target.value)}
          />
        </label>
      </div>
 
      <div className="mt-[60px]">
        <div className="flex w-full gap-[1%]">
          {/* 強度を表す5段階のバーを表示 */}
          {[0, 1, 2, 3, 4].map(v => (
            <div
              className={`h-[4px] w-[24%] ${
                result && v <= result.score ? 'bg-[#666]' : 'bg-[#ccc]'
              }`}
              key={v}
            />
          ))}
        </div>
        {result && result.score + 1}
      </div>
 
      {/* feedback.warning がある場合は表示 */}
      {result?.feedback && <div className="text-[#f00]">{result.feedback.warning}</div>}
    </div>
  )
}
 
export default SamplePage



強度を表示出来ました!🎉




ざっくりやっている事としては

  • パスワード用のinputの値を useState の password に設定(55行目)
  • 上記値が更新される度に zxcvbn が実行され useState の result が更新される(35行目)
  • result の値を元に結果をHTML上に表示(60 - 76行目)

となります。




簡単に強度が表示が出来て、しっかりTypeScriptの補完も効くので

zxcvbn-ts かなり使いやすい印象でした!



余談

そもそも今回zxcvbn-tsを使ってみようと思ったきっかけがこちらです。


読みやすく分かりやすい、大変ステキな記事です ☺️




上記記事に書かれている内容としまして

アプリケーションを作る時、皆さんは新規登録フォームで「パスワード再入力」というinputを用意してませんか?





私、今まで特に意識をしてなかったのですが、実は

  • コンバージョンが落ちる
  • 再入力の部分はパスワードをコピペするだけで再確認になっていない

という研究結果があるそうです

We achieved a 56.3% increase in form conversions by removing “Confirm Password” while not negatively affecting the password reset rate.



DeepL翻訳: パスワードのリセット率に悪影響を与えることなく、「パスワードの確認」を削除することで、フォームコンバージョンを56.3%増加させることができました。

フォームで「パスワードの確認」を使用する必要がありますか?: ケーススタディ より引用



私「パスワード再入力」inputがある新規登録ページの実装何度かしてきたのですが、

この辺りのUXてこんな考える事が色々あるんですね。。


いやぁ難しい…!

プロフィール画像
Yuki Takara
都内でフリーランスのエンジニアをやってます。フロントとアプリ開発メインに幅広くやってます。