Jak poprawnie używać odnośników POST
W aplikacjach webowych, zwłaszcza w interfejsach administracyjnych, podstawową zasadą powinno być, że akcje zmieniające stan serwera nie są wykonywane metodą HTTP GET. Jak sama nazwa wskazuje, GET służy tylko do pobierania danych, nie do ich zmiany. Do akcji takich jak usuwanie rekordów bardziej odpowiednia jest metoda POST. Idealna byłaby metoda DELETE, ale nie da się jej wywołać bez JavaScriptu, dlatego historycznie używa się do takich akcji POST.
Jak to zrealizować w praktyce? Wykorzystaj prosty trik. Na początku szablonu layoutu utwórz pomocniczy formularz o ID
postForm. Będziesz go potem używać dla akcji takich jak przyciski usuwania:
<form method="post" id="postForm"></form>
Dzięki temu formularzowi zamiast zwykłego odnośnika <a> możesz użyć <button>.
Przycisk da się ostylować tak, by wyglądał jak zwykły odnośnik. Na przykład framework CSS Bootstrap oferuje klasy
btn btn-link, dzięki którym przycisk staje się wizualnie nie do odróżnienia od pozostałych odnośników. Za
pomocą atrybutu form="postForm" powiążesz przycisk z przygotowanym formularzem pomocniczym:
<table>
<tr n:foreach="$posts as $post">
<td>{$post->title}</td>
<td>
<button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">usuń</button>
<!-- zamiast <a n:href="delete $post->id">usuń</a> -->
</td>
</tr>
</table>
Kliknięcie tego przycisku wywoła teraz akcję delete. Aby żądania były przyjmowane tylko metodą POST
i pochodziły z tej samej domeny (skuteczna obrona przed atakami CSRF), użyj atrybutu #[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); // hipotetyczny kod usuwający rekord
$this->redirect('default');
}
}
Atrybut ten jest dostępny od Nette Application 3.2. O jego możliwościach dowiesz się więcej na stronie Jak używać atrybutu #Requires.
Gdybyś zamiast akcji actionDelete() używał sygnału handleDelete(), podawanie
sameOrigin: true jest zbędne, bo sygnały mają tę ochronę włączoną domyślnie:
#[Requires(methods: 'POST')]
public function handleDelete(int $id): void
{
$this->facade->deletePost($id);
$this->redirect('this');
}
Takie podejście nie tylko zwiększa bezpieczeństwo Twojej aplikacji, ale też sprzyja przestrzeganiu właściwych standardów i praktyk webowych. Używanie metod POST do akcji zmieniających stan prowadzi do solidniejszej i bezpieczniejszej aplikacji.