🎃
laravelとvueで作る認証機能
前提
- backend側はlaravel、frontend側はvueに分離した完全SPAでの機能になります。
- 意外と調べてみた感じでは完全SPAでの認証機能の説明が少なくて困りましたので、備忘録として残しておこうと思います。
- ちなみに、laravel側はbreezeを使ってcontrollerやrequestなどの記述はしてあります。
- 今回はログイン、ログアウト、新規登録のみを作成しております。
概要
- api側のルート保護は、
laralve-sanctum
を使用 - vue側のルート保護は、
beforeEach
を使用 - ログイン認証は、sessionを使用
laravel側
- ほとんどこちらの記事を参考にしました。
- 一部変更した部分のみ記述していこうと思います。
ログインエラー時のレスポンスの記述方法を変更
-
こちらの記事を参考にして、書き換えました。
- デフォルトの状態だとエラーのときでも200が返ってしまっていたので、エラーの際はエラーが起きるように変更を加えました。
LoginRequest.php
/**
* Ensure the login request is not rate limited.
*
* @return void
*
* @throws \Illuminate\Validation\ValidationException
*/
public function ensureIsNotRateLimited()
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
return;
}
event(new Lockout($this));
$seconds = RateLimiter::availableIn($this->throttleKey());
- throw ValidationException::withMessages([
- 'email' => trans('auth.throttle', [
- 'seconds' => $seconds,
- 'minutes' => ceil($seconds / 60),
- ]),
- ]);
// FIXME: responseに何を詰めるべきか検討
+ $response = response()->json([
+ 'status' => Response::HTTP_UNAUTHORIZED,
+ 'seconds' => $seconds,
+ 'minutes' => ceil($seconds / 60),
+ 'errors' => trans('auth.throttle'),
+ ], Response::HTTP_UNAUTHORIZED);
+ throw new HttpResponseException($response);
}
ログイン判定はどうやってやるのか
- サーバーのセッションにて管理して、クッキーを使ってやり取りする形になります。
- 以下のように、ログイン済みかどうかをチェックする処理を記述します。
- 結果的にはシンプルな仕組みだったのですが、この箇所の仕組みがわからず結構苦戦しました。
- こちらの記事のおかげで助かりました。
IsAuthenticatedController.php
public function __invoke()
{
return response()->json(Auth::check());
}
api側のルート保護
- 以下のようにmiddlewareとして
sanctum
を使用することにしました。- その他の説明はこちらの記事を参考にしてもらったらと思います。
api.php
Route::post('signup', [RegisteredUserController::class, 'store'])->name('signup');
Route::post('login', [AuthenticatedSessionController::class, 'store'])->name('login');
// NOTE: 認証済みのrequestとして受信できる
Route::group(['middleware' => ['auth:sanctum']], function () {
// ログアウト処理
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout');
});
-
sanctum/csrf-cookie
というルートが作成されるので、このurlにアクセスするとxsrf-token
が発行され、そのtokenがヘッダーに含まれているrequestのみ許可するという状態になります。- 以下のように他のapiにアクセスする前に、app.vueにてアクセスして、
xsrf-token
をヘッダーにセットするようにすることで、ルート保護を行うことができます。
- 以下のように他のapiにアクセスする前に、app.vueにてアクセスして、
app.vue
<script setup lang="ts">
await axios
.get(API_URL + '/sanctum/csrf-cookie')
.then((res) => {
if (res.config.headers) {
useUserStore().setXsrfToken(res.config.headers['X-XSRF-TOKEN'] as string)
}
console.log(res)
})
.catch((err) => console.log(err))
</script>
- 以下にてaxios通信のヘッダーに必ず
XSRF-TOKEN
が設定されるようにしてあります。- ちなみに、usetStoreにて
XSRF-TOKEN
は管理するようにしています。 - storeはリロードなどすると消えますが、画面アクセスのタイミングで
XSRF-TOKEN
を取得するようにしているので、問題はないかと思います。
- ちなみに、usetStoreにて
plugins/axios.ts
const axiosInstance: AxiosInstance = axios.create()
axiosInstance.interceptors.request.use((config: AxiosRequestConfig<any>) => {
config.headers = {
// TODO: ヘッダーにx-xsrf-tokenを詰めて通信を行いたい
'X-XSRF-TOKEN': useUserStore().xsrfToken,
}
return config
})
axiosInstance.defaults.withCredentials = true
export default axiosInstance
フロント側のルート保護
authGuard.ts
export const authenticationGuard = (router: Router) => {
const userStore = useUserStore()
router.beforeEach(async (to) => {
const requiresAuth = to.matched.some((record) => record.meta.requiresAuth)
// NOTE: ルート保護されており且つ、ログインされていない場合はログイン画面に飛ばす
if (requiresAuth && !(await isAuthenticated())) {
return { name: UserRoute.LOGIN_ROUTE }
}
// TODO: ログイン後に元のルートにリダイレクトさせたい
// routingStore.setRedirectFrom(to.name)
})
}
- これをvue-routerにて登録すれば全てのルーティングにて、上記のメソッドが適用されるようになります。
router/index.ts
const router = createRouter({
routes: []
})
authenticationGuard(router)
export default router
まとめ
- こういう感じで、認証系の設定をしました。
- 色々とわからないことが当初は多かったですが、認証系がわかると通信の理解が深まるなと思いました。
- この方法がベストプラクティスかは不明ですので、これからも調べつつどのやり方がベターかを模索しようかと思います。
Discussion