Laravel: 認証のカスタマイズ

標準のデータベース以外のソースで認証をおこなうため、カスタマイズする。

参考) https://laravel.com/docs/6.x/authentication#adding-custom-guards

環境

  • Laravel 6

概要

以下のインタフェースを実装したクラスを作成する。

Interface Purpose
Guard Illuminate\Contracts\Auth\Guard 認証要求を処理し、認証結果を保持管理する。
Provider Illuminate\Contracts\Auth\UserProvider 認証情報から User のインスタンスを返す。
User Illuminate\Contracts\Auth\Authenticatable 認証情報を持っているクラス。

どの実装を使用するかは、config/auth.php に設定する。

// config/auth.php
return [
    ...
    'guards' => [
        'web' => [
            'driver' => 'session',  ★使用する Guard の名前
            'provider' => 'users',  ★↓に定義した providers の名前
        ],
        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],
    'providers' => [
        'users' => [
            'driver' => 'eloquent',     ★使用する Provider の名前
            'model' => App\User::class, ★と、パラメタ
        ],
    ],
    ...
];

標準で用意されている Guard

driver の名前 実装 備考
request Illuminate\Auth\RequestGuard
session Illuminate\Auth\SessionGuard 認証結果をセッションに保持する。
token Illuminate\Auth\TokenGuard

標準で用意されている Provider

driver の名前 実装 備考
database Illuminate\Auth\DatabaseUserProvider DB による認証。
eloquent Illuminate\Auth\EloquentUserProvider Eloquent 経由 DB による認証。User の Eloquent モデルには Illuminate/Auth/Authenticatable トレイトを use するだけで実装完了。

ログインシーケンス

uml diagram

ログインユーザーの取得

uml diagram

実装

参考) Adding Custom User Providers - Laravel

ユーザープロバイダクラスを作成

UserProvider インタフェースを実装した、ユーザーを提供するプロバイダクラスを作成する。実装する関数は以下の通り。

Method Description
retrieveById ID からユーザーインスタンスを生成する。
retrieveByToken ID と Remember Token からユーザーインスタンスを取得する。ログイン時に「Remember me」をチェックし、ログインセッション切れ後に自動再ログインするときに使用する。「Remember me」機能を実装しない場合は使用されない。
updateRememberToken 「Remeber me」をチェックしてログインした際に呼ばれるので、Remember Token を保存する。「Remember me」機能を実装しない場合は使用されない。
retrieveByCredentials ログイン時に入力された認証情報(ユーザー名、パスワード等)からユーザーインスタンスを生成する。パスワードを含む認証情報が渡ってくるが、ここで認証はしないこと。
validateCredentials 取得したユーザーインスタンスと入力された認証情報をもとに、ここで認証をおこなう。

実際のデータソースへのアクセスをおこなう MyUserRepository クラスが存在する場合の例。(「Remeber me」機能の実装は省略した。)

<?php
namespace App\Providers;

use App\Model\MyUserRepositoryInterface;

class MyUserProvider implements UserProvider
{
    protected $userReposotiry;

    public function __construct(MyUserRepositoryInterface $userReposotiry)
    {
        $this->userReposotiry = $userReposotiry;
    }

    public function retrieveById($identifier): ?Authenticatable
    {
        return $this->userReposotiry->getUserById($identifier);
    }

    public function retrieveByToken($identifier, $token): ?Authenticatable
    {
        throw new Exception(sprintf('Not implemented: %s->%s()', self::class, __FUNCTION__));
    }

    public function updateRememberToken(Authenticatable $user, $token): void
    {
        throw new Exception(sprintf('Not implemented: %s->%s()', self::class, __FUNCTION__));
    }

    public function retrieveByCredentials(array $credentials): ?Authenticatable
    {
        if (!isset($credentials['email'])) {
            return null;
        }
        return $this->userReposotiry->getUserByLogin($credentials['email']);
    }

    public function validateCredentials(Authenticatable $user, array $credentials): bool
    {
        if (!isset($credentials['email']) || !isset($credentials['password'])) {
            return false;
        }
        return $this->userReposotiry->authenticateUser($credentials['email'], $credentials['password']);
    }
}

作成したユーザープロバイダクラスを登録

<?php
namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;

class AuthServiceProvider extends ServiceProvider
{
    public function boot()
    {
        ...
        Auth::provider('my_user', function ($app, array $config) {
            return $app->make(MyUserProvider::class);
        });
    }
}

作成したユーザープロバイダクラスを設定

config/auth.php の provider に追加し、guards から指定する。

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'my_users',
    ],
],

'providers' => [
    'my_users' => [
        'driver' => 'my_user',
    ],
],

ユーザークラスを作成

プロバイダが返すユーザーインスタンスは、Authenticatable インタフェースを実装する必要がある。

Method Description
getAuthIdentifierName ユーザーのプライマリキー名を返す。(例: "id")
getAuthIdentifier ユーザーのプライマリキーを返す。
getAuthPassword ユーザーのハッシュ化されたパスワードを返す。「Remember me」機能を実装しない場合は使用されない。
getRememberToken ユーザーの Remember Token を返す。「Remember me」機能を実装しない場合は使用されない。
setRememberToken ユーザーの Remember Token を設定する。「Remember me」機能を実装しない場合は使用されない。
getRememberTokenName ユーザーの Remember Token 名を返す。「Remember me」機能を実装しない場合は使用されない。
namespace App\Model;

use Exception;
use Illuminate\Contracts\Auth\Authenticatable;

/**
 * MyUser model
 */
class MyUser implements Authenticatable
{
    public function getAuthIdentifierName()
    {
        return 'id';
    }

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

    public function getAuthPassword(): string
    {
        throw new Exception(sprintf('Not implemented: %s->%s()', self::class, __FUNCTION__));
    }

    public function getRememberToken(): string
    {
        throw new Exception(sprintf('Not implemented: %s->%s()', self::class, __FUNCTION__));
    }

    public function setRememberToken($value): void
    {
        throw new Exception(sprintf('Not implemented: %s->%s()', self::class, __FUNCTION__));
    }

    public function getRememberTokenName(): string
    {
        throw new Exception(sprintf('Not implemented: %s->%s()', self::class, __FUNCTION__));
    }
}