Useful to send data to the server without relying on the user clicking on a link, which would send a GET request, which should not be used for destructive operations (operations with database writing).
For reference, see https://softwareengineering.stackexchange.com/questions/188860/why-shouldnt-a-get-request-change-data-on-the-server and https://stackoverflow.com/questions/46585/when-do-you-use-post-and-when-do-you-use-get
You could use it for a like button, a confirm button, a delete button...
You need to activate an account. You send a link containing an activation token to the user. You could stop there and just activate the account once a GET request is sent to this url. But what if the user clicks on this link by mistake? What if his email client automatically opens every link in emails? (some clients do that by default)
A safer way to implement that feature is to redirect the user to a confirmation page when he clicks on the link.
Let's say the link looks like that: mywebsite.com/activate-account?token=ntdctzDNCfPbv8a7Exzi2yhbFEkUQ3
src/Controller/User/AccountActivationController.php
<?php
namespace App\Controller\User;
use App\Controller\DefaultController;
use App\Entity\User;
use App\Helper\StringHelper;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
/**
* Class AccountActivationController
* @package App\Controller\User
*/
class AccountActivationController extends DefaultController
{
/**
* Renders account activation confirmation view where user can click a button to confirm the activation.
*
* @param Request $request
* @Route("/activate-account/confirm", name="account_activation_confirm", methods="GET")
* @return RedirectResponse
*/
public function confirm(Request $request): Response
{
$accountActivationToken = $request->get('token');
/*
* If no token was sent AND if accountActivationToken field is nullable (let's say you
* clear the field once the account has been activated) we must abort here or we will get
* the first user without accountActivationToken instead of no user at all.
*/
if (empty($accountActivationToken)) {
return $this->redirectToRoute('home');
}
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository(User::class)->findOneBy([
'accountActivationToken' => $accountActivationToken
]);
/*
* If no user matches the token, we abort here.
*/
if ($user === null) {
return $this->redirectToRoute('home');
}
return $this->render('user/account-activation-confirm.html.twig', [
'user' => $user
]);
}
/**
* Activates account matching activation token.
*
* @param Request $request
* @Route("/activate-account/activate", name="account_activation_activate", methods="POST")
* @return RedirectResponse
* @throws AccessDeniedException
*/
public function activate(Request $request): RedirectResponse
{
// Form is not managed by TypeForm so we need to verify the CSRF token manually.
if ($this->isCsrfTokenValid('account_activation_activate', $request->get('_csrf_token')) === false) {
throw new AccessDeniedException('Invalid CSRF token.');
}
$accountActivationToken = $request->get('account_activation_token');
if (empty($accountActivationToken)) {
return $this->redirectToRoute('home');
}
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('AppBundle:User')->findOneBy([
'accountActivationToken' => StringHelper::truncateToMySQLVarcharMaxLength($accountActivationToken)
]);
$this->addFlash(
'account-activation-success',
$this->get('translator')->trans('flash.user.account_activated_successfully')
);
if ($user !== null && $user->isActivated() === false) {
$user->setActivated(true);
$user->setAccountActivationToken(null);
$em->flush();
}
return $this->redirectToRoute('login');
}
}
templates/user/account_activation_confirm.html.twig
{% extends '_base.html.twig' %}
{% block body %}
<div class="row">
<h1 class="col">Account Activation</h1>
</div>
<div class="row mb-3">
<div class="col">
<p>
Hey {{ user.username }}! Please click on the button below to activate your account.
</p>
</div>
</div>
{{ include('form/user/button/_account_activation_confirm.html.twig') }}
{% endblock %}
templates/form/user/button/_account_activation_confirm.html.twig
<form action="{{ path('account_activation_activate') }}" method="post" id="form-button-account-activation-activate">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('account_activation_activate') }}" id="csrf-token"/>
<input
type="hidden" name="account_activation_token" value="{{ user.accountActivationToken }}"
id="account-activation-token"
/>
<input
class="btn btn-primary border-0 btn-block rounded-0 text-white" type="submit" id="_submit"
name="_submit"
value="{{ 'user.activate_my_account'|trans }}"
/>
</form>
Maybe you don't have to pass any data because you already have access to the data you need (e.g. through the User object making the request). In that case, you juste have to send a CSRF token. For example, a button the user can click to receive an email with a confirmation link to delete his account (leading to a confirmation page such as the one presented in the example with passing data).
templates/form/user/button/account-deletion-request.html.twig
<form action="{{ path('account_deletion_request') }}" method="post" id="form-button-account-deletion-request">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('account_deletion_request') }}" id="csrf-token"/>
<input
class="btn btn-danger border-0 rounded-0 text-white border-0" type="submit" id="_submit"
name="_submit"
value="{{ 'user.send_me_deletion_link'|trans }}"
/>
</form>
src/Controller/User/AccountDeletionController.php
/**
* Sends account deletion link by email to user then log him out.
*
* @param Request $request
* @Route("account/deletion-request", name="account_deletion_request", methods="POST")
* @return RedirectResponse
* @throws AccessDeniedException|Exception
*/
public function request(Request $request): RedirectResponse
{
if ($this->isCsrfTokenValid('account_deletion_request', $request->get('_csrf_token')) === false) {
throw new AccessDeniedException('Invalid CSRF token.');
}
$user = $this->getUser();
// Your own logic