eyecatch

SvelteKit Routes を保護する2つの方法

Posted on 2025/02/01
# Technology

多くのアプリケーションが認証機能などを使いアカウントページなどの特定のページを保護する必要があります。今回このブログでは2つの例を用いり、どうやって認証ロジックを実行させるかを紹介していきます。

実際のコードは下記の GitHub repository で確認することができます。

どこで認証ロジックを実行するか?

Auth cookies can be checked inside server hooks. If a user is found matching the provided credentials, the user information can be stored in locals.

SvelteKitの公式サイトによると、src/hooks.server.ts で認証ロジックを実行するのが良さそうです。
この例では hooks.server.ts Server にリクエストが飛んできた時のみに実行されるます。なので SPA mode では実行されませんのでお気をつけてください。

Folder structure

src
├── routes
│   ├── (protected)
│   │   ├── user
│   │       ├── +page.svelte
│   │       ├── +page.server.ts
│   ├── about
│   │   ├── +page.svelte
│   │   ├── +page.server.ts
│   ├── login
│   │   ├── +page.svelte
│   │   ├── +page.server.ts
│   ├── +page.svelte
│   ├── +page.server.ts
│   ├── +layout.svelte
│   ├── +layout.server.ts
├── hooks.server.ts

(protected) というフォルダ以下のページを守っていきたいと思います。
hooks.server.ts を呼ぶためにそれぞれのディレクトリに空の +page.server.ts を作成します。
詳しいページの説明は今回は割愛し、 src/hooks.server.ts をみていきます。

2つの Routes Protection の方法

  • ディレクトリ名によるガード (おすすめ)
  • Pathname によるガード

ディレクトリ名によるガード (おすすめ)

import { redirect, type Handle } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks';

const authHandle: Handle = async ({ event, resolve }) => {
	const response = await resolve(event);

	// Get the root directory name
	const rootDirName = event.route.id?.split('/')[1] || '';
	const protectedDirName = '(protected)';

	// Get the auth token from the cookie
	const auth_token = event.cookies.get('auth_token');

        // You may need authorize auth_token in real world.

	// Redirect to the root if the user is not authenticated
	if (
          rootDirName === protectedDirName
          && !auth_token
        ) {
		redirect(301, '/login');
	}

	return response;
};

export const handle = sequence(authHandle);

このアプローチでは (protected) 以下にあるページを守ります。ページを追加したい場合はこのディレクトリ以下にページを追加してあげるだけなので readability や maintenancability が良いです。

event.route.id プロパティはカレントルートのIDを返します。
例えば src/routes/(protected)/user のページに遷移しようとした場合に、 /(protected)/user が返り値となります。このIDに (proteted) が含まれてるかを判断して必要であれば認証などの実行をします。認証が失敗すればログインページなどにリダイレクトも可能です。
今回は例などでクッキーに auth_token があるかないかを判断してます。

個人的な感想ですが、このやり方がスモールスタート時にはわかりやすく、メンテなどもやりやすくおすすめです。

より詳細はこちらから確認できます。

Pathname によるガード

import { redirect, type Handle } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks';

// Protected path list
const protectedPaths = ['/user'];

const authHandle: Handle = async ({ event, resolve }) => {
	const response = await resolve(event);

	// Get the path name
	const pathName = event.url.pathname;

	// Get the auth token from the cookie
	const auth_token = event.cookies.get('auth_token');

        // You may need authorize auth_token in real world.

	// Redirect to the root if the user is not authenticated
	// and check if the path is in the protected path list
	if (
          protectedPaths.some((protectedPath) => pathName.startsWith(protectedPath))
          && !auth_token
        ) {
		redirect(301, '/login');
	}

	return response;
};

export const handle = sequence(authHandle);

protectedPaths に定義されたルートを保護できます。これにより、パスごとに柔軟な保護を設定できます。ただし、新しい保護対象のルートを追加するたびに protectedPaths を更新する必要があり、プロジェクトが大きくなるにつれて管理が難しくなる可能性があります。

さらに、protectedPaths.some((protectedPath) => pathName.startsWith(protectedPath)) の計算量は O(n) です。protectedPaths に含まれるルートの数が増えると、それに伴い処理の時間も増加します。

詳細なコードはこちらで確認できます。

まとめ

私の経験から、ディレクトリ名ベースの保護を使用することをおすすめします。

以前、パス名ベースの保護を採用していたときは、アプリケーションの開発が大変でした。protectedPaths の更新を忘れることが多く、プロジェクトのフォルダ構成もすぐに乱雑になってしまいました。

一方で、ディレクトリ名ベースの保護を採用すると、保守性が向上し、より直感的に管理できます。長期的に見ても、こちらの方法のほうが扱いやすいです。