Linphone User Registry WEB Front-end:Vol.2

The sequel to Vol.1 Create Linphone account manager web front-end based on laravel-auth. Linphone account management model has the major differences from Laravel user management model which are the following: (1) The password table is independent from the user(account) table. (2)The password algorithm used on the middleware for account authentication. Futhermore, need to add the input field for SIP domain, change User model class to Account model class and add Password model class. Laravel is composed of MVC architecture, write down about the Model part, the View part and the Controller part separately.
Work Flow
1.Model
- Create files for Account Model and Password Model
- Create table files for account as "accounts" and for password as "passwords"
- Create the table data for default accounts
2.Controller
- Correspond to SHA256 password hash algorithm
- Replacement to the account table carries modifications
3.View
- Replacement to the account table carries modifications
- Set start-up page to login
Differential data between laravel-auth, please also refer Github linphone-account-manager repository
1.Model
1-1. Create files for Account Model and Password Model
Replace Laravel default User model to Account model, and create Password model linked to Account model to manage passwords in the separated table from the accounts table.
The user name called "name " in Laravel default "users" table, but Linphone called it "username" in "accounts" table.
Also need to add the additional fields like SIP domain and so on for the registry from Linphone app. Please refer to Laravel document Eloquent: Relationships about models(tables) relation.
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);
}
}
Create Password model to manage "passwords" table separated from "accounts" table.
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. Create table files for account as "accounts" and for password as "passwords"
Create table files for "accounts" and "passwords" tables. Created each table by implementing Laravel artisan migration command.
NOTE) The order of file names follows to cretate tables order.
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. Create the table data for default accounts
Create the table data file defined as the admin account and normal account.
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 = 'admin@admin.com';
$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', '=', 'user@user.com')->first();
if ($user === null) {
$user = Account::create([
'username' => $faker->userName,
'first_name' => $faker->firstName,
'last_name' => $faker->lastName,
'domain' => $faker->domainName,
'email' => 'user@user.com',
'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. Correspond to SHA256 password hash algorithm
Modify the codes of the RegisterController and the UsersManagementController to correspond to the password hash algorithm:SHA256.
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 (a part of selected)
/**
* 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'));
}
Modify the middleware RedirectIfAuthenticated.php which is used for login, password reset, account registry and other authentications to correspond to 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);
}
}
Change the trait in app/Http/Controllers/Auth/ResetPasswordController.php to App\Traits\ResetsPasswords, and create the trait file App\Traits\ResetsPasswords and the trait file app/Traits/RedirectsUsers.php used in it.
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. Replacement to the account table carries modifications
Changed the name of "users" table to "accounts", and "name" called in "users" table to "username" in "accounts" table, each controller should be modified in accordance wiht these changes.
- App\Models\User ---> App\Models\Account Modify User:: ---> Account::
- user_id ---> account_id へ変更
- use App\Models\Password;の追加
config/auth.php Modify model class to Account::
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\Account::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
3.View
3-1. Replacement to the account table carries modifications
Modify "name" ---> "username" and add SIP domain field.
resources/views/auth/register.blade.php (a part of selection)
.....
<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>
.....
jQuery and Popper.js required by Bootstrap in laravel-auth are not correspond to Larevl ver.7, make app.js invalid and add the following script in app.blade.php.
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/popper.js@1.16.0/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. Set start-up page to login
Set the login page as default.
routes/web.php (a part of selection)
// 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');
});