voryx / ThruwayBundle

Bundle for building Real-time Apps in Symfony
98 stars 47 forks source link

Implementing authentication issue #92

Open karelVanGeerdeghom opened 5 years ago

karelVanGeerdeghom commented 5 years ago

Hey,

I've created a worker service as follows:

voryx.yaml:

services:
    Voryx\ThruwayBundle\Client\ClientManager: '@thruway.client'

    app.service.websocket.worker:
        class: App\Service\Websocket\Worker\WorkerService
        arguments:
            - '@thruway.client'
        tags: ['thruway.resource']

Class:

<?php

namespace App\Service\Websocket\Worker;

use Voryx\ThruwayBundle\Annotation\Register;
use Voryx\ThruwayBundle\Annotation\Subscribe;
use Voryx\ThruwayBundle\Client\ClientManager;

class WorkerService
{
    private $clientManager;

    public function __construct
    (
        ClientManager $clientManager
    )
    {
        $this->clientManager = $clientManager;
    }

    /**
     * @Register("websocket.call")
     */
    public function call($num1, $num2)
    {
        return $num1 + $num2;
    }

    /**
     * @Subscribe("websocket.publish")
     */
    public function publish($value)
    {
        $this->clientManager->publish('websocket.subscription', ['Server responds with: "Hello from server!"']);
    }
}

Client:

var onevent = function(args) {
    console.log(args[0]);
}

session.call('websocket.call', [5, 6])
    .then(
        function (res) {
            console.log(res);
        }
    )
;

session
    .subscribe('websocket.subscription', onevent)
;

session
    .publish('websocket.publish', ['Hello from client!'], {}, {acknowledge: true})
;

When I set authentication to false under voryx_thruway.router, I get the in my console as result '11'. All ok sofar. Also on subscribing to 'websocket.subscription', when I subsequently do the publish, I get the response I'm expecting.

Then I implemnet authentication following this post http://voryx.net/integrating-symfony-authentication-for-thruwaybundle/:

voryx.yaml:

voryx_thruway:
    ...
    router:
        ...
        authentication: true

services:
    app.security.websocket.authentication.provider:
        class: App\Security\Websocket\Authentication\AuthenticationProvider
        arguments:
            - ["@=parameter('voryx_thruway')['realm']"]
            - '@voryx.thruway.loop'
            - '@lexik_jwt_authentication.jwt_manager'
            - '@app.entity.user.repository'
        tags: ['thruway.internal_client']

class

<?php

namespace App\Security\Websocket\Authentication;

use App\Repository\UserRepository;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use React\EventLoop\LoopInterface;
use Thruway\Authentication\AbstractAuthProviderClient;

class AuthenticationProvider extends AbstractAuthProviderClient
{
    private $JWTTokenManager;

    private $userRepository;

    public function __construct
    (
        array $authRealms,
        LoopInterface $loop = null,
        JWTTokenManagerInterface $JWTTokenManager,
        UserRepository $userRepository
    )
    {
        parent::__construct($authRealms, $loop);

        $this->JWTTokenManager = $JWTTokenManager;
        $this->userRepository = $userRepository;
    }

    public function getMethodName()
    {
        return "jwt";
    }

    public function processAuthenticate($signature, $extra = null)
    {
        $token = new JWTUserToken();
        $token->setRawToken($signature);
        $payload = $this->JWTTokenManager->decode($token);
        if ($payload) {
            $user = $this->userRepository->findOneBy(["username" => $payload["username"]]);
            if ($user) {
                $details = [
                    "authid" => $user->getId()
                ];

                return ["SUCCESS", $details];
            }
        }

        return ["FAILURE"];
    }
}

Now authentication works when I create a connection from client side as such:

connection = new autobahn.Connection({
    url: url,
    realm: realm,
        authmethods: ['jwt'],
        onchallenge: function (session, method, extra) {
            return token;
        }
});

As long as my token is still valid, connection succeeds. However, the 'websocket.call' and 'websocket.publish' no longer work. I've put some logging in my worker __construct method, and it logs nothing. When I set voryx_thruway.router.authentication back to false, I get logging and everything works again.

I'm using latest versions of Symfony 4, this bundle and Autobahn.js.

What am I doing wrong?

karelVanGeerdeghom commented 5 years ago

I've gotten a little further:

I've added an authorization manager thus: voryx.yaml:

voryx_thruway:
    ...
    router:
        ...
        authorization: app.security.websocket.authorization.manager

services:
    app.security.websocket.authorization.manager:
        class: App\Security\Websocket\Authorization\AuthorizationManager
        arguments:
            - "@=parameter('voryx_thruway')['realm']"
            - '@voryx.thruway.loop'

Class:

<?php

namespace App\Security\Websocket\Authorization;

use Thruway\Event\MessageEvent;
use Thruway\Module\RealmModuleInterface;
use Thruway\Module\RouterModuleClient;

class AuthorizationManager extends RouterModuleClient implements RealmModuleInterface
{
    public function getSubscribedRealmEvents()
    {
        return [
            'PublishMessageEvent'   => ['authorize', 100],
            'SubscribeMessageEvent' => ['authorize', 100],
            'RegisterMessageEvent'  => ['authorize', 100],
            'CallMessageEvent'      => ['authorize', 100],
        ];
    }

    public function onSessionStart($session, $transport)
    {
        parent::onSessionStart($session, $transport);

        $session->subscribe('wamp.metaevent.session.on_join', [$this, 'onSessionJoin']);
        $session->subscribe('wamp.metaevent.session.on_leave', [$this, 'onSessionLeave']);
    }

    public function onSessionJoin($args, $kwArgs, $options)
    {
        var_dump('onSessionJoin');
    }

    public function onSessionLeave($args, $kwArgs, $options)
    {
        var_dump('onSessionLeave');
    }

    public function authorize(MessageEvent $msg)
    {
        var_dump($msg);

        return true;
    }
}

I can see the onSessionJoin and onSessionLeave events in my CLI, but when I try to publish, subscribe or call, nothing happens.

karelVanGeerdeghom commented 5 years ago

So adding trusted_url and trusted_port allows me to publish from server to a specific user by authid (ie. user id), thusly: voryx.yaml:

voryx_thruway:
    ...
    trusted_url: 'ws://<ip>:<port>'
    router:
        ...
        trusted_port: '<port>'

services:
    app.service.websocket.publisher:
        class: App\Service\Websocket\Publisher\PublisherService
        arguments:
            - '@thruway.client'

Class:

<?php

namespace App\Service\Websocket\Publisher;

use Voryx\ThruwayBundle\Client\ClientManager;

class PublisherService
{
    private $clientManager;

    public function __construct
    (
        ClientManager $clientManager
    )
    {
        $this->clientManager = $clientManager;
    }

    public function publish(string $topic, string $message, int $authid)
    {
        $this->clientManager->publish($topic, [$message], [], ['_thruway_eligible_authids' => [$authid]]);
    }
}
karelVanGeerdeghom commented 5 years ago

K, yet more implementation: I can now register when client subscribes (with https://github.com/voryx/ThruwaySubscriptionMeta)

use Thruway\Module\SubscriptionMetaModule;

class AuthorizationManager extends RouterModuleClient implements RealmModuleInterface
{
    ...

    public function onSessionStart($session, $transport)
    {
        ...
        $this->router->getRealmManager()->getRealm('<realm>')->addModule(new SubscriptionMetaModule());

        $session->subscribe('wamp.subscription.on_subscribe', [$this, 'onSubscribe']);
        $session->subscribe('wamp.subscription.on_unsubscribe', [$this, 'onUnsubscribe']);
    }

    ...

    public function onSubscribe($args, $kwArgs, $options)
    {
        var_dump('onSubscribe');
    }

    public function onUnsubscribe($args, $kwArgs, $options)
    {
        var_dump('onUnsubscribe');
    }
}

Still unable to publish and call from client