作りたいものがありすぎる

40歳を過ぎてプログラミングを始めた人の顛末とこれからなど

ゆるふわLaravel勉強会 (認証/JWT) 認証に関する資料

Laravel 認証についての色々まとめ

以下の記事は 2019/4/1 コワーキングスペース秋葉原Weeybleで行われる輪読会 [秋葉原] ゆるふわLaravel勉強会 (認証/JWT)のための認証に関する資料となります。

内容は以下の有志によるリファレンスサイトの記事の要約となります。 Laravel 5.8 認証

また、バージョンはLaravel 5.8.8 を前提にしています。

認証クイックスタート

Laravelインストール直後は認証系がフロント側で動く状態にはなっていないが、Controller等は既に準備されている
Controllers/Auth配下

コントローラー 用途
RegisterController 新ユーザーの登録
LoginController 認証処理
ForgotPasswordController パスワードリセットのためのメールリンク処理
ResetPasswordController パスワードリセット処理

ひとまず認証付きのアプリを作るには、まずは以下のコマンドを打って、フロント側やルーティングに認証系の処理を自動生成させる

php artisan make:auth

コマンドを叩くとファイルに記述が追加されたり、新規ファイルが作られたりする
もし認証付きのアプリケーションを作るのであればfirst commit直後位に実施してしまうのが良い

変化のあるファイルの紹介

ルーティング
routes/web.php

// 以下2行が追加される
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');

Auth::routes();で登録画面、ログイン画面、パスワードリセットのすべてのルーティングを設定してくれている、個別に編集が必要な場合は、この行を廃止して画面毎にルーティングを定義する。


HomeControllerの追加
app\Http\Controllers\HomeController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HomeController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function index()
    {
        return view('home');
    }
}

ルーティングの2行目に追加された処理を行うControllerだが、ログイン直後の画面のサンプル例となる。 ログインすると/homeに移動するので、アプリケーションの仕様に従い、表示を作り込めば良いし、Homeという名前が気に食わないなら随意に変更する


その他以下の各viewファイルが自動生成されます。

resources\views\home.blade.php
resources\views\auth\login.blade.php
resources\views\auth\register.blade.php
resources\views\auth\verify.blade.php
resources\views\auth\passwords\email.blade.php
resources\views\auth\passwords\reset.blade.php
resources\views\layouts\app.blade.php

ログイン・登録・パスワードリマインダ等のページと機能も自動で生成してほぼ機能するようになります。

ブラウザでルートのpathにアクセスすると、画面左上に[LOGIN]と[REGISTER]のリンクが表示されるようになります。


認証の動作確認

まずはDBが無いので作ります。(vagrant環境でMySQLがある前提)

$ mysql -u root -p secret
mysql> create database your_database_name;
mysql>exit

migrateしてDBにtableを作ります。

$ php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table

user table以外にpassword resetのtableもcreateされました。
これでREGISTER出来る様になりました。
登録画面に移動して、登録すると、DBにuserが生成され、ログインして/homeにリダイレクトされます。
当然一度ログアウトしてログインができることも確認できます。
また、ログイン画面にパスワードを忘れた際のMailリマインダ―がありますが、mailの設定をしないと飛びませんので、今回は割愛

以上が、クイックスタートで作られた認証の初期概要です。


認証のカスタマイズ (初級編)

初期状態は以下の様な認証の仕様となっていますが、これは簡単に変更が可能です。
以下はリファレンスサイトの内容をほぼ転載しています。

ログイン後のリダイレクト先 /homeの変更

LoginControllerRegisterControllerResetPasswordControllerVerificationControllerredirectToプロパティで、認証後のリダイレクト先の場所を定義してください。

protected $redirectTo = '/';


ログイン時のEmailをユニークな username, user_id,等に変更する

ログイン時はemailとpasswordの組み合わせが認証のデフォルトだが、emailをuser_id等に変更したい場合。

これをカスタマイズしたい場合は、LoginControllerusernameメソッドを定義してください。

public function username()
{
    return 'user_id';
}

当然user Tableをmigrateして変更したいユニークとなるカラムを追加してください。


登録済みユーザーのロール別制限 guard メソッド

webアプリを普通に作ってる場合は個人的にあまり使いませんが、APIやSPAの際にはよく使う事になるそうです。

LoginControllerRegisterControllerResetPasswordControllerguardメソッドを定義してください。メソッドからガードインスタンスを返してください。

use Illuminate\Support\Facades\Auth;

protected function guard()
{
    return Auth::guard('guard-name');
}


認証済みユーザーの取得 -Authファサード超便利-

Auth::user()->email とかでuser関連のデータをControllerやviewですぐ取得できる。
基本は認証時に行った user Tableのカラムのデータが取得できるので、自分のデータを取得したい際にとても便利に使えます。

use Illuminate\Support\Facades\Auth;

Auth::user()->email; // taro@gmail.com


認証中のユーザーか調べる -必須並みの便利機能-

Auth::check() これも 認証してる/してない を簡単に切り替え判断できる。controllerでもviewでも使える。認証の有無で処理や表示を変える際に便利!よく使う。

use Illuminate\Support\Facades\Auth;

if (Auth::check()) {
    // ログイン中の場合の処理
} else {
    // 非ログイン中の処理
}


認証済みのみ通すページをルーティングで指定

この辺はルーティングの説明時にも紹介した内容で、ルーティングrouter/でチェーンメソッド->middleware('auth')と書くと、認証時のみ有効となるルーティングとして定義できます。

Route::get('profile', function() {
    // 認証済みのユーザーのみが入れる
})->middleware('auth');

それ以外でも例えばコントローラのコンストラクタでもmiddlewareメソッドを呼べる

public function __construct()
{
    $this->middleware('auth');
}


認証回数制限

brute-force対策が最初から出来ている感じ?

Laravelの組み込みLoginControllerクラスを使用している場合、Illuminate\Foundation\Auth\ThrottlesLoginsトレイトが最初からコントローラで取り込まれています。デフォルトでは何度も正しくログインできなかった後、一分間ログインできなくなります。制限はユーザーの名前/メールアドレスとIPアドレスで限定されます。

自前のユーザー認証 (中級編)

リファレンスにある内容を紹介する
app\Http\Controllers\Auth\LoginController.php
authenticateメソッドを新たに定義する。

認証系のカスタマイズはvendor\laravel\framework\src\Illuminate\Foundation\Auth\AuthenticatesUsers.phpにあるメソッドをオーバーライドするのが応用編の入り口の様です。

ちなみに下記のIlluminate\Foundation\Auth\AuthenticatesUsers.php内のauthenticatedメソッドの実体は空メソッドで、カスタマイズ専用のメソッドである事がわかります。

    /**
     * The user has been authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $user
     * @return mixed
     */
    protected function authenticated(Request $request, $user)
    {
        //
    }

これを以下の様にLoginController.phpに追加で記述をします。
以下、リファレンスサイトの例を転載します。

<?php

namespace App\Http\Controllers;

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

class LoginController extends Controller
{
    /**
     * 認証を処理する
     *
     * @param  \Illuminate\Http\Request $request
     *
     * @return Response
     */
    public function authenticate(Request $request)
    {
        $credentials = $request->only('email', 'password');

        if (Auth::attempt($credentials)) {
            // 認証に成功した
            return redirect()->intended('dashboard');
        }
    }
}

ここで重要なのはAuth::attempt($credentials)です。
これが認証するか否かを判定できる仕組みで、引数に渡すのはモデルのカラム名となります。

オレオレの実装例

    public function authenticate(Request $request)
    {
        // login_id カラムは email + '@' + community_id(int) で構成されたユニークの文字列として登録時に保存された値、これでログイン認証を行う
        $login_id = $request->email . '@' . $request->community_id;
        $credentials  = array(
            'login_id' => $login_id,
            'password' => $request->password,
        );
        $request->validate([
            'email' => 'required|string|email|max:170',
            'password' => 'required|string|min:6',
        ]);
        if (Auth::attempt($credentials)) {
            return redirect('/')->with('message', 'ログインしました');
        } else {
            return redirect()->back()->withErrors(array('email' => 'E-mailかPasswordが正しくありません'))->withInput();
        }
    }

Auth::attempt($credentials)に渡す認証の値は追加が可能です。以下の様に認証時の条件を3つ以上に設定することができます。

if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
    // ユーザーは存在しており、かつアクティブで、資格停止されていない
}

以降の認証に関する記述はリファレンスサイトを参考にしてください。
これ以降はケースバイケースで使用するかも。といったものが多い印象です。


よくありそうなカスタマイズについての実例など

RegisterController のカスタマイズ

ユーザー登録を行う際のvalidatorcreateメソッドの変更が必要であれば変える。
この辺は大変解りやすいコードだし、見たまんまで弄ってしまって基本OKです。
バリデートの変更や、ユーザー登録時に発行すべきカラムのデータ等を生成します。ありがちなのはユーザーの権限を初期状態で追加する。等の処理を行う事になるかと思います。

app\Http\Controllers\Auth\RegisterController.php抜粋

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    }


認証カスタマイズ(オレオレ編)

実はAuth::user()で呼べるのは通常,user Tableのカラムだけとなります。ところがアプリの仕様上 user Tableがユニークにならない様な場合は、Auth::user() で欲しい一意のユーザーのデータが取得できず、偉い苦労しました。

どんなことをやったかというと、この記事にあるような事をしました。

Laravel 認証カスタマイズ 複数tableを結合しての認証で Auth::user() に必要な値を入れる方法

Laravelの認証機能をカスタマイズして、認証時に3つのカラム条件で認証をし、さらに認証後にAuth::user() ファサードに複数tableからの値を取得できるようにしました。

という訳で後半はこれについて説明します。