Best practices

認証と認可

共通のミドルウェアポリシーを使い、Routes と Server Function に認証・認可を適用します。

ページへのアクセスと Server Function の実行は、それぞれ独立して保護します。 フォームを表示する Page を開かなくても、Server Function は呼び出せます。

呼び出し口ごとにチェックを適用する

保護された処理を始める前に、現在のリクエストを認証します。

呼び出し口保護する処理チェックの適用先
Route(Page と Layout)保護されたデータの読み取りと表示Page を登録する Routes の Middleware。
Server Function独立して呼び出せる操作の実行関数自体の Middleware またはハンドラー。
カスタム HTTP APIエンドポイント経由のデータの読み取りや変更HTTP ミドルウェアまたはエンドポイントのハンドラー。

カスタム HTTP エンドポイントには、HTTP ミドルウェアを使います。

両方の登録で一つのポリシーを共有する

保護する Routes と Server Function に、同じミドルウェア派生ファクトリーを使います。 この例の ./auth はアプリケーション側のモジュールであり、Effront の認証機能ではありません。 checkEditorAccess Effect は、現在のリクエストのセッションと編集権限を検証します。 このヘルパーは、アクセスを許可する場合に undefined を、失敗時に 401 や 403 などの HTTP 拒否レスポンスを返します。

src/effront.ts を作ります。

import { Application } from "@effront/core";
import { Effect } from "effect";
import { checkEditorAccess } from "./auth";

export const EFFRONT = Application.effront();

const RequireEditor = EFFRONT.Middleware.make((next) =>
  Effect.fn("RequireEditor")(function* () {
    const rejection = yield* checkEditorAccess;
    if (rejection !== undefined) return rejection;
    return yield* next;
  })(),
);

export const EditorEFFRONT = EFFRONT.withMiddleware(RequireEditor);

同じファクトリーを使って src/record-edit.ts を作ります。

"use server";

import { Effect, Schema } from "effect";
import { EditorEFFRONT } from "./effront";

export const recordEdit = EditorEFFRONT.ServerFn.make({
  input: Schema.fromFormData(Schema.Struct({})),
  handler: () => Effect.logInfo("Editor action accepted"),
});

src/entry.effront.tsx で、EditorEFFRONT.Routes を通して Page を登録します。

import { Effect } from "effect";
import { EFFRONT, EditorEFFRONT } from "./effront";
import { recordEdit } from "./record-edit";

const EditorPage = EditorEFFRONT.Page.make({
  render: () =>
    Effect.succeed(
      <form action={recordEdit}>
        <button type="submit">Record edit</button>
      </form>,
    ),
});

const RootLayout = EFFRONT.Layout.make({
  render: ({ children }) =>
    Effect.succeed(
      <html>
        <body>{children}</body>
      </html>,
    ),
});

export default EFFRONT.make({
  routes: EditorEFFRONT.Routes.make({ layout: RootLayout }).page("/editor", EditorPage),
});

Routes の登録は、Page と Layout のレンダー前にアクセス権を確認します。 Server Function の登録にも同じポリシーが適用され、ハンドラーが受理した操作をログに記録する前にチェックします。 EditorEFFRONT から Page や Layout だけを作っても、ルートのミドルウェアは有効になりません。 リクエスト固有のサービスやネストしたスコープは、Middleware ガイドを参照してください。

操作ごとに認可する

認証後に、確認済みのユーザーが対象レコードを読み取ったり変更したりできるかを確認します。 たとえば、プロジェクトの更新対象は、そのユーザーが編集できるプロジェクトに絞ります。 権限は、送信された所有者やロールではなく、サーバー側のデータから判断します。 Schema の検証とクライアント由来の状態については、Server Function の入力契約を参照してください。

次のリクエストを別々にテストします。

  • 権限のないユーザーは、保護された Page を見られない。
  • 同じユーザーが、その Server Function のリクエストを送信しても成功しない。
  • 認証済みのユーザーが他人のレコード ID を送信しても、そのレコードを変更できない。