Help us understand the problem. What is going on with this article?

Laravel5.5LTS: ライブラリを使わないマルチ認証-Fell版

Laravelのマルチ認証の記事はたいていコピペを多用してます。
コピペの多用バグの温床になりやすいので条件分岐で極力減らしてみました。

前提条件: Laravel5.5 インストール

Laravel5.5認証(公式)

.env(DB設定)

.env
//DB接続設定
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE={データベース名}
DB_USERNAME={user}
DB_PASSWORD={password}

AppServiceProvider.php

app/Providers/AppServiceProvider.php
//DBの191文字超えエラー回避設定
class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
++      \Illuminate\Support\Facades\Schema::defaultStringLength(191);
    }
}

Authとマイグレーション

認証のインストールとマイグレーション
//認証のインストール
$ php artisan make:auth
//認証用DBテーブル作成
$ php artisan migrate
//-> DBにusers・password_resetsテーブルが作成されたか確認。

Adminモデルの生成

Adminモデルとマイグレーションの生成
$ php artisan make:model Admin -m
//-> App/Admin.php
//-> database/migrations/{日付}_create_admins_table.php

Adminモデル

app/Admin.php
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Admin extends Authenticatable //modify
{
    use Notifiable;
    protected $fillable = [
        'name', 'email', 'password',
    ];
    protected $hidden = [
        'password', 'remember_token',
    ];
}

Adminテーブルのマイグレーション

create_users_table.phpの内容をコピーして変更

database/migrations/{日付}create_admins_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateAdminsTable extends Migration //modify
{
    public function up()
    {
        Schema::create('admins', function (Blueprint $table) { //modify
            $table->increments('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }
    public function down()
    {
        Schema::dropIfExists('admins'); //modify
    }
}

adminテーブルのマイグレーション実行

$ php artisan migrate
//adminsテーブルが生成されたか確認

tinker -> adminユーザーの生成

password_hash(公式)

tinker
$ php artisan tinker
>>> $admin = new App\Admin;
>>> $admin->name = 'admin';
>>> $admin->email = 'test@test.com';
>>> $admin->password = password_hash('password', PASSWORD_DEFAULT);
>>> $admin->save();
//phpMyadmin等でユーザー生成の確認

auth.php(公式)

admin側の定義の追加

config/auth.php
<?php
return [
    'defaults' => [
        'guard' => 'user',
        'passwords' => 'users',
    ],
    'guards' => [
        'api' => [
            'driver' => 'token',
            'provider' => 'users',
        ],
        'user' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
 +       'admin' => [
 +           'driver' => 'session',
 +           'provider' => 'admins',
 +      ],
    ],
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],
+        'admins' => [
+            'driver' => 'eloquent',
+            'model' => App\Admin::class,
+        ],
    ],
    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
        ],
+        'admins' => [
+            'provider' => 'admins',
+            'table' => 'password_resets',
+            'expire' => 60,
+        ],
    ],
];

Laravelの認証機能は「ガード」「プロバイダ」を中心概念として構成されています。ガードは各リクエストごとに、どのようにユーザーを認証するかを定義します。たとえば、Laravelにはセッションストレージクッキーを使いながら状態を維持するsessionガードが用意されています。

ログインしていないときのリダイレクト先

app/Exceptions/Handler.php
<?php
namespace App\Exceptions;

use Exception;
use Illuminate\Auth\AuthenticationException; //add
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

class Handler extends ExceptionHandler
{
    protected $dontReport = [
        //
    ];
    protected $dontFlash = [
        'password',
        'password_confirmation',
    ];
    public function report(Exception $exception)
    {
        parent::report($exception);
    }
    public function render($request, Exception $exception)
    {
        return parent::render($request, $exception);
    }
    //未ログイン時 -> ガード属性のログインページにリダイレクト
    public function unauthenticated($request, AuthenticationException $exception)
    {
        if($request->expectsJson()){
            return response()->json(['message' => $exception->getMessage()], 401);
        }
        if (in_array('admin', $exception->guards())) {
            return redirect()->guest(route('admin.login'));
        }
        return redirect()->guest(route('login'));
    }
}

ルーティング

route/web.php
<?php
Auth::routes();
// User 認証不要
Route::get('/', function () { 
    return redirect('home');
});
// User ログイン後
Route::group(['middleware' => 'auth:user'], function() {
    Route::get('home', 'HomeController@index')->name('home');
});
// Admin 認証不要
Route::group(['prefix' => 'admin'], function() {                                  
    Route::get('login', 'Admin\LoginController@showLoginForm')->name('admin.login');
    Route::post('login', 'Admin\LoginController@login');     
});                                                                                 
// Admin ログイン後
Route::group(['prefix' => 'admin', 'middleware' => 'auth:admin'], function() {
    Route::post('logout', 'Admin\LoginController@logout')->name('admin.logout');
    Route::get('home', 'HomeController@index')->name('admin.home');
});

コントローラーの構成

adminフォルダを作成してAuthフォルダからLoginController.phpをコピーします。
WS000039.png

LoginController.php(Auth)

app/Http/Controller/Auth/LoginController
<?php
namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;            //add
use Illuminate\Support\Facades\Auth;    //add

class LoginController extends Controller
{
    use AuthenticatesUsers;

    protected $redirectTo = '/home';

    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }
    public function logout(Request $request)
    {
        Auth::guard('user')->logout();  //modify
        return redirect('login');  //modify
    }
}

LoginController.php(Admin)

app/Http/Controller/Admin/LoginController.php
<?php
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;            //add
use Illuminate\Support\Facades\Auth;    //add

class LoginController extends Controller
{
    use AuthenticatesUsers;

    protected $redirectTo = 'admin/home'; //modify

    public function __construct()
    {
        $this->middleware('guest:admin')->except('logout');//modify
    }
    public function showLoginForm()
    {
        //login.blade.phpは共用
        return view('auth.login');  //modify
    }
    protected function guard()
    {
        return Auth::guard('admin');  //modify
    }
    public function logout(Request $request)
    {
        Auth::guard('admin')->logout();  //modify
        return redirect('admin/login');  //modify
    }
}

コントローラーの使用例(条件分岐の例)

HomeController.php
コントローラーではルート名からguardを判断します。
ルーティングがしっかりしていれば問題ないと考えます。

app/Http/Controller/HomeController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;    //add

class HomeController extends Controller
{
    /**
     * 条件分岐に使用できるメソッド例
     * ヘルパ関数に登録しておけば便利になると思います。
     * ログインしているユーザーのadmin権限の有無
        public function isAdminRoute() {
            $bool = strpos(\Route::currentRouteName(), 'admin') !== false;
            return $bool;
        }
     * ログインしているユーザー情報を取得します。
        public function getUser() {
        if ($this->isAdminRoute()) {
            return Auth::guard('admin')->user();
        } else {
            return Auth::guard('user')->user();
        }
     */
    public function __construct()
    {
        $this->middleware('auth');
    }
    public function index()
    {
        //ユーザー情報の取得テスト
        //dd($this->getUser());
        return view('home');
    }
}

ログイン後のリダイレクト先

app/Http/Middleware/RedirectIfAuthenticated.php
<?php                                                               
namespace App\Http\Middleware;                                      

use Closure;                                                        
use Illuminate\Http\Request;                                        
use Illuminate\Support\Facades\Auth;                                

class RedirectIfAuthenticated                                       
{                                                                   
    public function handle($request, Closure $next, $guard = null) {
        if (Auth::guard($guard)->check()) {
            switch ($guard) {
                case 'admin':
                    return redirect('/admin/home');
                    break;
                default:
                    return redirect('/home');
            }
        }
        return $next($request);
    }                                                               
}                                                                   

ビュー

ルート名からguardを判断しています。
これもルーティングがしっかりしていれば問題ないと思ってます。

WS000041.JPG
ビューはコピペせずに条件分岐でuseradminの制御を行います。

app.blade.php

resources/views/layouts/app.blade.php
<!-- $now_route -> 現在のルート名 -->
<?php $now_route = \Route::currentRouteName(); ?>
~~略~~
    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
    @if (strpos($now_route, 'admin') !== false)
        <style>body{background-color: tomato;}</style>
    @endif
~~略~~
    <!-- Branding Image -->
    @if (Auth::guard('user')->user() && strpos($now_route, 'admin') === false)
        <a class="navbar-brand" href="{{ url('/') }}">
            {{ config('app.name', 'Laravel') }}</a>
    @elseif (Auth::guard('admin')->user() && strpos($now_route, 'admin') !== false)
        <a class="navbar-brand" href="{{ route('admin.home') }}">
            {{ config('app.name', 'Laravel') }}</a>
    @endif
~~略~~
    <!-- Right Side Of Navbar -->
    <ul class="nav navbar-nav navbar-right">
        <!-- Authentication Links -->
        <!-- 未ログインの`user` -->
        @if (is_null(Auth::guard('user')->user()) && strpos($now_route, 'admin') === false)
            <li><a href="{{ route('login') }}">Login</a></li>
            <li><a href="{{ route('register') }}">Register</a></li>
        <!-- 未ログインの`admin` -->
        @elseif (is_null(Auth::guard('admin')->user()) && strpos($now_route, 'admin') !== false)
            <li><a href="{{ route('admin.login') }}">Login</a></li>
        @endif
            <li class="dropdown">
                <!-- ログイン中の`user` -->
                @if (!empty(Auth::guard('user')->user()) && strpos($now_route, 'admin') === false)
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" aria-haspopup="true" v-pre>
                        {{ Auth::guard('user')->user()->name }} <span class="caret"></span>
                    </a>
                    <ul class="dropdown-menu">
                        <li>
                    <a href="{{ route('logout') }}"
                    onclick="event.preventDefault();
                             document.getElementById('logout-form').submit();">
                    Logout</a>
                    <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                        {{ csrf_field() }}
                    </form>
                <!-- ログイン中の`admin` -->
                @elseif (!empty(Auth::guard('admin')->user()) && strpos($now_route, 'admin') !== false)
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" aria-haspopup="true" v-pre>
                        {{ Auth::guard('admin')->user()->name }} <span class="caret"></span>
                    </a>
                    <ul class="dropdown-menu">
                        <li>
                    <a href="{{ route('admin.logout') }}"
                    onclick="event.preventDefault();
                             document.getElementById('logout-form').submit();">
                    Logout</a>
                    <form id="logout-form" action="{{ route('admin.logout') }}" method="POST" style="display: none;">
                        {{ csrf_field() }}
                    </form>
                @endif
                </li>
            </ul>
        </li>
    </ul>
~~略~~

Login.blade.php

resources/views/auth/login.blade.php
@extends('layouts.app')
<?php $now_route = \Route::currentRouteName(); ?>
@section('content')
~~略~~
    <div class="panel-body">
        <!-- 未ログインの`user` -->
        @if (is_null(Auth::guard('user')->user()) && strpos($now_route, 'admin') === false)
            <form class="form-horizontal" method="POST" action="{{ route('login') }}">
        <!-- 未ログインの`admin` -->
        @elseif (is_null(Auth::guard('admin')->user()) && strpos($now_route, 'admin') !== false)
            <form class="form-horizontal" method="POST" action="{{ route('admin.login') }}">
        @endif
            {{ csrf_field() }}
~~略~~
@endsection

Home.blade.php

resources/views/home.blade.php
<?php
$now_route = \Route::currentRouteName();
//ログイン者の属性条件選択の例
//ルート名からガード選択
$loginName = '';
if (strpos($now_route, 'admin') === false) {
    $loginName = Auth::guard('user')->user()->name;
} elseif (strpos($now_route, 'admin') !== false) {
    $loginName = Auth::guard('admin')->user()->name;
}
?>
@extends('layouts.app')
@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">
                    {{ $loginName . 'さん' }} <!-- 使用例 -->
                </div>
                <div class="panel-body">
                    @if (session('status'))
                        <div class="alert alert-success">
                             {{ session('status') }}
                        </div>
                    @endif
                    You are logged in!
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

いかがでしたでしょうか?
条件分岐のやり方を押さえることができれば改変・応用は簡単です。
少なくとも1つ機能を増やすためにファイル間を移動することは確実に減るでしょう。
改修デバッグ作業も同様です。
この記事は友人マルチ認証に苦しんでいたため書きました。
special thanks to uta & u.

LGTMお願いします!
ストックのついでにお願いします!
モチベーションがあがります!

Fell
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away