sagnik Posted May 23 Share Posted May 23 (edited) Hi, I'm creating a social media with a real-time chat application using PHP WebSocket, the basic functionalities are working properly but I need to implement some other features as well. So I need help with the following related to the chat application: Login to the WebSocket using the credentials users use to log into the social media. Allow users to chat with their friends only. Identify users from the database using their ID and fetch data from the database. Show online/offline status based on the WebSocket. Show if a message is pending/sent/delivered/seen status. Show typing notification to the other user. I'm attaching a screenshot of the chat screen: Edited May 23 by sagnik Added another reason Quote Link to comment Share on other sites More sharing options...
Haradion Posted June 6 Share Posted June 6 Logins via social media are usually done using OAuth and OpenID Connect, which can be non-trivial to implement. For development purposes, as long as you're just testing on your own computer, I'd recommend building a mock login flow that just takes a username. It would be far too insecure to expose to the public Internet, but it would give you an opportunity to work out your database layer, which will be required for OAuth anyway. Quote Link to comment Share on other sites More sharing options...
sagnik Posted June 7 Author Share Posted June 7 Thanks for the advice, but the service has its login service, which uses SSO but the problem is handling the sessions within the WebSocket server for multiple clients. Quote Link to comment Share on other sites More sharing options...
Haradion Posted June 20 Share Posted June 20 First of all, sorry about the slow response. I was expecting to get a notification e-mail, but it turns out that I needed to adjust my settings for that to happen. So, it sounds like you've got an external SSO provider of some kind. Is it based on OAuth/OpenID Connect or on some other SSO protocol? For OAuth, if you have access to self-encoded access tokens, those can be relatively straightforward to validate without having to track too much session-related state in your own database. Since you mentioned social media logins, I'm going to guess they probably do use self-encoded access tokens since that's common for services that operate at a very large scale. For the database layer, is there already a specific database you're looking at using? Quote Link to comment Share on other sites More sharing options...
sagnik Posted June 21 Author Share Posted June 21 The platform doesn't use any third-party service. The SSO is also its own which I've developed. Quote Link to comment Share on other sites More sharing options...
Haradion Posted June 22 Share Posted June 22 (edited) OK, if you've already got SSO sorted out, is your first question about how to handle authentication specifically when you're opening the WebSocket connection? Â On 5/23/2024 at 7:37 AM, sagnik said: Login to the WebSocket using the credentials users use to log into the social media. Edited June 22 by Haradion Quote Link to comment Share on other sites More sharing options...
sagnik Posted June 26 Author Share Posted June 26 Yes, as I cannot start a session within WebSocket because it will replace the previous session whenever a new client is authenticated. Quote Link to comment Share on other sites More sharing options...
Haradion Posted June 28 Share Posted June 28 When you say the session will be "replaced", do you mean that the session data would be overwritten on the backend? The request to initiate the WebSocket connection should include the same session cookies that other requests would include, so if you're using cookies for sessions, the logic shouldn't be too different from the normal procedure for opening an existing session. It should be possible to use the same session ID that your normal HTTP requests are using rather than having to start a separate session ID dedicated to just the WebSocket. Quote Link to comment Share on other sites More sharing options...
sagnik Posted July 1 Author Share Posted July 1 I use some keys namely "sgn-login-uid", "sgn-login-sessid", "sgn-login-ip", "sgn-login-timestamp" & "sgn-login-expires" to authenticate a user by setting $_SESSION variables for the aforementioned keys. So every time a new user is authenticated those values have to be changed. Quote Link to comment Share on other sites More sharing options...
badrihippo Posted July 1 Share Posted July 1 I didn't understand how exactly the $_SESSION variables are getting overwritten. I thought if a new user joins, they would have their own set of $_SESSION variables which doesn't interfere with the first user's $_SESSION? Maybe you can try sharing the part of the code that updates the $_SESSION variables so I can see how it's working? Quote Link to comment Share on other sites More sharing options...
Haradion Posted July 2 Share Posted July 2 On 7/1/2024 at 2:29 AM, sagnik said: I use some keys namely "sgn-login-uid", "sgn-login-sessid", "sgn-login-ip", "sgn-login-timestamp" & "sgn-login-expires" to authenticate a user by setting $_SESSION variables for the aforementioned keys. So every time a new user is authenticated those values have to be changed. Are those variables changed on every request, or do they only change when the user submits login credentials? If normal requests after login only read those fields, the WebSocket connection can do the same thing, as it should receive the PHPSESSID cookie just like normal requests do. Quote Link to comment Share on other sites More sharing options...
sagnik Posted July 3 Author Share Posted July 3 13 hours ago, Haradion said: Are those variables changed on every request, or do they only change when the user submits login credentials? If normal requests after login only read those fields, the WebSocket connection can do the same thing, as it should receive the PHPSESSID cookie just like normal requests do. Those variables are changed when a user submits their login credentials and are validated. So, I need to pass the PHPSESSID cookie as well to the WS server, right? Quote Link to comment Share on other sites More sharing options...
sagnik Posted July 3 Author Share Posted July 3 (edited) On 7/1/2024 at 6:48 PM, badrihippo said: I didn't understand how exactly the $_SESSION variables are getting overwritten. I thought if a new user joins, they would have their own set of $_SESSION variables which doesn't interfere with the first user's $_SESSION? Maybe you can try sharing the part of the code that updates the $_SESSION variables so I can see how it's working? @badrihippo Here is the code of SSO SignOn.php: <?php /* * Copyright (c) 2022-2023 SGNetworks. All rights reserved. * * The software is an exclusive copyright of "SGNetworks" and is provided as is exclusively with only "USAGE" access. "Modification", "Alteration", "Re-distribution" is completely prohibited. * VIOLATING THE ABOVE TERMS IS A PUNISHABLE OFFENSE WHICH MAY LEAD TO LEGAL CONSEQUENCES. */ session_start(); $SGNSSO = ['accounts.sgnetworks.net', 'accounts.sgnetworks.eu.org']; function is_base64(string $data): bool { $base64 = base64_encode(base64_decode($data, true)); return ($base64 === $data); } function is_base64URL(string $data): bool { $base64 = strtr($data, '-_', '+/'); $base64 = base64_encode(base64_decode($base64)); $base64 = strtr(rtrim($base64, '='), '+/', '-_'); return ($base64 === $data); } function Base64UrlEncode(string $data, bool $force = false): string { if($force) { return strtr(rtrim(base64_encode($data), '='), '+/', '-_'); } $base64 = (!is_base64($data)) ? base64_encode($data) : $data; return (!is_base64URL($base64)) ? strtr(rtrim($base64, '='), '+/', '-_') : $base64; } function Base64UrlDecode(string $base64, bool $strict = false): string|false { $data = (is_base64URL($base64)) ? strtr($base64, '-_', '+/') : $base64; return (is_base64($data)) ? base64_decode($data, $strict) : base64_decode($data); } function server(string $key, string|int|bool|array $default = null): array|bool|int|string|null { $server = $_SERVER; $null = ($default === null && !is_bool($default) && !is_array($default) && !is_integer($default) && !is_string($default)) ? null : $default; return (array_key_exists($key, $server)) ? $server[$key] : $null; } function session(string $key, string|int|bool|array $default = null): array|bool|int|string|null { $session = $_SESSION; $null = ($default === null && !is_bool($default) && !is_array($default) && !is_integer($default) && !is_string($default)) ? null : $default; return (array_key_exists($key, $session)) ? $session[$key] : $null; } function post(string $key, string|int|bool|array $default = null): array|bool|int|string|null { $post = $_POST; $null = ($default === null && !is_bool($default) && !is_array($default) && !is_integer($default) && !is_string($default)) ? null : $default; return ((array_key_exists($key, $post)) ? $post[$key] : $null); } function get(string $key, string|int|bool|array $default = null): array|bool|int|string|null { $get = $_GET; $null = ($default === null && !is_bool($default) && !is_array($default) && !is_integer($default) && !is_string($default)) ? null : $default; return (array_key_exists($key, $get)) ? $get[$key] : $null; } function buildURL(string $uri, ?string $params = null, ?string $args = null): string { $params = (!empty($params)) ? ltrim($params, '?' . '&') : ''; $args = (!empty($args)) ? ltrim($args, '?' . '&') : ''; if(!empty($params) && !empty($args)) { $url = (str_contains($uri, '?') || str_contains($params, '?') || str_contains($args, '?')) ? "$uri&$params&$args" : "$uri?$params&$args"; } elseif(!empty($params) && empty($args)) { $url = (str_contains($uri, '?') || str_contains($params, '?')) ? "$uri&$params" : "$uri?$params"; } elseif(empty($params) && !empty($args)) { $url = (str_contains($uri, '?') || str_contains($args, '?')) ? "$uri&$args" : "$uri?$args"; } else { $url = $uri; } $url_parts = parse_url($url); $qs = ''; if(array_key_exists('query', $url_parts)) { $qs = $url_parts['query']; parse_str($qs, $qo); $qs = (count($qo) > 0) ? http_build_query($qo) : ''; } $constructed_url = $url_parts['scheme'] . '://' . $url_parts['host'] . ($url_parts['path'] ?? ''); return (!empty($qs)) ? "$constructed_url?$qs" : $constructed_url; } function redirect(string $uri, string $vars = ''): void { $qm = (str_contains($uri, '?') || str_contains($vars, '?')) ? '&' : '?'; $url = buildURL($uri); if(!headers_sent()) { header("Location: $url"); exit(); } else { echo '<script>'; echo "window.location.href=('$url');"; echo '</script>'; echo "You will be redirected shortly. If you are not redirected automatically, please <a href='$url'>click here</a> to redirect"; } } function get_domain(string $url): string|false { $urlobj = parse_url($url); $domain = $urlobj['host']; if(preg_match('/(?P<domain>[a-z0-9][a-z0-9\-]{1,63}\.[a-z.]{2,6})$/i', $domain, $regs)) { return $regs['domain']; } return false; } if(server('REQUEST_METHOD') == 'POST') { $redirectTo = (!post('continue')) ? post('redirect') : post('continue'); $params = post('params', ''); $args = post('args', ''); $session = post('session'); $origin = post('origin'); } else { $redirectTo = (!get('continue')) ? get('redirect') : get('continue'); $params = get('params', ''); $args = get('args', ''); $session = get('session'); $origin = get('origin'); } $sc = explode('-', $session); $sessid = Base64UrlDecode($sc[0]); $uid = Base64UrlDecode($sc[1]); $uid_hashed = Base64UrlDecode($sc[2]); $ssoProcessed = false; $continueHost = $continue = ''; if(in_array($origin, $SGNSSO)) { $args = Base64UrlDecode($args); $redirectTo = Base64UrlDecode($redirectTo); $redirectTo = buildURL($redirectTo, $args); if(empty(session('sgn-login-sid'))) { if(empty($session)) { $continue = (!$redirectTo) ? $origin . server('REQUEST_URI') : $redirectTo; } else { $_SESSION['sgn-login-sid'] = $sessid; $_SESSION['sgn-login-uid'] = $uid; $_SESSION['sgn-login-uid_hashed'] = $uid_hashed; $_SESSION['sgn-login-expires'] = time() + 3600; $_SESSION['sgn-login-timestamp'] = time(); $_SESSION['sgn-login-ip'] = server('REMOTE_ADDR'); $ssoProcessed = true; $url = parse_url($redirectTo); $p = (array_key_exists('path', $url)) ? $url['path'] : ''; $q = (array_key_exists('query', $url)) ? '?' . $url['query'] : ''; $s = (!$q) ? "?sessid=$sessid" : "&sessid=$sessid"; unset($redirectTo); unset($_GET['session']); $continueUrl = $url['scheme'] . '://' . $url['host'] . $p . $q; $continue = "$continueUrl$s"; $continue = (!$continue) ? $_SERVER['HTTP_REFERER'] : $continue; $continueHost = $url['host']; $continueLocation = "{$url['scheme']}://$continueHost"; } } elseif(!empty($session)) { $_SESSION['sgn-login-sid'] = $sessid; $_SESSION['sgn-login-uid'] = $uid; $_SESSION['sgn-login-uid_hashed'] = $uid_hashed; $_SESSION['sgn-login-expires'] = time() + 3600; $_SESSION['sgn-login-timestamp'] = time(); $_SESSION['sgn-login-ip'] = server('REMOTE_ADDR'); $ssoProcessed = true; if(!empty($redirectTo)) { $url = parse_url($redirectTo); $p = (array_key_exists('path', $url)) ? $url['path'] : ''; $q = (array_key_exists('query', $url)) ? '?' . $url['query'] : ''; $s = (!$q) ? "?sessid=$sessid" : "&sessid=$sessid"; unset($redirectTo); unset($_GET['session']); $continueUrl = $url['scheme'] . '://' . $url['host'] . $p . $q; $continue = "$continueUrl$s"; $continue = (!$continue) ? $_SERVER['HTTP_REFERER'] : $continue; $continueHost = $url['host']; $continueLocation = "{$url['scheme']}://$continueHost"; } else { $continue = $_SERVER['HTTP_REFERER']; } } $params = (!empty($params)) ? Base64UrlDecode($params) : ''; $continue = buildURL($continue, $params); } else { echo 'The Origin Host is not allowed to make SSO Requests'; } if($_SERVER['REQUEST_METHOD'] == 'GET'): ?> <script> function crossDomainLogin() { const url = "<?=$continueLocation;?>/SGNSSO/SignOn"; const xhr = new XMLHttpRequest(); xhr.onerror = function() { if(xhr.status === 0) { console.log("Cross-Domain Request Failed"); } else { console.log("Cross-Domain Request Failed with the following Status: ", xhr.status); } }; xhr.onreadystatechange = function() { if(this.readyState === 4 && this.status === 200) { if(xhr.responseText === "done") { window.location.replace("<?=$continue;?>"); } else { console.log("SGNSSO is available only for SGNetworks and its Subsidiaries"); } } else { //console.log("Cross-Domain Request is not ready or the request has failed with status: ",this.status); } }; xhr.open("POST", url); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.crossDomain = true; xhr.withCredentials = true; xhr.send("session=<?=$session;?>&origin=<?=$origin;?>&redirect=<?=Base64UrlEncode($continueUrl);?>"); } const sso = <?=($ssoProcessed) ? 'true' : 'false';?>; if(window.location.host !== '<?=$continueHost;?>') { crossDomainLogin(); } else { if(sso === true || sso === "true") { window.location.replace("<?=$continue;?>"); } } </script> <?php elseif($ssoProcessed): echo 'done'; else: echo 'failed'; endif;  Edited July 3 by sagnik Quote Link to comment Share on other sites More sharing options...
Haradion Posted July 4 Share Posted July 4 On 7/2/2024 at 10:08 PM, sagnik said: Those variables are changed when a user submits their login credentials and are validated. So, I need to pass the PHPSESSID cookie as well to the WS server, right? Yes; that's probably the easiest way to pick up the user's session in the WebSocket context. Based on my understanding (I haven't actually tested this), the browser should pass PHPSESSID in the request automatically, so you'd just need to call session_start() in the WebSocket request handler and read the appropriate values out of $_SESSION. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.