Laravelエラー:This password does not use the Bcrypt algorithm.が表示される

Laravel エラー対策 This password does not use the Bcrypt algorithm. Laravel
※当サイトはアフィリエイト広告を掲載しています。

とあるサイトの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などで、ユーザーのパスワード欄が同じような形式で保存されているか確認してみましょう。

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.phpLoginRequestクラス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でないか判定する……なんて一瞬頭をよぎりましたが合理的ではありません。

例外を受け止めることにしました。おなじみのtrycatchです。

   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であれば同様のエラーが発生します。

Lara
Lara

そもそもLINEでログインしているので、パスワードを設定していないはずではありますが(汗)

エラーが発生したのはBreezeのapp\Http\Controllers\Auth\PasswordController.phpPasswordControlleクラスです。

    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メソッドにより例外が発生しているようです。

こちらもtrycatchで対応しました。

    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が親切で出してくれている例外ですので、速やかに修正したいものです。

同じエラーが出てしまっている状況の方が、素早く対処するための参考になれば幸いです。

コメント

タイトルとURLをコピーしました