1

I'm setting up Login with Facebook (using Javascript SDK) and it's working for the most part, with one strange exception I'm hoping someone can help me with.

fb-login.php (full code below) has the Login with Facebook button. User clicks that and it gathers data from Facebook and then uses AJAX to send the data to fb-login-process.php (full code below).

On fb-login-process.php, some database updates/inserts are made depending on various scenarios (all of them are happening properly) and then I set two $_SESSION variables to allow them to be logged in, i.e. this part...

//set session variables so we can log them in
$_SESSION['adminid'] = $adminid;                            
$_SESSION['loggedinuser']="yes";

So back on fb-login.php, this is what triggers the AJAX request to fb-login-process.php and the subsequent redirect to a page on my app (/my-pools)...

if (response.authResponse) {
    // Send user data to database via AJAX
    getFbUserData();
    //redirect user to /my-pools (i.e. log them in)
    window.location.replace("/my-pools");
}

Here's what happens. Facebook login works fine and the database tables are updated/inserted properly. The $_SESSION variables mentioned above are also set properly (more on this in a minute). The redirect to /my-pools also works properly, however, on the /my-pools page I have this bit of code at the top of the page (to make sure user is logged in AND an adminid exists...i.e. those $_SESSION variables mentioned above)...

<?php 
if (isset($_SESSION['loggedinuser']) && isset($_SESSION['adminid'])) {
    //continue
} else {
    redirect_to('/');
}
?>

It keeps redirecting to /, so as far as that bit of code on /my-pools is concerned, one or both of those things is NOT set.

However, I added code on / to echo those two $_SESSION variables and they BOTH echo actual values (i.e. they are actually set).

So why is it redirecting me?

Furthermore, after getting redirected to / and seeing the properly echoed/clearly set $_SESSION variables, if I then manually change the URL to /my-pools it goes there properly and stays there.

I hope this makes some sense to someone (if you need any clarification on anything, please let me know).

In a nutshell, it appears that the $_SESSION variables are absolutely set, but that code above which checks if they're set doesn't agree/doesn't pass ON THE FIRST TRY ONLY. If try to go right back to /my-pools immediately after it didn't pass, it then does pass and stays on /my-page.

So strange.

Any ideas?

fb-login.php

<?php
require_once("includes/session.php");
require_once("includes/connection.php");
require_once("includes/functions.php");

?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link href="https://fonts.googleapis.com/css?family=Comfortaa" rel="stylesheet">
<meta name="robots" content="noindex,follow">
<title>FB Login</title>
<link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>

window.fbAsyncInit = function() {
    // FB JavaScript SDK configuration and setup
    FB.init({
      appId      : 'XXXXXXXXXXXXXXXX', // FB App ID
      cookie     : true,  // enable cookies to allow the server to access the session
      xfbml      : true,  // parse social plugins on this page
      version    : 'v2.8' // use graph api version 2.8
    });

    // Check whether the user already logged in
    FB.getLoginStatus(function(response) {
        if (response.status === 'connected') {
            //display user data
            getFbUserData();
            window.location.replace("/my-pools");
        }
    });
};

// Load the JavaScript SDK asynchronously
(function(d, s, id) {
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) return;
    js = d.createElement(s); js.id = id;
    js.src = "//connect.facebook.net/en_US/sdk.js";
    fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));

// Facebook login with JavaScript SDK
function fbLogin() {
    FB.login(function (response) {
        if (response.authResponse) {
             // Send user data to database via AJAX
            getFbUserData();
            //redirect user to /my-pools (i.e. log them in)
            window.location.replace("/my-pools");
        } else {
           /* document.getElementById('status').innerHTML = 'User cancelled login or did not fully authorize.';*/
        }
    }, {scope: 'email'});
}



// Logout from facebook
/*
function fbLogout() {
    FB.logout(function() {
        document.getElementById('fbLink').setAttribute("onclick","fbLogin()");
        document.getElementById('fbLink').innerHTML = '<img src="fblogin.png"/>';
        document.getElementById('userData').innerHTML = '';
        document.getElementById('status').innerHTML = 'You have successfully logout from Facebook.';
    });
}
*/


// Save user data to the database
function saveUserData(userData){
    $.post('fb-login-process.php', {oauth_provider:'facebook',userData: JSON.stringify(userData)}, function(data){ return true; });
} 

// Fetch the user profile data from facebook
function getFbUserData(){
    FB.api('/me', {locale: 'en_US', fields: 'id,first_name,last_name,email,link,gender,locale,picture'},
    function (response) {
        /*document.getElementById('fbLink').setAttribute("onclick","fbLogout()");
        document.getElementById('fbLink').innerHTML = 'Logout from Facebook';
        document.getElementById('status').innerHTML = 'Thanks for logging in, ' + response.first_name + '!';
        document.getElementById('userData').innerHTML = '<p><b>FB ID:</b> '+response.id+'</p><p><b>Name:</b> '+response.first_name+' '+response.last_name+'</p><p><b>Email:</b> '+response.email+'</p><p><b>Gender:</b> '+response.gender+'</p><p><b>Locale:</b> '+response.locale+'</p><p><b>Picture:</b> <img src="'+response.picture.data.url+'"/></p><p><b>FB Profile:</b> <a target="_blank" href="'+response.link+'">click to view profile</a></p>';
*/
        // Save user data
        saveUserData(response);
    });
}

</script>
</head>
<body>
<!-- Display login status -->
<div id="status"></div>

<!-- Facebook login or logout button -->
<a href="javascript:void(0);" onclick="fbLogin()" id="fbLink"><img src="fblogin.png"/></a>


<!--<div class="fb-login-button" data-max-rows="1" data-size="medium" data-button-type="continue_with" data-show-faces="false" data-auto-logout-link="false" data-use-continue-as="false" onlogin="fbLogin();"></div>-->

<!-- Display user profile data -->
<div id="userData"></div>
</body>
</html>

fb-login-process.php

<?php
require_once("includes/session.php");
require_once("includes/connection.php");
require_once("includes/functions.php");

$createddate = date("Y-m-d");

//Convert JSON data into PHP variable
$userData = json_decode($_POST['userData']);
if(!empty($userData)){
    $oauth_provider = $_POST['oauth_provider'];
    //Check whether user data already exists in social_users table
    $sql = "SELECT * FROM social_users WHERE oauth_provider = '".$oauth_provider."' AND oauth_uid = '".$userData->id."' LIMIT 1";
    $checkforsocial = mysqli_query($connection, $sql);
    if (!$checkforsocial) {
    die("Database query failed: " . mysqli_error());
    } else {
        if(mysqli_num_rows($checkforsocial) > 0){ //user info already exists in social_users
            //Update user data      
            $sql = "UPDATE social_users SET first_name = '".$userData->first_name."', last_name = '".$userData->last_name."', email = '".$userData->email."', gender = '".$userData->gender."', locale = '".$userData->locale."', picture = '".$userData->picture->data->url."', link = '".$userData->link."', modified = '".date("Y-m-d H:i:s")."' WHERE oauth_provider = '".$oauth_provider."' AND oauth_uid = '".$userData->id."'";
            $updatesocialusers = mysqli_query($connection, $sql);
            if (!$updatesocialusers) {
            die("Database query failed: " . mysqli_error());
            } else {
            }

            //should always exist but we'll check anyway in administrators table to see if email already exists
            $sql = "SELECT adminid FROM administrators WHERE email='".$userData->email."' LIMIT 1";
            $checkforemail = mysqli_query($connection, $sql);
            if (!$checkforemail) {
            die("Database query failed: " . mysqli_error());
            } else {
                $row=mysqli_fetch_array($checkforemail);
                $adminid=$row['adminid'];
            }

            //set session variables so we can log them in
            $_SESSION['adminid'] = $adminid;                            
            $_SESSION['loggedinuser']="yes";


        } else {  //user has not logged in via facebook before
            //Insert user data into social_users table  
            $sql = "INSERT INTO social_users (oauth_provider, oauth_uid, first_name, last_name, email, gender, locale, picture, link, created, modified) VALUES ('facebook', '".$userData->id."', '".$userData->first_name."', '".$userData->last_name."', '".$userData->email."', '".$userData->gender."', '".$userData->locale."', '".$userData->picture->data->url."', '".$userData->link."', '".date("Y-m-d H:i:s")."', '".date("Y-m-d H:i:s")."')";            
            $insertsocialusers = mysqli_query($connection, $sql);
            if (!$insertsocialusers) {
            die("Database query failed: " . mysqli_error());
            } else {
                $social_users_id=mysqli_insert_id($connection);
            }

            //check administrators table to see if email already exists
            $sql = "SELECT adminid FROM administrators WHERE email='".$userData->email."' LIMIT 1";
            $checkforemail = mysqli_query($connection, $sql);
            if (!$checkforemail) {
            die("Database query failed: " . mysqli_error());
            } else {
                $row=mysqli_fetch_array($checkforemail);
                $adminid=$row['adminid'];
                if (mysqli_num_rows($checkforemail) > 0) {  //email already exists, so add social_users id to administrators table to link it to social account
                    $sql = "UPDATE administrators SET social_users_id = '".$social_users_id."' WHERE email = '".$userData->email."'";
                    $updatesocialusers = mysqli_query($connection, $sql);
                    if (!$updatesocialusers) {
                    die("Database query failed: " . mysqli_error());
                    } else {

                    }

                    //set session variables so we can log them in
                    $_SESSION['adminid'] = $adminid;                            
                    $_SESSION['loggedinuser']="yes";

                } else {  //email doesn't exist, so need to make an entry for them in the administrators table
                    // first create adminid
                    $mmdd=date('md');
                    $year=date("Y");
                    $lastdigitofyear=substr($year, -1);
                    //create random identifier for rest of poolid to help with uniqueness
                    $seed = str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZ'.'0123456789'); // and any other characters
                    shuffle($seed); // probably optional since array_is randomized; this may be redundant
                    $rand ='';
                    foreach (array_rand($seed, 4) as $k) $rand .= $seed[$k];
                    $adminid="A".$mmdd.$rand.$lastdigitofyear."EN";
                    //now insert data into administrators table
                    $sql = "INSERT INTO administrators (adminid, social_users_id, firstname, lastname, email, createddate) VALUES ('".$adminid."', '". $social_users_id."', '".$userData->first_name."', '".$userData->last_name."', '".$userData->email."', '".$createddate."')";
                    $insertsocialusers = mysqli_query($connection, $sql);
                    if (!$insertsocialusers) {
                    die("Database query failed: " . mysqli_error());
                    } else {
                    }
                    //set session variables so we can log them in
                    $_SESSION['adminid'] = $adminid;                            
                    $_SESSION['loggedinuser']="yes";
                }

            }
        }


        //send confirmation email here??????

        //log them in

    }
} else {
}
?>
user3304303
  • 1,027
  • 12
  • 31
  • You have SQL Injection vulnerabilities! Here's some info on what to do: http://bobby-tables.com/. As for your session problem, are you sure `session_start()` is called and the user has a session before loading the initial page? – Cfreak Jun 01 '17 at 15:13
  • Yep, session_start() is at the top of every page I mentioned. And I will check out bobby-tables.com thx. – user3304303 Jun 01 '17 at 15:23
  • @Cfreak I don't want to get too far away from main issue, but vulnerability wise, the stuff I'm sending to the datbase is all Facebook-provided data in the form of "$userData->first_name" for example. I guess I wrongly assumed Facebook-provided data would be OK and should still "sanitize" all of that data before inserting? Does that sound about right? – user3304303 Jun 01 '17 at 15:28
  • The problem I see is `$_POST['oauth_provider']` can be sent by anyone, not just Facebook. As a good practice you should never trust external input from anyone (including Facebook) :). For the session: make sure you don't have something printing before `session_start()` in one of your files (check the logs for those "Headers already sent" messages). Anything that would cause the cookie to not be set can cause this problem. Another option would be to create a token and send that as a GET parameter in your redirect and then set up your session (would have to be unique to each user though) – Cfreak Jun 01 '17 at 15:47

1 Answers1

1

You have async problems.

FB.api is async, it takes a callback parameter which is called when the request is processed, you're correctly saving the data inside the callback, but your getFbUserData returns immediately and then user is redirected before facebook returns the info you request and before you call fb-login-process.php.

You should only redirect user after the facebook callback is executed and your XHR is complete. Something like this:

// Save user data to the database
function saveUserData(userData, callback){ //new parameter CALLBACK
    $.post('fb-login-process.php', {oauth_provider:'facebook',userData: JSON.stringify(userData)}, function(data){ callback(); /* execute callback when XHR completes */ return true; });
} 

// Fetch the user profile data from facebook
function getFbUserData(callback){
    FB.api('/me', {locale: 'en_US', fields: 'id,first_name,last_name,email,link,gender,locale,picture'},
    function (response) {
        /*document.getElementById('fbLink').setAttribute("onclick","fbLogout()");
        document.getElementById('fbLink').innerHTML = 'Logout from Facebook';
        document.getElementById('status').innerHTML = 'Thanks for logging in, ' + response.first_name + '!';
        document.getElementById('userData').innerHTML = '<p><b>FB ID:</b> '+response.id+'</p><p><b>Name:</b> '+response.first_name+' '+response.last_name+'</p><p><b>Email:</b> '+response.email+'</p><p><b>Gender:</b> '+response.gender+'</p><p><b>Locale:</b> '+response.locale+'</p><p><b>Picture:</b> <img src="'+response.picture.data.url+'"/></p><p><b>FB Profile:</b> <a target="_blank" href="'+response.link+'">click to view profile</a></p>';
*/
        // Save user data
        saveUserData(response, callback);
    });
}

And then you redirect the user in the callback:

// Check whether the user already logged in
FB.getLoginStatus(function(response) {
    if (response.status === 'connected') {
        //display user data
        getFbUserData(function(){
            window.location.replace("/my-pools"); //pass this anonymous function as callback of getFbUserData
        });

    }
});

I created callback parameters getFbUserData and saveUserData, you then use this callback which will get executed only after both XHR (from facebook and your own) are completed to pass a anonymou fuction to which will redirect user.

Luizgrs
  • 4,765
  • 1
  • 22
  • 28
  • This seems to be working for me, thank you! One question. I had two instances of getFbUserData() on the fb-login.php page... So I should add your updated code to both instances right?, i.e. change them both to: getFbUserData(function(){ window.location.replace("/my-pools"); //pass this anonymous function as callback of getFbUserData }); – user3304303 Jun 01 '17 at 18:07
  • 1
    if you want execute what is after `getFbUserData` after all XHR calls complete yes, otherwise no. – Luizgrs Jun 02 '17 at 12:22