SvelteKitで認証・セッション管理を実装した話

TODOアプリにログイン機能を追加しました。 SvelteKit独自の仕組みである hooks.server.tslocals を使い、ライブラリなしでCookieベースの認証を実装しています。

このシリーズの記事

  1. SvelteKitで記事一覧・詳細ページを作った話
  2. SvelteKitでTODOアプリを作った話
  3. SvelteKitで認証・セッション管理を実装した話(この記事)
  4. 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.tshandle 関数を通ってからページ処理に入ります。

ブラウザ → 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 関数の引数 localshooks.server.ts でセットした値が入っています。未ログインの場合はそのまま /login に飛ばします。

ログアウト処理

// src/routes/todo/+page.server.ts(actionsに追加)
  logout: async ({ cookies }) => {
    cookies.delete('session_id', { path: '/' });
    redirect(303, '/login');
  },

cookies.delete でCookieを削除するだけです。pathcookies.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.tsLocals locals の型定義
保護ルート load 関数で locals を確認してリダイレクト
default アクション フォームが1つだけのページで使うアクション名

hooks.server.ts を使うと、認証チェックをページごとに書かずに一箇所にまとめられます。より本格的な実装では Lucia などのライブラリと組み合わせるのが一般的です。


前の記事:SvelteKitでTODOアプリを作った話

次の記事:SvelteKitをCloudflare Pagesにデプロイした話

← トップページに戻る