ORM(オブジェクトリレーショナルマッピング) でなく、SQL を使う、という場面は多々あると思う。 何をやっているのかわかりやすい、デバッグしやすい、パフォーマンスを追求できる、既存の DB がある、といった場面だろうか。

そこで、Laravel で Eloquent ORM でなく MySQL などの RDB(リレーショナルデータベース) に対する SQL クエリを使って認証する方法について書きたいと思う。 (laravel mysql auth で出てくる Google 検索結果には、本当に、失望してしまった。)

Adding Custom User Providers がそのものであろう。

ただ、そのままだとわかりづらいので、少し解説を加えたいと思う。

データベースには DB facades でアクセスできるようになっているものとする。

テーブルの DB 定義

テーブル名は、example_users とし、以下のカラムが定義されているものとする。

  • login_id
  • login_password
  • user_name

また、login_password には、password_hash 関数で生成した文字列がセットされているものとする。

UserProvider を追加する

次のように UserProvider 実装した ExampleProvider クラスを作る。

この例では、App\Models\ExampleUser に Authenticatable を実装するものとする。

app\Providers\ExampleUserProvider.php

<?php

namespace App\Providers;

use App\Models\ExampleUser;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Facades\DB;

class ExampleUserProvider implements UserProvider
{
    public function retrieveById($identifier)
    {
        $user = DB::table('example_users')->where('login_id', $identifier)->first();
        if (empty($user)) {
            return null;
        } else {
            return new ExampleUser($user); // Authenticatable
        }
    }

    public function retrieveByToken($identifier, $token)
    {
        return null; // 対応しないので null を返す
    }

    public function updateRememberToken(Authenticatable $user, $token)
    {
    }

    public function retrieveByCredentials(array $credentials)
    {
        $user = DB::table('example_users')->where(
            'login_id', // テーブルカラム名
             $credentials['id'] // 入力値など
        )->first();
        if (empty($user)) {
            return null;
        } else {
            return new ExampleUser($user); // Authenticatable
        }
    }

    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        // boolean 値を返す        
        return password_verify(
            $credentials['password'], // 入力値
            $user->getAuthPassword()  // Authenticatable にセットされたハッシュ化されたパスワードなど
        );
    }
}

Authenticatable を追加する

次のように Authenticatable を実装したクラスを作る。

app\Models\ExampleUser.php

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\Authenticatable;

class ExampleUser implements Authenticatable
{
    private $login_id;
    private $login_password;
    private $user_name;

   public function __construct($obj)
    {
        $this->login_id = $obj->login_id;
        $this->login_password = $obj->login_password;
        $this->user_name = $obj->user_name;
    }

    public function getAuthIdentifierName()
    {
        return "login_id";
    }

    public function getAuthIdentifier()
    {
        return $this->{$this->getAuthIdentifierName()};
    }

    public function getAuthPassword()
    {
        return $this->login_password;
    }

    public function getRememberToken()
    {
        return null; // 対応しないので null を返す。
    }

    public function setRememberToken($value)
    {
        return; // 対応しないので、何もしない。
    }

    public function getRememberTokenName()
    {
        return null; // 対応しないので null を返す。
    }

    // ユーザー名を表示するために拡張
    public function getUserName()
    {
        return $this->user_name;
    }
}

UserProvider を登録する

app/Providers/AuthServiceProvider.php

冒頭の use に以下を追加する。

use Illuminate\Support\Facades\Auth;

boot 関数の末尾に以下を追加する。

        Auth::provider('example', function($app, array $config) {
            return new ExampleUserProvider();
        });

config/auth.php

providers を以下のようにする。

    'providers' => [
        'users' => [
            'driver' => 'example', // Auth::provider の第一引数
        ],
    ],

これで、Auth facades が使えるようになっているはずだ。

検証

Manually Authenticating Users を参考に検証してみたいと思う。

プロジェクト生成

Installtion Via Composer にあるように、以下を実行してアプリの雛形を作る。

composer create-project laravel/laravel example-app
cd example-app
php artisan serve

特に問題なければ、ブラウザで http://127.0.0.1:8000 にアクセスすると、何か表示されるはずだ。

DB 設定

mysql の設定がめんどうなので、sqlite を使ってみる。

SQLite Configuration や、 config/database.php を参考に設定する。

.env

#DB_CONNECTION=mysql
#DB_HOST=127.0.0.1
#DB_PORT=3306
#DB_DATABASE=laravel
#DB_USERNAME=root
#DB_PASSWORD=
DB_CONNECTION=sqlite

これで、database/database.sqlite が参照されるようになる。

touch database/database.sqlite 

などで空ファイルを作って、

php artisan tinker
DB::statement('create table example_users(login_id, login_password, user_name)');
DB::table('example_users')->insert(['login_id' => 'kuromame', 'login_password' => password_hash('mya', PASSWORD_DEFAULT), 'user_name' => 'ku-tan']);

のようにしてデータを作っておく。 もちろん、sqlite3 コマンドを使って作ってもよい。

DB::table('example_users')->get()

を打つと、登録されていることを確認できる。

シェルからは、

quit

で抜けることができる。

routes/web.php

既存の Route::get('/') 〜 は削除し、以下を追加する。

Route::get('/', [App\Http\Controllers\LoginController::class, 'onGet'])->name('login');
Route::post('/', [App\Http\Controllers\LoginController::class, 'onPost']);
Route::get('/logout', function(Request $request) {
    Auth::logout();
    session()->invalidate();
    session()->regenerateToken();
    return redirect()->route('login');
})->name('logout');
Route::middleware(['auth'])->group(function() {
    Route::get('/dashboard', function() {
        return view('dashboard', // resources/views/dashboard.blade.php を描画
                    array(
                        'user_name' => Auth::user()->getUserName()
                    ));
    })->name('dashboard');
});

app/Http/Controllers/LoginController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;

class LoginController extends Controller
{
    public function onGet(Request $request)
    {
        return view('login'); // resources/views/login.blade.php を描画
    }

    public function onPost(Request $request)
    {
        $credentials = $request->validate([
            'id' => 'required',
            'password' => 'required'
        ]);
        // validate ok なら、値は $credentials array に格納される。
        // validate ng の場合、この例の場合は、前画面にリダイレクトされる

        if (Auth::attempt($credentials)) {
            $request->session()->regenerate();
            return redirect()->intended('dashboard');
        }

       return back()->withErrors([
            'other' => 'ID、または、パスワードが違います。'
       ]);
    }
}

resources/views/login.blade.php

<html>
<head>
</head>
<body>
    <form method="post">
        @csrf
        <label for="id">ID</label>
        <input type="text" id="id" name="id">
        @error('id')
            {{ $message }}            
        @enderror
        <label for="password">パスワード</label>
        <input type="password" id="password" name="password">
        @error('password')
            {{ $message }}            
        @enderror
        @error('other')
            {{ $message }}            
        @enderror
        <input type="submit" value="ログイン">
    </form>
</body>
</html>

resources/views/dashboard.blade.php

<html>
<head>
</head>
<body>
    <p>Hello, {{ $user_name }}!</p>
    <a href="{{ url('logout') }}">LOGOUT</a>
</body>
</html>

前述した以下のファイルを追加する。

  • app\Providers\ExampleUserProvider.php
  • app\Models\ExampleUser.php

以上の変更をした後、再度ブラウザで http://127.0.0.1:8000 にアクセスする。

ID: kuromame、パスワード: mya でログインすると、DB テーブルで認証ができるようになっていることがわかる。

laravel 関連書籍ランキング

2021/11/30 PA-API で検索

  1. 速習 Laravel 6 速習シリーズ【ランキング 8846 位】
  2. 動かして学ぶ!Laravel開発入門【ランキング 9531 位】
  3. PHPフレームワークLaravel Webアプリケーション開発 バージョン8.x対応【ランキング 21273 位】
  4. 1週間で基礎から学ぶLaravel入門【ランキング 25145 位】
  5. PHPフレームワーク Laravel入門 第2版【ランキング 48582 位】
  6. PHPフレームワーク Laravel実践開発【ランキング 87658 位】
  7. PHPフレームワークLaravel Webアプリケーション開発【ランキング 193843 位】
  8. LaravelでStripeを使った決済処理付き簡易ファッションECサイトを作ろう!: Laravelと決済プラットフォームであるStripeを使用して、ファッションECサイトの作り方を学ぶ (Techpitブックス)【ランキング 216636 位】
  9. Laravelエキスパート養成読本 [モダンな開発を実現するPHPフレームワーク!]【ランキング 324807 位】