Laravelで代理ログインの方法
こんにちは。
ニシザワです。
久々の投稿です。
本日はLaravelの代理ログインについて書きたいと思います。
認証機構の要件として、管理者ユーザを別のユーザとしてログインさせる機能(以下、代理ロググイン)を求められることがあります。
マルチ認証はできていることとして、代理ログインにフォーカスして書きます。
今回はSessionGuardを継承して使います。
proxyLoginメソッドで代理ログインのロジックを詰め込んでいます。
代理ログイン前のユーザーがどういったユーザーかも一応保存しておきます。
また、管理者かどうかの判定ロジックも入れておきます。
こうすることで、auth('guard')->isAdmin()のように、使うことができます。
SessionGuardに必要な引数はコンテナから取得して注入しておきます。
今回はadminがuserへログインするパターンで実装します。
リクエストパラメーターからuser_idを取得していますが、sessionから代理ログインする先のUserIdを取得しても大丈夫です。
今回はhomeに追加しています。
上記のURLにAdminをログイン後投げるとuserId=1のUserで代理ログインが可能です。
例えば認証済みのAdmin側でredirect関数を使ってリダイレクトさせるといいでしょう。
Controllerでの例
以上になります。
Guardをうまく使いこなせば、簡単に実装できますので参考にしてみてください。
ニシザワです。
久々の投稿です。
本日はLaravelの代理ログインについて書きたいと思います。
認証機構の要件として、管理者ユーザを別のユーザとしてログインさせる機能(以下、代理ロググイン)を求められることがあります。
準備
環境
- ・Laravel5.8
- ・PHP7.3
説明しないこと
この記事では、マルチ認証については書きません。マルチ認証はできていることとして、代理ログインにフォーカスして書きます。
ユーザー定義
- 一般ユーザー(user)
- 管理者ユーザー(admin)
代理ログインを実装するソースコード
- /app/Auth/Guard.php
- /app/Providers/AppServiceProvider.php
- /config/auth.php
- /app/Http/Middleware/ProxyLogin.php
- /app/Http/Kernel.php
- /routes/web.php
カスタムガードを作成
代理ログインのロジックをカスタムガードで追加します。今回は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をうまく使いこなせば、簡単に実装できますので参考にしてみてください。