Laravelやってみる#5―Airlockの導入と認証

Laravel 7.x

ぬにょす(挨拶)。

表題の通りですが、PHPフレームワークであるところのLaravelを使ってみようということで、やったこと・できたこと・できなかったこと等を自分の備忘録として残していきます。

#5の今回はLaravel Airlockを使ったユーザー認証を実装します。

Laravel Airlockとは

Page not found - Laravel - The PHP Framework For Web Artisans

Laravel Airlock provides a featherweight authentication system for SPAs (single page applications), mobile applications, and simple, token based APIs. Airlock allows each user of your application to generate multiple API tokens for their account. These tokens may be granted abilities / scopes which specify which actions the tokens are allowed to perform.

https://laravel.com/docs/7.x/airlock#introduction

SPAとかシンプルなトークンベースのAPI向けの軽量な認証システムらしいです。Laravel6のドキュメントにあったトークンベースの認証ガードがLaravel7では無くなっているので、その部分がAirlockというパッケージになったんでしょうか?

インストール

Page not found - Laravel - The PHP Framework For Web Artisans

composer でインストールしたのち、artisan コマンドでゴニョゴニョします。migrateはデータベースのあるゲストOSで実行しましょう。

composer require laravel/airlock php artisan vendor:publish --provider="Laravel\Airlock\AirlockServiceProvider" php artisan migrate

設定

Page not found - Laravel - The PHP Framework For Web Artisans

リクエストを受け付けるSPAのドメインを設定するのですが、設定ファイル(config/airlock.php)では環境変数を参照しています。

'stateful' => explode(',', env('AIRLOCK_STATEFUL_DOMAINS', 'localhost')),

なので .env ファイルにエントリーを追加します。

.env
AIRLOCK_STATEFUL_DOMAINS=ubuntu1804.local

AirlockのミドルウェアをAPIミドルウェアグループに追加します。

app/Http/Kernel.php
protected $middlewareGroups = [ 'web' => [ ... ], 'api' => [ \Laravel\Airlock\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 'throttle:60,1', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ];

ログイン処理

Page not found - Laravel - The PHP Framework For Web Artisans

axios で /login にユーザー情報をPOSTしますが、ドキュメントによると最初に /airlock/csrf-cookie をリクエストしてCSRF保護を初期化すべし、とあります。

resources/js/views/LoginView.vue
methods: { login: function() { axios .get("/airlock/csrf-cookie") .then(() => { axios .post("/login", this.form) .then(response => { this.$router.push({ name: "home" }); }) .catch(error => { console.log("Login Error", error); }); }) .catch(error => { console.log("Airlock Error", error); }); } }

しかし試してみた範囲では、/airlock/csrf-cookie へのリクエストを省略しても動作に違いは見られませんでした。今回のようにバックエンドとフロントエンドが同一ドメインの場合は特に必要ないかもしれません。

コントローラーからのレスポンス変更

Laravelのスカフォールドで作成されたコントローラーでは、ログイン/ログアウトが成功した場合にリダイレクトするようになっています。そこで、AuthenticatesUsers トレイトのメソッドをオーバーライドして、JSONレスポンスを返すように変更します。

app/Http/Controllers/Auth/LoginController.php
use Illuminate\Http\Request; class LoginController extends Controller { /** * The user has been authenticated. * * @param \Illuminate\Http\Request $request * @param mixed $user * @return mixed */ protected function authenticated(Request $request, $user) { if ($request->wantsJson()) { return response()->json($user); } } /** * The user has logged out of the application. * * @param \Illuminate\Http\Request $request * @return mixed */ protected function loggedOut(Request $request) { if ($request->wantsJson()) { return response()->json('Logout successful.'); } } }

登録処理についても同様に RegistersUsers トレイトのメソッドをオーバーライドして、JSONレスポンスを返すように変更します。

app/Http/Controllers/Auth/RegisterController.php
use Illuminate\Http\Request; class RegisterController extends Controller { /** * The user has been registered. * * @param \Illuminate\Http\Request $request * @param mixed $user * @return mixed */ protected function registered(Request $request, $user) { if ($request->wantsJson()) { return response()->json($user); } } }

ログアウト処理

ログイン処理と似たような感じで実装しました。

resources/js/components/NavbarComponent.vue
methods: { logout: function() { axios .get("/airlock/csrf-cookie") .then(() => { axios .post("/logout") .then(response => { this.$router.push({ name: "welcome" }); }) .catch(error => { console.log("Logout Error", error); }); }) .catch(error => { console.log("Airlock Error", error); }); } }

AirlockでガードしたAPI

もともと定義されている /user API のミドルウェアを Airlock に変更します。

routes/api.php
// Route::middleware('auth:api')->get('/user', function (Request $request) { Route::middleware('auth:airlock')->get('/user', function (Request $request) { return $request->user(); });

正しく保護されているかを検証するために、WelcomeページからこのAPIを呼んでみます。

resources/js/views/WelcomeView.vue
<template> ... <div> <b-button @click="onClick">Get User</b-button> <span>{{ message }}</span> </div> ... </template> <script> export default { data: function() { return { message: "" }; }, methods: { onClick: function() { this.message = ""; axios .get("/api/user") .then(response => { this.message = `Now ${response.data.email} is logged in.`; }) .catch(error => { this.message = `Oops! ${error.response.data.message}`; }); } } }; </script>

実行結果

非ログイン時はエラーメッセージが表示され、ログイン時はアカウント名が表示されました。

ユーザー登録処理

ログイン処理と同様にフォームデータをPOSTして、ホーム画面にリダイレクトします。

resources/js/views/RegisterView.vue
methods: { register: function() { axios .get("/airlock/csrf-cookie") .then(() => { axios .post("/register", this.form) .then(response => { this.$router.push({ name: "home" }); }) .catch(error => { console.log("Register Error", error); }); }) .catch(error => { console.log("Airlock Error", error); }); } }

非ログイン状態での /home へのアクセス

Vueスクリプトでログイン後は /home へリダイレクトするようにしました。では、ログイン前に /home へアクセスするとどうなるでしょう。

ブラウザのアドレスバーに URL を直接打ち込んで読み込んだ場合は、Laravelフレームワークの auth ミドルウェアによる保護が働いて、ログイン画面にリダイレクトされます。

ところが、Vue Router での画面遷移はサーバーにリクエストするわけではないので、保護は効きません。ですので、Vue 側では独自にログイン状態により出力するリンクやリダイレクト先をコントロールする必要があります。

次回は

バリデーションメッセージの取得と表示を実装します。

コメント

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