Websocket / Cakephp 4

Hi, how can I get access to my user-id in /src/MyWebsocket.php? In src/controller i can just get access to it with $this->Authentication->getIdentity()->getIdentifier(); ? I load the Plugins in my Application.php with : $this->addPlugin(‘Authentication’); $this->addPlugin(‘Authorization’);

Any Idea?

We have no idea what your MyWebsocket looks like. In general, you’d want to do something like pass the user ID to the constructor of that thing, or to the specific function that needs it, depending on the lifecycle and usage of the thing. For any more concrete advice, we’d need more details about your use case.

here… this is how /src/MyWebSocket.php looks like: unfortunately $this->authentication->getIdentity() is not working… but i need the ID which is only available in /src/Controller/…


<?php

namespace App;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Cake\Cache\Cache;
use Cake\Http\Session;

class MyWebSocket implements MessageComponentInterface {
    protected $clients;    
    private $users = [];
    
    
    public function initialize()
    {
        parent::initialize();
        
    }
    
    
    public function __construct() {
        $this->clients = new \SplObjectStorage();
        $xusers = array();

    }
    
    public function onOpen(ConnectionInterface $conn) {
        // Store the new connection to send messages to later

        $this->users[$conn->resourceId] = $conn;
       
        $this->clients->attach($conn);
        
        echo "New connection! ({$conn->resourceId})\n";

//         print_r($this->authentication->getIdentity()); 

      //  $xusers[] = array( $identity = $this->authentication->getIdentity(), $conn->resourceId);
  
    
        
    }
    
    
    public function onMessage(ConnectionInterface $from, $data) {
        $numRecv = count($this->clients) - 1;
        echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n"
            , $from->resourceId, $data[1][0], $numRecv, $numRecv == 1 ? '' : 's');


    }

Where do you create this object and call the on* functions?

You could use a nasty hack and fetch the current Request object from the router via

$request = \Cake\Routing\Router::getRequest();
$identity = $request->getAttribute('identity');
if ($identity){
    $userObject = $identity->getOriginalData();
}

It all depends on where/how you call your custom MyWebSocket class since you are not really inside the usual CakePHP files.

E.g.

public function initialize()
{
    parent::initialize();     
}

doesn’t do anything in your class because you don’t extend any other class which could be called here. You only implement an interface.

hey! e.g. in a view of my UserController:


<script>
var conn = new WebSocket('ws://localhost:8080');
conn.onopen = function(e) {
    console.log("Connection established!");
};

conn.onmessage = function(e) {
   
      console.log("Messaget: " + e.data);
      var data = JSON.parse(e.data);
      console.log("New Message " + data[0].sender + ": " + data[0].message);


};

This is the JS part which connects to the websocket but what actually starts the websocket server on the php side?

Unless you manually bootstrap cakephp you can’t access an identity from CakePHP because identities are only checked and available after the middleware queue has run.

i start the Websocket from the shell and it looks like that:

<?php
// in src/WebSocket.php

use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use App\MyWebSocket;

require dirname(__DIR__) . '/vendor/autoload.php'; // Autoload Composer packages

$webSocketServer = IoServer::factory(
    new HttpServer(
        new WsServer(
            new MyWebSocket()
            )
        ),
    8080 // Port number
    );

$webSocketServer->run();

should i change the bootstrap like that? :

//  config/bootstrap.php

use Authentication\Middleware\AuthenticationMiddleware;
use Cake\Http\MiddlewareQueue;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;

// MiddlewareQueue-Instanz 
$middlewareQueue = new MiddlewareQueue();

// Authentifizierungs-Middleware 
$middlewareQueue->add(new AuthenticationMiddleware($config));

// WebSocket-Server 
$webSocket = new HttpServer(new WsServer(new MyWebSocket()));

// MiddlewareQueue / WebSocket-Server
$server = new Server($request, $response, $middlewareQueue);
$server->define('webSocket', function ($request, $response) use ($webSocket) {
    return $webSocket;
});

I inclde the plugins in the application.php atm…

I am not an expert in Websockets so this is all guessing for me but do your websocket requests contain the same information as your normal HTTP requests?

By default CakePHP authenticates a user via e.g. FormData and then sets a cookie to authenticate the users through multiple requests because HTTP is stateless.

So how do you authenticate a Websocket requests/connection? Does it contain the same cookie as a normal HTTP request? I know the Websocket Protocoll is HTTP compatible but its not 1:1 the same.

So you will have to find a solution on your own (or wait for someone to give more guidence) but I have never doven into this topic. I can only give you hints on how CakePHP handels default HTTP requests and its associated authentication logic.

after you connect to the ws server you will get a $conn->resourceId

MyWebSocket.php

public function onOpen(ConnectionInterface $conn) {
        // Store the new connection to send messages to later
        
        
        $this->users[$conn->resourceId] = $conn;
       
        $this->clients->attach($conn);
     
       // Cache::write('users_list', $users, '10 minutes');
        
        echo "New connection! ({$conn->resourceId})\n";

you have to save the $conn->resourceId next to the user ID in the cache of the server!
the websocket server always knows the $conn->resourceId of any connected user… you have to route all messages according to the $user->id and the $conn->resourceId which you saved in the cache…

but I cant access the $user->id… because $this->Authentication->getIdentity()->getIdentifier(); is not working in /src

you only have to provide the $user->id to the cache at the beginning when you do $this->users[$conn->resourceId]… the connection between the user and ws will stay until you cancel the connection by e.g. refreshing the browser…

well yes because any client (no matter if they have an account on your page or not) gets an ID from the websocket implementation so you can reference it accordingly.

But this “user” in the websocket is not the same “user” you would get from $this->Authentication->getIdentity()->getIdentifier(); if you do a normal HTTP request after being logged in

you have to write an array that saves $conn->resourceId and $this->Authentication->getIdentity()->getIdentifier(); for any user that connects to the server… the array needs to be written to the cache…

but i cant use $this->Authentication->getIdentity()->getIdentifier(); … its not working in /src

you seem to not understand what I am trying to tell you…

i do not get anything with $this->Authentication->getIdentity()->getIdentifier();

Yes, because this method only works in CakePHP controllers, not in anything inside the src directory.

so its better to do this in my UserController:

require_once '/src/mywebsocket.php';

class UserController {
  private $webSocket;

  public function __construct() {
    $this->webSocket = new MyWebSocket();

    $this->webSocket->connect();
  }

  public function sendMessage($message) {
    $this->webSocket->sendMessage($message);
  }

  public function closeWebSocket() {
    $this->webSocket->close();
  }
}

is that a lack in performance?

What Kevin is trying to tell you is that Cake authentication and authorization components apply only to requests made in the web context, where there is a cookie with a session ID, sent from the browser, which the server uses to look up the current logged in user.

You are making a completely separate connection to a different service. You will need to do something to get user information from the browser to that server. What form that takes is entirely up to you, and completely outside the scope of Cake’s built-in auth functionality. The first question you need to answer is “how is the user being communicated from the browser to the service”. As I said, in the web context, the answer to that is “a browser cookie with a session ID in it”. Maybe your JS can also send that same cookie, and your socket can use it to find the session and hence the user?

well, you can easily provide the PHPSESSIONID to the /src/Controller/MyWebSocket.php or /src/MyWebSocket.php script ( Websocket SERVER SIDE) if you do this in your view :

<?php
  $session_id = $_COOKIE['PHPSESSID'] ?? null;
?>
<script>
const sessionId = "<?php echo $session_id; ?>";
console.log(sessionId);
console.log("Connection established!");
var conn = new WebSocket('ws://localhost:8080?session_id=' + sessionId);

in the Controller you can use this through ws provided PHPSESSIONID like that:

public function onOpen(ConnectionInterface $conn)
    {
        $uri = $conn->httpRequest->getUri();
        $query_params = [];
        parse_str($uri->getQuery(), $query_params);
        $session_id = $query_params['session_id'] ?? null;
        echo "Session ID: " . $session_id . PHP_EOL;

there you need to do something like:

$session = new Session();
$session->start();
$session->id($session_id);
$user_id = $session->read(‘Auth.User.id’);

this should give you access to the information that is saved in $this->request->getSession()->read(‘Auth’); which is available on your view ( HTTP SERVER SIDE)… but i dont know how to do it… you have to use the bootstrap / middleware in somehow!..

btw do you think it is enough to use WSS instead of WS? WSS will be the same like HTTPS… i saw some plugins to encrypt cookies etc but wss should do it already i guess…
idea?