Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mercure with Symfony and API Platform: 401 Unauthorized Error for Real-Time Notifications #914

Open
sayou opened this issue Jun 6, 2024 · 0 comments

Comments

@sayou
Copy link

sayou commented Jun 6, 2024

Hello everyone,

I’m working on a Symfony 6 project with API Platform where users can log in and receive JWT tokens (I use lexik_jwt_authentication) to access private pages. I’m using the Mercure protocol to send real-time updates, and I’d like to create private updates, such as notifications, for authenticated users. I’m also using MongoDB as my database.

I’ve set up my Notification entity as an API resource and configured it to use Mercure for real-time updates. Here’s the relevant configuration for my Notification entity:

// src/Document/Notification.php

namespace App\Document;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Get;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use Symfony\Component\Serializer\Annotation\Groups;

#[MongoDB\Document]
#[ApiResource(
    normalizationContext: [
        'groups' => [
            'notification:get',
        ]
    ],
    mercure: ['private' => true],
)]
#[GetCollection(
    uriTemplate: '/notifications',
    normalizationContext: [
        'groups' => [
            'notification:get',
        ]
    ],
    security: "is_granted('IS_AUTHENTICATED_FULLY')",
)]
#[Get(
    normalizationContext: [
        'groups' => [
            'notification:get',
        ]
    ],
    security: "is_granted('IS_AUTHENTICATED_FULLY') and object.getTo() === user",
)]
class Notification
{
    #[MongoDB\Id()]
    #[Groups(['notification:get'])]
    private $id;

    #[MongoDB\Field(type: 'string')]
    #[Groups(['notification:get'])]
    private $message;

    // Other fields and getter/setter methods...
}

I’ve also created a JWTCreatedListener to add Mercure claims to the JWT payload:

// src/EventListener/JWTCreatedListener.php

namespace App\EventListener;

use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;
use Symfony\Component\Security\Core\User\UserInterface;

class JWTCreatedListener
{
    public function onJWTCreated(JWTCreatedEvent $event): void
    {
        $user = $event->getUser();
        if (!$user instanceof UserInterface) {
            return;
        }

        $payload = $event->getData();

        // Define the topics the user can subscribe to
        $topics = ["http://localhost/api/notifications/65437333e5ddba2e970a2dd2"];

        // Add Mercure claims to the payload
        $payload['mercure'] = [
            'subscribe' => $topics,
        ];

        $event->setData($payload);
    }
}

On the client side, I’m using the EventSource to include the JWT token in the request headers:

<!DOCTYPE html>
<html>
<head>
    <title>Mercure Real-Time Updates Test</title>
</head>
<body>
<h1>Real-Time Updates with JWT</h1>
<div id="update-container">
    <!-- Real-time updates will be displayed here -->
</div>

<script type="module">

    // Replace with your actual Mercure hub URL
    const mercureHubURL = 'http://localhost:8082/.well-known/mercure';

    // Replace with the actual topic URL for the resource you want to track updates for
    const topicURL = 'http://localhost/api/notifications/65437333e5ddba2e970a2dd2';
    const userJwtToken = 'your_jwt_token_here';

    const eventSource = new EventSource(`${mercureHubURL}?topic=${topicURL}`, {
        withCredentials: true,
        headers: {
            Authorization: `Bearer ${userJwtToken}`
        }
    });

    eventSource.onmessage = (event) => {
        // Handle the real-time update received from Mercure
        const updateData = JSON.parse(event.data);

        // Display the real-time update in the container
        const updateContainer = document.getElementById('update-container');
        updateContainer.innerHTML += `<p>Real-time update: ${JSON.stringify(updateData)}</p>`;

        // You can update your UI or perform any necessary actions with the updated data here
    };
</script>
</body>
</html>

Despite this setup, I’m encountering a 401 Unauthorized error when trying to receive updates:

GET http://localhost:8082/.well-known/mercure?topic=http://localhost/api/notifications/65437333e5ddba2e970a2dd2 net::ERR_ABORTED 401 (Unauthorized)

I’ve verified that the JWT token includes the correct Mercure claims and that the secret used to sign the JWT token matches the one configured in the Mercure hub.

Can anyone help me identify what might be going wrong or suggest any steps I might have missed?

Thank you in advance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant