Laravelとnuxt/authでSPA認証!ログイン機能を実装しよう!(第1回 バックエンド編)

Laravel

Laravel とフロントエンドフレームワーク「Nuxt.js」を使い、SPA認証ログイン機能)をできるだけかんたんに実装する方法を紹介していきます。

全2回の連載とし、第1回はSPA認証に対応したバックエンドをLaravelで構築する方法について紹介していきます。Laravelでは認証システムを簡単に実装できるライブラリが豊富に用意されており、専門的な知識が十分ではなくても、SPA認証が実装できるようになっています。そこで当記事では、SPA認証におすすめのライブラリ「Laravel Sanctum」の紹介から、実際に実装する手順について解説していきたいと思います。

第2回では、フロントエンドフレーム「Nuxt.js」にログイン機能を実装していきます。Nuxt.js公式の認証モジュール「nuxt/auth」を活用し、認証に関する難しいコーディングはほぼゼロでログイン機能を実装していく方法を紹介していきます。

私の開発環境はコレ!
  • PHP 8.0.3(7.4でも問題ありません!)
  • Laravel Framework 8.81.0(当記事の内容はLaravel 6・7でも対応しています)
  • Nuxt.js 2

LaravelとNuxt.jsのプロジェクトは、完全に分離して開発されたものとして解説していきます。

なお、LaravelとNuxt.jsを組み合わせた際のSPA認証としては、今回紹介する以外にもたくさんの方法があります。当記事では、LaravelとNuxt.jsに用意されているライブラリをフル活用し、できる限りシンプルに実装する方法を取り上げています。

スポンサーリンク

まずはSPA認証の全体像をチェック!

はじめにSPA認証がどのような流れで行われるかについて、確認しておきましょう。

SPA認証の処理の流れ
  1. 【フロントエンド】CSRFトークンを要求
  2. 【バックエンド】CSRFトークンを返し、ブラウザのクッキーにセット
    【フロントエンド】ログインAPIへメールアドレスとパスワードをPOST
  3. 【バックエンド】認証(正しいパスワードか確認する)し、フロントエンドへ結果を返す
  4. 【フロントエンド】認証に成功したら、ログイン済みページへ遷移する

まず、第一にCSRFトークンの取得が必要となります。

CSRF(Cross-site Request Forgery)トークンというのは、正規のページからアクセスが行われていることを証明するための値のことです。いわば合言葉のようなもので、フロントエンドは必ずCSRFトークンを事前に取得しておき、APIへアクセスするときはCSRFトークンも一緒に渡す必要があります。CSRFトークンが渡されていないときはアクセスを拒否することで、不正なアクセスを防ぐことができます。

おそらくSPA認証を構築するうえで多くの人が躓くのは、CSRFにかかわる部分です。実際にはLaravel Sanctumとnuxt/authを使うことで、よしなに処理してくれるのですが、SPA認証におけるCSRFトークンの仕組みについては、事前にしっかり理解しておいたほうが後々(とくにフロントエンドを実装する第2回で)楽になります。Google検索してあらかじめイメージを掴んでおくことをおすすめします。

CSRFトークンがうまく取得できれば、あとは通常のログインと同様です。定義しておいたログイン用のルートにメールアドレスやパスワードをPOSTすることで認証できます。

スポンサーリンク

SPA認証の面倒な処理はLaravel Sanctumにおまかせ!

SPA認証を実装するには、CSRFトークンの発行や正規のフロントエンドかどうかの判定など、やらなくてはならない処理がたくさん出てきます。それらすべてを自前で実装することも可能ですが、バグの混入のおそれがありますし、セキュリティの観点からも推奨されません。

そこで、今回はLaravel Sanctumというライブラリを導入していきます。

Laravel - The PHP Framework For Web Artisans
Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing ...

Laravel Sanctumは①SPA認証 ②トークン認証 ③モバイルアプリの認証を可能とすることに特化したライブラリです。Laravel Sanctumを導入することで、CSRFトークンに関する処理など、SPA認証に特有の処理を自前でコーディングすることなく実装することができます。第2回でフロントエンドに導入する「nuxt/auth」などのモジュールとの相性もとても良いため、単にSPAフロントエンドと連携させたいのであれば、もっともおすすめされるライブラリとなります。

Laravelの公式ドキュメントにも、SPA認証にはLaravel Sanctumが推奨される旨が記載されています。

 if you are attempting to authenticate a single-page application, mobile application, or issue API tokens, you should use Laravel Sanctum

https://laravel.com/docs/8.x/passport#passport-or-sanctum

もちろん、ほかのライブラリにもSPA認証を実装できるものはありますが、各ライブラリの比較をしていると到底説明しきれない量になってしまうため、ここでは詳細に踏み込むことは避けておきましょう。とりあえず、シンプルにSPA認証を実装するならLaravel Sanctumという認識で問題ありません。

今回はSPA認証ができればOKということで、Laravel Sanctumを導入してAPIからのログインを実装してみたいと思います。

Laravel Sanctumをインストール

まずはLaravel Sanctumをインストールしましょう。

ただし、Laravel 8ではプロジェクトを作成した時点で、すでにLaravel Sanctumがインストールされていますので、この手順は不要です。Laravel SanctumはLaravel 6から使用できます。Laravel 6~7を使用している方は以下の手順でインストールしましょう。

ComposerでLaravel Sanctumをインストールします。

composer require laravel/sanctum
Using version ^2.14 for laravel/sanctum
Package manifest generated successfully.
77 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
> @php artisan vendor:publish --tag=laravel-assets --ansi --force
No publishable resources for tag [laravel-assets].
Publishing complete.

Laravel Sanctumのインストールができたら、Sanctumの利用に必要なファイルを生成するコマンドを実行します。

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
Copied Directory [\vendor\laravel\sanctum\database\migrations] To [\database\migrations]
Publishing complete.

Laravel Sanctumをミドルウェアに追加する

次に、ミドルウェアにLaravel Sanctumを登録して、Laravel Sanctumを有効にします。

app\Http\Kernel.phpを開き、$middlewareGroupsapiにLaravel Sanctumのクラス(\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class)を追加します。

・・・中略・・・   
 protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

Laravel SanctumがもともとインストールされているLaravel 8では、該当の行がコメントアウトされています。この場合はコメントを外せばOKです。

アクセスを許可するドメインを設定する

次にLaravel Sanctumへのアクセスを許可するドメインを指定します。

Laravel Sanctumの機能を利用するには、フロントエンドのドメインを設定する必要があります。アクセス元となるフロントエンドのドメインを指定することで、外部からSanctumへの不正なアクセスを防ぐようになっています。

設定はconfig/sanctum.phpから行いますが、デフォルトでは.envファイルで環境変数として設定することができるようになっています。

.envファイルにSANCTUM_STATEFUL_DOMAINSキーを追記して、フロントエンドのドメイン(ポート番号含む)を指定します。

SANCTUM_STATEFUL_DOMAINS=localhost:3000

ここではNuxt.jsの開発環境(http://lcoalhost:3000)からアクセスできるように設定しておきました。

localhost:3000はconfig/sanctum.phpであらかじめ許可設定がなされているため、本当は設定する必要はありませんが、本番環境に移行したときに設定を忘れないよう、あえて明示的に設定しています。

CORSを有効にする

クロスオリジンになる場合は、LaravelでCORSを有効にしておきます。

クロスオリジンとは、バックエンドとフロントエンドのオリジン(アドレス)が異なる状態のことをいいます。この場合はCORSを明示的に有効にしておかないと、セキュリティ対策のためアクセスが拒否されてしまいます。

ローカル環境でもFQDNが異なればクロスオリジンとなります。

例えば、http://localhost:3000でNuxt.jsの開発をしつつ、http://localhost:8080で動作させたLaravelの開発サーバーにアクセスした場合、http://localhost:3000からhttp://localhost:8080へのアクセスが生じることになります。この場合も両者のオリジンは異なるため、CORSの有効化が必要です。

CORSを有効にするにはconfig\cors.phpを編集し、supports_credentialsをtrueにします。

<?php

return [
    'paths' => ['*', 'sanctum/csrf-cookie'],
    'allowed_methods' => ['*'],
    'allowed_origins' => ['*'],
    'allowed_origins_patterns' => [],
    'allowed_headers' => ['*'],
    'exposed_headers' => [],
    'max_age' => 0,
    'supports_credentials' => true,

];

このとき、特定のオリジンからのアクセスのみ許可したい場合は、*の代わりに許可したいオリジンをallowed_originsに設定しておきましょう。

'allowed_origins' => ['http://localhost:3000'],

指定する値はオリジンなのでFQDN(httpからポート番号まで)を指定します。ホスト名だけではダメ!

ただし、テスト環境ではひとまず*にしておくことをおすすめします。正常にログインできることを確認してから制限を厳しくしていくほうが、無用なトラブルを避けることができます。

セッション・クッキーを許可するドメインを設定する

クロスドメインになっている場合は、セッション・クッキーを許可するドメインも設定する必要があります。

クロスドメインとは、バックエンドとフロントエンドのドメインが異なることをいいます。例えば、Laravelがhoge.com、フロントエンド(Nuxt.jsなど)がfuga.comでホスティングされているような状況です。クロスドメインではバックエンドからブラウザのクッキーを設定することができず、セッションも張ることができなくなります。許可するドメインをしっかり設定しておきましょう。

config/session.phpから設定しますが、.envからも設定できるようになっているので、.envから設定しましょう。

SESSION_DOMAIN=localhost

セッションクッキードメインを設定することで、クロスドメインになっていてもLaravel側からクライアントにクッキーを設定できるようになります。後で出てくるX-CSRF-TOKENを使用する上で必要となるため、ここで設定を行っておきます。

なお、クロスドメインになっていない場合、当手順は省略できます。

ログインコントローラーの実装

これにてLaravel Sanctumのセットアップは完了です。SPAフロントエンドとの認証用の通信はできるようになりました。

次に認証用のコントローラーを作成していきましょう。

Artisanコマンドを実行し、新たに認証用のコントローラー「AuthController」を作成します。

php artisan make:controller AuthController 

ログインとログアウトができるように実装していきます。通常の認証と同様に、Laravel標準の認証ファサードIlluminate\Support\Facades\Authを使って実装することができます。

<?php

namespace App\Http\Controllers;

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

class AuthController extends Controller
{
    /**
     * ログイン
     *
     * @param  mixed $request
     * @return void
     */
    public function login(Request $request)
    {
        if (Auth::attempt($request->only(["email", "password"]))) {
            // レスポンスを返す
            return response()->json(['message' => 'success'], 200);
        } else {
            // エラーレスポンスを返す
            return response()->json(['message' => 'failed'], 401);
        }
    }

    /**
     * ログアウト
     *
     * @param  mixed $request
     * @return void
     */
    public function logout(Request $request)
    {
        // ログアウトする
        Auth::logout();
        // レスポンスを返す
        return response()->json(['message' => 'Logged out'], 200);
    }

もちろんAuthファサードのほかのメソッドも使えます。Auth::checkでログイン中か判定したり、Auth::id()でログイン中のユーザーのIDを取得したりすることもできます。Laravel Sanctumを導入していても、通常の認証とまったく同じです。

routes/web.apiでルートを設定します。

<?php

use Illuminate\Http\Request;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/


// SPA認証
Route::post('/login', 'AuthController@login');
Route::post('/logout', 'AuthController@logout');

nuxt/authからはここで定義したルートにログイン・ログアウトのアクセスを行うようにします。

api.phpに記載しても構わないとは思いますが、その場合はエンドポイントが/api/login,logoutになることに注意しましょう。

ルートをLaravel Sanctumで保護する

Laravel Sanctumでガードしたいルートにauth:sanctumミドルウェアを適用します。

// routes/api.php
Route::group(['middleware' => ['auth:sanctum']], function () {

    Route::get('/user', function (Request $request) {
        return $request->user();
    });

Route::groupで適用したいルートをまるごと囲ってしまうと安心です。

テストユーザーの作成・マイグレーション

最後にテストユーザーを作成しておきましょう。

シーダーとは、マイグレーション時に自動的にレコードを作成する機能のことです。あらかじめシーダーを作成しておくと、テストデータなどをマイグレーション時に作成することができます。

database\seeders\DatabaseSeeder.phpを編集し、テストユーザーを作成するシーダーを定義します。

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        DB::table('users')->insert([
            'name' => 't',
            'email' => 't@localhost',
            'password' => bcrypt('password'),
        ]);
    }
}

マイグレーション時に–seedオプションをつけると上のプログラムが実行され、自動的にユーザーが作成されるようになります。

マイグレーションを実行し、必要なテーブルを作成します。

php artisan migrate:fresh --seed 
Dropped all tables successfully.
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (26.50ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (21.52ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (32.99ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (42.32ms)
Database seeding completed successfully.

テーブルが作成されたら、バックエンドは完成です!長かった~

なお、データベースへの接続設定については、あらかじめマイグレーション前に行っておいてください。MySQLなどのセットアップがお済みでない方はSQLiteをデータベースとして利用することができます。

ちなみにアクセストークン関係のテーブルもマイグレーションされていますが、SPA認証の場合は不要となります。マイグレーションファイルを削除しても構いませんが、後からトークン認証も実装するかもしれないため、一応残しておきました。

以上でバックエンドの準備は完了です。ローカルサーバーを起動して、フロントエンドからのアクセスを待機させておきましょう。

php artisan serve

Nuxt.jsのセットアップ

次にNuxt.jsによるフロントエンドを作成していきます。第2回「フロントエンド編」につづきます。

コメント

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