【Laravel】メールアドレスとアカウントIDのどちらかでログインできるようにする

Twitterやその他SNSのようにメールアドレスとユーザーIDのどちらかとパスワードを入力することでログインできるようにカスタマイズする方法です。

環境

  • Laravel 9x
  • Fortify

https://readouble.com/laravel/9.x/ja/fortify.html

ユーザーテーブルにカラムを追加する

ユーザーテーブルにユーザーIDを保存するためのカラムを追加します。

 public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name')->nullable();
            $table->string("account_id")->unique()->comment('アカウントID'); #追加
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

コントローラー作成

ログイン処理のためのコントローラーをapp/Http/Controllers/Auth/LoginController.phpなどにして作成します。

Laravel Fortifyのデフォルトではvendor/laravel/fortify/src/Http/Controllers/AuthenticatedSessionController.phpにログインとログアウトの処理が記載されているのでそれをコピペして作成したLoginController.phpに貼り付けます。

ルーティング追記

Laravel Fortifyではカスタマイズがなければ特にルーティングを新たに書かなくても問題ないですが
今回はLoginController.phpを新たに作成したのでルーティングを書いてあげる必要があります。

Route::group([
    'namespace' => 'App\Http\Controllers\Auth',
], function () {
    Route::post('/login', 'LoginController@store');
    Route::post('/logout', 'LoginController@destroy');
});

LoginControllerの内容を修正

先ほどコピペしたAuthenticatedSessionController.phpの内容を修正します。
ログインに関する処理はstoreメソッドに書かれていますのでここをカスタマイズします。

<?php

namespace App\Http\Controllers\Auth; //変更

use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Routing\Pipeline;
use Laravel\Fortify\Actions\AttemptToAuthenticate;
use Laravel\Fortify\Actions\EnsureLoginIsNotThrottled;
use Laravel\Fortify\Actions\PrepareAuthenticatedSession;
use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable;
use Laravel\Fortify\Contracts\LoginResponse;
use Laravel\Fortify\Contracts\LoginViewResponse;
use Laravel\Fortify\Contracts\LogoutResponse;
use Laravel\Fortify\Features;
use Laravel\Fortify\Fortify;
use App\Http\Requests\LoginRequest; //変更
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    //省略
    public function store(LoginRequest $request)
    {
        //追記
        $account_id = $request->input('account_id');
        $password = $request->input('password');

        if (preg_match('/^[a-z0-9._+^~-]+@[a-z0-9.-]+$/i', $account_id)) {
            $credentials = ['email' => $account_id, 'password' => $password];
        } else {
            $credentials = ['account_id' => $account_id, 'password' => $password];
        }
        if (Auth::attempt($credentials)) {
            $request->session()->regenerate();
            return app(LoginResponse::class);
        }

        return $this->loginPipeline($request)->then(function ($request) {
            return app(LoginResponse::class);
        });
    }
    protected function loginPipeline(LoginRequest $request)
    {
        if (Fortify::$authenticateThroughCallback) {
            return (new Pipeline(app()))->send($request)->through(array_filter(
                call_user_func(Fortify::$authenticateThroughCallback, $request)
            ));
        }

        if (is_array(config('fortify.pipelines.login'))) {
            return (new Pipeline(app()))->send($request)->through(array_filter(
                config('fortify.pipelines.login')
            ));
        }

        return (new Pipeline(app()))->send($request)->through(array_filter([
            config('fortify.limiters.login') ? null : EnsureLoginIsNotThrottled::class,
            Features::enabled(Features::twoFactorAuthentication()) ? RedirectIfTwoFactorAuthenticatable::class : null,
            AttemptToAuthenticate::class,
            PrepareAuthenticatedSession::class,
        ]));
    }

   //省略
}

メールアドレスかどうか判別する

preg_match()関数で正規表現を使いリクエストで飛んできた値がメールアドレスかどうか判別します。
メールアドレスであればemailメールアドレスでなければaccount_idとしてキー/値ペアの配列を作成します。
Auth::attempt()メソッドは引数の値をuserテーブルと照合し、OKの時点でログイン状態にして返してくれます。

if (preg_match('/^[a-z0-9._+^~-]+@[a-z0-9.-]+$/i', $account_id)) {
    $credentials = ['email' => $account_id, 'password' => $password];
} else {
    $credentials = ['account_name' => $account_id, 'password' => $password];
}
if (Auth::attempt($credentials)) {
    $request->session()->regenerate();
    return app(LoginResponse::class);
}

LoginRequestを作成

LoginRequestを編集します。
デフォルトのままではFortify::username()がemailとなっていますのでリクエストの値にemailがなければバリデーションではじかれてしまいます。

Laravel Fortifyのデフォルトの記述

public function rules()
{
    return [
        Fortify::username() => 'required|string',
        'password' => 'required|string',
    ];
}

LoginRequestをカスタマイズする

ログイン処理時にaccount_idとpasswordを飛ばしたいので以下の様に変更します。

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class LoginRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'account_id' => 'required|string',
            'password' => 'required|string',
        ];
    }
    /**
     *  バリデーション項目名定義
     * @return array
     */
    public function attributes()
    {
        return [
            'account_id'         => "アカウント名またはメールアドレス",
        ];
    }
}

コメントを残す

入力エリアすべてが必須項目です。メールアドレスが公開されることはありません。

内容をご確認の上、送信してください。