65
82

More than 3 years have passed since last update.

Laravelで認証処理を作ってみる

Last updated at Posted at 2019-09-02

いよいよ本格的にLaravelを使ってみるのですが。
まずはログイン周りをきちんと作ろう。

Laravel Ver8の認証処理も作ってみたよ。最新版を使う人は、こっちも見てね。

環境
ホストOS:Windows10
laravel開発環境:Homestead(だからubuntu & MySQL)
laravelバージョン:5.8.17
phpバージョン:7.3.8

仕様

サンプルサイトの構成

以下の画面を作成。
・ログイン画面
・メニュー画面
・照会画面
・更新画面

照会画面、更新画面はとりあえずダミーで、表示するだけにしておきます。

ログイン方法

ログイン画面でID,パスワードを入力することでログインする。
今時ログインはメールアドレスでするって知っているけど、IDでログインしたいんだよ。許してくれ。
テーブル定義はデフォルトのマイグレーションファイルで作られているusersテーブルを使用する。

実装

make:authしてみる

バージョン6から手順が変わっています!!
下記参照してください
Laravel 6.0 ログイン機能を実装する
https://blog.capilano-fw.com/?p=4576
おじさんはnpmインストールするところで結構はまったけど。suでnpmコマンド実行してね。npmはホストOSで実行してね。

--ここからlaravel バージョン5.8の手順です--
なにしろコマンド一発で基本的な設定ができるっていうじゃないか。
試してみる。

$ php artisan make:auth

新旧フォルダーでコンペアしてみたら、以下の差異があった。
・/app/Http/ControllersにHomeController.phpが追加されていた。
・/resource/viewにauthフォルダ、layoutsフォルダ、home.blade.phpが追加されていた。
・/routes/web.phpに/homeのルートが追加されていた。

web.php
Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');

このままlaravelを起動してみる。
ブラウザで/loginでアクセスすると、なんともう画面ができている。

image.png

右上の「Register」をクリックすると、ユーザー登録画面が表示される。
image.png
まだテーブル作ってなければ、マイグレーションしてから項目入力してRegisterボタンをクリック。
念のためマイグレーションは下記コマンドで実行

$ php artisan migrate

入力例
image.png
なんと、パスワードを「test」にしたら、「8文字以上入れろよ!」というエラーになった。
image.png
何にもしてないのに、Validationまでしちまうのか。laravelすげー。
言われた通り8文字にしてみる。
image.png
「Register」をクリックすると、完了画面が表示される。

image.png

右上にログインユーザーのNameが表示される。
ユーザー名をクリックすると、「Logout」が表示される。

image.png

「Logout」をクリックすると、Laravelのメニュー画面に戻る。

image.png

なんだ、もう出来たじゃないか。

大枠の流れを作ってしまう

本当はここから認証周りのカスタマイズをするのが記事としては正しい気がするが、気持ち的に自分のやりたい動きを先に作ってしまいたくなる。
自分の気持ちに正直に生きることにして、全体の流れを作ってしまう。
・Login後のリダイレクト先をメニュー画面にする。
・メニューから照会画面に遷移する。
 ログインしないで照会画面を開こうとしても、ログインしていないから開けないようにする。

以下のページを参考にさせていただきました。
LaravelのAuth認証機能をカスタマイズし意図した挙動へ変更するTips

自作のメニュー画面へ遷移させる

とりあえず細かい理屈は気にせず作ってみる。Don't think,Feel.

メニュー画面を作る

/resources/viewフォルダに/testフォルダを追加して、その中にmenu.blade.phpを作る。
そして/homeにリダイレクトしているところをすべて/menuに変更してみる。
・・・表示されないよ。ネットに書いてあった通りにやったのに。やっぱネットなんてフェイクと嘘とデマばっかりなんだ。
なんて思考停止していないで、考える。やっぱthink大事。

普通にMVC経験していた人なら、conntrollerもなくてなんで処理動くの?って思うことでしょう。
laravelではartisanでcontrollerも作れちゃう。
とりあえずTestフォルダ内にMenuControllerを作ってみます。

$php artisan make:controller Test/MenuController

コマンドを実行すると、/app/Http/Controllersの下にTestフォルダ(なかったら新規作成してくれる)とMenuController.phpが作成される。

MenuController.php
<?php

namespace App\Http\Controllers\Test;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class MenuController extends Controller
{
    //
}

中身はこれだけでした。
これだけじゃさすがに何も動かないよね。でもextendsしてくれたりするので、いちから作るときは便利かも。
ここに作成したmenu.blade.phpを表示するよう処理を記述する。

MenuController.php
<?php

namespace App\Http\Controllers\Test;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class MenuController extends Controller
{
    // 以下の記述を追加
    public function menu()
    {
        return view('test/menu');
    }
    // ここまで
}

/resource/view/test/menu.blade.phpを表示するだけの内容です。

あと、Routeも追加する。
/routes/web.phpの最後に以下の処理を記述する。

web.php
Route::get('/test/menu', 'Test\MenuController@menu');

ちなみにviewはまだ適当な内容しか書いていない。

menu.blade.php
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
    <a href="">照会処理</a><br />
    <a href="">更新処理</a>
</body>
</html>

この状態で、ブラウザからmenuにアクセスしてみる。

image.png

適当なメニューが表示された。

Login画面からメニューに遷移させる

Loginしたら、この適当なメニューを表示させたいんだけど。
なので、LoginController.phpのredirectToを変更してみる。

LoginController.php
// $redirectTo = 'home';から変更
protected $redirectTo = '/test/menu'; 

変更したら、Laravelのログイン画面から適当なメニューに遷移するかテストしてみる。

image.png

image.png

image.png

ちゃんと遷移できました。
しかーし!!
このままでは、ログインしていなくても/test/menuにアクセスできてしまう。
ちゃんと認証のチェックしようよ。

認証チェック処理を組み込む

ログイン済みかどうかの判断は、Facades\AuthにあるAuth::check()で行うことができる。

MenuController.php
<?php

namespace App\Http\Controllers\Test;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
// Facades\Authを追加
use Illuminate\Support\Facades\Auth;

class MenuController extends Controller
{
    public function menu()
    {
        // ログインしていたら、test/menuを表示
        if (Auth::check()) {
            return view('test/menu');
        } else {
            // ログインしていなかったら、Login画面を表示
            return view('auth/login');
        }
    }
}

これだけで、認証済みか判断できる。簡単!

Logoutできるようにする

このままだとログアウトできないので、ログアウト処理を組み込んでみる。
デフォルトの処理ではユーザー名をクリックすると「Logout」が表示される。

image.png

home.blade.phpの中を見てみると。

home.blade.php
@extends('layouts.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 logged in!
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Logoutの処理はここには書いてないらしい。上のほうに書いてあるから、layouts.appの中を見てみる。
layouts.appは/resources/views/layoutsの中のapp.blade.phpが本体らしい。
ログアウトに絡む処理は下記の部分。

<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
    <a class="dropdown-item" 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
    </form>
</div>

ドロップダウンにlogoutのルートが設定されて、クリックしたらlogout-formを呼び出す様子。
展開されるとこうなる。

image.png

Javascriptが駆使されていて、getElementByIdしつつpreventDefaultするという。日本語で言うと、「id='logout-form'を取り込むよ。画面には出さないけど。」ていう感じか。
とりあえずこれを丸パクリする。

<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

@csrfは入れておかないと、419のエラーページが表示される。csrfトークンが有効期限切れ、ということらしい。csrfもデフォルト対応とは、手厚くてありがたい。

これで適当なメニュー画面からログアウトできるようになりました。

image.png

ログアウトをクリックすると。

image.png

認可処理を組み込む

さて、照会処理は誰でもできるけど、更新処理は特定の人しかできない、なんて処理はどのように組み込めばいいか?

ユーザ属性の追加

今回は単純に更新できる人かそうでないかをbooleanで持たせることにする。
userテーブルに項目を追加する。
/databases/migrationsのの中身を変えなくても項目追加ができる。まじか。
まずはartisan make:migrationで項目追加する処理を作成する。
※artisanコマンドでphpファイルを追加しないとmigrateをするときに項目追加として扱われないから注意。

$ php artisan make:migration add_column_users_table --table=users

正常終了すると、/databases/migrationsの下にadd_column_users_table.phpが作成される。
中身は例によってほぼ空。

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddColumnUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            //
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            //
        });
    }
}

これに追加する項目の処理を追加する。

    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            //
            $table->boolean('admin_chk')->default(0)->after('password'); //これを追加
        });
    }

    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            //
            $table->dropColumn('admin_chk'); //これを追加
        });
    }

そしてmigrate:refreshでテーブルを再作成します。データ消えちゃうけどね。
image.png

作成後のテーブルはこんな感じ。
image.png

画面にも項目追加する。
/resources/auth/register.blade.phpに追加したupdateを追加する。

//追加した部分だけ
<div class="form-group row">
    <label for="admin_chk" class="col-md-4 col-form-label text-md-right">{{ __('Admin') }}</label>
    <div class="col-md-6">
        <div>
            {{ Form::hidden('admin_chk', '0') }}
            {{ Form::checkbox('admin_chk', '1') }}
        </div>
    </div>
</div>

/app/user.phpにも追加した項目を追加する。

user.php
protected $fillable = [
    'name', 'email', 'password','admin_chk',
];

ここだけの話、チェックボックスで結構はまった。上記のようにFormを使ったのだが、デフォルトのままではFormがインストールされていない。
下記サイトを参考にしました。ありがとう。

checkboxが未チェックのときの値を設定する方法
Laravelで「Formクラスが無い」とエラーが出た時の対処法

ちなみにapp.phpは/configの中にあるやつです。

さて、画面はcssがちゃんと当たってないので微妙な表示だが、とりあえずこれで進めてしまう。

image.png

updateをチェックした人としていない人をそれぞれ登録する。
image.png

ぞんざいな照会画面、更新画面を作る

とりあえずテストなので、ぞんざいな画面をふたつ作る。

search.blade.php
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
<h1>照会画面</h1>
こんにちは{{ Auth::user()->name }}<br /><br />
何か表示するよそのうちね
<br />
{!! Form::open() !!}
<button type="submit" name="action" value="back">{{ __('back') }}</button>
<br />
</body>
</html>
update.blade.php
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
<h1>更新処理</h1>
こんにちは{{ Auth::user()->name }}<br /><br />
入力してね本当は何も更新しないけど<br />
@if(session('adminmsg'))
    {{ Session('adminmsg') }}<br />
@endif

{!! Form::open() !!}
<input type="text" name="inputdata">
<br /><br />
<button type="submit" name="action" value="update">{{__('update')}}</button>
<button type="submit" name="action" value="back">{{ __('back') }}</button>
<br />
</body>
</html>

一応updateボタンを押したらそれっぽく更新画面を出すようにした。実際は何も更新してないけどね。

complete.blade.php
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
<h1>更新完了画面</h1>
こんにちは{{ Auth::user()->name }}<br /><br />
{!! Form::open() !!}
    これを入力したよね<br />
    {{ $inputdata }}<br /><br />
    <button type="submit" name="action" value="back">{{ __('back') }}</button>
</body>
</html>

web.phpに上記画面のrouteを追加する。

web.php
//下記を追加
Route::post('/test/search', 'Test\MenuController@search')->name('search');
Route::post('/test/update', 'Test\MenuController@update')->name('update');
Route::post('/test/complete', 'Test\MenuController@complete')->name('complete');

MenuController.phpにbackボタンを押された場合の処理を追加する。

MenuController.php
//下記を追加
public function search(Request $request )
{
    $action = $request->get('action', 'back');
    $input = $request->except('action');

    if($request->action === 'back') {
        return view('test/menu');
    }
}

public function update(Request $request )
{
    $action = $request->get('action', 'back');
    $input = $request->expect('action');

    if($request->action === 'back') {
        return view('test/menu');
    }
}

public function complete(Request $request )
{
    $action = $request->get('action', 'back');
    $input = $request->except('action');

    if($request->action === 'back') {
        return view('test/menu');
    }
}

これでとりあえずぞんざいな画面が表示できるようになった。

image.png
image.png

updateボタンをクリックすると、更新完了画面に遷移する。

image.png

認可処理を実装する

概要

さて。これらの画面に、以下のような制約をつけたい。
・usersのadmin_chkが1の人は、照会画面も更新画面もアクセス可能。
・usersのadmin_chkが0の人は、照会画面しかアクセスできない。

laravelの認可処理には、GateとPolicyの二種類の方法がありんす。
勝手な解釈で、以下のような違いかな、と。
・Gate
認可する処理ひとつひとつを個別に作成する方法。
1.defineで認可処理を記述する。
2.defineでつけた名前で認可処理を行う
・Policy
特定のモデルやリソースに紐付けたようなクラスが作成できる。
CRUDに紐づけた認可処理を書けるので、updateやdeleteの時の認可処理などが簡単に書ける。

どっちでも同じように認可処理は書けるけど、全体的な認可処理はgate、細かい設定をしたいときはpolicyにするのがよさそう。

今回はざっくりなので、gateで認可処理を書いてみる。

実装1 更新画面に認可処理を入れる

gateは/app/providersの中にあるAuthServiceProvider.phpの中に設定する。

AuthServiceProvider.php
public function boot()
{
    $this->registerPolicies();
    //以下の処理を追加
    @Gate::define('admin', function (User $user) {
        return $user->admin_chk == true;
    });
}

usersテーブルのadmin_chkがtrueだったらtrueを返す、という設定。
これを受けてMenuController.phpに認可処理を追加する。

MenuController.php
public function update(Request $request )
{
    $action = $request->get('action', 'update');
    $input = $request->except('action');

    if($request->action === 'update') {
        //認可処理
        if(Gate::allows('admin')) {
            $res = $request->input('inputdata');
            return view('test/complete', ['inputdata' => $res]);
        } else {
            session()->flash('adminmsg', 'あんた更新できないよ!!');
            return view('test/update');
        }
    }
    $action = $request->get('action', 'back');
    $input = $request->except('action');
    if($request->action === 'back') {
        return view('test/menu');
    }
}

AuthServiceProvider.phpに設定した「admin」という条件を満たしているかチェックを行い、条件を満たしているとき=認可されたときの処理と、条件を満たさなかったとき=認可されなかったときの処理を記述する。今回は認可されたら更新完了画面を表示、認可されなかったらメッセージを出力するようにした。

権限のないテストユーザーでログインして、更新画面を表示する。

image.png

これでupdateボタンを押すと、認可エラーになる。

image.png

できた!できたよ!

実装2 メニュー画面に認可処理を入れる

ここでひとつ疑問が。
どうせ更新処理できないんだったら、そもそもメニューに出すなよ。
ごもっともで。
それじゃあ、bladeのほうに認可処理を入れてみよう。
AuthServiceProvider.phpに設定した認可処理がtrueの時、@can@endcanで囲まれた処理を実行する。

menu.blade.php
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
    こんにちは{{ Auth::user()->name }}<br /><br />
    <a href="/test/search">照会処理</a><br />
    @can('admin')
        <a href="/test/update">更新処理</a><br /><br />
    @endcan
    <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
    </form>
</body>
</html>

更新処理を表示する部分を@can-@endcanで囲んだので、admin_chkがtrueの人しか更新処理が表示されない。

image.png

見やすくするためにユーザー名を出すようにしたよ。
admin_chkがfalseの人の場合、更新処理は表示されない。

image.png

独自項目でログインできるようにする

昨今はメアドでログインするのが常識だって、よくわかっているけどさ。
レガシーな人たちは、ユーザーIDとかでログインしたがるんだよね。
そういう訳で、「会員番号」という独自項目を作って、「会員番号」とパスワードでログインできるように変更する。

項目を追加する

admin_chkを追加したadd_column_users_table.phpに、「memberno」を追加する。

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->boolean('admin_chk')->default(0)->after('password');
        $table->string('memberno')->after('id')->unique();
    });
}
public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('admin_chk');
        $table->dropColumn('memberno');
    });
}

項目を追加したuserテーブルの構成はこんな感じ。
image.png
データはこんな感じで入れてみた。
image.png

ログインIDは/Illuminate/Foundation/Auth/内のAuthenticatesUsers.phpの中にある、usernameメソッドで定義しているそうな。
ここに「email」を返すよう設定してあるから、emailがログインIDになっている。
これを好きな項目に変えれば、ログインIDがその項目に変わる。
AuthenticatesUsers.phpはLoginController.phpがuseしているので、LoginController.phpでusernameメソッドをオーバーライドしてあげればよい。
Illminate配下のソースはあまりいじりたくないし。

LoginController.php
public function username()
{
    return 'memberno';
}

ログイン画面のemailを、membernoに変更。

login.blade.php
<div class="form-group row">
    <label for="memberno" class="col-md-4 col-form-label text-md-right">{{ __('Member No') }}</label>
    <div class="col-md-6">
        <input id="memberno" type="text" class="form-control @error('memberno') is-invalid @enderror" name="memberno" value="{{ old('memberno') }}" required autocomplete="memberno" autofocus>
        @error('memberno')
            <span class="invalid-feedback" role="alert">
                <strong>{{ $message }}</strong>
            </span>
        @enderror
    </div>
</div>

こんな画面になった。
image.png

ちゃんとログインできました。

image.png

これで認証・認可処理をざっくり実装できました。ありがとう!

65
82
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
65
82