Skip to content

5. Server Action の引数のvalidation

Server Action の引数の validation を行う

Server Action で受け取る引数は、かならずバリデーションを行うようにしましょう。

とくに typescript で実装していると引数に型があるので、引数は安全であると思いがちですが、実際にはそうではありません。

引数のバリデーションを行わなかった場合の問題点

実際に、引数のバリデーションを行わなかった場合の問題を体験してみましょう。

apps/workspace/app/actions.ts に、簡単な関数を追加してみます。

apps/workspace/app/actions.ts
...
export async function sum(a: number, b: number) {
return a + b;
}

apps/workspace/app/sum-button.tsx を追加します。

Terminal window
touch apps/workspace/app/sum-button.tsx
apps/workspace/app/sum-button.tsx
"use client";
import { sum } from "@/app/actions";
import { Button } from "@/components/ui/button";
export function SumButton() {
return (
<Button
onClick={async () => {
const res = await sum(1, 2);
console.log(res);
}}
>
足し算する
</Button>
);
}

最後に、apps/workspace/app/page.tsx<SumButton /> を追加します。

apps/workspace/app/page.tsx
import { LoginForm } from "@/app/components/login-form";
import { SignInForm } from "@/app/components/sign-in-form";
import { SumButton } from '@/app/sum-button';
...
return (
<main className="h-dvh p-8">
<SumButton />

これで、画面に「足し算する」というボタンが表示されます。

足し算するというボタンが追加されたページのキャプチャ

このボタンをクリックすると、コンソールに 3 と表示されるはずです。

console に"3"と表示されている

では、terminal からこの Server Action を呼び出したらどうなるでしょうか?

これを確かめるために、devtool の network タブから、server action のリクエストを curl としてコピーして実行してみましょう。

確認の仕方は少し複雑なので動画を見ながら確認してみてください。

  1. devtool を開きます

  2. network タブを開きます

  3. 画面の足し算するボタンをクリックします

  4. reqeust を確認します

  5. curl としてコピーします

ここまでで、clipboard に curl がコピーされました。

terminal に貼り付けて実行してみましょう。

下記のような結果が得られるはずです。

Terminal window
0:["$@1",["development",null]]
1:3

これのうち、1:33 が、sum 関数の結果です。

そこで、先ほどのcurlを少し変更してみましょう。

--data-raw の部分を ["1","2"] に変更してください。

Terminal window
curl 'http://localhost:3000/' \
...
--data-raw '[1,2]'
--data-raw '["1","2"]'

結果はどうなるでしょうか?

Terminal window
0:["$@1",["development",null]]
1:"12"

1:"12" となりました。文字列としての 12 を送った結果、"1" + "2"の結果として、"12" が返ってきました。

あらためて、sum の 引数を確認してみましょう。

apps/workspace/app/actions.ts
export async function sum(a: number, b: number) {
return a + b;
}

実装上はnumber型ですが、実際にはクライアントから好きな値を送信することができます。

(typescript はただの静的な型の確認をしているだけで、validationなどは行いません。)

今回は文字列が連結されてしまうだけでしたが、ここで DB にアクセスするなどの処理を行っていた場合、セキュリティリスクが発生する可能性があります。

このことから、引数のバリデーションが重要であることがわかります。

今回追加した変更を削除する🧹

ここまで確認ができたら、今回追加した変更は不要なので削除しておきましょう。

  1. action.ts から sum 関数を削除します

    apps/workspace/app/actions.ts
    ...
    export async function sum(a: number, b: number) {
    return a + b;
    }
  2. sum-button.tsx を削除します

    Terminal window
    rm apps/workspace/app/sum-button.tsx
  3. page.tsx から SumButton を削除します

    apps/workspace/app/page.tsx
    import { LoginForm } from "@/app/components/login-form";
    import { SignInForm } from "@/app/components/sign-in-form";
    import { SumButton } from '@/app/sum-button';
    ...
    return (
    <main className="h-dvh p-8">
    <SumButton />

引数のバリデーションを行う

引数のバリデージョンは、お好きなライブラリで行うことができますが、ここでは zod を使用します。 まずは、actions.ts にバリデーションを追加しましょう

apps/workspace/app/actions.ts
"use server";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { type inferFlattenedErrors, z } from "zod";
const authSchema = z.object({
email: z.string().email(),
password: z.string().min(1),
});
export async function auth(formData: FormData) {
const email = formData.get("email") as string;
const password = formData.get("password") as string;
export async function auth(formData: FormData) {
const parsedFormData = authSchema.safeParse(
Object.fromEntries(formData.entries()),
);
if (!parsedFormData.success) {
throw new Error("Invalid form data", { cause: parsedFormData.error });
}
const { email, password } = parsedFormData.data;
const response = await fetch("http://localhost:8000/auth", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",

authSchema.safeParse でバリデーションを行い、成功した場合は parsedFormData.data に、失敗した場合は parsedFormData.error にエラー情報が格納されます。 parsedFormData.success で成功か失敗かを判定できます。

この状態で、http://localhost:3000 にアクセスして、ログインフォームに下記のような値を入れてみてください。

  • email: foo@example
  • password: "" // 何も入力しない

すると、画面にエラーメッセージが表示されるはずです。

画面にエラーメッセージが表示されている

これで、バリデーションが正常に動作していることが確認できました。

次は、画面にエラーメッセージを表示するように変更していきましょう。

まとめ

今回は、Server Action で引数のバリデーションを行う方法を学びました。

Server Action で受け取る引数は、かならずバリデーションを行うようにしましょう。

参考リンク