序章
パスワードは、ほとんどのWebアプリケーションでユーザー認証に一般的に使用されます。 このため、パスワードを安全な方法で保存することが重要です。 長年にわたり、データベースに保存されているパスワードの実際の表現を隠すために、一方向パスワードハッシュなどの手法が採用されてきました。
パスワードハッシュはパスワードを保護するための大きな一歩ですが、ユーザーは依然としてパスワードのセキュリティに大きな課題を抱えています。ブルートフォース攻撃のため、パスワードとして一般的な単語を使用するユーザーは、ハッシュの努力を無駄にします。 ]は、そのようなパスワードをすばやく解読できます。
これに対処するために、今日の多くのWebアプリケーションは、パスワードの最小長を確保するか、パスワードに英数字と記号を組み合わせることにより、強力なパスワードを持つユーザーを要求しています。 パスワード強度を測定するために、Dropboxは、パスワードクラッカーに触発された現実的なパスワード強度推定器のアルゴリズムを開発しました。 このアルゴリズムは、zxcvbnというJavaScriptライブラリにパッケージ化されています。 さらに、パッケージには、一般的に使用される英語の単語、名前、およびパスワードの辞書が含まれています。
このチュートリアルでは、React JavaScriptフレームワークを使用して、氏名、電子メール、およびパスワードのフィールドを持つフォームを作成します。 軽量のフォーム検証を実行し、 zxcvbn ライブラリを使用して、視覚的なフィードバックを提供しながら、フォーム内のパスワードの強度を推定します。
このチュートリアルの終わりまでに作成するもののこのCodeSandboxデモをチェックしてください。
前提条件
開始する前に、システムにNodeの最新バージョンがインストールされていることを確認してください。
このチュートリアルに従うには、次のものが必要です。
- マシンにインストールされているNodeの最新バージョン。 これをインストールする方法の詳細については、 How To InstallNode.jsコレクションからディストリビューションを選択してください。
- yarn をインストールして、すべての NPM スクリプトを実行し、プロジェクトの依存関係をインストールします。 このYarnインストールガイドに従ってインストールできます
yarn
システム上で。
ステップ1—アプリケーションのセットアップ
このチュートリアルでは、 create-react-app パッケージを使用して、新しいReactアプリケーションを生成します。 次のコマンドを実行してインストールします create-react-app
まだインストールしていない場合は、システムに次のように入力します。
- npm install -g create-react-app
インストールが完了したら、次のコマンドを使用して新しいReactアプリケーションを起動します。
- create-react-app react-password-strength
このコマンドはそれに名前を付けます react-password-strength
、ただし、好きな名前を付けることができます。
注:を使用している場合 npm
バージョン5.2以降では、追加で出荷されます npx
バイナリ。 を使用して npx
バイナリ、インストールする必要はありません create-react-app
システム上でグローバルに。 次のコマンドを使用して、新しいReactアプリケーションを開始できます。 npx create-react-app react-password-strength
.
次に、アプリケーションに必要な依存関係をインストールします。 次のコマンドを実行して、必要な依存関係をインストールします。
- yarn add zxcvbn isemail prop-types node-sass bootstrap
このコマンドは、次の依存関係をインストールします。
zxcvbn
-前述のパスワード強度推定ライブラリ。isemail
-電子メール検証ライブラリ。prop-types
-コンポーネントに渡される意図されたタイプのプロパティのランタイムチェック。node-sass
-SassファイルをCSSにコンパイルするために使用されます。
お気づきかもしれませんが、 bootstrap
デフォルトのスタイルを取得するためのアプリケーションの依存関係としてのパッケージ。 アプリケーションにBootstrapを含めるには、 src/index.js
ファイルを作成し、次の行を他のすべての前に追加します import
声明:
import 'bootstrap/dist/css/bootstrap.min.css';
最後に、アプリケーションを開始します。
- yarn start
これでアプリケーションが開始され、開発を開始できます。 ライブリロード機能を備えたブラウザタブが開いていることに注意してください。 これにより、開発時にアプリケーションの変更と同期が保たれます。
この時点で、アプリケーションビューは次のスクリーンショットのようになります。
ステップ2—コンポーネントの構築
このアプリケーションは、氏名、電子メール、およびパスワードのフォームを使用します。 また、フィールドで軽量のフォーム検証を実行します。 このステップでは、次のReactコンポーネントを作成します。
-
FormField-フォーム入力フィールドをその属性と変更イベントハンドラーでラップします。
-
EmailField-メールをラップします
FormField
それに電子メール検証ロジックを追加します。 -
PasswordField-パスワードをラップします
FormField
パスワード検証ロジックを追加します。 また、パスワード強度メーターとその他の視覚的な手がかりをフィールドに添付します。 -
JoinForm-フォームフィールドを格納する架空のJoinSupportTeamフォーム。
作成する components
内部のディレクトリ src
すべてのコンポーネントを格納するアプリケーションのディレクトリ。
The FormField
成分
新しいファイルを作成する FormField.js
の中に src/components
ディレクトリに次のコードスニペットを追加します。
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
class FormField extends Component {
// initialize state
state = { value: '', dirty: false, errors: [] }
hasChanged = e => {
e.preventDefault();
// destructure props - assign default dummy functions to validator and onStateChanged props
const { label, required = false, validator = f => f, onStateChanged = f => f } = this.props;
const value = e.target.value;
const isEmpty = value.length === 0;
const requiredMissing = this.state.dirty && required && isEmpty;
let errors = [];
if (requiredMissing) {
// if required and is empty, add required error to state
errors = [ ...errors, `${label} is required` ];
} else if ('function' === typeof validator) {
try {
validator(value);
} catch (e) {
// if validator throws error, add validation error to state
errors = [ ...errors, e.message ];
}
}
// update state and call the onStateChanged callback fn after the update
// dirty is only changed to true and remains true on and after the first state update
this.setState(({ dirty = false }) => ({ value, errors, dirty: !dirty || dirty }), () => onStateChanged(this.state));
}
render() {
const { value, dirty, errors } = this.state;
const { type, label, fieldId, placeholder, children } = this.props;
const hasErrors = errors.length > 0;
const controlClass = ['form-control', dirty ? hasErrors ? 'is-invalid' : 'is-valid' : '' ].join(' ').trim();
return (
<Fragment>
<div className="form-group px-3 pb-2">
<div className="d-flex flex-row justify-content-between align-items-center">
<label htmlFor={fieldId} className="control-label">{label}</label>
{/** Render the first error if there are any errors **/}
{ hasErrors && <div className="error form-hint font-weight-bold text-right m-0 mb-2">{ errors[0] }</div> }
</div>
{/** Render the children nodes passed to component **/}
{children}
<input type={type} className={controlClass} id={fieldId} placeholder={placeholder} value={value} onChange={this.hasChanged} />
</div>
</Fragment>
);
}
}
FormField.propTypes = {
type: PropTypes.oneOf(["text", "password"]).isRequired,
label: PropTypes.string.isRequired,
fieldId: PropTypes.string.isRequired,
placeholder: PropTypes.string.isRequired,
required: PropTypes.bool,
children: PropTypes.node,
validator: PropTypes.func,
onStateChanged: PropTypes.func
};
export default FormField;
このコンポーネントでは、いくつかの作業を行っています。 少し分解してみましょう。
入力状態:最初に初期化しました state
フォームフィールドコンポーネントが現在を追跡するため value
入力フィールドの dirty
フィールドのステータス、および既存の検証 errors
. フィールドは、その値が最初に変更されてダーティのままである瞬間にダーティになります。
入力変更の処理:次に、 hasChanged(e)
状態を更新するイベントハンドラー value
入力が変更されるたびに、現在の入力値に変更されます。 ハンドラーでは、 dirty
フィールドの状態。 フィールドが required
小道具に基づくフィールド、および状態に検証エラーを追加します errors
値が空の場合は配列。
ただし、フィールドが必須フィールドではない場合、または必須であるが空ではない場合は、オプションで渡された検証関数に委任します validator
prop、現在の入力値で呼び出し、スローされた検証エラーを状態に追加します errors
配列(エラーがある場合)。
最後に、状態を更新し、更新後に呼び出されるコールバック関数を渡します。 コールバック関数は、オプションで渡された関数を呼び出します onStateChanged
prop、更新された状態を引数として渡します。 これは、コンポーネントの外部に状態変化を伝播するのに便利です。
レンダリングと小道具:ここでは、入力フィールドとそのラベルをレンダリングしています。 また、状態の最初のエラーを条件付きでレンダリングします errors
配列(エラーがある場合)。 Bootstrapの組み込みクラスを使用して、検証ステータスを表示するように入力フィールドのクラスを動的に設定する方法に注目してください。 また、コンポーネントに含まれるすべての子ノードをレンダリングします。
コンポーネントに見られるように propTypes
、このコンポーネントに必要な小道具は次のとおりです。 type
('text'
また 'password'
), label
, placeholder
、 と fieldId
. 残りのコンポーネントはオプションです。
The EmailField
成分
新しいファイルを作成する EmailField.js
の中に src/components
ディレクトリに次のコードスニペットを追加します。
import React from 'react';
import PropTypes from 'prop-types';
import { validate } from 'isemail';
import FormField from './FormField';
const EmailField = props => {
// prevent passing type and validator props from this component to the rendered form field component
const { type, validator, ...restProps } = props;
// validateEmail function using the validate() method of the isemail package
const validateEmail = value => {
if (!validate(value)) throw new Error('Email is invalid');
};
// pass the validateEmail to the validator prop
return <FormField type="text" validator={validateEmail} {...restProps} />
};
EmailField.propTypes = {
label: PropTypes.string.isRequired,
fieldId: PropTypes.string.isRequired,
placeholder: PropTypes.string.isRequired,
required: PropTypes.bool,
children: PropTypes.node,
onStateChanged: PropTypes.func
};
export default EmailField;
の中に EmailField
コンポーネント、あなたはレンダリングしています FormField
コンポーネントと電子メール検証関数を validator
小道具。 あなたは使用しています validate()
の方法 isemail
電子メール検証用のパッケージ。
また、を除く他のすべての小道具に気付くかもしれません type
と validator
小道具はから転送されます EmailField
コンポーネントに FormField
成分。
The PasswordField
成分
新しいファイルを作成する PasswordField.js
の中に src/components
ディレクトリに次のコードスニペットを追加します。
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import zxcvbn from 'zxcvbn';
import FormField from './FormField';
class PasswordField extends Component {
constructor(props) {
super(props);
const { minStrength = 3, thresholdLength = 7 } = props;
// set default minStrength to 3 if not a number or not specified
// minStrength must be a a number between 0 - 4
this.minStrength = typeof minStrength === 'number'
? Math.max( Math.min(minStrength, 4), 0 )
: 3;
// set default thresholdLength to 7 if not a number or not specified
// thresholdLength must be a minimum value of 7
this.thresholdLength = typeof thresholdLength === 'number'
? Math.max(thresholdLength, 7)
: 7;
// initialize internal component state
this.state = { password: '', strength: 0 };
};
stateChanged = state => {
// update the internal state using the updated state from the form field
this.setState({
password: state.value,
strength: zxcvbn(state.value).score
}, () => this.props.onStateChanged(state));
};
validatePasswordStrong = value => {
// ensure password is long enough
if (value.length <= this.thresholdLength) throw new Error("Password is short");
// ensure password is strong enough using the zxcvbn library
if (zxcvbn(value).score < this.minStrength) throw new Error("Password is weak");
};
render() {
const { type, validator, onStateChanged, children, ...restProps } = this.props;
const { password, strength } = this.state;
const passwordLength = password.length;
const passwordStrong = strength >= this.minStrength;
const passwordLong = passwordLength > this.thresholdLength;
// dynamically set the password length counter class
const counterClass = ['badge badge-pill', passwordLong ? passwordStrong ? 'badge-success' : 'badge-warning' : 'badge-danger'].join(' ').trim();
// password strength meter is only visible when password is not empty
const strengthClass = ['strength-meter mt-2', passwordLength > 0 ? 'visible' : 'invisible'].join(' ').trim();
return (
<Fragment>
<div className="position-relative">
{/** Pass the validation and stateChanged functions as props to the form field **/}
<FormField type="password" validator={this.validatePasswordStrong} onStateChanged={this.stateChanged} {...restProps}>
<span className="d-block form-hint">To conform with our Strong Password policy, you are required to use a sufficiently strong password. Password must be more than 7 characters.</span>
{children}
{/** Render the password strength meter **/}
<div className={strengthClass}>
<div className="strength-meter-fill" data-strength={strength}></div>
</div>
</FormField>
<div className="position-absolute password-count mx-3">
{/** Render the password length counter indicator **/}
<span className={counterClass}>{ passwordLength ? passwordLong ? `${this.thresholdLength}+` : passwordLength : '' }</span>
</div>
</div>
</Fragment>
);
}
}
PasswordField.propTypes = {
label: PropTypes.string.isRequired,
fieldId: PropTypes.string.isRequired,
placeholder: PropTypes.string.isRequired,
required: PropTypes.bool,
children: PropTypes.node,
onStateChanged: PropTypes.func,
minStrength: PropTypes.number,
thresholdLength: PropTypes.number
};
export default PasswordField;
このコンポーネントはを使用しています zxcvbn
JavaScriptパスワード強度推定パッケージ。 パッケージは zxcvbn()
パスワード文字列を最初の引数として受け取り、パスワード強度を推定するためのいくつかのプロパティを持つオブジェクトを返す関数。 このチュートリアルでは、scoreプロパティのみに関心があります。これはからの整数です。 0
– 4
、これは視覚的な強度バーを実装するのに役立ちます。
これがで起こっていることの内訳です PasswordField
成分:
初期化: constructor()
、2つのインスタンスプロパティを作成しました。 thresholdLangth
と minStrength
、コンポーネントに渡された対応するプロップから。 The thresholdLength
は、十分に長いと見なされる前のパスワードの最小の長さです。 デフォルトは 7
低くすることはできません。 The minStrength
最小です zxcvbn
パスワードが十分に強力であると見なされる前にスコアを付けます。 その値の範囲は 0-4
. デフォルトは 3
指定されていない場合。
また、パスワードフィールドの内部状態を初期化して、現在のパスワードフィールドを保存しました password
とパスワード strength
.
パスワード変更の処理:に渡されるパスワード検証関数を定義しました validator
基礎となる小道具 FormField
成分。 この関数は、パスワードの長さが thresholdLength
最小値もあります zxcvbn()
指定されたスコア minStrength
.
また、 stateChanged()
関数は、に渡されます onStateChanged
の小道具 FormField
成分。 この関数は、の更新された状態を取得します FormField
コンポーネントを使用して、の新しい内部状態を計算および更新します。 PasswordField
成分。
コールバック関数は、内部状態の更新後に呼び出されます。 コールバック関数は、オプションで渡された関数を呼び出します onStateChanged
の小道具 PasswordField
コンポーネント、更新されたものを渡す FormField
その引数として状態。
レンダリングと小道具:ここでは、基になるレンダリングを行いました FormField
入力ヒント、パスワード強度メーター、およびパスワード長カウンターのいくつかの要素と一緒のコンポーネント。
パスワード強度メーターは、 strength
現在の password
状態に基づいており、動的に構成されています invisible
パスワードの長さが 0
. メーターは、強度レベルごとに異なる色を示します。
パスワード長カウンターは、パスワードが十分に長い場合を示します。 パスワードがパスワードより長くない場合は、パスワードの長さが表示されます thresholdLength
、それ以外の場合は、 thresholdLength
続いて plus(+)
.
The PasswordField
コンポーネントは、2つの追加のオプションフィールドを受け入れます。 minStrength
と thresholdLength
、コンポーネントで定義されているように propTypes
.
The JoinForm
成分
新しいファイルを作成する JoinForm.js
の中に src/components
ディレクトリに次のコードスニペットを追加します。
import React, { Component } from 'react';
import FormField from './FormField';
import EmailField from './EmailField';
import PasswordField from './PasswordField';
class JoinForm extends Component {
// initialize state to hold validity of form fields
state = { fullname: false, email: false, password: false }
// higher-order function that returns a state change watch function
// sets the corresponding state property to true if the form field has no errors
fieldStateChanged = field => state => this.setState({ [field]: state.errors.length === 0 });
// state change watch functions for each field
emailChanged = this.fieldStateChanged('email');
fullnameChanged = this.fieldStateChanged('fullname');
passwordChanged = this.fieldStateChanged('password');
render() {
const { fullname, email, password } = this.state;
const formValidated = fullname && email && password;
// validation function for the fullname
// ensures that fullname contains at least two names separated with a space
const validateFullname = value => {
const regex = /^[a-z]{2,}(\s[a-z]{2,})+$/i;
if (!regex.test(value)) throw new Error('Fullname is invalid');
};
return (
<div className="form-container d-table-cell position-relative align-middle">
<form action="/" method="POST" noValidate>
<div className="d-flex flex-row justify-content-between align-items-center px-3 mb-5">
<legend className="form-label mb-0">Support Team</legend>
{/** Show the form button only if all fields are valid **/}
{ formValidated && <button type="button" className="btn btn-primary text-uppercase px-3 py-2">Join</button> }
</div>
<div className="py-5 border-gray border-top border-bottom">
{/** Render the fullname form field passing the name validation fn **/}
<FormField type="text" fieldId="fullname" label="Full Name" placeholder="Enter Full Name" validator={validateFullname} onStateChanged={this.fullnameChanged} required />
{/** Render the email field component **/}
<EmailField fieldId="email" label="Email" placeholder="Enter Email Address" onStateChanged={this.emailChanged} required />
{/** Render the password field component using thresholdLength of 7 and minStrength of 3 **/}
<PasswordField fieldId="password" label="Password" placeholder="Enter Password" onStateChanged={this.passwordChanged} thresholdLength={7} minStrength={3} required />
</div>
</form>
</div>
);
}
}
export default JoinForm;
The JoinForm
コンポーネントは、フォームを構成するフォームフィールドコンポーネントをラップします。 3つのフォームフィールドの有効性を保持するために状態を初期化しました。 fullname
, email
、 と password
. それらはすべてです false
、 また invalid
、最初は。
また、各フィールドの状態変更監視関数を定義して、それに応じてフォームの状態を更新しました。 ウォッチ機能は、存在しないかどうかをチェックします errors
フィールドで、そのフィールドのフォームの内部状態を次のように更新します true
、 また valid
. これらの監視機能は、 onStateChanged
状態の変化を監視するための各フォームフィールドコンポーネントの小道具。
最後に、フォームがレンダリングされます。 検証関数をに追加したことに注意してください fullname
スペースで区切られ、英字のみを含む少なくとも2つの名前が指定されていることを確認するフィールド。
The App
成分
この時点まで、ブラウザーはボイラープレートReactアプリケーションをレンダリングします。 次に、を変更します App.js
のファイル src
レンダリングするディレクトリ JoinForm
中 AppComponent
.
The App.js
ファイルは次のスニペットのようになります。
import React from 'react';
import JoinForm from './components/JoinForm';
import './App.css';
function App() {
return (
<div className="main-container d-table position-absolute m-auto">
<JoinForm />
</div>
);
}
export default App;
ステップ3—Sassを使用したスタイリング
アプリケーションの最終的なルックアンドフィールから一歩離れています。 現時点では、すべてが少し場違いに見えるかもしれません。 このステップでは、先に進み、フォームのスタイルを設定するためのいくつかのスタイルルールを定義します。
強力なSass変数、ネスト、およびループを利用するために、以前にの依存関係をインストールしました node-sass
. Sassを使用して、ブラウザーが理解できるCSSファイルを生成しています。
依存関係をインストールした後、アプリケーションでSassを利用するには、次の2つの変更が必要になります。
- ファイルの名前を変更します
src/App.css
にsrc/App.scss
. - でインポート行を編集します
src/App.js
名前が変更されたファイルを参照します。
名前を変更した後 src/App.css
ファイル、更新 src/App.js
次のファイルにファイルします。
import './App.scss';
ファイルを保存して閉じます。
次に、の既存のコンテンツを置き換えます App.scss
アプリケーションをフォーマットするための次のコードを含むファイル:
/** Declare some variables **/
$primary: #007bff;
// Password strength meter color for the different levels
$strength-colors: (darkred, orangered, orange, yellowgreen, green);
// Gap width between strength meter bars
$strength-gap: 6px;
body {
font-size: 62.5%;
}
.main-container {
width: 400px;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.form-container {
bottom: 100px;
}
legend.form-label {
font-size: 1.5rem;
color: desaturate(darken($primary, 10%), 60%);
}
.control-label {
font-size: 0.8rem;
font-weight: bold;
color: desaturate(darken($primary, 10%), 80%);
}
.form-control {
font-size: 1rem;
}
.form-hint {
font-size: 0.6rem;
line-height: 1.4;
margin: -5px auto 5px;
color: #999;
&.error {
color: #C00;
font-size: 0.8rem;
}
}
button.btn {
letter-spacing: 1px;
font-size: 0.8rem;
font-weight: 600;
}
.password-count {
bottom: 16px;
right: 10px;
font-size: 1rem;
}
.strength-meter {
position: relative;
height: 3px;
background: #DDD;
margin: 7px 0;
border-radius: 2px;
// Dynamically create the gap effect
&:before,
&:after {
content: '';
height: inherit;
background: transparent;
display: block;
border-color: #FFF;
border-style: solid;
border-width: 0 $strength-gap 0;
position: absolute;
width: calc(20% + #{$strength-gap});
z-index: 10;
}
// Dynamically create the gap effect
&:before {
left: calc(20% - #{($strength-gap / 2)});
}
// Dynamically create the gap effect
&:after {
right: calc(20% - #{($strength-gap / 2)});
}
}
.strength-meter-fill {
background: transparent;
height: inherit;
position: absolute;
width: 0;
border-radius: inherit;
transition: width 0.5s ease-in-out, background 0.25s;
// Dynamically generate strength meter color styles
@for $i from 1 through 5 {
&[data-strength='#{$i - 1}'] {
width: (20% * $i);
background: nth($strength-colors, $i);
}
}
}
アプリケーションに必要なスタイルを追加することに成功しました。 で生成されたCSSコンテンツの使用に注意してください .strength-meter:before
と .strength-meter:after
パスワード強度メーターにギャップを追加するための疑似要素。
あなたもSassを使用しました @for
さまざまなパスワード強度レベルで強度メーターの塗りつぶし色を動的に生成するディレクティブ。
最終的なアプリ画面は次のようになります。
検証エラーがある場合、画面は次のようになります。
エラーがない場合、すべてのフィールドが有効な場合、画面は次のようになります。
結論
このチュートリアルでは、に基づいてパスワード強度メーターを作成しました zxcvbn
ReactアプリケーションのJavaScriptライブラリ。 詳細な使用ガイドとドキュメントについては zxcvbn
ライブラリについては、GitHubのzxcvbnリポジトリを参照してください。 このチュートリアルの完全なコードサンプルについては、GitHubのpassword-strength-react-demoリポジトリを確認してください。 また、CodeSandboxでこのチュートリアルのライブデモを入手することもできます。
この記事のAngularJSバージョンに興味がある場合は、AngularJSのパスワード強度メーターをご覧ください。