1

I have a Joomla 3.9.14 site with a subdomain app.example.com. I have a login form, and I want to allow users to login using their Joomla credentials.

Everything is working as expected, users are able to login and authenticate successfully, however I need help with a function. I want to be sure I'm doing things correctly before proceeding.

My understanding was that I setup a connection to my Joomla database using $db = JFactory::getDbo(); and this would allow me to run queries throughout my site.

I have included the Joomla connection in a conn.php file, but it seems I also have to include $db = JFactory::getDbo(); within each of my functions - is this correct?

I have three files, see below for current code;

conn.php

<?php
    define('_JEXEC', 1);    
    define('JPATH_BASE', realpath(dirname(__FILE__) . '/..'));
    require_once JPATH_BASE . '/includes/defines.php';
    require_once JPATH_BASE . '/includes/framework.php';

    $app   = JFactory::getApplication('site');
    $db     = JFactory::getDbo(); 
?>

index.php

<?php
  error_reporting(E_ALL);

  include('conn.php');
  include('user.php');

  if(isset($_POST['username']) && isset($_POST['password'])) {

      $credentials['username'] = trim($_POST['username']);
      $credentials['password'] = trim($_POST['password']);
      // do user login
      userLogin($credentials);
  } 

?>

  <form method="POST">
      <input type="text" name="username">
      <input type="password" name="password">
    <button type="submit">Sign in</button>
  </form>

user.php

<?php

function userLogin($credentials) {

    $credentials['username'] = trim($_POST['username']);
    $credentials['password'] = trim($_POST['password']);

    // $credentials['username'] = 'admin';
 //     $credentials['password'] = 'password';

    $app    = JFactory::getApplication('site');
    $db     = JFactory::getDbo();

    if(class_exists(JFactory)) {

        $query  = $db->getQuery(true);

        $query->select('id, password');
        $query->from('#__users');
        $query->where('username=' . $db->quote($credentials['username']));

        $db->setQuery($query);
        $result = $db->loadObject();

        if ($result)
        {
            $match = JUserHelper::verifyPassword($credentials['password'], $result->password, $result->id);

            if ($match === true)
            {
                $user = JUser::getInstance($result->id); // Bring this in line with the rest of the system
                $app->login($credentials, $options=array());
                // $app->login('username' => 'admin', 'password' => 'password', $options=array());

                echo ("This user is logged in and Joomla also logged in! ");
       //          $logged_user = JFactory::getUser();
                // var_dump($logged_user );
            }
            else
            {
                echo ("User name and password not match! ");
            }
        }
        else
        {
            echo ("THE USER IS NOT REGISTERED! ");
        }
    }
}

?>

Do I need to keep $db = JFactory::getDbo(); and $app = JFactory::getApplication('site') within the function? I though by including the conn.php I was also including them? If I move them outside then function I get error messages call to member function null...

| improve this question | | | | |
  • You would need to declare them as global to be used anywhere. Common practice is to include the DB call in each query. Just be sure you close that connection with $db->freeResult(); – Terry Carter Jan 16 at 17:24
  • 1
    You could actually just do away with the conn.php file and do everything in a single file. Are you building a component or are you running a site outside of Joomla and just want authentication for users? Technically you could add a few lines of jQuery with Ajax and write a small com_ajax plugin that would handle your authentication for you. I will post an answer below in a few minutes with an Ajax solution that does everything you need easily and can be reused. – Terry Carter Jan 18 at 17:52
  • See my answer below. I think I included everything you need. – Terry Carter Jan 18 at 19:15
  • @jonboy some notes about your snippets: if(isset($_POST['username']) && isset($_POST['password'])) { is more simply written as if(isset($_POST['username'], $_POST['password'])) { because isset() accepts multiple parameters BUT in Joomla you should always be using JFactory::getApplication('site')->input->post('username', '', 'USERNAME'); // or RAW You must never modify the user's submitted password -- not even with trim(). It is okay to validate it for strength at registration time, but never sanitise it. – mickmackusa Jan 18 at 21:34
  • The variable $match is a "single-use variable" and therefore does not need to be declared -- just check if (JUserHelper::verifyPassword($credentials['password'], $result->password, $result->id)) { – mickmackusa Jan 18 at 21:34
2

Create a simple ajax plugin that handles your user login logic and returns a message for your users. The following 2 files should be zipped together and installed on your site.

userlogin.php

<?php
defined('_JEXEC') or die;

// Import library dependencies
jimport('joomla.plugin.plugin');

class plgAjaxUserLogin extends JPlugin{
    function onAjaxUserLogin(){
        jimport('joomla.plugin.helper');
        $app = JFactory::getApplication('site');
        $JInput = $app->input;

        //GET FORM INPUT
        $username = $JInput->get('username','','raw');
        $password = $JInput->get('password','','raw');

        //CREATE CREDENTIALS ARRAY
        $credentials = array();
        $credentials['username'] = $username;
        $credentials['password'] = $password;

        //VERIFY USER EXISTS (ALSO CHECKS EMAIL IF YOU ARE USING AN EMAIL LOGIN AUTHENTICATION PLUGIN AND ALLOW EMAIL ADDRESSES AS USERNAMES)
        $db = JFactory::getDbo();
        $query = $db->getQuery(true)
            ->select('id, password')
            ->from('#__users')
            ->where('username=' . $db->quote($credentials['username']). ' OR email = '.$db->quote($credentials['username']));
        $db->setQuery($query);
        $result = $db->loadObject();

        if ($result){

            if (JUserHelper::verifyPassword($credentials['password'], $result->password, $result->id)){
                //GET USER INSTANCE FROM DATABASE ID
                $user = JUser::getInstance($result->id);
                $msg = "Login Successful. Welcome ".$user->name;

                //LOGIN THE USER
                $error = $app->login($credentials, $options = array());
                $this->sendMessage('success', false, array('msg' => $msg));
            }else{
                //HANDLE INCORRECT PASSWORD
                $msg = "Password Incorrect.";
                $this->sendMessage('error', true, array('msg' => $msg));
            }
        }else{
            //HANDLE INVALID USERNAME
            $msg = "Username Incorrect or not found.";
            $this->sendMessage('error', true, array('msg' => $msg));
        }
    }

    //FUNCTION FOR QUEUEING AJAX MESSAGE RESPONSES
    public static function sendMessage($message, $error = false, $data = array()){
        ob_clean();
        if(!$error){
            JFactory::getApplication()->enqueueMessage($message, 'success');
            echo new JResponseJson($data);
        }else{
            JFactory::getApplication()->enqueueMessage($message, 'error');
            $response = new JResponseJson($data, $message, true);
            echo $response;
        }
        JFactory::getApplication()->close();
    }
}

userlogin.xml

<?xml version="1.0" encoding="utf-8"?>
<extension version="3.0" type="plugin" group="ajax" method="upgrade">
    <name>Ajax User Login</name>
    <version>0.1</version>
    <creationDate>Jan 18, 2020</creationDate>
    <author>YOUR NAME</author>
    <authorEmail>email@domain.com</authorEmail>
    <authorUrl>http://www.domain.com</authorUrl>
    <description>AJAX User Login Plugin</description>
    <files>
        <filename plugin="userlogin">userlogin.php</filename>
    </files>
</extension>

Once you have installed the plugin go to your Plugins administration and enable it. There are no other options for the plugin so just enabling it is fine.

For the HTML form below you can remove the if you are not wanting to display the register and reminder links. They have nothing to do with the form operation they are just nice helpers for users.

HTML Form

<div id="userLoginPopup">
    <form method="post" action="#" id="userForm">
        <div id="loginErrorText" style="display:none;color:red;font-weight:bold;"></div>
        <div>
            <label style="font-weight:bold;" for="login-username">Username</label>  
            <input id="login-username" type="text" value="" name="username">
        </div>
        <div>
            <label style="font-weight:bold;" for="login-password">Password</label>  
            <input id="login-password" type="password" value="" name="password">
        </div>
        <div>
            <button class="btn btn-wide" id="login-user">Login</button>
        </div>
        <div>
            <ul>
                <li><a href="/register">Create an account</a></li>
                <li><a href="/username-reminder">Forgot your username?</a></li>
                <li><a href="/password-reset">Forgot your password?</a></li>
            </ul>
        </div>
    </form>
</div>

The last part is the jQuery that sends the credentials over to the plugin for authentication. The plugin will return both success and error messages based on standard logic.

Javascript

<script type="text/javascript">
jQuery(document).ready(function($){

    $("#login-user").click(function(e){
        e.preventDefault();
        var errorText = $("#loginErrorText");
        var userName = $('#login-username').val();
        var passWord = $('#login-password').val();

        var dataString = 'username='+userName+'&password='+passWord;
        jQuery.ajax({
            url: '/index.php?option=com_ajax&plugin=userlogin&method=userlogin&format=raw',
            type: 'POST',
            data: dataString,
            cache: false,
            success: function(result){
                var jmsgs = $.parseJSON(result);
                var success = jmsgs.success;
                var msg = jmsgs.data.msg;
                console.log(jmsgs);
                if(!success){
                    errorText.text(msg).show();
                }else{
                    errorText.text(msg).show();
                }
            }
        });
    });
});
</script>

You can easily manipulate the Javascript to add FancyBox functionality in as well that gives a nice welcome message. Of you can wrap the entire login form in a modal and have it all as a popup. Options are basically limitless. Let me know if you have questions about any of this. I am using these scripts myself so I know that they work.

| improve this answer | | | | |
  • Wouldn't it be more appropriate to use ->post() instead of ->get() to receive the submitted input? I am more concerned about another part of your userlogin.php script. Something looks fishy about your $response variable in the else branch. You are generating a json string with JResponseJson() then trying to iterate it as if it is an object. Lastly, I also don't think you need the condition block around errorText.text(msg).show();. – mickmackusa Jan 18 at 21:44
  • ->post() would work as well. The conditions are there for the errorText portion in the event the OP wants to handle success or error messages separately. As an example, change just the errorText message and display it for errors, or completely redirect to a new page for successes. What looks fishy about the $response? If the user is found and the password matches it returns success and logs the user in, else it returns a password error. If the user is not found it gives an error that the user doesn't exist. – Terry Carter Jan 19 at 22:32
  • Specifically, $response = new JResponseJson($data, $message, true); creates a json string. Then you are feeding that string into the foreach() loop as if the string was an object with a messages property containing an error-keyed subarray containing indexed elements -- but strings are not iterable. – mickmackusa Jan 20 at 4:19
  • @TerryCarter Thank you for taking the time to provide this code and explain what's going on, it's more useful than most of the tutorials I have found online! I could also manipulate this into a user registration form which is great. I'm finding the Joomla custom development a steep learning curve and there's no ta lot of up to date resources for beginners. Is there a reason why this AJAX login isn't a Joomla default feature? – jonboy Jan 20 at 9:13
  • 1
    $JInput->post() didn't work after testing so I changed it back to my original ->get() – Terry Carter Jan 24 at 18:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.