とあるサイトのLaravelのログを見ていると、以下の様なエラーログが表示されていました。
production.ERROR: This password does not use the Bcrypt algorithm. {"exception":"[object] (RuntimeException(code: 0): This password does not use the Bcrypt algorithm. at /home/example/example.com/example/vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php:71)
開発環境で再現してみると、こんな感じでRuntimeException
の例外が発生しています。焦ります(汗)。
この問題を結論から言えば、エラーメッセージそのままです。パスワードがBcryptで暗号化されていないよ、ということです。
config/hashing.php
のdriverがbcrypt
を指定されている場合、当然パスワードもBcryptになっていないと駄目、ということだと思います。(初期設定であるbcrypt
以外使ったことがないので適当)
問題を考える
本問題を抱える方のために、私が調べた問題解決方法を記載しておきます。
本当にBcryptで暗号化されていないか調べる
ユーザーのパスワードはBcryptの形式か調べてみます。例えば以下は、私の開発環境で適当なパスワードにてBcryptで保存された場合のデータを2例挙げます。
$2y$12$xc.abd04gbJm4DNx4CD/FuJnjlqef2IJjmoEmnYhH5D.DIJWaYZLi
$2y$12$apQ47k82roqEoVtFowVOveXziX1L3jUr7wpyF20f.oufuGvDDn.Pe
もちろんこれと同じになることは無いでしょうが、だいたいこんな感じの文字列です。
例えばphpMyAdminなどで、ユーザーのパスワード欄が同じような形式で保存されているか確認してみましょう。
通常Bcrypt形式は、パスワード保存時にHash::make()
を使用すればそのように保存されます。
$request->user('admin')->update([
'password' => Hash::make($validated['password']),
]);
もしくは新しめのLaravelであれば、Eloquentモデルでhashed
を指定すればわざわざ変換しなくても良いらしいです(試してはない)。
protected $casts = [
'password' => 'hashed',
];
パスワードがnullでないか
パスワードが設定されていない、つまりnull
の場合も本エラーになり得ます。
パスワード無しなんてあり得ないでしょう?、と思う方もいるかもしれません。しかし、外部認証を利用する場合等、ユーザーがパスワードを持たないケースもあります。
つまり言い換えると、ユーザーのテーブルをnullable
にする場合は注意です。例えば以下の様な感じです。
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name')->comment('お名前');
$table->string('email')->unique()->nullable()->comment('メールアドレス');
$table->timestamp('email_verified_at')->nullable()->comment('メールアドレス認証時間');
$table->string('password')->nullable()->comment('パスワード');
$table->string('provider')->nullable()->comment('外部認証プロバイダ');
$table->rememberToken();
$table->timestamps();
});
}
};
私の場合→Laravel Breeze利用時1:ログイン時
私の場合、Laravel Breezeを利用時に発生していましたので、同環境の方のために解決法を記載しておきます(他の環境でも似たような対策になると思いますが)。
結論として、パスワードがnull
だったことが原因でした。LINEログイン等で登録するユーザー向けに、nullable
にしていたのです。
元々のコード
問題箇所は、ログイン認証時のBreezeのコードです。app\Http\Requests\Auth\LoginRequest.php
のLoginRequest
クラス5行目の、Auth::attempt()
メソッドにて例外が発生します。
public function authenticate(): void
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}
改善後のコード
Auth::attempt()
の実施前に、IDに対してパスワードがnull
でないか判定する……なんて一瞬頭をよぎりましたが合理的ではありません。
例外を受け止めることにしました。おなじみのtry
~catch
です。
public function authenticate(): void
{
$this->ensureIsNotRateLimited();
try {
if (!Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.failed'),
]);
}
} catch (\RuntimeException $e) {
if ($e->getMessage() === 'This password does not use the Bcrypt algorithm.') {
RateLimiter::hit($this->throttleKey());
// ユーザーには通常のエラーメッセージを表示
throw ValidationException::withMessages([
'email' => trans('auth.failed'),
]);
}
// 予期しない他の例外の場合は再度投げる
throw $e;
}
RateLimiter::clear($this->throttleKey());
}
エラーメッセージから、RuntimeException
が投げられていたことが分かりましたので、さらにエラーメッセージで該当エラーだけを受け止めます。そして通常のエラーと見分けが付かないようにエラーを出しています。
ただ、もしもエラーメッセージが変更されたら例外がキャッチできずに、500エラーが表示されます。なのでもっと別の案もあるかと思いますが、良い方法があればご教示ください。
私の場合→Laravel Breeze利用時2:パスワード変更時
これで安心……と思っていたら、抜けがありました(汗)。
ログインユーザーによるパスワードの変更時も、もしnull
であれば同様のエラーが発生します。
そもそもLINEでログインしているので、パスワードを設定していないはずではありますが(汗)
エラーが発生したのはBreezeのapp\Http\Controllers\Auth\PasswordController.php
のPasswordControlle
クラスです。
public function update(Request $request): RedirectResponse
{
$validated = $request->validateWithBag('updatePassword', [
'current_password' => ['required', 'current_password'],
'password' => ['required', Password::defaults(), 'confirmed'],
]);
$request->user()->update([
'password' => Hash::make($validated['password']),
]);
return back()->with('status', 'password-updated');
}
3行目のvalidateWithBag
メソッドにより例外が発生しているようです。
こちらもtry
~catch
で対応しました。
public function update(Request $request): RedirectResponse
{
try {
$validated = $request->validateWithBag('updatePassword', [
'current_password' => ['required', 'current_password'],
'password' => ['required', Password::defaults(), 'confirmed'],
]);
$request->user()->update([
'password' => Hash::make($validated['password']),
]);
} catch (\RuntimeException $e) {
if ($e->getMessage() === 'This password does not use the Bcrypt algorithm.') {
// current_passwordにエラーをセット
return back()->withErrors(
['current_password' => 'パスワードが登録されていません'],
'updatePassword'
)->withInput();
} else {
// 予期しない他の例外の場合は再度投げる
throw $e;
}
}
return back()->with('status', 'password-updated');
}
リクエスト元のページは複数のフォームがあるため、MessageBag(ここでは'updatePassword'
)を指定してあげる必要があります。
実際には、これだけだとユーザーが混乱する可能性があります。適宜パスワードのリセットなどを案内してあげるようにすると良さそうですね。
まとめ
エラーログがある度、焦りますね。Laravelが親切で出してくれている例外ですので、速やかに修正したいものです。
同じエラーが出てしまっている状況の方が、素早く対処するための参考になれば幸いです。
コメント