Come usare correttamente i link POST

Nelle applicazioni web, in particolare nelle interfacce di amministrazione, dovrebbe valere una regola fondamentale: le azioni che modificano lo stato del server non si eseguono con il metodo HTTP GET. Come dice il nome, GET dovrebbe servire solo a ottenere dati, non a modificarli. Per azioni come l'eliminazione di record è più appropriato usare il metodo POST. Il metodo DELETE sarebbe ideale, ma non si può richiamare senza JavaScript, ed è per questo che storicamente si è usato POST per azioni del genere.

Come realizzarlo in pratica? Usate questo semplice trucco. All'inizio del vostro template di layout create un form di supporto con l'ID postForm. Userete poi questo form per azioni come i pulsanti di eliminazione:

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

Grazie a questo form, al posto di un normale link <a> potete usare un <button>. Questo pulsante si può stilizzare perché sembri un normale link. Il framework CSS Bootstrap, per esempio, offre le classi btn btn-link, che rendono il pulsante visivamente indistinguibile dagli altri link. Con l'attributo form="postForm" collegate il pulsante al form di supporto preparato:

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

Cliccando questo pulsante viene ora richiamata l'azione delete. Perché le richieste vengano accettate solo con il metodo POST e provengano dallo stesso dominio (una difesa efficace contro gli attacchi CSRF), usate l'attributo #[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); // codice ipotetico per eliminare un record
		$this->redirect('default');
	}
}

Questo attributo è disponibile da Nette Application 3.2. Potete saperne di più sulle sue possibilità nella pagina Come usare l'attributo #Requires.

Se invece dell'azione actionDelete() usaste il segnale handleDelete(), non è necessario indicare sameOrigin: true, perché i segnali hanno questa protezione attiva per impostazione predefinita:

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

Questo approccio non solo aumenta la sicurezza della vostra applicazione, ma favorisce anche il rispetto dei corretti standard e delle buone pratiche del web. Usare i metodi POST per le azioni che cambiano lo stato porta a un'applicazione più solida e più sicura.