I' m having a difficulty in getting a clear view on Joomla (I'm using version 3.6.x) authentication and login.
I've seen many approaches and snippets on how to "Login" in a Joomla site from an external source (which is what i m interesting in).
But what i see after testing and testing (and more testing), is that at the end of the day, you re simply not logged in.
You may have the ability to verify that the user who's attempting to login is a valid registered user and also to be able to retrieve his name, email, phone and blah blah blah but in Joomla backend, this user is still not logged in.
I created a brand new user in Administrator Panel (it shows the user NEVER visited the site), I wrote the login script in external PHP, I "authenticated" the new user a dozen times and when i go back to the Backend, the user still NEVER been there. Not even once.
Which, i guess, it also means that i can't retrieve a list of "active" users, since Joomla can't see them as logged in users.
So what i want to achieve is users to be able to login from outside the Joomla site and to have the ability to know IF they're logged in or not. So I can get a list of them and assign tasks to them.
I guess it has something to do with tokens and cookies. But no matter how much i searched to that direction, i can't find any examples to enlighten me.
Any help (and specially "scripting" help) is much appreciated.
Thank you.
<?php
if (version_compare(PHP_VERSION, '5.3.1', '<')) {
die('Your host needs to use PHP 5.3.1 or higher to run this version of Joomla!');
}
define('_JEXEC', 1);
if (file_exists(__DIR__ . '/defines.php')) {
include_once __DIR__ . '/defines.php';
}
if (!defined('_JDEFINES')) {
define('JPATH_BASE', __DIR__);
require_once JPATH_BASE . '/includes/defines.php';
}
require_once JPATH_BASE . '/includes/framework.php';
$app = JFactory::getApplication('site');
$app->initialise();
require_once (JPATH_BASE .'/libraries/joomla/factory.php');
$credentials['username'] = $_GET["usrname"];
$credentials['password'] = $_GET["passwd"];
$db = JFactory::getDbo();
$query = $db->getQuery(true)
->select('id, password')
->from('#__users')
->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);
$session =& JFactory::getSession();
echo "<p>Your name is {$user->name}, your email is {$user->email}, and your username is {$user->username}</p>";
$session->set('userid', $user->id);
if ($user->guest) {
echo 'SORRY, NO LOGIN YET...';
} else {
echo 'user is LOGGED IN !';
}
} else {
die('Invalid password');
}
} else {
die('Cound not find user in the database');
}
?>
Joomla log in is handled in authentication plugins, by writing and enabling your own you can bypass the token verification and allow logging in by a simple get request.
Keep in mind that Joomla! keeps administrator and frontend sessions separate (you can be logged into the administrator, and browse the site as a guest). Additionally, a user needs a Manager, Administrator or Super User group membership to login to the backend.
Administrator
The common use case for admin login is to run a background script.
In order to bypass the Joomla Authentication in the backend, check out the /cli folder as it contains some examples.
Frontend
The use case for frontend login is much wider: server-to-server communication, app authentication, single sign on etc.
The best practice to perform frontend authentication is to build your own authentication plugin. The authentication will be implemented by the onUserAuthenticate() function. Check out the /plugins/authentication/joomla/ plugin for an example.
If you need extra data for the authentication plugin to perform its magic (e.g. the remote token/api key to authenticate using a remote service) you might want to check out the user plugin group, for example the /plugins/user/profile plugin.
Verify last login date/time
The frontend approach should automatically work, showing users as logged in in the administrator control panel.
However, if you use the CLI approach to login to the administrator, this may not work depending on how you perform login.
To be able to check if a user is logged in only authentication is not enough but you need a forced login. Only then you will be able to view them at the backend.
This is the code you can use to check if a user is logged in or not
<?php
if (version_compare(PHP_VERSION, '5.3.1', '<')) {
die('Your host needs to use PHP 5.3.1 or higher to run this version of Joomla!');
}
if (!ini_get('display_errors')) {
ini_set('display_errors', '1');
}
echo ini_get('display_errors');
define('_JEXEC', 1);
define('JPATH_BASE', "C:\Vertrigo\www\joomla" );//Define the Base path as per your installation directory
if (!defined('_JDEFINES'))
{
require_once JPATH_BASE . '/includes/defines.php';
}
require_once JPATH_BASE . '/includes/framework.php';
// Instantiate the application.
$app = JFactory::getApplication('site');
$app->initialise();
require_once (JPATH_BASE .'/libraries/joomla/factory.php');
//Check if a user exists else create one with forced login
$user = JFactory::getUser();
jimport('joomla.plugin.helper');
$credentials = array();
$credentials['username'] = JRequest::getVar('username', '');
$credentials['password'] = JRequest::getVar('passwd', '');
if (!$user->get('gid')){
$forcevars = array();
$forcevars['silent'] = true;
$forcevars['forecelogon'] = true;
$response = $app->login($credentials, $forcevars);
if($response){
echo "Login Successful";
}else{
echo "Login Unsuccessful. Check Username and Password. Give just Plain password.";
}
}
?>
I have modified the code as you neeeded to call username and password in url. better to use getVar in joomla rather than GET method. You url will be in the format http://www.yoursite.com/thisscript.php?username=yourusername&passwd=yourpassword
Related
I'm currently asking users for two read permissions i.e. email and user_location and one write permssion i.e. publish_actions. Following is the code snippet I'm using to verify if the user has granted all requested permissions:
$facebook = new Facebook(APP_ID, APP_SECRET, REDIRECT_URI);
if ( $facebook->IsAuthenticated() ) {
// Verify if all of the scopes have been granted
if ( !$facebook->verifyScopes( unserialize(SCOPES) ) ) {
header( "Location: " . $facebook->getLoginURL( $facebook->denied_scopes) );
exit;
}
...
}
Facebook is a class I've customly built to wrap the login flow used by various classes in the SDK. IsAuthenticated() makes the use of code get variable to check if the user is authorized. verifyScopes() checks granted permissions against SCOPES and assings an array of denied scopes to denied_scopes property. getLoginURL()` builds a login-dialog URL based on permissions passed as an an array as a only paramter.
Now, the problem is when the user doesn't grant write permissions, publish_actions in this case, write permission dialog is shown until user grants the write permission. But if the user chooses to deny of the read permissions, say email, the read login dialog isn't show. Instead Facebook redirects to the callback URL (that is REDIRECT_URI) creating a redirect loop.
The application I'm builiding requires email to be compulsorily provided but apparently the above approach (which seems to be the only) is failing. So, is there a workaround or a alternative way to achieve this? Or Facebook doesn't allow to ask for read permissions once denied?
As of July 15, 2014, an update has been made to the Facebook PHP SDK 4.x that allows user to re-ask the declined permissions. The function prototype of getLoginUrl() now looks like this.
public function getLoginUrl($redirectUrl, $scope = array(), $rerequest = false, $version = null)
So, to re-ask declined permissions we'd do something like this:
<?php
// ...
$helper = new FacebookRedirectLoginHelper();
if ($PermissionIsDeclined) {
header("Location: " . $helper->getLoginUrl( $redirect_uri, $scopes, true );
exit;
}
// ....
?>
For the time being you can append &auth_type=rerequest to the getLoginUrl return value to enable a rerequest, kind of lame but it works.
$helper = new FacebookRedirectLoginHelper($app_url, $app_id, $app_secret);
$login = $helper->getLoginUrl(array('scope'=>'user_likes', 'user_location'));
print $login."&auth_type=rerequest";
what about getReRequestUrl(); ?
That works just fine.
Read more at https://developers.facebook.com/docs/php/FacebookRedirectLoginHelper/5.0.0
There seems to be very few to no up to date resources on integration of Facebook login with the cakephp Auth component online. I have found the following resources:
Old Bakery Article using cakephp 1.3? and an older version of Facebook SDK
Cakephp Plugin by webtechnick that seems to be in development
Other than this I found no definitive resources. I wanted the integration to be as flexible (without the use of a magic plugin) as possible. So after much research I finally baked a decent solution which I am sharing here today. Please contribute as I am rather new to cake.
Integration of Cakephp 2.x Auth with Facebook Auth for seamless user authentication
To start off you should read up on the fantastic cakePHP Auth Component and follow the Simple Authentication and Authorization Application tutorial from the cakephp book 2.x (Assuming you have also followed the first two tutorials from the series. After you are done, you should have managed to build a simple cakePHP application with user authentication and authorization.
Next you should download the facebook SDK and obtain an App ID from facebook.
First we will copy the Facebook sdk in to App/Vendors. Then we will import and initialize it in the AppController beforeFilter method.
//app/Controller/AppController.php
public function beforeFilter() {
App::import('Vendor', 'facebook-php-sdk-master/src/facebook');
$this->Facebook = new Facebook(array(
'appId' => 'App_ID_of_facebook',
'secret' => 'App_Secret'
));
$this->Auth->allow('index', 'view');
}
We are initializing the Facebook SDK in AppController so that we will have access to it through out the application. Next we will generate the Facebook login URL using the SDK and pass it to the view. I normally do this in the beforeRender method.
Note: The above configuration details (appId & secret) should preferably be saved in App/Config/facebook.php. You should then use cake Configure.
//app/Controller/AppController.php
public function beforeRender() {
$this->set('fb_login_url', $this->Facebook->getLoginUrl(array('redirect_uri' => Router::url(array('controller' => 'users', 'action' => 'login'), true))));
$this->set('user', $this->Auth->user());
}
We will update our layout so that we can display this link to facebook login for all users who have not logged in. Notice how we have set redirect_uri to our applications User/login action. This is so that once facebook has authenticated a user, we can log him in using cake::Auth as well. There are various benefits to this, including the solution for this question.
<!-- App/Views/Layouts/default.ctp just after <div id="content"> -->
<?php
if($user) echo 'Welcome ' . $user['username'];
else {
echo $this->Html->link('Facebook Login', $fb_login_url) . ' | ';
echo $this->Html->link('Logout', array('controller' => 'user', 'action' => 'logout'));
?>
When the user clicks the login link, facebook SDK will login the user and redirect them to our app Users/login. We will update this action for handling this:
// App/Controller/UsersController.php
// Handles login attempts from both facebook SDK and local
public function login()
{
// If it is a post request we can assume this is a local login request
if ($this->request->isPost()){
if ($this->Auth->login()){
$this->redirect($this->Auth->redirectUrl());
} else {
$this->Session->setFlash(__('Invalid Username or password. Try again.'));
}
}
// When facebook login is used, facebook always returns $_GET['code'].
elseif($this->request->query('code')){
// User login successful
$fb_user = $this->Facebook->getUser(); # Returns facebook user_id
if ($fb_user){
$fb_user = $this->Facebook->api('/me'); # Returns user information
// We will varify if a local user exists first
$local_user = $this->User->find('first', array(
'conditions' => array('username' => $fb_user['email'])
));
// If exists, we will log them in
if ($local_user){
$this->Auth->login($local_user['User']); # Manual Login
$this->redirect($this->Auth->redirectUrl());
}
// Otherwise we ll add a new user (Registration)
else {
$data['User'] = array(
'username' => $fb_user['email'], # Normally Unique
'password' => AuthComponent::password(uniqid(md5(mt_rand()))), # Set random password
'role' => 'author'
);
// You should change this part to include data validation
$this->User->save($data, array('validate' => false));
// After registration we will redirect them back here so they will be logged in
$this->redirect(Router::url('/users/login?code=true', true));
}
}
else{
// User login failed..
}
}
}
And we are done! Most of the heavy lifting is done by this action as you can see. You should preferably move some of the above code in to UserModel. So here's a summary of what is going on.
At first we check if the login request is send from the login form of our application # Users/login. If it is, then we simply log the user in. Otherwise we check if the user exists in our database and if he does log him in or create a new user, and then log him in.
Be careful to verify the user here with more than their email, like their facebook_id. Otherwise there is a chance the user could change their facebook email and hijack another user of your application.
Happy Coding!
I have built an app using the PHP Facebook SDK. It allows users to authenticate my app with facebook OAth so that they can update their status' via my App. This works great, however a lot of my users have business pages and they want to update the status on their business page not their main personal feed. How is this possible? Below is what I have so far.
if ($status != ''){
try {
$parameters = array(
'message' => "$status"/*,
'picture' => $_POST['picture'],
'link' => $_POST['link'],
'name' => $_POST['name'],
'caption' => $_POST['caption'],
'description' => $_POST['description']*/
);
//add the access token to it
$parameters['access_token'] = $access_token;
//build and call our Graph API request
$newpost = $facebook->api(
'/me/feed',
'POST',
$parameters
);
$success['status'] = "$xml->status";
$xml2 = new XMLGenerator($success, 'facebook','status');
$returnData = $xml2->output;
$returnData = APIResponse::successResponse('200', "$xml->status");
} catch (Exception $e) {
$returnData = APIResponse::errorResponse('400', 'Facebook Error: '.$e);
}
I assume I would have to change '/me/feed'? but to what? What is they have multiple pages how would my app know which page to post to?
Any help with this would be much appreciated.
You can substitute me with the PAGE_ID to post to a page e.g., /013857894/feed. Make sure that you have completed the OAuth process with the manage_pages and publish_stream permissions. You can learn more at the link below:
https://developers.facebook.com/docs/reference/api/page/#statuses
If the user has multiple Pages then you will first need to give them some way of selecting which page they want to post to. You can find out which Facebook Pages a given user is the administrator of by calling /me/accounts for that user. You can find out more about this approach in the Connections section of this page:
https://developers.facebook.com/docs/reference/api/user/
I need to share a page from my php website to user's facebook wall. If user is not logged into facebook, my website redirects to the facebook login page using
$user = $facebook->getUser();
if($user) {
$result = $facebook->api(
'/me/feed/',
'POST',
array('access_token' => $this->access_token, 'link'=>'google.com', 'message' => 'Test link')
);
$this->Session->setFlash('Your link has been succesfully posted on your wall');
$this->redirect($this->referer());
}else {
$login_url_params = array(
'req_perms' => 'publish_stream',
'redirect_uri' => 'localhost/pictrail' //thts the same path I gave to my facebook app
);
$login_url = $facebook->getLoginUrl($login_url_params);
header("Location: {$login_url}");
exit();
}
So, when the user is not logged into facebook .. it redirects to facebook for the user to log in, afterwards it redirects to localhost/pictrail regardless of the url i put in there. I want to redirect it to say localhost/pictrail/images/param ... how can i achieve this? I tried 'localhost/pictrail/images/99' but it didn't work. Everything works fine when the user is logged into facebook.
You cannot redirect from Facebook to localhost as it's not a valid address.
The way that I got around this, for development, was to edit my hosts file and replace the domain with my local ip.
www.example.com 127.0.0.1
Then you can redirect to www.example.com from your Facebook app and it will render your local development version.
Be sure to add this domain to your local server's vhost or similar.
Sorry I misread your question. To change the Facebook redirect url you need to change it in your Facebook app. So you'd have to pass a custom field through to Facebook and then tack it back on afterwards.
Have a look through this, https://developers.facebook.com/docs/authentication/server-side/
I currently force users to authenticate through Facebook each time they start a new session with my site. This means I force users to hit the url obtained through $facebook->getLoginURL() each time the browser is restarted.
When a user completes the login process, they are redirected to my login script:
{checks for CSRF token and error status...}
$facebook = new Facebook(array(
'appId' => '{my app id}',
'secret' => '{my app secret}',
'cookie' => true
));
//Get user info
$user = $facebook->getUser();
if($user)
echo 'user found';
else
echo 'no user!';
$user always results in 0; I am getting the expected 'state' variable and no errors in the API response. Also, I see that the relevant App permissions exist on my profile. However, $facebook->getUser() still returns 0. Is there some intermediate step that I'm missing (am I expected to do anything with the user's auth token?)
I know there are many threads on this problem but haven't found any resolutions. Also, I think this is a very basic example so I'm hoping the answer here will be more useful for future users dealing with the problem. Thanks!