sagnik Posted May 23 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
Haradion Posted June 6 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.
sagnik Posted June 7 Author 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.
Haradion Posted June 20 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?
sagnik Posted June 21 Author Posted June 21 The platform doesn't use any third-party service. The SSO is also its own which I've developed.
Haradion Posted June 22 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
sagnik Posted June 26 Author 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.
Haradion Posted June 28 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.
sagnik Posted July 1 Author 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.
badrihippo Posted July 1 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?
Haradion Posted July 2 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.
sagnik Posted July 3 Author 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?
sagnik Posted July 3 Author 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
Haradion Posted July 4 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.
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now