Laravel Sanctum(Laravel8)とNuxt.jsによるSPA認証
概要
業務でフロントを分離したくなってLaravelのAPI認証について調べてみると、
Laravel7からSanctumの利用が推奨されていた。
Nuxtとの連携を調査したので作業ログも兼ねて記事として残します。
Sanctumについて
APIトークンを発行するタイプと、SPA認証の2種類を提供するライブラリです。
ここではSPA認証を紹介します。
和訳ドキュメント
完成品
最終的なコードを置いておきます。
環境
docker-composeで構成し、nginxでRPしています。
- Host:Mac
- Docker
- nginx:1.19-alpine
- node:13.8-alpine (Nuxt2.14.x)
- php:7.4-fpm-alpine (Laravel8.x)
docker-composeは下記の通りです。
version: '3'
services:
nginx:
container_name: nginx
build:
context: ./.docker/nginx
dockerfile: Dockerfile
ports:
- 80:80
tty: true
restart: always
depends_on:
- web
web:
container_name: web
build:
context: ./.docker/web
dockerfile: Dockerfile
environment:
PORT: '3000'
HOST: '0.0.0.0'
expose:
- 3000
volumes:
- ./web:/app
stdin_open: true
tty: true
restart: always
depends_on:
- api
api:
container_name: api
build:
context: ./.docker/api
dockerfile: Dockerfile
environment:
LANG: 'ja_JP.UTF-8'
TZ: 'Asia/Tokyo'
LOG_CHANNEL: 'stderr'
DB_CONNECTION: 'pgsql'
DB_HOST: 'db'
DB_PORT: '5432'
DB_DATABASE: 'laravel_development'
DB_USERNAME: 'docker'
DB_PASSWORD: 'docker'
SESSION_DOMAIN: 'localhost'
SANCTUM_STATEFUL_DOMAINS: 'localhost'
volumes:
- ./api:/app
expose:
- 9000
tty: true
restart: always
depends_on:
- db
db:
image: postgres:12
container_name: db
environment:
TZ: 'Asia/Tokyo'
POSTGRES_USER: 'docker'
POSTGRES_PASSWORD: 'docker'
POSTGRES_DB: 'laravel_development'
volumes:
- db:/var/lib/postgresql/data
ports:
- 5432:5432
volumes:
db:
「nginx」「web」「api」コンテナの細かい構成については下記から確認してください。
検証環境構築
完成品をCloneしていただいて、下記のコマンドで準備。
$ docker-compose up
Nuxt側
$ docker-compose exec web yarn install
$ docker-compose exec web yarn dev
Laravel側
$ docker-compose exec api composer install
$ docker-compose exec api cp .env.example .env
$ docker-compose exec api php artisan key:generate
$ docker-compose exec api php artisan migrate:refresh --seed
以上で、Nuxt側・Laravel側の準備が完了です。
動作確認
メールアドレスとpasswordについてはLaravel側のseedで確認してください。 にアクセスできれば認証成功です。
認証設定について
認証設定について何をしたのか記載していきます。
Laravel側
Sanctumの準備
$ docker-compose exec api composer require laravel/sanctum
$ docker-compose exec api php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
Sanctumの設定
api/app/Http/Kernel.phpにClassを追加
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; // 追加
// ...
protected $middlewareGroups = [
'api' => [
EnsureFrontendRequestsAreStateful::class, // 追加
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
api/config/cors.phpにCORS設定
<?php
return [
'paths' => ['api/*'], // 変更
'allowed_methods' => ['*'],
'allowed_origins' => ['http://localhost:3000'], // 変更
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true, // 変更
];
.envに環境変数をセットする
※サンプルコードではdocker-compose.ymlに環境変数をセットしています。
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost
LoginControllerの準備
最低限の動作さえすればいい実装にしています。実際に使う場合はValidation含め作り込んでください。
api/app/Http/Controllers/Api/Auth/LoginController.php
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class LoginController extends Controller
{
/**
* @Method POST
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function login(Request $request): JsonResponse
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required'
]);
if (auth()->attempt($credentials)) {
return response()->json(['message' => 'OK!'], 200);
}
return response()->json(['message' => 'ユーザーが見つかりません。'], 422);
}
}
Routeの設定
認証用のRouteとUser情報取得用のRouteを設定します。
※ Laravel8からControllerの指定の仕方が変わったので注意してください。
api/routes/api.php
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\Auth\LoginController;
Route::prefix('auth')->group(function() {
Route::post('/login', [LoginController::class, 'login']);
});
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
Laravel側は以上で準備完了です。
Nuxt側
Nuxtのプロジェクト作成にはUniversalモードとSPAモードとありますが、SPAモードで作成します。
※1. SSRではstoreの関係上、MiddlewareでAuthの設定が上手く反映されません。
※2. プロジェクト作成時にAxiosとBootStrapを指定しています。
必要ライブラリのインストール
サンプルを動かせばinstallされていますが、作業ログとして記録しておきます。
$ docker-compose exec web yarn add @nuxtjs/auth
Auth設定
web/nuxt.config.js
export default {
// ....
modules: [
'bootstrap-vue/nuxt',
'@nuxtjs/auth', // 追記
'@nuxtjs/axios', // 追記
],
axios: {
baseURL: "http://localhost",
credentials: true,
},
auth: {
redirect: {
login: '/login',
logout: '/',
callback: false,
home: '/'
},
strategies: {
local: {
endpoints: {
login: { url: '/api/auth/login', method: 'post', propertyName: false },
user: { url: '/api/user', method: 'get', propertyName: false },
logout: false
},
tokenRequired: false,
tokenType: false,
}
},
localStorage: false,
},
router: {
middleware: ['auth']
},
}
Store設定
store利用するためにindex.js用意しろと怒られるので作成します。
$ docker-compose exec web touch store/index.js
Loginページ設定
サンプルコードを見ていただければよいのですが、Loginページだけ記載しておきます。
web/pages/login.vue
<template>
<div class="container">
<h1 class="h1 pb-1 border-bottom border-dark">Login</h1>
<p>ログイン状態: {{ $auth.loggedIn }}</p>
<div class="row justify-content-md-center">
<div class="col-6">
<form name="login" @submit.prevent="login()">
<div class="form-group">
<label for="email">Email</label>
<input type="email" class="form-control" id="email" placeholder="Enter email" v-model="form.email" required />
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" placeholder="Enter password" v-model="form.password" required />
</div>
<button type="submit" class="btn btn-success">Submit</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
form: {
email: '',
password: ''
},
};
},
mounted() {
// csrf対策
// nginxでRPする場合は/sanctumがapi側を見に行くようにしてください
this.$axios.get('/sanctum/csrf-cookie');
},
methods: {
async login() {
try {
const response = await this.$auth.loginWith('local', { data: this.form });
console.log(response);
} catch(error) {
console.log(error);
}
},
},
};
</script>
作業は以上となります。
最後に
今回はLaravel Sanctumを利用したSPA認証について書きました。
課題はSSR時にどうするかですね。そちらも解決できたらまた記事を書きたいと思います。
Discussion