Jak prawidłowo używać linków POST

W aplikacjach internetowych, zwłaszcza w interfejsach administracyjnych, podstawową zasadą powinno być to, że działania zmieniające stan serwera nie powinny być wykonywane za pomocą metody HTTP GET. Jak sugeruje nazwa metody, GET powinien być używany tylko do pobierania danych, a nie do ich zmiany. W przypadku działań takich jak usuwanie rekordów, bardziej odpowiednie jest użycie metody POST. Chociaż idealnym rozwiązaniem byłoby użycie metody DELETE, nie można jej wywołać bez JavaScript, dlatego POST jest historycznie używany.

Jak to zrobić w praktyce? Użyj tej prostej sztuczki. Na początku szablonu utwórz formularz pomocniczy o identyfikatorze postForm, który następnie użyjesz dla przycisków usuwania:

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

W tym formularzu możesz użyć <button> zamiast klasycznego linku <a> który można wizualnie zmodyfikować, aby wyglądał jak zwykły link. Na przykład framework Bootstrap CSS oferuje klasy btn btn-link, które pozwalają na wizualne odróżnienie przycisku od innych linków. Korzystając z atrybutu form="postForm", łączymy go z wcześniej przygotowanym formularzem:

<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>

Po kliknięciu linku wywoływana jest akcja delete. Aby upewnić się, że żądania są akceptowane tylko za pomocą metody POST i z tej samej domeny (co jest skuteczną obroną przed atakami CSRF), użyj atrybutu #[Requires] atrybut:

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');
	}
}

Atrybut ten jest dostępny od wersji Nette Application 3.2, a więcej o jego możliwościach można dowiedzieć się na stronie Jak używać atrybutu #Requires.

Jeśli używasz sygnału handleDelete() zamiast akcji actionDelete(), nie jest konieczne określanie sameOrigin: true, ponieważ sygnały mają domyślnie ustawioną tę ochronę:

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

Takie podejście nie tylko poprawia bezpieczeństwo aplikacji, ale także przyczynia się do przestrzegania odpowiednich standardów i praktyk internetowych. Używając metod POST dla akcji zmieniających stan, uzyskuje się bardziej niezawodną i bezpieczną aplikację.