はじめに
みなさん、おはこんばんにちは。今回は、LaravelとNuxt.jsとSanctumを使って、認証機能を実装してみました。その備忘録等とか、アウトプットを兼ねて書いてみやす〜。
大前提ですが、まだまだ駆け出し的な身分なので、もしも変な記載とか間違ってるところがあった場合、誹謗中傷ではなく前向きなFBがもらえるとめっっっっっっちゃ嬉しいです〜。
バージョン
バックエンド
- PHP 8.3.10
- Laravel 11.20.0
フロントエンド
- node 20.15.0
- npm 10.7.0
- Nuxt 3.12.4
こんな感じになっております。この記事では、バックエンドメインで書きますが、別記事でフロントエンドも書いていこうと思っております。
前提として、
- laravelの基本的なセットアップ(laravelのかっこいいトップページが表示されている)
- Nuxt3の基本的なセットアップ(Nuxt3のかっこいいトップページが表示されている)
ところまで完了していると仮定して、やっていきます。
また、Githubにもソースコードをあげてます〜。
Laravel Sanctumのインストール
まずは、公式ドキュメントを参考に、Laravel Sanctumをインストールします。
php artisan install:api
コマンドで言うとこれっぽいですね。
これを実行すると、
- laravel/sanctumのアップデート or インストール
-
/api/user
ルートの作成 -
personal_accesstokens
テーブルの作成(これはyesの答えた場合のみですね)
がされてるっぽく
routes/api.php
composer.json
composer.lock
config/sanctum.php
等に対して変更がされてます。
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');
このようにユーザー情報を返すルートができてますね。実際にルート情報を確認してみると、
php artisan route:list
Xdebug: [Step Debug] Could not connect to debugging client. Tried: localhost:9003 (through xdebug.client_host/xdebug.client_port).
GET|HEAD / ............................................................................................................................. generated::U5yHuUYjTAs5WbRm
GET|HEAD api/user ...................................................................................................................... generated::DemIuMPv3cZDFuo2
GET|HEAD sanctum/csrf-cookie ..................................................................... sanctum.csrf-cookie › Laravel\Sanctum › CsrfCookieController@show
GET|HEAD up ............................................................................................................................ generated::vAXMrfhkXs8J7sb1
Showing [4] routes
- ユーザー情報を取得するルート
- csrfトークンとかを含むレスポンスを返すルート
これらのルートが作成されています。
また、composer.json
には、laravel/sanctum
の追記がされています。
"laravel/sanctum": "^4.0",
こんな感じの追記がされています。多分無事にsanctumがインストールされていますね。
インストールコマンドの実行結果
php artisan install:api
./composer.json has been updated
Running composer update laravel/sanctum
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
- Locking laravel/sanctum (v4.0.2)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
- Downloading laravel/sanctum (v4.0.2)
- Installing laravel/sanctum (v4.0.2): Extracting archive
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
INFO Discovering packages.
laravel/sail .......................................................... DONE
laravel/sanctum ....................................................... DONE
laravel/tinker ........................................................ DONE
nesbot/carbon ......................................................... DONE
nunomaduro/collision .................................................. DONE
nunomaduro/termwind ................................................... DONE
79 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
INFO No publishable resources for tag [laravel-assets].
No security vulnerability advisories found.
INFO Published API routes file.
One new database migration has been published. Would you like to run all pending database migrations? (yes/no) [yes]:
> yes
INFO Running migrations.
2024_08_17_034630_create_personal_access_tokens_table ................................................................................ 9.13ms DONE
INFO API scaffolding installed. Please add the [Laravel\Sanctum\HasApiTokens] trait to your User model.
次に、Sanctumミドルウェアを追加してみます。これも、公式ドキュメントによると、bootstrap/app.php
にミドルウェアメソッドってのを呼ぶことで、簡単に実装できました。
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
// このメソッドを追加
$middleware->statefulApi();
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
Cookieの設定
続いて、cookieの設定をします。
公式ドキュメントには
If you are having trouble authenticating with your application from an SPA that executes on a separate subdomain, you have likely misconfigured your CORS (Cross-Origin Resource Sharing) or session cookie settings.
別のサブドメインで実行されるSPAからアプリケーションを認証する際に問題が発生する場合は、CORS (クロスオリジン リソース共有) またはセッション クッキーの設定が誤っている可能性があります。
と書いてまして、個人的には
- フロントエンド:
localhost:3000
- バックエンド:
localhost:8080
でやっているので、この設定はいらないかなと思っていたのですが、この設定をしないと、『CORS ポリシーによってブロックされました』と弾かれちゃったので、設定をしたところうまくいきました。
原因とかなぜかわかる人いたら教えて欲しいです、知識不足すんません...
と、言うわけで
php artisan config:publish cors
このコマンドを実行します。そうすると、config/cors.php
が生成されます。
次に、supports_credentials
の設定がfalse
になっているので、true
に変更します。
config/cors.phpの完成系
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*', 'login', 'logout', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
これで最低限の実装は多分完了しました。laravel10に比べてちょっと楽になったのかなって印象です。
認証機能を実装していく
今回は最低限
- サインイン
- アインアップ
- アインアウト
- ユーザー情報を返す
これらの機能のみ実装してみようと思います
実装
コントローラーとリクエストクラスを作っていきます。
# サインアップ系
php artisan make:controller Auth/SignUpController
php artisan make:request Auth/SignUpRequest
# サインイン系
php artisan make:controller Auth/SignInController
php artisan make:request Auth/SignInRequest
# サインアウト系
php artisan make:controller Auth/SignOutController
SignUpControllerの完成系
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\SignUpRequest;
use App\Models\User;
use Exception;
use Illuminate\Support\Facades\DB;
class SignUpController extends Controller
{
public function handle(
SignUpRequest $http_request
) {
DB::beginTransaction();
try {
User::create(
[
'name' => $http_request->name,
'email' => $http_request->email,
'password' => $http_request->password,
]
);
DB::commit();
} catch (Exception $exception) {
DB::rollBack();
return response()->json(
[
'handle_error' => $exception->getMessage(),
],
500
);
}
return response()->json();
}
}
SignUpRequestの完成系
<?php
declare(strict_types=1);
namespace App\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
class SignUpRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'between:1,256'],
'email' => ['required', 'email:filter:dns', 'between:1,256', 'unique:App\Models\User,email'],
'password' => ['required', 'string', 'confirmed'],
];
}
}
SignInControllerの完成系
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\SignInRequest;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Support\Facades\Auth;
class SignInController extends Controller
{
public function handle(
SignInRequest $http_request
) {
$credentials = $http_request->only('email', 'password');
if (Auth::attempt($credentials)) {
$http_request->session()->regenerate();
return response()->json(
[
'message' => 'Authenticated',
]
);
}
throw new AuthenticationException('Credentials error');
}
}
SignInRequestの完成系
<?php
declare(strict_types=1);
namespace App\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
class SignInRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'email' => ['required', 'email:filter:dns', 'between:1,256'],
'password' => ['required', 'string'],
];
}
}
SignOutControllerの完成系
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class SignOutController extends Controller
{
public function handle(Request $http_request)
{
Auth::guard('web')->logout();
$http_request->session()->invalidate();
$http_request->session()->regenerateToken();
return response()->json();
}
}
routes/web.phpの完成系
<?php
declare(strict_types=1);
use App\Http\Controllers\Auth\SignInController;
use App\Http\Controllers\Auth\SignOutController;
use Illuminate\Support\Facades\Route;
Route::post('/login', [SignInController::class, 'handle']);
Route::post('/logout', [SignOutController::class, 'handle']);
routes/api.phpの完成系
<?php
declare(strict_types=1);
use App\Http\Controllers\Auth\SignUpController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::post('/signup', [SignUpController::class, 'handle']);
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');
参考文献