Home » Php » javascript – Sessions in AngularJS and PHP application

javascript – Sessions in AngularJS and PHP application

Posted by: admin July 12, 2020 Leave a comment

Questions:

I have an AngularJS application that I am updating to use PHP 7. Currently I have a custom session handler setup for sessions:

Custom Session Handler (session.php)

function sess_open( $path, $name ) {
    return true;
}

function sess_close( ) {
    $sessionId = session_id(); 
    return true;
}

function sess_read( $id ) {
    $db = dbConn::getConnection();

    $stmt = "SELECT session_data FROM session where session_id =" . $db->quote($id);
    $result = $db->query($stmt);
    $data = $result->fetchColumn();
    $result->closeCursor();
    return $data;
}

function sess_write( $id, $data ) {
    $db = dbConn::getConnection();

    $tstData = sess_read( $id );
    if (!is_null($tstData)) {
        // if it does then do an update
        $stmt = "UPDATE session SET session_data =" . $db->quote($data) . " WHERE session_id=" . $db->quote($id);
        $db->query($stmt);
    }
    else {
        // else do an insert
        $stmt = "INSERT INTO session (session_id, session_data) SELECT ". $db->quote($id) . ", ". $db->quote($data) . " WHERE NOT EXISTS (SELECT 1 FROM session WHERE session_id=" . $db->quote($id) . ")";
        $db->query($stmt);
    }

    return true;
}

function sess_destroy( $id ) {

    $db = dbConn::getConnection();

    $stmt = "DELETE FROM session WHERE session_id =" . $db->quote($id);
    setcookie(session_name(), "", time() - 3600);
    return $db->query($stmt);
}

function sess_gc( $lifetime ) {

    $db = dbConn::getConnection();

    $stmt = "DELETE FROM session WHERE timestamp < NOW() - INTERVAL '" . $lifetime . " second'";
    return $db->query($stmt);
}

session_name('PROJECT_CUPSAW_WEB_APP');
session_set_save_handler("sess_open", "sess_close", "sess_read", "sess_write", "sess_destroy", "sess_gc");
session_start();
ob_flush();

In my app.js I have a continuous check to see if the user is authenticated and can access the application.

App.js

/*
 * Continuous check for authenticated permission to access application and route
 */
app.run(function($rootScope, $state, authenticationService, ngToast) {
    $rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams) {
        authenticationService.isAuthenticated()
            .success(function () {
                if(toState.permissions) {
                    ngToast.dismiss();
                    event.preventDefault();
                    $state.go("logout"); // NEEDS TO CHANGE - Unauthorized access view
                    return;
                }
            })
            .error(function () {
                ngToast.dismiss();
                event.preventDefault();
                localStorage.clear();
                $state.go("authentication"); // User is not authenticated; return to login view
                return;
            });
        ngToast.dismiss();
    });
});

In the code above, isAuthenticated runs isUserAuthorized.php

isAuthenticated

/*
 * Check if user is authenticated; set role/permissions
 */
this.isAuthenticated = function() {
    return $http.post(baseUrl + '/isUserAuthorized.php');
};

isUserAuthorized.php

<?php

require_once 'session.php';
// Check to ensure user is authenticated to initiate request
if (array_key_exists('authenticated', $_SESSION) && $_SESSION['authenticated']) {
    return http_response_code(200);
} else {
    // Clear out all cookies and destroy session
    if( array_key_exists('HTTP_COOKIE', $_SERVER)){
      $cookies = explode(';', $_SERVER['HTTP_COOKIE']);
      foreach($cookies as $cookie) {
          $parts = explode('=', $cookie);
          $name = trim($parts[0]);
          setcookie($name, '', time()-1000);
          setcookie($name, '', time()-1000, '/');
      }
    }
    session_destroy();

    return http_response_code(401);
}

The session should be started when session.php is required. It appears that this is not happening though. Upon accessing the application, the login page is displayed, but isUserAuthorized.php is throwing a warning:

Warning: session_start(): Failed to read session data: user (path: /var/lib/php/mod_php/session) in session.php

When I select the Login button, login.php is called, but the user gets brought right into the application, despite incorrect credentials.

login.php

<?php

require_once '../database.php';
require_once 'session.php';
require_once 'ldap.php';

$_SESSION['authenticated'] = false;

//$conn = connect_db();

try {

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

    $username = strtolower($data->username);
    $password = $data->password;

    // Check domain credentials; return user token if verified
    if(ldap_authenticate($username, $password)) {
        $_SESSION['authenticated'] = true;
    }
    else {
        echo('Invalid username and/or password!');
        return http_response_code(400);
    }
}

catch(PDOException $e) {
    return http_response_code(400);
}

I’m not entirely sure what’s causing this odd behavior, and why the session isn’t being created. Do I need to explicitly call the sess_write function?


Update

I discovered that removing the require_once 'session.php' from login.php causes the proper behavior. The user is able to login when they provide valid credentials. However, the session data is still never being written to the database. Any idea why?

How to&Answers:

The issues came down to my session handler. As of PHP 7, the sess_read function must return a string. This was causing the warning:

Warning: session_start(): Failed to read session data: user (path: /var/lib/php/mod_php/session) in session.php

I fixed this by returning '' when $data was null.

This caused issues with my sess_write function knowing when to insert and when to update. I fixed this by changing the SQL.

Ultimately I ended up making the session handler a class, as shown in the final result:

<?php

require_once ('../database.php');

class CustomSessionHandler implements SessionHandlerInterface{
    public function open( $path, $name ) {
        return true;
    }

    public function close( ) {
        return true;
    }

    public function read( $id ) {
        $db = dbConn::getConnection();

        $stmt = "SELECT session_data FROM session where session_id =" . $db->quote($id);
        $result = $db->query($stmt);
        $data = $result->fetchColumn();
        $result->closeCursor();
        if(!$data){
                return '';
        }
        return $data;
    }

    public function write( $id, $data ) {
        $db = dbConn::getConnection();

        //Works with Postgres >= 9.5
        //$stmt = "INSERT INTO session (session_id, session_data) VALUES (" . $db->quote($id) . ", " . $db->quote($data) . ") ON CONFLICT (session_id) DO UPDATE SET session_data=" . $db->quote($data) . ";";

        //Works with Postgres < 9.5
        $stmt = "UPDATE session SET session_data=" . $db->quote($data) . " WHERE session_id=" . $db->quote($id) . ";";
        $db->query($stmt);

        $stmt = "INSERT INTO session (session_id, session_data) SELECT ". $db->quote($id) . ", ". $db->quote($data) . " WHERE NOT EXISTS (SELECT 1 FROM session WHERE session_id=" . $db->quote($id) . ");";
        $db->query($stmt);

        return true;
    }

    public function destroy( $id ) {
        $db = dbConn::getConnection();

        $stmt = "DELETE FROM session WHERE session_id =" . $db->quote($id);
        setcookie(session_name(), "", time() - 3600);
        $data = $db->query($stmt);

        return true;
    }

    public function gc( $lifetime ) {
        $db = dbConn::getConnection();

        $stmt = "DELETE FROM session WHERE timestamp < NOW() - INTERVAL '" . $lifetime . " second'";
        $data = $db->query($stmt);
        return true;
    }
}

session_name('PROJECT_CUPSAW_WEB_APP');
$handler = new CustomSessionHandler();
session_set_save_handler($handler, false);
session_start();
ob_flush();