👏
Drizzle+remix-auth+Cloudflare Workers+Cloudflare D1で簡易認証機能を作成する
はじめに
RemixとCloudflare D1を使用して、シンプルな管理画面認証を実装する方法を解説します。特に認証周りの実装に焦点を当てます。前提としてレコードの登録はDrizzle Studioなどを使って、IDとパスワードをハッシュ化したものを入れておきます。
実装の概要
- Remix Authを使用した認証
- Cloudflare D1をデータベースとして使用
- DrizzleORMでのデータベース操作
データベーススキーマ
app/db/schema.ts
export const adminsSchema = sqliteTable("admins", {
id: text("id").primaryKey(),
email: text("email").notNull().unique(),
password: text("password").notNull(),
createdAt: integer("created_at", { mode: "timestamp" })
.notNull()
.default(sql`CURRENT_TIMESTAMP`),
updatedAt: integer("updated_at", { mode: "timestamp" })
.notNull()
.default(sql`CURRENT_TIMESTAMP`),
});
認証ストラテジーの実装
app/strategies/admin-form.ts
export function getAdminFormStrategy(env: Env) {
const db = drizzle(env.DB, { schema });
return new FormStrategy<Admin>(async ({ form }) => {
const email = form.get("email");
const password = form.get("password");
if (!email || !password) {
throw new Error("Invalid form submission");
}
const admin = await db.query.adminsSchema.findFirst({
where: eq(adminsSchema.email, email),
});
if (!admin || !(await bcrypt.compare(password, admin.password))) {
throw new Error("Invalid credentials");
}
return admin;
});
}
ログインページの実装
app/routes/admin.login.tsx
export async function action({ request, context }: ActionFunctionArgs) {
const { env } = context.cloudflare;
const authenticator = new Authenticator<Admin>();
authenticator.use(getAdminFormStrategy(env), "admin");
try {
const admin = await authenticator.authenticate("admin", request);
const session = await sessionStorage.getSession(request.headers.get("cookie"));
session.set("admin", admin);
return redirect("/admin", {
headers: { "Set-Cookie": await sessionStorage.commitSession(session) },
});
} catch (error) {
if (error instanceof Response) throw error;
return { error: "ログインに失敗しました" };
}
}
重要なポイント:Authenticatorのインスタンス生成
従来の実装方法
通常、Remix Authの実装では、以下のようにauth.server.tsでAuthenticatorのインスタンスを作成します:
auth.server.ts
// 従来の実装(この方法は今回使えない)
export const authenticator = new Authenticator<Admin>(sessionStorage);
authenticator.use(strategy, "admin");
Cloudflare D1対応の実装
Cloudflare D1を使用する場合、envオブジェクトはリクエストのたびにCloudflareから提供されます。そのため、以下の変更が必要です:
- Authenticatorのインスタンスをリクエストごとに生成
- ストラテジーにenvを渡して初期化
- action関数内でこれらの処理を実行
admin.login.tsx
export async function action({ request, context }: ActionFunctionArgs) {
const { env } = context.cloudflare;
// リクエストごとにAuthenticatorを初期化
const authenticator = new Authenticator<Admin>();
// envをストラテジーに渡す
authenticator.use(getAdminFormStrategy(env), "admin");
// ...
}
この実装方法により:
- Cloudflare D1への接続をリクエストごとに適切に管理
- 環境変数やコンテキストを正しくストラテジーに渡すことが可能
- サーバーレス環境での適切な認証処理の実現
まとめ
Cloudflareの環境で認証を実装する際は、従来のシングルトンパターンではなく、リクエストごとの初期化が必要になります。これにより、サーバーレス環境での適切なリソース管理と認証処理が実現できます。
これが良さそうな方法だと思い実装しましたが、より良い方法があればアドバイスいただければと思います。
Discussion