How to Check if a User Is Logged In from an External Script in Joomla 4

We were recently tasked with an interesting and what we thought was a simple task: a client asked us to write a script that checks if a user is logged in to a Joomla 4 website from an external website using Ajax. “Simple!” – we thought: we can just copy the code of the root index.php file until the line $app->createExtensionNamespaceMap(); and then add the following code:

$objUser= JFactory::getUser();
if (empty($objUser->id))
	die('0');
die('1');

0 in the above code indicates that the user is not logged in, and 1 (you guessed it), indicates that the user is logged in. So, we created a file called check-user-login.php containing the index.php code until the line $app->createExtensionNamespaceMap(); followed by the above code, and we placed it at the same level of the index.php file, and then we checked if it worked. Well, to our rejoice, it did! Well – when using the direct link at least!

The issue was when we tried to check whether the user is logged in from an external website (e.g. a different website) using the below ajax function:

function checkUserLogin(){
	$.ajax({
		url: "https://[URL]/check-user-login.php",
		cache: false,
		type: "GET",
		success: function(data) {
			if (data == '1'){
				console.log('User is logged in');
			}
			else{
				console.log('User is not logged in');
			}
		}
	});
}

The above code did not work – instead the JavaScript console complained about CORS (which stands for Cross-Origin Resource Sharing). “Easy”, we thought. Let’s fix that CORS! Let’s just add the following code to the .htaccess of the website (where we have the check-user-login.php script):

<IfModule mod_headers.c>
	SetEnvIf Origin "http(s)?://(www\.)?(clientexternaldomain.com)$" AccessControlAllowOrigin=$0
	Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
</IfModule>

The above fixed the main CORS problem, but we had another problem – the script was always returning 0. It turned out that we had 1) to tell the JavaScript code on the external server that we need to send the cookies, and 2) we need to tell the source website (where we have the check-user-login.php script) to accept cross site cookies over a secure connection.

The first part was resolved by modifying the checkUserLogin function to the following:

function checkUserLogin(){
	$.ajax({
		url: "https://[URL]/check-user-login.php",
		cache: false,
		type: "GET",
		xhrFields: { withCredentials: true },
		success: function(data) {
			if (data == '1'){
				console.log('User is logged in');
			}
			else{
				console.log('User is not logged in');
			}
		}
	});
}

Notice the addition of the xhrFields: { withCredentials: true }, line.

The second part was resolved by adding the following code to the very beginning of the check-user-login.php file:

ini_set('session.cookie_samesite', 'None');
ini_set('session.cookie_secure', 1);
header('Access-Control-Allow-Credentials: true');

We then tried to see if the external website was able to check if the Joomla user is logged in to the main website, and, it worked! Well, sometimes it worked…

We spent a lot of time and tried many things in our quest to resolve (or at least understand) the problem – but none worked – we ended up with the same stability issues – sometimes the login worked, other times it didn’t (to be fair [or unfair?] most of the times it didn’t). Eventually (after so much time), we had this great idea! Here’s what we did:

  • We created an article on the source (main) Joomla website, and we ensured that only registered users can access it. The name of the article was Check User Login.
  • The article only had the following content “Success”.
  • We created a menu item, pointing to that article, and we named it Check User Login, so the direct URL to that article was https://[URL]/check-user-login.html.
  • We assigned the menu item to a near blank template called user-login where the main index.php of that template had the following code:

    defined('_JEXEC') or die;
    <jdoc:include type="component" />

    and the article layout override file (e.g. the default.php file under user-login/html/com_content/article folder) had the following code:

    defined('_JEXEC') or die;
    $objUser= JFactory::getUser();
    if (empty($objUser->id))
    	die('Fail');
    die($this->item->introtext);

  • We changed the JS function to use check-user-login.html instead of check-user-login.php.
  • We added (for CORS reasons) the following code to the defines.php file at the site root (e.g. at the same level of the root index.php file):

    $strCurrentURL = 'https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
    if (stripos($strCurrentURL , 'https://[URL]/check-user-login.html') === 0){
    	ini_set('session.cookie_samesite', 'None');
    	ini_set('session.cookie_secure', 1);
    	header('Access-Control-Allow-Credentials: true');
    }

    Note that we initially forgot about doing the above step and immediately jumped to the next step, but we were “greeted” with the following error in the JS console:

    Access to XMLHttpRequest at [URL] from origin [clientexternaldomain] has been blocked by CORS policy: The value of the ‘Access-Control-Allow-Credentials’ header in the response is ” which must be ‘true’ when the request’s credentials mode is ‘include’. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

  • We checked whether the feature is now stable, and it was! Hooray! We solved the problem!

As mentioned at the very beginning of the article – we thought this was an easy task (and, in our opinion, it should have been an easy task), but quirks in JavaScript, Joomla, browsers, and the way cookies work in general made this a really challenging one, but it’s no longer challenging for you, our dear reader (now that you have the secret recipe). But, if you need help with the implementation, or if you think this whole coding scheme is way outside your realm, then you can always contact us. Our fees are reasonable and we are possibly the friendliest* programmers on planet earth.

*Internal data.

No comments yet.

Leave a comment