9
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Laravelでのマルチ認証でログイン後のリダイレクト先をroleで変更する方法と認証したユーザー情報にJOINしたテーブルのフィールドを追加する方法

Last updated at Posted at 2019-07-13

概要

セッション衝突問題とかは置いておいてシンプルにroleを使ってログイン先を変更する話です。
Laravelデフォルトの認証を使います。

UsersテーブルにRoleを追加

migrationファイルでroleを追加しておきまましょう。
roleは数値でもenumでも良いです。
今回は admin,teacher,parentを用意したと仮定して進めます。

redirectTo()

Auth\LoginControllerにredirectToメソッドを定義してあげます。

そうすると既に定義してあるprotected $redirectToではなくredirectTo()の戻り地でredirect先が決定するようになります。

この辺りのことは\Illuminate\Foundation\Auth\RedirectsUsers.phpに書いてあります。

trait RedirectsUsers
{
    /**
     * Get the post register / login redirect path.
     *
     * @return string
     */
    public function redirectPath()
    {
        if (method_exists($this, 'redirectTo')) {
            return $this->redirectTo();
        }

        return property_exists($this, 'redirectTo') ? $this->redirectTo : '/home';
    }
}

traitがtrait先のコントローラーでメソッドかプロパティの定義を期待しているのはちょっと変わった実装ですね。

LoginControllerにredirectTo()を実装

public function redirectTo(){
    $role = $this->guard()->user()->role;
    if($role === 'admin'){
      return '/admin';
    }
    if($role === 'teacher'){
      return '/teacher';
    }
    if($role === 'parent'){
      return '/parent';
    }
    return '/';
}

roleに当てはまらなかったら例外投げても良いのですが今回は'/'にします。

まとめ

これでログイン後のリダイレクト先を変更することが出来ました。
role別に認証や認可でアクセス先を制限するのは別な実装が必要です。

Authで取得するuser()にJOINでパラメータを追加する場合

Provider/AuthUserProviderを作成

とりあえず下で実装するので空で良い

Provider/AuthServiceProviderに以下を追記

public function register() {
	\Auth::provider('auth_ex', function($app){
		$model = $app['config']['auth.providers.users.model'];
		return new AuthUserProvider($app['hash'], $model);
	});
}

config/auth.phpを変更

    'providers' => [
        'users' => [
            'driver' => 'auth_ex',
            'model' => App\User::class,
        ],
    ]

Provider/AuthUserProviderを実装

class AuthUserProvider extends EloquentUserProvider{
	public function retrieveById($identifier) {
		$user = $this->createModel()->newQuery()->select(['users.role'])->where('users.id', $identifier)->first();
		if(empty($user)){
			$user = parent::retrieveById($identifier);
		}
		if($user->role == 'parent'){
			$result = $this->createModel()->newQuery()
				->leftJoin('parents', 'parents.user_id', '=', 'users.id')
				->select(['users.id', 'users.email', 'users.role', 'users.remember_token',
					'parents.id as parent_id', 'parents.classroom_code as classroom_code', 'parents.color as color'])
				->find($identifier);
			return $result;
		}
		if($user->role == 'teacher'){
			$result = $this->createModel()->newQuery()
				->leftJoin('teachers', 'teachers.user_id', '=', 'users.id')
				->select(['users.id', 'users.email', 'users.role', 'users.remember_token',
					'teachers.id as teacher_id', 'teachers.classroom_id as classroom_id', 'teachers.color as color', 'teachers.name as name'])
				->find($identifier);
			return $result;
		}
		return parent::retrieveById($identifier);
	}
}

retrieveById()をオーバーライドする形で内容を変えます。parentのケースとteaecherのケースではjoinを使って別テーブルのパラメータを取得してきます。こうすることでAuth::user()とか認証ユーザーの情報を取得するときにjoioした状態のテーブルのフィールドを加えることが出来ます。

retrieveById()はSessionGuardのuser()で主に利用されます。
デフォルトだとAuth::user()とやるとSessionGuardのuser()が呼び出されます。

Seessionが一度切れてCookieから読みに行くときにエラーになる問題

上記のようにretrieveById()を使ってフィールドを加える場合、特定の条件下でretrieveById()が呼び出されず、joinしたフィールドを呼び出してエラーになってしまうときがあります。

SessionGuardのuser()のソースです。

public function user()
{
	if ($this->loggedOut) {
		return;
	}

	if (! is_null($this->user)) {
		return $this->user;
	}

	$id = $this->session->get($this->getName());

	if (! is_null($id)) {
		if ($this->user = $this->provider->retrieveById($id)) {
			$this->fireAuthenticatedEvent($this->user);
		}
	}

	$recaller = $this->recaller();

	if (is_null($this->user) && ! is_null($recaller)) {
		$this->user = $this->userFromRecaller($recaller);

		if ($this->user) {
			$this->updateSession($this->user->getAuthIdentifier());

			$this->fireLoginEvent($this->user, true);
		}
	}

	return $this->user;
}

retrieveById()を呼び出さずユーザーをreturnしている箇所があります。2つめのif分です。
節のタイトル通り、Sessionが切れてCookieから読み直す場合、そこの分岐にたどり着く場合があります。

Authファサードのguard()を呼び出したときCookieから読み取りユーザー情報をセットされてしまいます。
そのユーザー情報にはjoinしたフィールドは含まれません。

ではどうしたら良いか?

ちなみにAuthファサードのguard()は Illuminate\Auth\AuthManagerクラスのメソッドを呼び出しています。

SessionGuardを継承したCustomGuardを用意する。

自作のGuardを作る

user()の内容を変えます。
以下、CustomGuardの実装です。


class CustomGuard extends SessionGuard {
	private function retrieve(){
		$id = $this->session->get($this->getName());
		if (! is_null($id)) {
			if ($this->user = $this->provider->retrieveById($id)) {
				$this->fireAuthenticatedEvent($this->user);
			}
		}
		return $this->user;
	}
	public function user()
	{
		if ($this->loggedOut) {
			return;
		}

		if (! is_null($this->user)) {
			if($this->user->role === 'parent'){
				if($this->user->classroom_code === null){
					$this->user = $this->retrieve();
				}
			}
			if($this->user->role === 'teacher'){
				if($this->user->classroom_id === null){
					$this->user = $this->retrieve();
				}
			}
			return $this->user;
		}

		$id = $this->session->get($this->getName());

		if (! is_null($id)) {
			if ($this->user = $this->provider->retrieveById($id)) {
				$this->fireAuthenticatedEvent($this->user);
			}
		}

		$recaller = $this->recaller();

		if (is_null($this->user) && ! is_null($recaller)) {
			$this->user = $this->userFromRecaller($recaller);

			if ($this->user) {
				$this->updateSession($this->user->getAuthIdentifier());

				$this->fireLoginEvent($this->user, true);
			}
		}

		return $this->user;
	}
}

roleで分岐を行い、join対象のフィールドがnullであったらretrieveById()を呼び出してユーザー情報を再加工しています。

次にSessionGuardからCustomGuardに切り替える設定を行います。

AuthServiceProviderクラスのboot()でAuth::extendをします。

public function boot()
{
	$this->registerPolicies();

	Auth::extend('custom', function($app, $name, array $config) {
		$guard = new CustomGuard($name,Auth::createUserProvider($config['provider']), $app["session.store"]);
		if (method_exists($guard, 'setCookieJar')) {
			$guard->setCookieJar($this->app['cookie']);
		}

		if (method_exists($guard, 'setDispatcher')) {
			$guard->setDispatcher($this->app['events']);
		}

		if (method_exists($guard, 'setRequest')) {
			$guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
		}
		return $guard;
	});
}

まず、前提知識です。自作Guardの作成は2種類方法があります。
上記のextend()を使ったやり方とAuthManagerを継承してメソッドを追加する方法です。

少しソースの説明をします。

CustomGuardを生成した後の3つのmethod_existsですが通常のSessionGuardを利用するときに呼び出されるものです。
AuthManagerクラスのcreateSessionDriver()に記述されています。

createSessionDriver()の呼び出し方が可変関数を使って呼び出されています。

このような感じです。

$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (method_exists($this, $driverMethod)) {
    return $this->{$driverMethod}($name, $config);
}

extendを使うとcallCustomCreator()が先に呼び出されてその行にたどり着けません。

if (isset($this->customCreators[$config['driver']])) {
	return $this->callCustomCreator($name, $config);
}
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (method_exists($this, $driverMethod)) {
    return $this->{$driverMethod}($name, $config);
}

このような順番になっているためextendのクロージャーを呼び出してメソッドから抜け出してしまいます。
そのため、createSessionDriver()で記述してある、3つのmethod_existsが実行されなくなります。

CustomGuardの生成後にすぐにreturnしていないのはそのためです。

設定ファイル変更

config/auth.phpを変更します

'guards' => [
    'web' => [
        'driver' => 'custom',
        'provider' => 'users',
    ],

これでCustomGuardが代わりに使われるようになります。
目的達成です。

参考

Laravelで複数テーブルを使ったログイン認証を実装する
https://qiita.com/sakuraya/items/248d1bed5857c69e44c4
【Laravel】Laravel5.5でユーザーと管理者の認証(MultiAuth)を実装する(マルチログイン実装時のセッション分割対応)
https://qiita.com/sola-msr/items/65634826bcedf3ea4ca4
やはりお前らのMulti-Auth は間違っている
https://qiita.com/h1na/items/bbe4d17af21860de1930
Laravel5.5 カスタムGuardでログイン時にEgerLoadする
https://nextat.co.jp/staff/archives/195

9
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?