sagnik Posted May 23, 2024 Posted May 23, 2024 (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, 2024 by sagnik Added another reason Quote
Haradion Posted June 6, 2024 Posted June 6, 2024 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
sagnik Posted June 7, 2024 Author Posted June 7, 2024 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
Haradion Posted June 20, 2024 Posted June 20, 2024 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
sagnik Posted June 21, 2024 Author Posted June 21, 2024 The platform doesn't use any third-party service. The SSO is also its own which I've developed. Quote
Haradion Posted June 22, 2024 Posted June 22, 2024 (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, 2024 by Haradion Quote
sagnik Posted June 26, 2024 Author Posted June 26, 2024 Yes, as I cannot start a session within WebSocket because it will replace the previous session whenever a new client is authenticated. Quote
Haradion Posted June 28, 2024 Posted June 28, 2024 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
sagnik Posted July 1, 2024 Author Posted July 1, 2024 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
badrihippo Posted July 1, 2024 Posted July 1, 2024 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
Haradion Posted July 2, 2024 Posted July 2, 2024 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
sagnik Posted July 3, 2024 Author Posted July 3, 2024 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
sagnik Posted July 3, 2024 Author Posted July 3, 2024 (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, 2024 by sagnik Quote
Haradion Posted July 4, 2024 Posted July 4, 2024 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
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.