Lately, I've been working on a notification system. I made it possible to create them, and to the user that receive them to read them, and now I want for the user to be able to dismiss and delete them. I want to handle there deletion asynchronously so the user doesn't have to refresh the page everytime he dismisses a notification.
The least important notifications only remain during one request, so I handled them with the session flashbag, and, on click, just remove them from the DOM in javascript.
But some more important messages remain until the user click them. So in javascript, when the user clicks them, I send an Ajax request to a controller that should delete the notification. The problem is : the controller can't access to the currently connected user ! I tried with $this->getUser()
and $tokenInterace->getToken()
, but both return null
.
Having access to the user would permit me to check if he really owns the notification and did not just type a random url trying to suppress someone's notification. It would also permit to clear all the notifications of the user (as another feature).
Here is all the configuration and code I have that I think can relate to this issue.
Symfony
# config/packages/framework.yaml
framework:
session:
handler_id: ~
SecurityBundle
# config/packages/security.yaml
security:
# ...
firewalls:
dev:
pattern: ^/(_(profiler|wdt))/
security: false
login:
pattern: ^/login$
anonymous: true
main:
pattern: ^/
anonymous: false
form_login:
# ...
remember_me:
secret: '%env(APP_SECRET)%'
lifetime: 604800
access_control:
# - { path: ^/api, role: ROLE_USER }
# If I uncomment this line, Ajax request get redirected to login page
# ...
FOSUserBundle
# config/packages/fos_user.yaml
fos_user:
db_driver: orm
firewall_name: main
user_class: App\Entity\User\User
from_email:
address: '%env(MAILER_URL)%'
sender_name: '%env(MAILER_URL)%'
The controller
// The controller is imported with options prefix="/api", name_prefix="api_" and expose=true
// (name_prefix is new from Symfony 4.1, it permits to automatically prefix all imported route names)
/**
* @Route("/notification", name="notification_")
*/
class NotificationApiController extends BaseController
{
/**
* @Route("/{id}", name="delete", requirements={"id"="\d+"})
* @Method({"DELETE"})
*/
public function delete(Notification $notification, TranslatorInterface $translator)
{
if ($notification->getUser() !== $this->getUser()) {
return $this->createJsonResponse([
'error' => $translator->trans('error.notification.not_owner'),
], 403);
}
$em = $this->getDoctrine()->getEntityManager();
$em->remove($notification);
$em->flush();
return $this->createJsonResponse();
}
/**
* @Route("/clear", name="clear")
* @Method({"DELETE"})
*/
public function clear(NotificationRepository $notificationRepository, TranslatorInterface $translator)
{
$user = $this->getUser();
if ($user === null) {
return $this->createJsonResponse([
'error' => $translator->trans('error.notification.not_user'),
]);
}
$notificationRepository->clearForUser($user);
return $this->createJsonResponse();
}
}
The Javascript
// Notifications are in the header
const $header = document.querySelector('header');
$header.addEventListener('click', (e) => {
const $notif = e.target.closest('.notif');
if ($notif !== null) {
// Parse the notification id
const id = +$notif.getAttribute('data-id');
if (id !== null) {
const route = Routing.generate('api_notification_delete', { id });
// I don't care about browser support
fetch(route, { method: 'DELETE' })
.then((response) => {
// check for errors
return response.json();
})
.then((response) => {
// remove notification from DOM;
})
}
}
});
I'm almost sure the problem isn't about session expiring. Or maybe session works differently for asynchronous request ? (That would be very weird)
Anyone ever encountered the problem, or has an idea on how to fix it ? If you need anything more, feel free to ask !
EDIT
I found the origin of the problem. It's the 'fetch' fonction. The Fetch API doesn't use cookies by default. To use cookies, you must pass { credentials: 'same-origin' }
as parameter to fetch each time you use it (as I found here).
Hope this can help someone one day !