Next.jsの新機能でTwitter(X)上でも少しバズったのがServer Actionsです。クライアントコンポーネント上にサーバー上で行うロジックを直接書き込むことが可能です。しかし、今までサーバーAPIを実装したことがあるのであれば、サーバー上のロジックであれば何かしらの認証のチェックやらCSRF対策などが必要なのではないか? という疑問を持つはずです。公式ドキュメントを見ても、ブログなどを検索してもそのあたりの話が出てこなかったので、少し動かしてみて検証してみました。
Server Actionsとは
Server Actionsは<form>
タグのaction
に設定する特殊なイベントハンドラです。2種類の書き方があります。1つはサーバーコンポーネントの定義の中に書いてしまう方法です。
"use server" |
もう1つは関数だけを作成する方法です。このファイルだけ"use server"
がついていますが、この関数はクライアントコンポーネントでインポートして利用できます。
"use server" |
通常、サーバー側でロジックを動かす場合、ウェブサーバーのルーターにエンドポイント(ハンドラ)を追加し、レスポンスを受け取ってパースしてそれを使って動かすといったボイラープレートを実装する必要があります。外部システムからのリクエストであるため、CSRFトークンをフォームに設定したり、バリデーションなども実装する必要があります。ですが、Server Actionsではエンドポイントを追加したりといった手間はありません。
どのような通信が起きているのか確認する
実際にどのような通信が起きているのか見てみましょう。通常のフォーム送信と比較するために2つフォームを作成しています。
export default function Home() { |
通常のPOST処理ではエンドポイントが必要になるため、これも実装しておきます。
export async function POST(req: Request) { |
まず通常の方を見てみると、フォームのnameがキーとして利用されており、素直なフォームデータが送られています。
name: しぶかわ |
次にServer Actionsを見てみます。
サーバー側のハンドラで受け取ったコンテンツは次の通りで、$ACTION_IDなる項目が増えています。
$ACTION_ID_0826d283a8eec2e3e98a3ef9e3e4269aca493681: |
しかし、開発者ツールで見ると、キーの名前などが改変されていたり、ハンドラで受け取っていない値なども入っていますね。Next.jsのサーバー側のコードが、このあたりを成型してからハンドラを呼んでいるようです。
まず、送り先ですが表示しているページと同じURLにPOSTで送っています。ちょっと変わったところとしては、いくつかNext-
がつくヘッダーフィールドを送っています。
Next-Actionの方はリロードしても値は変わらないため、Server Actionsのハンドラの識別子なのではないかと思います。別パスに同じ内容で作成しても別のIDになりました。よく見ると、Next-Actionヘッダーフィールドの値と、$ACTION_IDは同じですね。いろいろついていますが、curlで次のようなリクエストを送ったところ、外からもServer Actionsのハンドラを起動できました。CSRFトークンのようなものはなく、わかってしまえば外からリクエストが投げられてしまうというのは、パブリックに公開するAPIの場合はちょっと警戒しておいた方が良さそうですね。
$ curl -F '1_$ACTION_ID_0826d283a8eec2e3e98a3ef9e3e4269aca493681=' |
クッキーの認証情報を取得する方法もサンプルにありますが、ヘッダー値も取得できます。いきなりSQLを呼ぶとか話題になっていますが、セキュリティの防護はいつも通り行う必要がありそうです。次のコードはヘッダーとCookieをすべてダンプしています。必要な情報を取得してリクエスト元の認証や認可の検証をしたり、入力情報のバリデーションは行いましょう。
async function onAction(formData: FormData) { |
お客さんから教えてもらったのですが、React-Hook-Formにはzodを使って検証できるアダプターがあるらしいので、これを使えばクライアントのフォーム検証とサーバーの検証を同じzodのモデルを使ってできそうですね。
- React-Hook-Formの3rd partyへのアダプタ集: https://www.npmjs.com/package/@hookform/resolvers
- React-Hook-Form公式からリンクされているサイト: https://ui.shadcn.com/docs/components/form
おまけで、binding argumentsの仕組みを使ったら簡単にCSRFトークンの実装ができるかも試してみましたが簡単にはできませんでした。厳密にやるなら、クライアントコンポーネントにしつつ公開鍵も渡しつつ、それで署名して、サーバー側で秘密鍵で検証とかでしょうか。結構大がかりになってしまいますが。
まとめ
Server Actionsの実態としては、ちょっとキー名が改変されたリクエストを受け取るハンドラが生えて、そこに対するリクエストを投げるような動きをしていることが分かりました。
そのあたりの実装の手間が省けるのは便利ではありますが、外からリクエストが通ってしまうため、悪意のあるクライアントからのリクエストをチェックしてサニタイズするというロジックは別途必要なんだろうな、と思います。Next.jsのドキュメントに書いてあることとずれてしまいますが・・・