【簡単】Laravelでログイン機能を自作する方法

Laravel

こんにちは!

今回はLaravelのログイン機能の実装方法をご紹介します。

何らかのアプリケーションの開発を行う際、ログイン機能はほぼほぼ必須で必要になりますよね。ログイン機能がないとセキュリティも脆弱ですし、ログインユーザーを特定することもできなくなります。

Laravelを使えば、初学者でも簡単にログイン機能を実装することができます

読者
読者

「Laravelのログイン機能の具体的な実装方法が知りたい」

「できるだけ簡単な方法でログイン機能を実装したい」

このようにお考えの方には参考になると思います。

初学者の方でもわかりやすく、体系的に解説していくので、ぜひ最後まで読んでいってくださいね。
それでは、早速みていきましょう!

ログイン機能の実装手順

それでは、ログイン機能の具体的な実装手順を見ていきましょう。

前準備

まずは、前準備としてLaravelプロジェクトの作成やテストデータの作成などを行なっていきます。

以下コマンドからLaravelプロジェクトを作成します。

$ composer create-project laravel/laravel --prefer-dist laravel-login

筆者はMAMP環境で環境構築しているため、MAMPのMySQL接続設定を.envファイルに記述します。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_login_db
DB_USERNAME=root
DB_PASSWORD=root
DB_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sock

あとは、MAMPを起動すればOKです。MySQLの接続設定も書いているため、このままマイグレートします。

$ php artisan migrate

データベースを新しく作成しますか?の質問には「Yes」を選択しておきます。

次に、テストユーザーのデータを入れておきたいと思います。この場合には、Seederを使います。
Seederについてあまり詳しくない、という方は以下の記事で詳しく解説しているので、ぜひ見てみてください。

まずはUserSeederクラスを作っておきます。

$ php artisan make:seeder UserSeeder

「database/seeders/UserSeeder.php」ファイルを以下のように編集します。

use App\Models\User;
use Illuminate\Support\Facades\Hash;

public function run(): void
{
    User::insert([
      [
        'name' => 'admin',
        'email' => 'admin@example.com',
        'password' => Hash::make('adminpass')
      ],
      [
        'name' => 'member',
        'email' => 'member@example.com',
        'password' => Hash::make('memberpass')
      ],
      [
        'name' => 'creator',
        'email' => 'creator@example.com',
        'password' => Hash::make('creatorpass')
      ]
    ]);
}

Seederを実行します。

$ php artisan db:seed --class=UserSeeder

以下のように、usersテーブル内にデータが入っていればOKです。

これで前準備が完了しました!

ログイン画面実装

次はログイン画面を作っていきます。

まずはルーティングの設定から行っていきましょう。

「routes/web.php」ファイルを開いて、以下を追記します。

Route::get('/login', function () {
    return view('login');
});

これは「/login」にアクセスした場合はlogin.blade.phpというビューファイルを返してくださいという意味になります。

それでは、「resources/views/login.blade.php」を作成します。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        @vite(['resources/css/app.css'])
        <title>Document</title>
    </head>
    <body>
    <div class="wrapper">
        <div class="container">
            <h1>Login</h1>
            <form class="form">
                <input type="email" name="email" placeholder="username">
                <input type="password" name="password" placeholder="password">
                <button type="'submit" id="login-button">Login</button>
            </form>
        </div>

        <ul class="bg-bubbles">
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
        </ul>
    </div>
</body>
</html>

注意点として、Laravelのバージョン9以降ではフロントのビルドツールとして、Viteが使われています。この場合は、CSSファイルを読み込むために以下のような記述を行う必要があります。

@vite(['resources/css/app.css'])

少し特殊な書き方になるので、覚えておきましょう。

次に、「resources/css/app.css」ファイルを編集します。

* {
    box-sizing: border-box;
}
body {
    font-family: "Source Sans Pro", sans-serif;
    color: #fff;
    margin: 0;
    padding: 0;
}
.wrapper {
    background: linear-gradient(to bottom right, orange 0%, limegreen 100%);
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    overflow: hidden;
}
.container {
    max-width: 600px;
    margin: 0 auto;
    padding: 80px 0;
    height: 400px;
    text-align: center;
}
.container h1 {
    font-size: 40px;
    transition-duration: 1s;
    transition-timing-function: ease-in-out;
    font-weight: 200;
}
form {
    padding: 20px 0;
    position: relative;
    z-index: 2;
}
form input {
    outline: none;
    border: 1px solid rgba(255, 255, 255, 0.4);
    background-color: rgba(255, 255, 255, 0.2);
    width: 250px;
    border-radius: 3px;
    padding: 10px 15px;
    margin: 0 auto 10px auto;
    display: block;
    text-align: center;
    font-size: 18px;
    transition-duration: 0.25s;
    font-weight: 300;
    color: #fff;
}
form input:hover {
    background-color: rgba(255, 255, 255, 0.4);
}
form input:focus {
    background-color: #fff;
    width: 300px;
    color: #53e3a6;
}
form input::placeholder {
    color: #fff;
}
form button {
    outline: none;
    border: none;
    background-color: #fff;
    width: 250px;
    border-radius: 3px;
    padding: 10px 15px;
    margin: 0 auto 10px auto;
    display: block;
    text-align: center;
    font-size: 18px;
    transition-duration: 0.25s;
    font-weight: 300;
    color: #53e3a6;
    cursor: pointer;
}
form button:hover {
    background-color: #f5f7f9;
}
.bg-bubbles {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 1;
}
.bg-bubbles li {
    position: absolute;
    list-style: none;
    display: block;
    width: 40px;
    height: 40px;
    background-color: rgba(255, 255, 255, 0.15);
    bottom: -160px;
    animation: square 25s infinite;
    transition-timing-function: linear;
}
.bg-bubbles li:nth-child(1) {
    left: 10%;
}
.bg-bubbles li:nth-child(2) {
    left: 20%;
    width: 80px;
    height: 80px;
    animation-delay: 2s;
    animation-duration: 17s;
}
.bg-bubbles li:nth-child(3) {
    left: 25%;
    animation-delay: 4s;
}
.bg-bubbles li:nth-child(4) {
    left: 40%;
    width: 60px;
    height: 60px;
    animation-duration: 22s;
    background-color: rgba(255, 255, 255, 0.25);
}
.bg-bubbles li:nth-child(5) {
    left: 70%;
}
.bg-bubbles li:nth-child(6) {
    left: 80%;
    width: 120px;
    height: 120px;
    animation-delay: 3s;
    background-color: rgba(255, 255, 255, 0.2);
}
.bg-bubbles li:nth-child(7) {
    left: 32%;
    width: 160px;
    height: 160px;
    animation-delay: 7s;
}
.bg-bubbles li:nth-child(8) {
    left: 55%;
    width: 20px;
    height: 20px;
    animation-delay: 15s;
    animation-duration: 40s;
}
.bg-bubbles li:nth-child(9) {
    left: 25%;
    width: 10px;
    height: 10px;
    animation-delay: 2s;
    animation-duration: 40s;
    background-color: rgba(255, 255, 255, 0.3);
}
.bg-bubbles li:nth-child(10) {
    left: 90%;
    width: 160px;
    height: 160px;
    animation-delay: 11s;
}
@keyframes square {
    0% {
        transform: translateY(0);
    }
    100% {
        transform: translateY(-700px) rotate(600deg);
    }
}

あとは、ビルドするだけです。以下のコマンドでビルドできます。

$ npm install
$ npm run dev

以下のような画面になるはずです。

Screenshot

ログイン画面は無駄に凝ってしまいましたが、どんなデザインでも構いません。

最後に、Aboutページを用意しておきましょう。これは、後ほど動作確認を行う際のログイン後の遷移先ページにしたいと思います。

「routes/web.php」に以下を追記します。

Route::get('/about', function () {
    return view('about');
})->name('about');

->name('about')のように記述すると、ルート名をつけることができます。
これの何が嬉しいのかというと、プログラム上でこのルートを指定したいときに、ルート名で指定することができるようになるため、コードが読みやすい&理解しやすくなります。
このように、ルート名がつけられたルートのことを「名前付きルート」と呼ぶので頭の片隅に入れておくと良いと思います。

次に、「resources/views/about.blade.php」を作成し、以下のように編集します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h1>About Page</h1>
</body>
</html>

これで、Aboutページが表示できるはずです。以下のような表示になれば、OKです。

ログイン処理実装

それでは、本題であるログイン機能の実装に移ります。

まずは、AuthControllerを作成します。

$ php artisan make:controller AuthController

「Http/Controllers/AuthController.php」が作成されるので、以下のようにloginアクションを記述します。

public function login(Request $request)
{
    $credentials = $request->validate([
        'email' => ['required', 'email'],
        'password' => ['required']
    ]);

    if (Auth::attempt($credentials)) {
        $request->session()->regenerate();
        return redirect()->intended('about');
    }

    return back()->withErrors([
        'email' => 'The provided credentials do not match our records.'
    ]);
}

次に、ルーティングを設定しておきます。

use App\Http\Controllers\AuthController;

Route::post('/login', [AuthController::class, 'login'])->name('login');

最後に、ログイン画面のフォームを編集しておきましょう。「resources/views/login.blade.php」を開いて、以下のように変更します。

<form class="form" action="{{ route('login') }}" method="POST">
    @csrf
    <input type="email" name="email" placeholder="username">
    <input type="password" name="password" placeholder="password">
    <button type="'submit" id="login-button">Login</button>
</form>

formタグにaction属性とmethod属性を追記しました。また、各フォームにname属性を追記しました。
これで、フォームからサーバー側にリクエストを送れるようになりました。

@csrfは「クロスサイトリクエストフォージェリ(CSRF)」という攻撃を防ぐために実装します。ここでは詳しく説明しませんが、CSRF攻撃は不正なリクエストを送ることで不正にアプリケーションを操作する攻撃です。セキュリティ上の理由でつけているんだなぁーくらいの認識で大丈夫です。

動作確認

それでは、動作確認してみます。

無事、成功しましたね✨

次はログアウト機能も作ってみましょう!

ログアウト機能の実装手順

ログアウトボタンの設置

それでは、まずはログアウトボタンを設置します。

「about.blade.php」に以下を追記します。

<form action="{{ route('logout') }}" method="POST">
    @csrf
    <button type="submit">ログアウト</button>
</form>

次に、ログアウト用のルーティングを設定します。「web.php」に以下を追記。

Route::post('/logout', [AuthController::class, 'logout'])->name('logout');

そして、AuthControllerlogoutアクションを追加します。

public function logout(Request $request)
{
    Auth::logout();
    $request->session()->invalidate();
    $request->session()->regenerateToken();
    return redirect('login');
}

ログアウト時はセッションを無効にし、CSRFトークンを再生成することが推奨されています。これらを行っているのが、以下のコードです。

$request->session()->invalidate();
$request->session()->regenerateToken();

こちらはセキュリティ上の理由から実装しておいた方が良いでしょう。

動作確認

それでは、これでログアウト機能が実装できたので、動作確認してみます。

ログアウト機能もできました✨

アクセス制限を設置する

ここまでで、ログイン・ログアウト機能を実装してきましたが、これで完成とは言えません。
なぜなら、このままだとログインしなくてもAboutページに入れてしまうからです。

試しに、ログアウトした状態で「/about」をURLへ打ち込んでアクセスしてみてください。普通に入れてしまうはずです。
これではあまり意味がないので、Aboutページにはログインしていないと入れないようにアクセス制限を設けてみます。
といっても、やり方は簡単です。

「routes/web.php」に定義されている、Aboutページのルーティングを以下のように編集します。

Route::get('/about', function () {
    return view('about');
})->middleware('auth')->name('about');

これでアクセス制限を設けられました。
具体的には、middlewareを追加しただけです。これだけでアクセス制限を追加できます。
authという命名はLaravelのデフォルトで決められています。変えないようにしましょう。

それでは、試しに再度ログアウト状態から「/about」にアクセスしてみてください。
今度は移動できないはずです。

バリデーションエラーを表示する

既にログイン・ログアウトとしては、機能していますが、より完璧なシステムに仕上げていきましょう。
具体的には、フォームに不正な値が入力された場合に、エラーメッセージを表示するようにしていきます。

login.blade.phpを以下のように変更します。

<form class="form" action="{{ route('login') }}" method="POST">
    @if ($errors->any())
        <div class="alert">
            <ul>
                @foreach ($errors->all() as $error)
                    <li>{{ $error }}</li>
                @endforeach
            </ul>
        </div>
    @endif
    @csrf
    <input type="email" name="email" placeholder="username">
    <input type="password" name="password" placeholder="password">
    <button type="'submit" id="login-button">Login</button>
</form>

バリデーションエラーは$errorsという変数名で受け取ります。
フォームが複数ある場合は、バリデーションエラーも複数で返ってくるため、foreach文で繰り返し処理を行って表示します。

ただ、このままだと英語でエラーメッセージが表示されてしまうので、日本語化していきます。

まず、.envファイルを以下のように変更します。

APP_LOCALE=ja

次に、「resources/lang/ja」というディレクトリを作り、「validation.php」を作成します。

<?php
return [
    'required' => ':attributeは必須項目です。',
    'email' => ':attributeは有効なメールアドレス形式でなければなりません。',
    // 他のバリデーションルールとメッセージを追加する
];

このようにエラーメッセージを定義することができます。:attributeはフォームのname属性の値に置き換わります。

これで完了です。

それでは、動作確認してみましょう。
ログイン画面に入って、フォームに何も入力せずにログインボタンを押してみます。

Screenshot

無事、バリデーションメッセージを表示することに成功しました✨

まとめ

いかがでしたでしょうか?

ここまでみてきたように、Laravelを使えばログイン機能も簡単に実装することができます。

もしかすると難しく感じたかもしれませんが、慣れてくると非常に簡単に実装できることが実感できるかと思います。

また、Laravelには他にもLaravel BreezeやLaravel Sanctumなど、ログイン機能の実装方法が多数あります。興味があれば、他の実装方法も調べてみると良いと思います。
ちなみに、本ブログでもLaravel Breezeの実装方法を紹介しているので、よかったらぜひ見てみてください。

参考

参考記事

11.x 認証 Laravel

参考書籍


PHPフレームワークLaravel入門第2版 [ 掌田津耶乃 ]

コメント

タイトルとURLをコピーしました