symfony/mercure-bundle

Multi domain usage

Closed this issue · 1 comments

Hi,

Thank for this great project. We have an issue:

  • website have multiple domains for the same code/server: domain.tld, anotherdomain.tld2, newdomain.tld3
  • we have one mercure instance: mercuredomain.com

What value can we set in mercure config of the bundle with multiple domain to avoid Unable to create authorization cookie for a hub on the different second-level domain?

Thanks

Hi,

What we did:

  • kept domains we set
  • created mercure.domain.tld, mercure.anotherdomain.tld2, mercure.newdomain.tld3
  • kept one mercure instance: mercuredomain.com but configured domain above to point to this instance
  • added in mercure_extra_directives all domains that consume mercure: domain.tld, anotherdomain.tld2, newdomain.tld3
  • added in mercure.yaml:
mercure:
    hubs:
        # Default Hub config which is used to configure Hub instance we get when injecting HubInterface like in our MercureManager
        # This is where mercure update our publish by our symfony app
        default:
            url: 'https://mercuredomain.com'
            public_url: 'https://mercuredomain.com'
            jwt:
                secret: '%env(MERCURE_JWT_SECRET)%'
                publish: '*'
                algorithm: 'hmac.sha256'

        # Define one hub by domain name to allow setting mercure auth cookie on these domain inside MercureAuthorizationManager
        # Only "public_url" value change
        domain_tld:
            url: 'https://mercuredomain.com'
            public_url: 'https://mercure.domain.tld/.well-known/mercure'
            jwt:
                secret: '%env(MERCURE_JWT_SECRET)%'
                publish: '*'
                algorithm: 'hmac.sha256'
        anotherdomain_tld2:
            url: 'https://mercuredomain.com'
            public_url: 'https://mercure.anotherdomain.tld2/.well-known/mercure'
            jwt:
                secret: '%env(MERCURE_JWT_SECRET)%'
                publish: '*'
                algorithm: 'hmac.sha256'
        newdomain_tld3:
            url: 'https://mercuredomain.com'
            public_url: 'https://mercure.newdomain.tld3/.well-known/mercure'
            jwt:
                secret: '%env(MERCURE_JWT_SECRET)%'
                publish: '*'
                algorithm: 'hmac.sha256'

In our MercureAuthorizationManager.php

<?php

namespace App\Security;

use App\Entity\User\User;
use DateTimeImmutable;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Mercure\Authorization;
use Symfony\Component\Mercure\HubRegistry;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Security;

/**
 * MercureAuthorizationManager.
 */
class MercureAuthorizationManager
{
    /**
     * @var Authorization
     */
    protected Authorization $mercureAutorization;

    /**
     * @var RouterInterface
     */
    protected RouterInterface $router;

    /**
     * @var Security
     */
    protected Security $security;

    /**
     * @var HubRegistry
     */
    protected HubRegistry $hubRegistry;

    /**
     * @var Request
     */
    protected Request $currentRequest;

    /**
     * Constructor.
     */
    public function __construct(
        Authorization $mercureAutorization,
        RouterInterface $router,
        Security $security,
        HubRegistry $hubRegistry,
        RequestStack $requestStack
    ) {
        $this->mercureAutorization = $mercureAutorization;
        $this->router = $router;
        $this->security = $security;
        $this->hubRegistry = $hubRegistry;
        $this->currentRequest = $requestStack->getCurrentRequest();
    }

    /**
     * Add Mercure Authorization Cookie to request.
     *
     * @param Request $request
     * @param User|null $user
     * @return void
     */
    public function addMercureAuthorizationCookieToRequest(Request $request, User $user = null)
    {
        /** @var User $currentUser */
        $currentUser = $user ?: $this->security->getUser();

        // Generate allowed subscribe topics
        $subscribeTopics = $this->generateAllowedSubscribeTopics($user);

        // Generate allowed publish topics
        $publishTopics = $this->generateAllowedPublishTopics($user);

        // Get hub name from current request domain name to set cookie on the proper mercure domain name
        // (this value only change from "default" hub in production, in other env, mercure is on the same
        // domain name than symfony app so we do not have issue to create cookie)
        $hubName = $this->getMercureHubNameFromCurrentRequestDomainName($request);

        // Add Mercure Authorization Cookie to request
        $this->mercureAutorization->setCookie($request, $subscribeTopics, $publishTopics, [
            // Fixe years expiration date for the moment to avoid having to refresh jwt token
            // But we will create an expiration system
            'exp' => new DateTimeImmutable('+5 years'),
            'currentUserId' => $currentUser->getId(),
        ], $hubName);
    }

    /**
     * Generate allowed subscribe topics.
     *
     * YOU NEED TO ADD HERE THE TOPICS YOU WANT THE FRONT TO BE ABLE TO SUBSCRIBE ON.
     *
     * @param User $user
     * @return array
     */
    protected function generateAllowedSubscribeTopics(User $user): array
    {
        $userId = $user->getId();
        $topics = [
            // Async Task progress
            $this->router->generate('task_daily_progress', [
                'user' => $userId,
            ], UrlGeneratorInterface::ABSOLUTE_PATH),
            // Web Notification
            $this->router->generate('user_notifications_web', [
                'user' => $userId,
            ], UrlGeneratorInterface::ABSOLUTE_PATH),
        ];
        return $topics;
    }

    /**
     * Generate allowed publish topics.
     *
     * YOU NEED TO ADD HERE THE TOPICS YOU WANT THE FRONT TO BE ABLE TO PUBLISH ON.
     *
     * @param User $user
     * @return array
     */
    protected function generateAllowedPublishTopics(User $user): array
    {
        $topics = [];
        return $topics;
    }

    /**
     * Get hub name from current request domain name.
     *
     * See mercure.yaml for hub config.
     *
     * @param Request $request
     * @return string
     */
    protected function getMercureHubNameFromCurrentRequestDomainName(Request $request): string
    {
        switch ($request->getHost()) {
            case 'domain.tld':
                return 'domain_tld';
            case 'anotherdomain.tld2':
                return 'anotherdomain_tld2';
            case 'newdomain.tld3':
                return 'newdomain_tld3';
            default:
                return 'default';
        }
    }

    /**
     * Get mercure public url to use by our front.
     *
     * (used inside base.html.twig)
     * @return string
     */
    public function getMercurePublicUrl()
    {
        return $this->hubRegistry->getHub(
            $this->getMercureHubNameFromCurrentRequestDomainName($this->currentRequest)
        )->getPublicUrl();
    }
}

Hope that it can help so..