Come utilizzare correttamente i link POST

Nelle applicazioni web, soprattutto nelle interfacce amministrative, dovrebbe essere una regola di base che le azioni che modificano lo stato del server non dovrebbero essere eseguite tramite il metodo HTTP GET. Come suggerisce il nome del metodo, GET deve essere usato solo per recuperare dati, non per modificarli. Per azioni come la cancellazione di record, è più appropriato usare il metodo POST. Anche se l'ideale sarebbe usare il metodo DELETE, questo non può essere invocato senza JavaScript, quindi storicamente si usa POST.

Come fare nella pratica? Utilizzando questo semplice trucco. All'inizio del modello, creare un modulo di aiuto con l'identificatore postForm, che verrà poi utilizzato per i pulsanti di cancellazione:

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

Con questo modulo, è possibile utilizzare un elemento <button> invece del classico link <a> che può essere modificato visivamente per sembrare un normale link. Ad esempio, il framework CSS Bootstrap offre le classi btn btn-link, che consentono al pulsante di essere visivamente indistinguibile dagli altri link. Utilizzando l'attributo form="postForm", lo colleghiamo al modulo già pronto:

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

Quando si clicca sul link, viene richiamata l'azione delete. Per garantire che le richieste siano accettate solo attraverso il metodo POST e dallo stesso dominio (che è una difesa efficace contro gli attacchi CSRF), utilizzare l'attributo #[Requires] …l'attributo:

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); // hypothetical code for deleting a record
		$this->redirect('default');
	}
}

L'attributo è disponibile da Nette Application 3.2 e per saperne di più sulle sue funzionalità si può consultare la pagina Come usare l'attributo #Requires.

Se si utilizza il segnale handleDelete() invece dell'azione actionDelete(), non è necessario specificare sameOrigin: true, perché i segnali hanno questa protezione impostata implicitamente:

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

Questo approccio non solo migliora la sicurezza dell'applicazione, ma contribuisce anche a rispettare gli standard e le pratiche web. Utilizzando i metodi POST per le azioni che cambiano stato, si ottiene un'applicazione più robusta e sicura.