[Laravel] Custom guard and auth provider

前言

如果要客製自己的 auth gard 用來處理驗證與登入
首先可以從官網的 Authentication 中找到!
但是寫的有一點點不全面,在這邊重新整理一下。
如果是要用 jwt 登入這種,則可以直接參考官網即可!

大致需要
  1. 自己定義的 Auth Guard
  2. 自己定義的 User Provider (後面解釋)
  3. 用來驗證的 model

建立用來驗證的 model

<?php

namespace App\Models\Member;

use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Member extends Authenticatable
{
    use SoftDeletes;

    protected $table = 'members';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
        'cell_phone', 'verified', 'status',
        'facebook_id', 'line_id',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
    ];

    protected $dates = [
        'deleted_at',
    ];
}

在這邊我主要是先建立一個 Member.php 的 model
這裡有些地方要注意,Member 這個 model 必須繼承 Authenticatable
才可以符合 Laravel 的規則,因為後面會使用注入 Authenticatable 的方式。

建立 UserProvier

什麼時候會需要自己建立 UserProvider 呢?
假設今天使用的是 mongo db, 已經不是一般常用的 Relational DB
這時候就會需要建立一個客製化的 UserProvider
<?php

namespace App\Providers;

use App\Repositories\Member\MemberRepository;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Facades\Hash;

class MemberServiceProvider implements UserProvider
{
    protected $memberRepo;

    public function __construct(MemberRepository $memberRepo)
    {
        $this->memberRepo = $memberRepo;
    }

    /**
     * Retrieve a user by their unique identifier.
     *
     * @param  mixed $identifier
     *
     * @return null|Authenticatable
     */
    public function retrieveById($identifier)
    {
        return $this->memberRepo->getById($identifier);
    }

    /**
     * Retrieve a user by by their unique identifier and "remember me" token.
     *
     * @param  mixed $identifier
     * @param  string $token
     *
     * @return null|Authenticatable
     */
    public function retrieveByToken($identifier, $token)
    {
        return null;
    }

    /**
     * Update the "remember me" token for the given user in storage.
     *
     * @param  string $token
     */
    public function updateRememberToken(Authenticatable $member, $token)
    {
    }

    /**
     * Retrieve a user by the given credentials.
     *
     * @param  array $credentials array for auth
     *
     * @return null|Authenticatable
     */
    public function retrieveByCredentials(array $credentials)
    {
        if (!empty($credentials['cell_phone'])) {
            $member = $this->memberRepo->getByCellPhone($credentials['cell_phone']);
        }
        if (!empty($credentials['email'])) {
            $member = $this->memberRepo->getByEmail($credentials['email']);
        }

        if (!$member) {
            return null;
        }

        return Hash::check(
            $credentials['password'],
            $member->password
        ) ? $member : null;
    }

    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        // 驗證 email 與 password
        if (isset($credentials['email'], $credentials['password'])) {
            return strtolower($user->email) === strtolower($credentials['email'])
            && Hash::check($credentials['password'], $user->password);
        }

        // 驗證手機與 password
        if (isset($credentials['cell_phone'], $credentials['password'])) {
            return $user->cell_phone === trim($credentials['cell_phone'])
            && Hash::check($credentials['password'], $user->password);
        }

        return false;
    }
}

UserProvider 有點像是用來取得用戶身份的一個媒介
所以像我上面的範例,MemberServiceProvider 必須實作 UserProvider 這個 interface
我這裡是利用客製化的 Auth Guard (此處我命名為 MemberAuthGuard) 去呼叫 MemberServiceProvider,
來驗證前端傳遞過來的資訊是否正確,從而驗證用戶。
(MemberService - 呼叫 -> MemberAuthGuard - 呼叫 -> MemberServiceProvider)

建立 Auth Guard

<?php

namespace App\Services\Auth;

use App\Providers\MemberServiceProvider;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Guard;

class MemberAuthGuard implements Guard
{
    /**
     * Undocumented variable.
     *
     * @var Illuminate\Contracts\Auth\UserProvider
     */
    protected $provider;

    /**
     * member物件.
     *
     * @var Authenticatable
     */
    protected $member;

    public function __construct(MemberServiceProvider $provider)
    {
        $this->provider = $provider;
        $this->member = null;
    }

    /**
     * Determine if the current user is authenticated.
     *
     * @return bool
     */
    public function check()
    {
        return isset($this->member);
    }

    /**
     * Determine if the current user is a guest.
     *
     * @return bool
     */
    public function guest()
    {
        return !$this->check();
    }

    /**
     * Get the currently authenticated user.
     *
     * @return null|\Illuminate\Contracts\Auth\Authenticatable
     */
    public function user()
    {
        return $this->member;
    }

    /**
     * Get the ID for the currently authenticated user.
     *
     * @return null|int|string
     */
    public function id()
    {
        return $this->member ? $this->member->id : null;
    }

    /**
     * Validate a user's credentials.
     *
     * @return bool
     */
    public function validate(array $credentials = [])
    {
        $member = $this->provider->retrieveByCredentials($credentials);
        if (!$member) {
            return false;
        }

        $this->setUser($member);

        return true;
    }

    /**
     * Set the current user.
     */
    public function setUser(Authenticatable $member)
    {
        $this->member = $member;
    }
}
因為必須實作 Guard ,就大概上面的 function 要實作而已。


AuthServiceProvider 註冊自定義的 Provider 與 Auth Guard


<?php

namespace App\Providers;

use App\Repositories\Member\MemberRepository;
use App\Services\Auth\MemberAuthGuard;
use Illuminate\Auth\AuthManager;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        // 'App\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * Register any authentication / authorization services.
     */
    public function boot()
    {
        $this->registerPolicies();

        $memberRepo = $this->app->make(MemberRepository::class);
        // 自訂前端使用的驗證方式
        Auth::provider('frontend-auth-provider', function ($app, array $config) use ($memberRepo) {
            // Return an instance of Illuminate\Contracts\Auth\UserProvider...

            return new MemberServiceProvider($memberRepo);
        });

        // 定義 driver 的部分
        Auth::extend('frontend-auth', function ($app, $name, array $config) {

            return new MemberAuthGuard(Auth::createUserProvider($config['provider']));
        });

        $this->app->terminating(function () {
            $this->app->singleton('auth', function ($app) {
                $app['auth.loaded'] = true;

                return new AuthManager($app);
            });
        });
    }
}

在 Auth::provider('frontend-auth-provider') 這邊主要是註冊一個自定義的 User Provider


在 Auth::extend('frontend-auth') 是註冊 config/auth.php 裡面的 driver



接下來如果要使用自己定義的 auth guard,可以先去 config/auth.php 裡面

這樣就可以使用自己自定義的 Auth Guard 囉!

參考:
https://codershandbook.com/implementing-custom-auth-guard-in-laravel
https://laravel.com/docs/8.x/authentication#adding-custom-guards

留言

這個網誌中的熱門文章

[翻譯] 介紹現代網路負載平衡與代理伺服器

[MySQL] schema 與資料類型優化