htmx 逆引きレシピ
CSRFトークン等をヘッダ/パラメータに付けるには?

公開日:
最終更新日:

管理画面の保存APIでは、CSRFトークン画面固定値など、フォーム入力以外の情報も一緒に送る場面がよくあります。
そこで、htmxの hx-headers / hx-vals / hx-include を使って、必要な情報を“安全にまとめて送信”します。

このページでは、CSRF対策画面固定値を付与フォーム外の値も同送を1つのデモで実現します。
送信値を hx-params で絞りつつ、運用しやすい形に整理するのがポイントです。

使用するhtmx属性

  • hx-post:POSTで保存し、結果HTMLを返して差し替える(保存/更新向き)
  • hx-headers:リクエストヘッダを追加(例:X-CSRF-Token を送ってCSRF対策)
  • hx-vals:フォーム値とは別に、固定パラメータを追加して送信(例:画面ID/用途など)
  • hx-include:フォーム外の要素も送信に含める(例:ツールバーのselect/hiddenなど)
  • hx-params:送信するパラメータを絞る(必要なキーだけにして安全&デバッグしやすくする)
  • hx-encoding:送信形式を指定(例:multipart/form-data でFormData送信)

利用シーン

  • 「CSRF対策」:セッション型トークンをヘッダ送信し、保存APIの不正リクエストを防ぎたい
  • 「画面固定値を一緒に送る」:画面IDや用途(保存/承認など)を固定で付与し、ログや分岐に使いたい
  • 「フォーム外の値も含めたい」:ヘッダー/ツールバーの条件(テナント・操作ユーザーなど)を同時に送りたい

CSRFトークン等をヘッダ/パラメータに付けるには?

このデモは CSRF対策 と、画面固定値フォーム外の値をまとめて送る例です。
hx-headers / hx-vals / hx-include / hx-params を同時に使います。

HTML(一部PHP)

<?php
	// CSRFトークンが未生成なら作る
	if (!isset($_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token'])) {

		// ランダムなトークンを生成する
		$_SESSION['csrf_token'] = bin2hex(random_bytes(16));

	}

	// CSRFトークンを取り出す
	$csrfToken = (string)$_SESSION['csrf_token'];

?>
<div class="DEMO">

	<h4>CSRFトークン等をヘッダ/パラメータに付けるには?</h4>

	<section>

		<h5>フォーム外の値(固定値/外部値)</h5>

		<div class="CARD" style="display:grid; gap:.6rem;">

			<p class="HTMX-NOTE">
				ここはフォームの外です。<br>
				でも <code>hx-include</code> で一緒に送れます。
			</p>

			<!-- フォーム外の固定値(hidden) -->
			<input id="DEMO_CSRF_TENANT" type="hidden" name="tenant_id" value="TENANT-001">

			<!-- フォーム外の操作値(select) -->
			<label id="DEMO_CSRF_TOOLBAR">
				操作ユーザー(フォーム外)
				<select name="acting_as">
					<option value="tanaka">tanaka</option>
					<option value="sato">sato</option>
					<option value="suzuki">suzuki</option>
				</select>
			</label>

			<p class="HTMX-NOTE">
				送信に含めるのは「tenant_id / acting_as」だけにして、意図しない値は送らないのが安全です。
			</p>
		</div>

	</section>

	<section class="MT2rm">

		<h5>保存(CSRFヘッダ+固定値+フォーム外の値)</h5>

		<form
			id="DEMO_CSRF_FORM"
			class="FORM"
			method="post"
			hx-post="/htmx/demo/_csrf_attach_save.php"
			hx-target="#DEMO_CSRF_RESULT"
			hx-swap="innerHTML"
			hx-headers='{"X-CSRF-Token":"<?= htmlspecialchars($csrfToken, ENT_QUOTES, "UTF-8") ?>"}'
			hx-vals='{"screen_id":"csrf-attach-demo","request_kind":"save"}'
			hx-include="#DEMO_CSRF_TENANT, #DEMO_CSRF_TOOLBAR select"
			hx-params="title,amount,tenant_id,acting_as,screen_id,request_kind"
			hx-encoding="multipart/form-data"
		>
			<label>
				タイトル
				<input type="text" name="title" value="" placeholder="例:申請:端末貸与">
			</label>

			<label>
				金額
				<input type="text" name="amount" value="" placeholder="例:12000">
			</label>

			<button class="BTN is-ok" type="submit">
				保存する
			</button>

			<p class="HTMX-NOTE">
				CSRFトークンはヘッダに、画面固定値は <code>hx-vals</code> に、フォーム外の値は <code>hx-include</code> にまとめると運用が楽です。
			</p>
		</form>

		<div id="DEMO_CSRF_RESULT" class="CARD">
			<p class="HTMX-NOTE">ここに保存結果が表示されます。</p>
		</div>
	</section>

</div>

PHP

<?php
// 型を厳密に扱う
declare(strict_types=1);

// セッションを開始する(CSRF検証に使う)
session_start();

// HTMLとして返す
header('Content-Type: text/html; charset=UTF-8');

// HTMLエスケープ関数を用意する
function h(string $s): string {
	// 特殊文字をエスケープする
	return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

// セッションのCSRFトークンを取得する
$expectedToken = (string)($_SESSION['csrf_token'] ?? '');

// ヘッダからCSRFトークンを取得する(X-CSRF-Token)
$sentToken = (string)($_SERVER['HTTP_X_CSRF_TOKEN'] ?? '');

// CSRFトークンが一致するか判定する
$isOk = ($expectedToken !== '' && hash_equals($expectedToken, $sentToken));

// CSRFがNGならエラー表示する(デモなので200で返す)
if (!$isOk) {
	// エラーを返す
	echo '<div class="FORM-RESULT is-ng">';
	echo '<strong>CSRFトークンが不正です。</strong>';
	echo '<div class="HTMX-NOTE">ヘッダの <code>X-CSRF-Token</code> を確認してください。</div>';
	echo '</div>';
	exit;
}

// タイトルを受け取る
$title = (string)($_POST['title'] ?? '');

// 金額を受け取る
$amount = (string)($_POST['amount'] ?? '');

// フォーム外の固定値(tenant_id)を受け取る
$tenantId = (string)($_POST['tenant_id'] ?? '');

// フォーム外の値(acting_as)を受け取る
$actingAs = (string)($_POST['acting_as'] ?? '');

// hx-valsの固定値(screen_id)を受け取る
$screenId = (string)($_POST['screen_id'] ?? '');

// hx-valsの固定値(request_kind)を受け取る
$requestKind = (string)($_POST['request_kind'] ?? '');

// 前後空白を除去する
$title = trim($title);

// 前後空白を除去する
$amount = trim($amount);

// タイトルが空なら補正する
if ($title === '') $title = '申請:新規作成';

// 金額が空なら補正する
if ($amount === '') $amount = '0';

// tenant_idが空なら補正する
if ($tenantId === '') $tenantId = 'TENANT-UNKNOWN';

// acting_asが空なら補正する
if ($actingAs === '') $actingAs = 'unknown';

// screen_idが空なら補正する
if ($screenId === '') $screenId = '(none)';

// request_kindが空なら補正する
if ($requestKind === '') $requestKind = '(none)';

// デモ用に少し待つ(通信してる感)
usleep(500000);

// 新しいIDを作る
$newId = random_int(1000, 99999);

// 現在時刻を作る
$now = date('H:i:s');
?>
<div class="FORM-RESULT is-ok">
	<strong>保存しました。</strong>
	<div>ID:<?= h((string)$newId) ?></div>
	<div>タイトル:<?= h($title) ?></div>
	<div>金額:<?= h($amount) ?></div>

	<hr style="margin:.8rem 0; border:0; border-top:1px dashed rgba(0,0,0,.2);">

	<div><strong>一緒に送られた値</strong></div>
	<div>tenant_id(フォーム外) :<?= h($tenantId) ?></div>
	<div>acting_as(フォーム外) :<?= h($actingAs) ?></div>
	<div>screen_id(hx-vals) :<?= h($screenId) ?></div>
	<div>request_kind(hx-vals) :<?= h($requestKind) ?></div>
	<div>時刻:<?= h($now) ?></div>

	<p class="HTMX-NOTE">
		CSRFトークンはヘッダで検証し、画面固定値/フォーム外の値も同時に受け取れています。
	</p>
</div>

デモ

CSRFトークン等をヘッダ/パラメータに付けるには?

フォーム外の値(固定値/外部値)

ここはフォームの外です。
でも hx-include で一緒に送れます。

送信に含めるのは「tenant_id / acting_as」だけにして、意図しない値は送らないのが安全です。

保存(CSRFヘッダ+固定値+フォーム外の値)

CSRFトークンはヘッダに、画面固定値は hx-vals に、フォーム外の値は hx-include にまとめると運用が楽です。

ここに保存結果が表示されます。

解説

HTMLでやっていること

  • hx-headersX-CSRF-Token を付け、保存リクエストにCSRFトークンを同送します。
  • hx-valsscreen_idrequest_kind を付け、画面固定値を毎回送ります。
  • hx-include でフォーム外の tenant_id(hidden)や acting_as(select)も送信に含めます。
  • hx-params で送信キーを絞り、意図しない値が混ざらないようにします。
  • hx-encoding="multipart/form-data" にして、FormData送信に寄せた構成にします。

PHPでやっていること

  • セッションでCSRFトークンを保持し、リクエストヘッダの X-CSRF-Token と比較して検証します。
  • CSRFが不一致なら処理を止め、エラーHTMLを返します(保存しない)。
  • 一致していれば、POST値(フォーム+フォーム外+固定値)をまとめて受け取り、保存結果として表示します。

ポイント:「安全のための値(CSRF)」と「画面文脈の値(固定値/外部値)」を分けて送ると、設計が整理しやすくなります。
htmxなら、フォームを汚さずに hx-headers / hx-vals / hx-include でスマートに実現できます。

※デモでは、便宜的に自作のCSSを使用してます。

このページの著者

もちもちみかん(システムエンジニア)

社内SEとしてグループ企業向けの業務アプリを要件定義〜運用まで一気通貫で担当しています。

経験:Webアプリ/業務システム

得意:PHP・JavaScript・MySQL・CSS

個人実績:フォーム生成基盤クイズ学習プラットフォーム

詳しいプロフィールはこちら!  もちもちみかんのプロフィール

もちもちみかん0系くん
TOPへ

もちもちみかん.comとは


このサイトでは、コーディングがめんどうくさい人向けのお助けツールとして、フォームやCSSをノーコードで生成できる、
 もちもちみかん.forms
 もちもちみかん.css1
 もちもちみかん.css2
と言ったジェネレーターを用意してます。

また、このサイトを通じて、「もちもちみかん」のかわいさを普及したいとかんがえてます!