1

Dev environment info:

  • Developing locally
  • Network drive set up of my server on my PC
  • Access server files via directory path
  • Server contains SQL databases which server files access to and from

On application load

I have set up ng-init on my page to run the function $scope.nginit(); which sends a $_GET request to my server file called login.php:

$http.get(url + "server/login.php", 
            { transformResponse: [] })
.success(function(data, status, headers, config) 
{
    data = JSON.parse(data);
    window.localStorage.setItem("session_id",data.session_id);
})
.error( function(data, status, headers, config) 
{
    console.log("Error: " + data + "; " + status);
});

This is included in each of my server files at the top of the page:

// if(isset($_GET['session_id'])) {
//  session_id($_GET['session_id']);
// } else if(isset($_POST['session_id'])) {
//  session_id($_POST['session_id']);
// }

session_start();

include_once "../scripts/masterscript.php";

header("Access-Control-Allow-Origin: *");

$connection = connect_to_database();
try
{
    if($_SERVER['REQUEST_METHOD'] != 'POST')
    {
        setup_form();
    }
    else
    {
        process_form();
    }

    sqlsrv_close($connection);  
}
catch (Exception $e)
{
    exit("Exception $e->getMessage()");
}

Here is setup_form() on login.php:

function setup_form()
{
    global $connection;
    header('Content-Type: application/json');
    $user_data = array();

    $user_data["session_id"] = session_id();

    echo json_encode($user_data);   
}

On submitForm(); for logging in:

It adds the session_id stored in the localStorage as a parameter so it can be set back to the PHP session. It sets it on top of the PHP file but that section is currently commented out.

My intent is if it is an admin user then run the second $http.request and open a modal.

$http.post(url + "pages/login.php", data_string,
            {   headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8;' }, 
                params : { 'session_id' : window.localStorage.session_id }})
.success(function(data, status, headers, config) 
{
    var data = data[0];

    if(data.is_admin)
    {
        $http.get(url + "server/get_more_info.php", 
                    { params : {'user_id' : data.user_id },
                    transformResponse: [] })
        .success(function(data, status, headers, config) 
        {
            // setup to open a modal
        })
        .error( function(data, status, headers, config) 
        {
        }); 
    }
})
.error( function(data, status, headers, config) 
{
});

Since it is a $_POST request it will go to process_form() and here it is:

function process_form()
{
    global $connection;
    header('Content-Type: application/json');
    $user_data = array();

    $data_string = file_get_contents("php://input");
    $data = json_decode($data_string);

    $user_data['status_msg'] = "Error";

    // runs SQL query, grabs user details where email = $data->email
    $UserObj = new User(0,$connection, $data->email); 

    if($UserObj->password == $data->password)
    {
        $user_data['status_msg'] = "OK";
        $user_data['user_id']    = $UserObj->user_id;
        $user_data['user_type'] = $UserObj->user_type;

        if($UserObj->admin_user == 1)
        {
            $user_data['is_admin'] = true;
            $_SESSION['is_admin'] = 1;
        }

        $_SESSION['user_id'] = $UserObj->user_id;
        $_SESSION['user_type'] = $UserObj->user_type;
    }

    unset($UserObj);
    echo json_encode($user_data);
 }

In get_more_info.php

I try and access the session variables set in login.php but they are blank. I intend to use those variables in SQL queries:

$user_id = $_SESSION['user_id'];
$admin_user = $_SESSION['admin_user'];
$user_type = $_SESSION['user_type'];

$sql = " select * from info_table where admin_user = ? and user_type = ?";
$params = array($admin_user,$user_type);
$result = sqlsrv_query($connection, $sql, $params);       

while($row = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC))
{
}

sqlsrv_free_stmt($result);
unset($result);

Extra info

I did a var_dump(); for both of these below and are returning empty strings.

ini_get('session.cookie_httponly');
ini_get('session.cookie_secure');

On Firefox, the first network request for login.php which is the GET request is appearing and has a set $_COOKIE. It seems like it works in chrome but not firefox. I am currently just runing ionic serve --lab. On chrome it proceeds and opens a modal as it should but on firefox it does not.

On the network tab of firefox for the login.php GET request, it shows set-cookie under response headers but no cookie related attribute on request headers.

For the login.php POST request it has the same result as the previous for the cookie but this request is appearing as OPTIONS method under the network tab instead of POST.

Issues

  • The initial issue was that the session variables were blank in get_more_info.php. This was because it was not resuming to the same session started at $scope.nginit();. It was starting a new one instead.
  • The idea of getting the session_id to be stored so it can resume to the same session was brought up so that is what i've done here.
  • Initially I was trying to set the session_id by manually setting it at the top of my PHP files but it was brought to my attention that this was not needed and it should be doing it automatically which it isn't.
yivi
  • 42,438
  • 18
  • 116
  • 138
J.Do
  • 303
  • 6
  • 26
  • you have assigned $UserObj->user_id; to session and assigned $UserData->user_id; to userdata. Is that Ok. Try using localstorage – Shibu Jan 10 '19 at 12:35
  • @Chrisshi `$user_data['user_id'] = $UserObj->user_id` is what's being passed from the server which is then accessible by `data.user` on the client side. localstorage can only be used on the client side and I am trying to use the `$_SESSION` variables on the server side to have persistent access to the user_id rather than passing the user_id as a parameter every time when performing a `$http.post` or `$http.get`. – J.Do Jan 10 '19 at 12:43
  • You can generate a token at the time of registration and store that token for the user in the table. Using that token you can access the user's account – Shibu Jan 10 '19 at 12:50
  • @cbaconnier I've updated my question to include a potential fix. I didn't know what you meant by passing it to the header. I tried doing it via http.get/post but could not get it to work so I passed them via http params instead. What would be the difference in what you mentioned to my solution? Do you have an example that I can look at so I can understand what you mean? – J.Do Jan 14 '19 at 13:43

2 Answers2

2

Backend

First: This bit is unnecessary:

if(isset($_GET['session_id'])) {
    session_id($_GET['session_id']);
} else if(isset($_POST['session_id'])) {
    session_id($_POST['session_id']);
}

The only line you need at the start of your more_details.php file is session_start().

If you look at the documentation for this function it clearly says:

session_start() creates a session or resumes (*) the current one based on a session identifier passed via a GET or POST request, or passed via a cookie.

* emphasis mine

So after you call it, you can safely access $_SESSION properties.

Secondly, you need to set up the proper CORS headers.

header("Access-Control-Allow-Origin: http://localhost:8000");
header("Access-Control-Allow-Headers: content-type, authorization");
header("Access-Control-Allow-Credentials: true");

Very important: Access-Control-Allow-Origin can't be set to * or this will fail. When using credentials, the allowed origin need to be set explicitely.

Also, you need to account for an OPTIONS call, that should return this same headers. This call will be made by the browser as a pre-flight verification before the POST request.

For the purposes of this answer I have a common.php file that goes:

<?php
session_start();
header( "Access-Control-Allow-Origin: http://localhost:8000" );
header( 'Content-Type: application/json' );
header( "Access-Control-Allow-Headers: content-type,authorization" );
header( "Access-Control-Allow-Credentials: true" );

if ( $_SERVER['REQUEST_METHOD'] == 'OPTIONS' ) {
    die();
}

Another file one.php:

<?php
// one.php
<?php
require 'common.php';

$was_session_set     = $_SESSION['user_id'] ?? 'no session id yet';
$_SESSION['user_id'] = microtime( true );

echo '{"session": "' . $was_session_set . '"}';

die();

Another two.php:

<?php
require 'common.php';

$user_id = $_SESSION['user_id'] ?? 0;
echo '{"session": "' . $user_id  .'"}';

die();

Frontend

let app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope, $http) {
$http.get("http://localhost:8006/one.php",  { withCredentials: true })
    .then(function (response) {
      $scope.myWelcome = response.data;
      $http.post("http://localhost:8006/two.php", { withCredentials: true })
          .then(function (response2) {
            console.log(response2.data);
          })
    });
});
yivi
  • 42,438
  • 18
  • 116
  • 138
  • Thanks for the answer. I've just removed the manually setting of session_id at the top of my files but it's stopped working now. I am passing the session_id from when I run the http.get request and pass it via parameter `$http.get(url + "server/get_more_info.php", { params : {'session_id' : window.localStorage.session_id}, transformResponse: [] })`. How does it know which parameter to apply to the session_id? Would I need to change the parameter key to a specific one? – J.Do Jan 14 '19 at 16:57
  • @yivi this works perfectly! I've combined it with what user/msphn answered it so that I don't have to enter withCredentials:true for every request. – J.Do Jan 15 '19 at 09:49
  • @J.Do That's your choice. I rather be explicit about this, so you know exactly which requests you need this extra information. Otherwise you may have CORS problems in requests that do not need this kind of setup. – yivi Jan 15 '19 at 09:50
  • @yivi Understood. Thank you. One more thing, where I set `header( "Access-Control-Allow-Origin: http://localhost:8000" );`. Will the host port be the same every time or will it change? – J.Do Jan 15 '19 at 09:52
  • @J.Do The **host and port** should match wherever you are serving the angular application. In this example I'm using localhost:8000 because that's what I used for my local set-up. If you deploy it in a different domain or port, the allow-origin should match **that**. – yivi Jan 15 '19 at 09:54
  • Hi yivi. When I was doing this it was all on android and it's still all working. However, I am currently trying it out for the iOS using an iPhone simulator but the PHPSESSID seems to change, do you know why this could be? I haven't changed anything and it works fine for android. I'm still looking into it but I thought I would ask in case you had any ideas! – J.Do Feb 05 '19 at 10:57
1

To use cookies (which is required for php sessions) you need to tell your app to allow it.

Use this to configure it globally, afterwards the app will make use of your cookie and reuse it for other requests.

.config(['$httpProvider', function($httpProvider) {
   $httpProvider.defaults.withCredentials = true;
}])
msphn
  • 307
  • 2
  • 9
  • I've tried this and I am now getting the error Access to XMLHttpRequest at 'http://hostserver/server_files/pages/login.php' from origin 'http://localhost:8100' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute. – J.Do Jan 15 '19 at 09:17
  • Yes, the first request is of type OPTIONS, asking for what the server expects. Afterwards the actual request is send. OPTIONS requests doesn't allow wildcards in allow-origin. So the OPTIONS requests only respond with one concrete and not a list, even if you parse the request and decide which one you want to return. You can e.g. check a list of hosts and if the request host is in this list you return a header with exactly this host. – msphn Jan 15 '19 at 09:35
  • @J.Do In my answer I address the OPTIONS call. That's a pre-flight call executed by the browser. You need to respond properly to this request. You also need to send back the proper CORS headers as I show in my answer. – yivi Jan 15 '19 at 09:45