SvelteKitで認証・セッション管理を実装した話
TODOアプリにログイン機能を追加しました。
SvelteKit独自の仕組みである hooks.server.ts と locals を使い、ライブラリなしでCookieベースの認証を実装しています。
このシリーズの記事
- SvelteKitで記事一覧・詳細ページを作った話
- SvelteKitでTODOアプリを作った話
- SvelteKitで認証・セッション管理を実装した話(この記事)
- SvelteKitをCloudflare Pagesにデプロイした話
作ったもの
/login:パスワードでログインしてCookieをセット/todo:未ログイン時は/loginにリダイレクト- ログアウト:Cookieを削除して
/loginに戻る
ファイル構成
src/
hooks.server.ts # 全リクエストの共通処理
app.d.ts # locals の型定義
routes/
login/
+page.svelte # ログインページ(UI)
+page.server.ts # ログイン処理(actions)
todo/
+page.svelte # TODOページ(UI)
+page.server.ts # 保護ルート・ログアウト
リクエストの流れ
すべてのリクエストは hooks.server.ts の handle 関数を通ってからページ処理に入ります。
ブラウザ → handle() → resolve() → load() / actions() → レスポンス
resolve() を呼ぶ前が前処理(認証チェックなど)、呼んだ後が後処理(ヘッダー追加など)のタイミングです。
hooks.server.ts
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
const sessionId = event.cookies.get('session_id');
if (sessionId === 'logged-in') {
event.locals.user = { name: 'Jiru' };
} else {
event.locals.user = null;
}
return resolve(event);
};
event.cookies でCookieを読み、event.locals にユーザー情報をセットします。locals にセットした値はそのリクエスト内のどのファイル(load 関数・actions)からも参照できます。
locals の型定義
event.locals にセットする値の型は src/app.d.ts で定義します。
// src/app.d.ts
declare global {
namespace App {
interface Locals {
user: { name: string } | null;
}
}
}
export {};
ここに書かないと TypeScript が locals.user を認識しません。
ログイン処理
// src/routes/login/+page.server.ts
import { redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
export const actions: Actions = {
default: async ({ request, cookies }) => {
const data = await request.formData();
const password = data.get('password') as string;
if (password === '1234') {
cookies.set('session_id', 'logged-in', {
path: '/',
httpOnly: true,
maxAge: 60 * 60 * 24,
});
redirect(303, '/todo');
}
},
};
アクション名を default にするとフォームに action="?/default" の指定が不要になります。ページ内にフォームが1つだけの場合に使います。
cookies.set のオプションはこちらです:
| オプション | 内容 |
|---|---|
path |
Cookieを送るパスのスコープ(/ で全ページ) |
httpOnly |
JavaScriptからCookieを読めなくする(XSS対策) |
maxAge |
Cookieの有効期限(秒)。ここでは1日 |
保護ルート
// src/routes/todo/+page.server.ts(抜粋)
export const load: PageServerLoad = ({ locals }) => {
if (!locals.user) {
redirect(303, '/login');
}
return { todos, user: locals.user };
};
load 関数の引数 locals に hooks.server.ts でセットした値が入っています。未ログインの場合はそのまま /login に飛ばします。
ログアウト処理
// src/routes/todo/+page.server.ts(actionsに追加)
logout: async ({ cookies }) => {
cookies.delete('session_id', { path: '/' });
redirect(303, '/login');
},
cookies.delete でCookieを削除するだけです。path は cookies.set と合わせる必要があります。
actions で使えるプロパティ
actions の各関数が受け取る RequestEvent の主なプロパティです。load 関数でも同じものが使えます。
| プロパティ | 内容 |
|---|---|
request |
フォームデータやリクエストボディの取得 |
cookies |
Cookieの読み書き・削除 |
locals |
hooks.server.ts でセットしたデータ |
params |
URLのパラメータ([id] など) |
url |
URLオブジェクト(クエリパラメータなど) |
fetch |
認証情報付きのサーバー側fetch |
まとめ
| 概念 | 内容 |
|---|---|
hooks.server.ts |
全リクエストに共通処理を挟むファイル |
handle 関数 |
resolve() の前後で前処理・後処理を書く |
event.locals |
リクエスト内でデータを伝搬する入れ物 |
app.d.ts の Locals |
locals の型定義 |
| 保護ルート | load 関数で locals を確認してリダイレクト |
default アクション |
フォームが1つだけのページで使うアクション名 |
hooks.server.ts を使うと、認証チェックをページごとに書かずに一箇所にまとめられます。より本格的な実装では Lucia などのライブラリと組み合わせるのが一般的です。