開発ブログ

株式会社Nextatのスタッフがお送りする技術コラムメインのブログ。

電話でのお問合わせ 075-744-6842 ([月]-[金] 10:00〜17:00)

  1. top >
  2. 開発ブログ >
  3. PHP >
  4. Laravel >
  5. Laravelで代理ログインの方法

Laravelで代理ログインの方法

こんにちは。
ニシザワです。

久々の投稿です。
本日はLaravelの代理ログインについて書きたいと思います。
認証機構の要件として、管理者ユーザを別のユーザとしてログインさせる機能(以下、代理ロググイン)を求められることがあります。

準備

環境

  • ・Laravel5.8
  • ・PHP7.3

説明しないこと

この記事では、マルチ認証については書きません。
マルチ認証はできていることとして、代理ログインにフォーカスして書きます。

ユーザー定義

  • 一般ユーザー(user)
  • 管理者ユーザー(admin)

代理ログインを実装するソースコード

カスタムガードを作成

代理ログインのロジックをカスタムガードで追加します。
今回はSessionGuardを継承して使います。

<?php
declare(strict_types=1);

namespace App\Auth;

use App\Models\User;
use App\Models\Admin;
use Illuminate\Auth\SessionGuard;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;

class Guard extends SessionGuard
{

    /**
     * @var AuthenticatableContract
     */
    private $originalUser;

    /**
     * オリジナルユーザーの取得
     * @return AuthenticatableContract|Admin|null
     */
    public function originalUser(): ?AuthenticatableContract
    {
        return $this->originalUser;
    }

    /**
     * オリジナルユーザーのセッター
     * @param AuthenticatableContract $user
     */
    private function setOriginalUser(AuthenticatableContract $user): void
    {
        $this->originalUser = $user;
    }

    /**
     * 代理ログイン実行
     *
     * @param string $guard
     * @param int $userId
     *
     * @return bool
     */
    public function proxyLogin(string $guard, int $userId): bool
    {
        $user = User::query()->find($userId);
        if (is_null($user)) {
            return false;
        }

        $originalUser = auth($guard)->user();
        $this->setOriginalUser($originalUser);
        $this->setUser($user);
        return true;
    }

    /**
     * 管理者かどうか
     * @return bool
     */
    public function isAdmin(): bool
    {
        return $this-originalUser instanceof Admin;
    }
}


proxyLoginメソッドで代理ログインのロジックを詰め込んでいます。
代理ログイン前のユーザーがどういったユーザーかも一応保存しておきます。
また、管理者かどうかの判定ロジックも入れておきます。
こうすることで、auth('guard')->isAdmin()のように、使うことができます。

カスタムガードをProvider登録する

作成したCustomGuardクラスをコンテナに登録します。
SessionGuardに必要な引数はコンテナから取得して注入しておきます。

<?php

namespace App\Providers;

use App\Auth\Guard;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Session\Session;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Symfony\Component\HttpFoundation\Request;
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.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Auth::extend('custom_guard', function(Application $app, $name, array $config) {
            return new Guard(
                $name,
                Auth::createUserProvider($config['provider']),
                $app->make(Session::class),
                $app->make(Request::class)
            );
        });
    }
}

カスタムガードをconfigに登録

Providerに登録したのでCustomGuardをconfigで定義します。

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => 'user',
        'passwords' => 'users',
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | here which uses session storage and the Eloquent user provider.
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | Supported: "session", "token"
    |
    */

    'guards' => [
        'user' => [
            'driver' => 'custom_guard',
            'provider' => 'users',
        ],
        'admin' => [
            'driver' => 'session',
            'provider' => 'admins',
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | If you have multiple user tables or models you may configure multiple
    | sources which represent each model / table. These sources may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Models\Admin::class,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | You may specify multiple password reset configurations if you have more
    | than one user table or model in the application and you want to have
    | separate password reset settings based on the specific user types.
    |
    | The expire time is the number of minutes that the reset token should be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    */

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ],

];

 

代理ログイン用のmiddlewareを定義

代理ログインしたいルーティングに入る前にmiddlewareで処理することで、代理ログインが実装できます。
今回はadminがuserへログインするパターンで実装します。
リクエストパラメーターからuser_idを取得していますが、sessionから代理ログインする先のUserIdを取得しても大丈夫です。

<?php
declare(strict_types=1);

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class ProxyLogin
{

    /**
     * Handle an incoming request.
     *
     * @param  Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        if (!$this->isLoggedInForAdmin('admin')) {
            return $next($request);
        }

        //既に代理ログイン済みの場合はスルーする。
        if (auth('user')->user()) {
            return $next($request);
        }

        $userId = $request->get('user_id');

        //失敗した場合はAdminの画面へリダイレクトする
        if(!auth('user')->proxyLogin('admin', $userId)) {
            return redirect()->route('admin.home');
        }

        return $next($request);
    }

    /**
     * Adminで既にログインしているかの判定
     * @param string $guard
     *
     * @return bool
     */
    private function isLoggedInForAdmin(string $guard): bool
    {
        $originalUser = auth($guard)->user();
        return !is_null($originalUser);
    }
}

 

Kernelの登録

作成したmiddlewareをroutingで指定できるようにKernelへ登録します。

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'proxy_login' => ProxyLogin::class, これを追加
        〜
    ];
 

代理ログインが必要なrouteへmiddlewareを追加

代理ログインが必要なrouteへmiddlewareを追加します。
今回はhomeに追加しています。

Route::middleware(['auth:user,admin', 'proxy_login'])
        ->group(function() {
            Route::get('/', 'HomeController@home')->name('home');
        });

実際の使い方

実装が完了したら、代理ログインを実装してみましょう!

http://locale/home?user_id=1

上記のURLにAdminをログイン後投げるとuserId=1のUserで代理ログインが可能です。
例えば認証済みのAdmin側でredirect関数を使ってリダイレクトさせるといいでしょう。
Controllerでの例

    public function hoge(int $userId): Redirect
    {
        return redirect()->route('home', ['user_id' => $userId]);
    }


以上になります。
Guardをうまく使いこなせば、簡単に実装できますので参考にしてみてください。
TOPに戻る