Cómo usar correctamente los enlaces POST

En las aplicaciones web, especialmente en las interfaces administrativas, debería ser una regla básica que las acciones que cambian el estado del servidor no se realicen mediante el método HTTP GET. Como sugiere el nombre del método, GET solo debe usarse para obtener datos, no para modificarlos. Para acciones como eliminar registros, es preferible usar el método POST. Aunque lo ideal sería el método DELETE, pero no se puede invocar sin JavaScript, por lo que históricamente se usa POST.

¿Cómo hacerlo en la práctica? Utiliza este simple truco. Al principio de la plantilla, crea un formulario auxiliar con el identificador postForm, que luego usarás para los botones de eliminación:

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

Gracias a este formulario, en lugar del enlace clásico <a>, puedes usar un botón <button>, que se puede modificar visualmente para que parezca un enlace normal. Por ejemplo, el framework CSS Bootstrap ofrece las clases btn btn-link con las que conseguirás que el botón no sea visualmente diferente de otros enlaces. Mediante el atributo form="postForm", lo vinculamos con el formulario predefinido:

<table>
	<tr n:foreach="$posts as $post">
		<td>{$post->title}</td>
		<td>
			<button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">eliminar</button>
			<!-- en lugar de <a n:href="delete $post->id">eliminar</a> -->
		</td>
	</tr>
</table>

Al hacer clic en el enlace, ahora se invoca la acción delete. Para asegurar que las solicitudes se acepten solo mediante el método POST y desde el mismo dominio (lo cual es una defensa eficaz contra los ataques CSRF), utiliza el atributo #[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); // código hipotético que elimina el registro
		$this->redirect('default');
	}
}

El atributo existe desde Nette Application 3.2 y puedes aprender más sobre sus posibilidades en la página Cómo usar el atributo #Requires.

Si en lugar de la acción actionDelete() usaras la señal handleDelete(), no es necesario especificar sameOrigin: true, porque las señales tienen esta protección configurada implícitamente:

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

Este enfoque no solo mejora la seguridad de tu aplicación, sino que también contribuye al cumplimiento de los estándares y prácticas web correctos. Al utilizar métodos POST para acciones que cambian el estado, lograrás una aplicación más robusta y segura.