##初めに
ここでいうマルチログイン機能とは、「userの他に更にもう1つ管理者などのログイン機能を追加する」ことをイメージしています。他サイトでもマルチログイン機能の実装はありますが、初学者には少し難しいものが多かった気がします。私がかなり苦戦したので、初めての人でも実装できるようにまとめました。
実装しながら書いているので、コピペして頂ければマルチログイン機能は実装できるはずです。書き間違えないように気をつけます。。
今回はAグループとBグループという名前をつけて分けています。名前を自由に変更してください。よくあるのは「user(利用者)とAdmin(管理者)」でしょうか。基本的に全てA・Bの2個ずつファイルが必要です。途中から書くのが億劫になったので、説明はAグループのみになっています。
前置きが長くなりましたが、それではいきます。
##環境
PHP 7.2.34 / Laravel 6.20.5 / phpMyAdmin 4.9.3 /(MacBook)
##事前準備
①新規フォルダ作成
②コンポーザーのインストール
③スカフォールドのインストール
④データベースの作成(DB名:maltiloginとした)
などなど。これらの説明は省略しますが、下記のようにデフォルトのLaravelのhome画面まで準備できたところから始めます。
この時は、routes->web.php
は下記のようになっています。
<?php
Route::get('/', function () {
return view('welcome');
});
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
##テーブル作成
①Agroupのmigrationを作成。
$ php artisan make:model Models/Agroup -m
②テーブル作成に必要な情報を記述。
(今回は元々準備されているcreate_users_table.php
を貼り付け)
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateAgroupsTable extends Migration
{
public function up()
{
Schema::create('agroups', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('agroups');
}
}
③.envを修正してDBと接続
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=maitilogin //各自入力
DB_USERNAME=******* //各自入力
DB_PASSWORD=******** //各自入力
DB_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sock
④Migrationを実行してテーブル作成
コマンド入力はmigrate
なので注意。(migration
ではない。)
$ php artisan migrate
DB:Maltiloginの中にAgroupのテーブルが生成されているので、同様の手順で
Bgroupのテーブルも生成する。ちなみにテーブル作成は複数個同時にできます。わざわざ分けた意味は特にありません。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Agroup extends Model
{
//省略
}
⑥useで参照しているところがあるので変更。
・app/Http/Controllers/Auth/RegisterController.php
・config/auth.php
※configファイルを変更したら、ターミナルでphp artisan config:cache
を叩く。
私自身、これを書きながらAuth guard [] is not defined.
というエラーにハマりました。
参考:https://note.com/makoto0419/n/n9d0e1c1dc90d
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
//use App\User; //削除
use App\Models\Agroup; //追記
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
//省略
}
'providers' => [
'users' => [
'driver' => 'eloquent',
//'model' => App\User::class, //削除
'model' => App\Models\Agroup::class, //追記
],
⑦Modelを変更する。ファイル生成時にデフォルトで存在しているUser.phpをコピペしてnamespaceやclass名を変更すればOK。
Bgroupも同様。
※extendsがModelからAuthenticatableに変わっているので注意。
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class Agroup extends Authenticatable
{
use Notifiable;
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
}
##認証方式の追加
<?php
return [
// デフォルトの認証をwebからagroupに変更。bgroupを追加
'defaults' => [
'guard' => 'agroup',
'passwords' => 'agroups',
],
'bgroup' => [
'guard' => 'bgroup',
'passwords' => 'bgroups',
],
'guards' => [
'agroup' => [
'driver' => 'session',
'provider' => 'agroups',
],
'bgroup' => [
'driver' => 'session',
'provider' => 'bgroups',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
//下記へ変更
'providers' => [
'agroups' => [
'driver' => 'eloquent',
//'model' => App\User::class, //削除
'model' => App\Models\Agroup::class, //追加
],
'bgroups' => [
'driver' => 'eloquent',
'model' => App\Models\Bgroup::class,
],
],
//デフォルトのusersをagroupへ変更。bgroupを追加。
'passwords' => [
'agroups' => [
'provider' => 'agroups',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
'bgroups' => [
'provider' => 'bgroups',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],
'password_timeout' => 10800,
];
##コントローラー
コントローラの階層は下記のようにする。
①コマンドでAgroup・Bgroupのそれぞれの階層にhomeControllerを生成。
$ php artisan make:controller Agroup/HomeController --resource
$ php artisan make:controller Bgroup/HomeController --resource
※optionのresourceはリソースコントローラを生成する。
リソースコントローラは、あるデータの追加、読み取り、更新、削除についての決まり切った処理を簡潔にできる機能です。
参考:https://tech.windii.jp/backend/laravel/resource-controller
②コントローラーが呼び出された際に認証を通すため、middlewareを記述。Bgroupも同様。
<?php
namespace App\Http\Controllers\Agroup;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('auth:agroup');
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view('agroup.home');
}
authミドルウェアを設定することで、ログインしている人だけがコントローラーのメソッドを実行できる
参考:https://laraweb.net/surrounding/1472/
####新規登録用コントローラー (RegisterController.php)
デフォルトで入っているRegisterController.phpをコピぺしてAgroup・Bgroupの階層下でそれぞれ変更。
<?php
namespace App\Http\Controllers\Agroup\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\Agroup;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Http\Request;
use Illuminate\Auth\Events\Registered;
use Illuminate\Support\Facades\Auth;
class RegisterController extends Controller
{
use RegistersUsers;
protected $redirectTo = RouteServiceProvider::AGROUP_HOME;
public function __construct()
{
$this->middleware('guest:agroup');
}
protected function guard()
{
return Auth::guard('agroup');
}
public function showRegistrationForm()
{
return view('agroup.auth.register');
}
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:agroups'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
protected function create(array $data)
{
return Agroup::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
}
####ログイン用コントローラー(LoginController.php)
デフォルトで入っているLoginController.phpをコピぺしてAgroup・Bgroupの階層下でそれぞれ変更。
<?php
namespace App\Http\Controllers\Agroup\Auth; //要修正
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
use AuthenticatesUsers;
protected $redirectTo = RouteServiceProvider::AGROUP_HOME;
public function __construct()
{
$this->middleware('guest:agroup')->except('logout');
}
protected function guard()
{
return Auth::guard('agroup');
}
public function showLoginForm()
{
return view('agroup.auth.login');
}
public function logout(Request $request)
{
Auth::guard('agroup')->logout();
return $this->loggedOut($request);
}
public function loggedOut(Request $request)
{
return redirect(route('agroup.login'));
}
}
##ルーティング
<?php
//Agroup
Route::namespace('Agroup')->prefix('agroup')->name('agroup.')->group(function(){
//ログイン認証
Auth::routes([
'register' => true,
'reset' => false,
'verify' => false,
]);
//ログイン認証後
Route::middleware('auth:agroup')->group(function(){
//TOPページ表示
Route::resource('home','HomeController',['only'=>'index']);
});
});
//Bgroup
Route::namespace('Bgroup')->prefix('bgroup')->name('bgroup.')->group(function(){
//ログイン認証
Auth::routes([
'register' => true,
'reset' => false,
'verify' => false,
]);
//ログイン認証後
Route::middleware('auth:bgroup')->group(function(){
//TOPページ表示
Route::resource('home','HomeController',['only'=>'index']);
});
});
##ログイン認証後のリダイレクト先
デフォルトでは/home
にリダイレクトするようになっているため、Agroup,Bgroupを追記。
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
// agroupのリダイレクト先
public const AGROUP_HOME = '/agroup/home';
// bgroupのリダイレクト先
public const BGROUP_HOME = '/bgroup/home';
//以降、省略
}
###未ログイン時の挙動
ログインされていないのに、ログイン認証が必要なページにアクセスした場合のリダイレクト先を指定する。
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
protected $agroup_route = 'agroup.login';
protected $bgroup_route = 'bgroup.login';
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function redirectTo($request)
{
//ルーティングに応じて未ログイン時のリダイレクト先を振り分ける
if (! $request->expectsJson()) {
if(Route::is('agroup.*')) {
return route($this->agroup_route);
} elseif (Route::is('bgroup.*')){
return route($this->bgroup_route);
}
}
}
}
###ログイン時の挙動
ログインしている時に/login
にアクセスされた時のリダイレクト先を記述。
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check() && $guard === 'agroup') {
return redirect(RouteServiceProvider::AGROUP_HOME);
} elseif (Auth::guard($guard)->check() && $guard === 'bgroup'){
return redirect(RouteServiceProvider::BGROUP_HOME);
}
return $next($request);
}
}
##ビュー
階層は下記のようにして、Agroup・Bgroup同様に用意する。
###layout.app.blade.php
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div id="app">
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', '!Agroup!') }}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav mr-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ml-auto">
<!-- Authentication Links -->
@unless (Auth::guard('agroup')->check())
<li class="nav-item">
<a class="nav-link" href="{{ route('agroup.login') }}">{{ __('Login') }}</a>
</li>
@if (Route::has('agroup.register'))
<li class="nav-item">
<a class="nav-link" href="{{ route('agroup.register') }}">{{ __('Register') }}</a>
</li>
@endif
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }} <span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('agroup.logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('agroup.logout') }}" method="POST" style="display: none;">
@csrf
</form>
</div>
</li>
@endunless
</ul>
</div>
</div>
</nav>
<main class="py-4">
@yield('content')
</main>
</div>
</body>
</html>
###register.blade.php
@extends('layouts.agroup.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Register') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('agroup.register') }}">
@csrf
<div class="form-group row">
<label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>
@error('name')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email">
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Register') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
###login.blade.php
@extends('layouts.agroup.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Login') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('agroup.login') }}">
@csrf
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<div class="col-md-6 offset-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
<label class="form-check-label" for="remember">
{{ __('Remember Me') }}
</label>
</div>
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Login') }}
</button>
@if (Route::has('password.request'))
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
@endif
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
###home.blade.php
@extends('layouts.agroup.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Dashboard</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
You are Agroup logged in!
</div>
</div>
</div>
</div>
</div>
@endsection
最終的に下記のような表示画面となる。
####登録画面 register.blade.php
##動作確認
それぞれの画面をURLからアクセスして、動作を確認。
意図した挙動をして、ログイン機能の住み分けができたことも確認できた。
少し分かりづらいが、下記のように各テーブルに分けて保存されている。
参考:
https://qiita.com/darum/items/82f5b0ab83f175c45687
https://qiita.com/namizatork/items/5d56d96d4c255a0e3a87
##最後に
私がLaravelを初めて触った時に実装したので、それぞれのファイルの役割が分からず丸三日掛かった記憶があります。他のサイトでもマルチログイン機能の実装は見ることができますが、本当に初めての初学者には少し難しいものが多かった気がします。なるべく詳細に書いたつもりなので、少しでも力になれればと思います。
ただ、独学で学習しているので、あまり詳しく書けていないところがあります。今後、より分かりやすく改善していきます。
(本内容に誤りがあれば、ご教授いただけると幸いです。)