前回Vol.1の続き。laravel-authをベースにLinphoneユーザ管理ウエブフロントエンドを作成します。Linphoneのユーザ管理システムにおけるLaravel標準のユーザ管理システムとの大きな相違点は、(1)パスワードテーブルがユーザテーブルから独立していること、(2)ユーザ認証時などに使用されるミドルウェアのパスワード暗号化アルゴリズムです。その他、SIPアドレスの入力項目の追加、ユーザクラス名の変更などが必要となります。MVCアーキテクチャを採用したLaravelフレームワークのModel, View, Controllerの箇所に分けて説明します。
作業フロー
1.Model
- Accountモデル、Passwordモデルファイルの作成
- accountsテーブル、passwordsテーブルファイルの作成
- accountテーブル用データファイルの作成
2.Controller
- SHA256パスワードアルゴリズムへの対応
- accountsテーブルへの置換えに伴う変更
3.View
- accountsテーブルへの置換えに伴う変更
- 初期画面の設定
1.Model
1-1.Accountモデル、Passwordモデルファイルの作成
Laravel標準のUserモデルをAccountモデルに置換え、Accountに紐付けされたパスワードを専用テーブルで管理するためPasswordモデルを作成します。
標準のUserモデルではユーザ名を “name” としていますが、Accountモデルではアカウント名を “username” とします。
またSIPドメイン、Linphoneアプリからユーザ登録する際に必要な項目を追加します。パスワードテーブルとの紐付けについてはLaravelドキュメント Eloquent: Relationships も参考にして下さい。
app/Models/Account.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use jeremykenedy\LaravelRoles\Traits\HasRoleAndPermission;
class Account extends Authenticatable
{
use HasRoleAndPermission;
use Notifiable;
use SoftDeletes;
/**
* The database table used by the model.
*
* @var string
*/
protected $table = 'accounts';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
/**
* The attributes that are not mass assignable.
*
* @var array
*/
protected $guarded = [
'id',
];
/**
* The relationships that should always be loaded.
*
* @var array
*/
protected $with = ['password'];
/**
* The attributes that are hidden.
*
* @var array
*/
protected $hidden = [
'remember_token',
'activated',
'token',
'confirmation_key',
];
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = [
'created_at',
'updated_at',
'deleted_at',
'creation_time',
'expire_time'
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'username',
'first_name',
'last_name',
'domain',
'email',
'activated',
'token',
'signup_ip_address',
'signup_confirmation_ip_address',
'signup_sm_ip_address',
'admin_ip_address',
'updated_ip_address',
'deleted_ip_address',
'confirmation_key',
'ip_address',
'user_agent',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'username' => 'string',
'first_name' => 'string',
'last_name' => 'string',
'domain' => 'string',
'email' => 'string',
'activated' => 'boolean',
'token' => 'string',
'signup_ip_address' => 'string',
'signup_confirmation_ip_address' => 'string',
'signup_sm_ip_address' => 'string',
'admin_ip_address' => 'string',
'updated_ip_address' => 'string',
'deleted_ip_address' => 'string',
'confirmation_key' => 'string',
'ip_address' => 'string',
'user_agent' => 'string',
'creation_time' => 'datetime',
'expire_time' => 'datetime',
];
/**
* Get the socials for the user.
*/
public function social()
{
return $this->hasMany('App\Models\Social');
}
/**
* Get the passwords for the account.
*/
public function password()
{
return $this->hasOne('App\Models\Password');
}
/**
* Get the profile associated with the user.
*/
public function profile()
{
return $this->hasOne('App\Models\Profile');
}
/**
* The profiles that belong to the user.
*/
public function profiles()
{
return $this->belongsToMany('App\Models\Profile')->withTimestamps();
}
/**
* Check if a user has a profile.
*
* @param string $username
*
* @return bool
*/
public function hasProfile($username)
{
foreach ($this->profiles as $profile) {
if ($profile->account->username === $username) {
return true;
}
}
return false;
}
/**
* Add/Attach a profile to a user.
*
* @param Profile $profile
*/
public function assignProfile(Profile $profile)
{
return $this->profiles()->attach($profile);
}
/**
* Remove/Detach a profile to a user.
*
* @param Profile $profile
*/
public function removeProfile(Profile $profile)
{
return $this->profiles()->detach($profile);
}
}
Accountテーブルとは独立してパスワードを管理するためPasswordモデルを作成します。
app/Models/Password.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Password extends Model
{
/**
* The database table used by the model.
*
* @var string
*/
protected $table = 'passwords';
/**
* The attributes that are not mass assignable.
*
* @var array
*/
protected $guarded = [
'id',
];
/**
* The attributes that are hidden.
*
* @var array
*/
protected $hidden = [
'password',
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'account_id',
'password',
'algorithm',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'account_id' => 'integer',
'password' => 'string',
'algorithm' => 'string',
];
public function account()
{
return $this->belongsTo('App\Models\Account');
}
/* public function setPasswordAttribute($password)
{
$this->attributes['password'] = hash('sha256', $password->account->username.':'.$password->account->domain.':'.$password);
} */
}
1-2.accountsテーブル、passwordsテーブルファイルの作成
アカウントとパスワードのテーブルファイルを作成します。Laravelによるartisan migrationコマンドで指定データベース内に各テーブルが作成されます。
注)ファイル名の順序がテーブル作成の順序に反映されるため、テーブルの名称に注意
database/migrations/2013_04_29_123510_create_accounts_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateAccountsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('accounts', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('username', 64)->unique();
$table->string('domain', 64);
$table->string('email', 64)->unique()->nullable();
$table->string('first_name')->nullable();
$table->string('last_name')->nullable();
$table->timestamp('email_verified_at')->nullable();
$table->rememberToken();
$table->boolean('activated')->default(false);
$table->string('token');
$table->ipAddress('signup_ip_address')->nullable();
$table->ipAddress('signup_confirmation_ip_address')->nullable();
$table->ipAddress('signup_sm_ip_address')->nullable();
$table->ipAddress('admin_ip_address')->nullable();
$table->ipAddress('updated_ip_address')->nullable();
$table->ipAddress('deleted_ip_address')->nullable();
$table->string('confirmation_key', 14)->nullable();
$table->string('ip_address', 39)->nullable();;
$table->string('user_agent', 256)->nullable();;
$table->datetime('creation_time')->nullable();;
$table->datetime('expire_time')->nullable();
$table->timestamps();
$table->softDeletes();
});
Schema::create('passwords', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('account_id')->unsigned()->index();
$table->string('password', 255);
$table->string('algorithm', 10)->default('SHA-256');
$table->timestamps();
//Relationships
//$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('accounts');
Schema::dropIfExists('passwords');
}
}
database/migrations/2014_10_12_100000_create_password_resets_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePasswordResetsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('password_resets', function (Blueprint $table) {
$table->increments('id');
$table->string('email')->index();
$table->string('token')->index();
$table->timestamp('created_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('password_resets');
}
}
1-3.accountテーブル用データファイルの作成
管理者アカウントと一般アカウントを定義したaccounts,passwordsテーブル用データファイルを用意します。
database/seeds/AccountsTableSeeder.php
<?php
use App\Models\Profile;
use App\Models\Account;
use App\Models\Password;
use Illuminate\Database\Seeder;
use jeremykenedy\LaravelRoles\Models\Role;
class AccountsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$faker = Faker\Factory::create();
$password = new Password();
$profile = new Profile();
$adminRole = Role::whereName('Admin')->first();
$userRole = Role::whereName('User')->first();
// Seed test admin
$seededAdminEmail = '[email protected]';
$user = Account::where('email', '=', $seededAdminEmail)->first();
if ($user === null) {
$user = Account::create([
'username' => $faker->userName,
'first_name' => $faker->firstName,
'last_name' => $faker->lastName,
'domain' => $faker->domainName,
'email' => $seededAdminEmail,
'token' => str_random(64),
'activated' => true,
'signup_confirmation_ip_address' => $faker->ipv4,
'admin_ip_address' => $faker->ipv4,
'user_agent' => $faker->userAgent,
'ip_address' => $faker->ipv4,
'creation_time' => $faker->dateTime,
]);
$password = Password::create ([
'account_id' => $user->id,
'password' => hash('sha256', $user->username.':'.$user->domain.':testtest'),
'algorithm' => 'SHA-256',
]);
$user->password()->save($password);
$user->profile()->save($profile);
$user->attachRole($adminRole);
$user->save();
}
// Seed test user
$user = Account::where('email', '=', '[email protected]')->first();
if ($user === null) {
$user = Account::create([
'username' => $faker->userName,
'first_name' => $faker->firstName,
'last_name' => $faker->lastName,
'domain' => $faker->domainName,
'email' => '[email protected]',
'token' => str_random(64),
'activated' => true,
'signup_ip_address' => $faker->ipv4,
'signup_confirmation_ip_address' => $faker->ipv4,
'user_agent' => $faker->userAgent,
'ip_address' => $faker->ipv4,
'creation_time' => $faker->dateTime,
]);
$password = Password::create ([
'account_id' => $user->id,
'password' => hash('sha256', $user->username.':'.$user->domain.':testtest'),
'algorithm' => 'SHA-256',
]);
$user->password()->save($password);
$user->profile()->save(new Profile());
$user->attachRole($userRole);
$user->save();
}
// Seed test users
// $user = factory(App\Models\Profile::class, 5)->create();
// $users = Account::All();
// foreach ($users as $user) {
// if (!($user->isAdmin()) && !($user->isUnverified())) {
// $user->attachRole($userRole);
// }
// }
}
}
2.Controller
2-1.SHA256パスワードアルゴリズムへの対応
独立したパスワードテーブルとパスワードハッシュアルゴリズム SHA256 に対応するように、以下 RegisterController, UsersManagementController のコードを改変します。
app/Http/Controllers/Auth/RegisterController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\Profile;
use App\Models\Account;
use App\Models\Password;
use App\Traits\ActivationTrait;
use App\Traits\CaptchaTrait;
use App\Traits\CaptureIpTrait;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use jeremykenedy\LaravelRoles\Models\Role;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use ActivationTrait;
use CaptchaTrait;
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = '/activate';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest', [
'except' => 'logout',
]);
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
*
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
$data['captcha'] = $this->captchaCheck();
if (! config('settings.reCaptchStatus')) {
$data['captcha'] = true;
}
return Validator::make(
$data,
[
'username' => 'required|max:255|unique:accounts',
'first_name' => '',
'last_name' => '',
'domain' => 'required|max:64',
'email' => 'required|email|max:255|unique:accounts',
'password' => 'required|min:6|max:30|confirmed',
'password_confirmation' => 'required|same:password',
'g-recaptcha-response' => '',
'captcha' => 'required|min:1',
],
[
'username.unique' => trans('auth.userNameTaken'),
'username.required' => trans('auth.userNameRequired'),
'first_name.required' => trans('auth.fNameRequired'),
'last_name.required' => trans('auth.lNameRequired'),
'domain.required' => trans('auth.domainRequired'),
'email.required' => trans('auth.emailRequired'),
'email.email' => trans('auth.emailInvalid'),
'password.required' => trans('auth.passwordRequired'),
'password.min' => trans('auth.PasswordMin'),
'password.max' => trans('auth.PasswordMax'),
'g-recaptcha-response.required' => trans('auth.captchaRequire'),
'captcha.min' => trans('auth.CaptchaWrong'),
]
);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
*
* @return Account
*/
protected function create(array $data)
{
$ipAddress = new CaptureIpTrait();
if (config('settings.activation')) {
$role = Role::where('slug', '=', 'unverified')->first();
$activated = false;
} else {
$role = Role::where('slug', '=', 'user')->first();
$activated = true;
}
$user = Account::create([
'username' => $data['username'],
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'domain' => $data['domain'],
'email' => $data['email'],
'token' => str_random(64),
'signup_ip_address' => $ipAddress->getClientIp(),
'activated' => $activated,
]);
$password = Password::create ([
'account_id' => $user->id,
'password' => hash('sha256', $user->username.':'.$user->domain.':'.$data['password']),
'algorithm' => 'SHA-256',
]);
$user->attachRole($role);
$this->initiateEmailActivation($user);
if (! config('settings.activation')) {
$user->password()->save($password);
$profile = new Profile();
$user->profile()->save($profile);
$user->save();
}
return $user;
}
}
app/Http/Controllers/UsersManagementController.php(一部抜粋)
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make(
$request->all(),
[
'username' => 'required|max:255|unique:accounts',
'first_name' => '',
'last_name' => '',
'domain' => 'required|max:64',
'email' => 'required|email|max:255|unique:accounts',
'password' => 'required|min:6|max:20|confirmed',
'password_confirmation' => 'required|same:password',
'role' => 'required',
],
[
'username.unique' => trans('auth.userNameTaken'),
'username.required' => trans('auth.userNameRequired'),
'first_name.required' => trans('auth.fNameRequired'),
'last_name.required' => trans('auth.lNameRequired'),
'domain.required' => trans('auth.domainRequired'),
'email.required' => trans('auth.emailRequired'),
'email.email' => trans('auth.emailInvalid'),
'password.required' => trans('auth.passwordRequired'),
'password.min' => trans('auth.PasswordMin'),
'password.max' => trans('auth.PasswordMax'),
'role.required' => trans('auth.roleRequired'),
]
);
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}
$ipAddress = new CaptureIpTrait();
$profile = new Profile();
$user = Account::create([
'username' => $request->input('username'),
'first_name' => $request->input('first_name'),
'last_name' => $request->input('last_name'),
'domain' => $request->input('domain'),
'email' => $request->input('email'),
'token' => str_random(64),
'admin_ip_address' => $ipAddress->getClientIp(),
'activated' => 1,
]);
$password = Password::create ([
'account_id' => $user->id,
'password' => hash('sha256', $user->username.':'.$user->domain.':'.$request->input('password')),
'algorithm' => 'SHA-256',
]);
$user->password()->save($password);
$user->profile()->save($profile);
$user->attachRole($request->input('role'));
$user->save();
return redirect('users')->with('success', trans('usersmanagement.createSuccess'));
}
ログイン、アカウント登録、パスワードリセットなどの認証処理に使用されるミドルウェア RedirectIfAuthenticated.php をSHA256に対応するよう改変します。
app/Http/Middleware/RedirectIfAuthenticated.php
<?php
namespace App\Http\Middleware;
use Closure;
use App\Models\Account;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
*
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
$password_bool = false;
if ($request->has('email')) {
$email = $request->input('email');
$input_password = $request->input('password');
$account = Account::where('email', $email)->first();
if ($account == null) {
return $next($request);
}
$username = $request->input('username', $account->username);
$domain = $account->domain;
$hash_pass = hash('sha256', $username.':'.$domain.':'.$input_password);
if ($request->has('token')) {
if (Auth::guard($guard)->check()) {
return redirect('/home');
} else {
return $next($request);
}
}
$password = $account->password;
$password_bool = hash_equals($password->password, $hash_pass);
}
if ($password_bool) {
Auth::login($account, true);
Auth::guard($guard)->check();
return redirect('/home');
}
return $next($request);
}
}
パスワードリセットコントローラ app/Http/Controllers/Auth/ResetPasswordController.php 内で使用されているトレイトを App\Traits\ResetsPasswords へ変更し、 app/Traits/ResetsPasswords.php と、このトレイト内で使用されている app/Traits/RedirectsUsers.php を作成します。
app/Traits/ResetsPasswords.php
<?php
namespace App\Traits;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
trait ResetsPasswords
{
use RedirectsUsers;
/**
* Display the password reset view for the given token.
*
* If no token is present, display the link request form.
*
* @param \Illuminate\Http\Request $request
* @param string|null $token
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showResetForm(Request $request, $token = null)
{
return view('auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
);
}
/**
* Reset the given user's password.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
public function reset(Request $request)
{
$request->validate($this->rules(), $this->validationErrorMessages());
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$response = $this->broker()->reset(
$this->credentials($request), function ($user, $password) {
$this->resetPassword($user, $password);
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
return $response == Password::PASSWORD_RESET
? $this->sendResetResponse($request, $response)
: $this->sendResetFailedResponse($request, $response);
}
/**
* Get the password reset validation rules.
*
* @return array
*/
protected function rules()
{
return [
'token' => 'required',
'email' => 'required|email',
'password' => 'required|confirmed|min:8',
];
}
/**
* Get the password reset validation error messages.
*
* @return array
*/
protected function validationErrorMessages()
{
return [];
}
/**
* Get the password reset credentials from the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
protected function credentials(Request $request)
{
return $request->only(
'email', 'password', 'password_confirmation', 'token'
);
}
/**
* Reset the given user's password.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @param string $password
* @return void
*/
protected function resetPassword($user, $password)
{
$this->setUserPassword($user, $password);
$user->setRememberToken(Str::random(60));
$user->password->save();
$user->save();
event(new PasswordReset($user));
$this->guard()->login($user);
}
/**
* Set the user's password.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @param string $password
* @return void
*/
protected function setUserPassword($user, $password)
{
$username = $user->username;
$domain = $user->domain;
$user->password->password = hash('sha256', $username.':'.$domain.':'.$password);
//dd($user);
}
/**
* Get the response for a successful password reset.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetResponse(Request $request, $response)
{
if ($request->wantsJson()) {
return new JsonResponse(['message' => trans($response)], 200);
}
return redirect($this->redirectPath())
->with('status', trans($response));
}
/**
* Get the response for a failed password reset.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetFailedResponse(Request $request, $response)
{
if ($request->wantsJson()) {
throw ValidationException::withMessages([
'email' => [trans($response)],
]);
}
return redirect()->back()
->withInput($request->only('email'))
->withErrors(['email' => trans($response)]);
}
/**
* Get the broker to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker()
{
return Password::broker();
}
/**
* Get the guard to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard();
}
}
app/Traits/RedirectsUsers.php
<?php
namespace App\Traits;
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';
}
}
2-2.accountsテーブルへの置換えに伴う変更
users テーブルから accounts テーブルへの名称変更、 users テーブル内の" name" から accounts テーブル内の" username" へ変更したため、各コントローラの該当箇所を修正します。
- App\Models\User —> App\Models\Account インスタンスの箇所もUserからAccountへ変更
- user_id —> account_id へ変更
- use App\Models\Password;の追加
config/auth.php 以下モデルクラス変更
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\Account::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
3.View
3-1.accountsテーブルへの置換えに伴うファイル変更
“name” —> “username” へ変更し、SIPドメイン入力メニューの箇所を追加。
resources/views/auth/register.blade.php (一部抜粋)
.....
<div class="form-group row">
<label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Username') }}</label>
<div class="col-md-6">
<input id="username" type="text" class="form-control{{ $errors->has('username') ? ' is-invalid' : '' }}" name="username" value="{{ old('username') }}" required autofocus>
@if ($errors->has('username'))
<span class="invalid-feedback">
<strong>{{ $errors->first('username') }}</strong>
</span>
@endif
</div>
</div>
.....
.....
<div class="form-group row">
<label for="domain" class="col-md-4 col-form-label text-md-right">{{ __('SIP Domain') }}</label>
<div class="col-md-6">
<input id="domain" type="text" class="form-control{{ $errors->has('domain') ? ' is-invalid' : '' }}" name="domain" value="{{ old('domain') }}" required autofocus>
@if ($errors->has('domain'))
<span class="invalid-feedback">
<strong>{{ $errors->first('domain') }}</strong>
</span>
@endif
</div>
</div>
.....
laravel-authのBootstrapに必要な jQuery, Popper.js バージョンが Larevl ver.7 に対応していないので、 app.blade.php の app.js を無効にし最新版を指定。
resources/views/layouts/app.blade.php
{{-- Scripts --}}
<!-- script src="{{ mix('/js/app.js') }}"></script -->
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
3-2.初期画面の設定
ログイン画面がホームページになるように設定
routes/web.php(一部抜粋)
// Homepage Route
Route::group(['middleware' => ['web', 'checkblocked']], function () {
// Route::get('/', 'WelcomeController@welcome')->name('welcome');
Route::get('/', 'UserController@index')->middleware('auth');
Route::get('/terms', 'TermsController@terms')->name('terms');
});