POSTリンクの正しい使い方

Webアプリケーション、特に管理インターフェースでは、サーバーの状態を変更するアクションはHTTPメソッドGETを介して実行されるべきではないという基本的なルールがあるべきです。メソッド名が示すように、GETはデータの取得にのみ使用されるべきであり、変更には使用されるべきではありません。 レコードの削除などのアクションには、POSTメソッドを使用する方が適しています。理想的にはDELETEメソッドですが、JavaScriptなしでは呼び出せないため、歴史的にPOSTが使用されています。

実践的にはどうすればよいでしょうか?この簡単なトリックを利用してください。テンプレートの最初に、postForm という識別子を持つ補助フォームを作成し、それを削除ボタンに使用します。

<form method="post" id="postForm"></form>

このフォームのおかげで、古典的なリンク <a> の代わりに、通常のリンクのように見えるように視覚的に調整できるボタン <button> を使用できます。たとえば、CSSフレームワークBootstrapは、ボタンが他のリンクと視覚的に区別されないようにするクラス btn btn-link を提供します。属性 form="postForm" を使用して、事前に準備されたフォームにリンクします。

<table>
	<tr n:foreach="$posts as $post">
		<td>{$post->title}</td>
		<td>
			<button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">削除</button>
			<!-- <a n:href="delete $post->id">delete</a> の代わりに -->
		</td>
	</tr>
</table>

リンクをクリックすると、delete アクションが呼び出されます。リクエストがPOSTメソッドと同一ドメインからのみ受け入れられるようにするため(これはCSRF攻撃に対する効果的な防御策です)、#[Requires] 属性を使用します。

use Nette\Application\Attributes\Requires;

class AdminPresenter extends Nette\Application\UI\Presenter
{
	#[Requires(methods: 'POST', sameOrigin: true)]
	public function actionDelete(int $id): void
	{
		$this->facade->deletePost($id); // レコードを削除する仮のコード
		$this->redirect('default');
	}
}

この属性はNette Application 3.2以降存在し、その可能性については \#Requires属性の使い方 ページで詳しく知ることができます。

actionDelete() アクションの代わりに handleDelete() シグナルを使用する場合、シグナルにはこの保護が暗黙的に設定されているため、sameOrigin: true を指定する必要はありません。

#[Requires(methods: 'POST')]
public function handleDelete(int $id): void
{
	$this->facade->deletePost($id);
	$this->redirect('this');
}

このアプローチは、アプリケーションのセキュリティを向上させるだけでなく、正しいWeb標準と実践の遵守にも貢献します。状態を変更するアクションにPOSTメソッドを利用することで、より堅牢で安全なアプリケーションを実現できます。