2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravel11 × Nuxt3で、sanctumを使って認証機能を実装してみた(BE編)

Last updated at Posted at 2024-08-18

はじめに

みなさん、おはこんばんにちは。今回は、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

等に対して変更がされてます。

api.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の追記がされています。

composer.json
"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にミドルウェアメソッドってのを呼ぶことで、簡単に実装できました。

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の完成系
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の完成系
app/Http/Controllers/Auth/SignUpController.php
<?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の完成系
app/Http/Requests/Auth/SignUpRequest.php
<?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の完成系
app/Http/Controllers/Auth/SignInController.php
<?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の完成系
app/Http/Requests/Auth/SignInRequest.php
<?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の完成系
app/Http/Controllers/Auth/SignUpController.php
<?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の完成系
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の完成系
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');

参考文献

2
0
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?