0

I want to build a login system in PHP. There are 5 files:

  1. db.php contains database utilities.
  2. user.php for login and logout functions.
  3. login.php is login page.
  4. index.php is the main page.
  5. logout.php is a page that logs users out and redirects to login.php.

The login part seems to work as expected (by checking the database, omitted here), but the redirection from login.php to index.php doesn't seem to work. The same login page appears again.

But when I remove this part in index.php:

// Must be logged in first
if (!isset($_SESSION['username'])) {
    gotoPage('login.php');
}

it redirects successfully, and then the logout fails.

Also, if a user logs out and clicks the undo button in the browser, it takes him to the main page (index.php) which he is not supposed to see after logging out.

I don't know what exactly is preventing the redirection and the logout. Any help (or advice) is appreciated.

Note: I've looked at similar questions on SO, none of the answers provided solved the issue.


db.php content:

<?php
function getDBInstance() : PDO
{
    static $dbInstance;
    if (!$dbInstance) {
        return new PDO(
            'mysql:host=localhost;dbname=DummyUserAccounts;charset=UTF8',
            'dummyuser',
            '...',
            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
        );
    }
    return $dbInstance;
}

function register_user(string $firstname, string $lastname, string $username, string $password): bool
{
    // PROC_USER_ADD inserts one user into the User table.
    $query = 'CALL PROC_USER_ADD(:firstname, :lastname, :username, :password)';

    $statement = getDBInstance()->prepare($query);

    $statement->bindValue(':firstname', $firstname, PDO::PARAM_STR);
    $statement->bindValue(':lastname', $lastname, PDO::PARAM_STR);
    $statement->bindValue(':username', $username, PDO::PARAM_STR);
    $statement->bindValue(':password', password_hash($password, PASSWORD_BCRYPT), PDO::PARAM_STR);

    return $statement->execute();
}
?>

user.php content:

<?php
require 'db.php';

function sanitize($field)
{
    $field = trim($field);
    $field = stripslashes($field);
    $field = htmlspecialchars($field);
    return $field;
}

function gotoPage(string $page) : void
{
    header('Location: ' . $page);
    exit;
}

function loginUser(string $username, string $password) : bool
{    
    // Search for the user in the database
    $queryString = 'SELECT username, password FROM user WHERE username = :username';
    $statement = getDBInstance()->prepare($queryString);
    $statement->bindValue(':username', $username, PDO::PARAM_STR);
    $statement->execute();
    $user = $statement->fetch(PDO::FETCH_ASSOC);

    // Successful login?
    if ($user && password_verify($password, $user['password'])) {
        // Create a new Session ID
        session_start();

        // Write session data
        $_SESSION['username'] = $username;

        return true;
    }

    return false;
}

function logoutCurrentUser() : void
{    
    if (isset($_SESSION['username'])) {
        unset($_SESSION['username']);
        session_destroy();
        gotoPage('login.php');
    }
}
?>

login.php content:

<?php
require 'user.php';

// Can't login twice.
if (isset($_SESSION['username'])) {
    gotoPage('index.php');
}

$errorMessage = "";

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    // Check for empty fields
    if (empty($_POST['username']) || empty($_POST['password'])) {
        $errorMessage = "Both Username and Password are required!";
    } else {
        // Sanitize fields
        $username = sanitize($_POST['username']);
        $password = sanitize($_POST['password']);

        // Login user
        if (!loginUser($username, $password)) {
            $errorMessage = "Invalid username and/or password.";
        } else {
            gotoPage('index.php');
        }
    }
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
</head>

<body>
    <form action="login.php" method="post">
        <div>
            <label>Username</label>
            <input type="text" name="username">
        </div>
        <div>
            <label>Password</label>
            <input type="password" name="password">
        </div>
        <div>
            <button type="submit">Login</button>
        </div>
        <div>
            <span style="color:red"><?php echo $errorMessage ?></span>
        </div>
    </form>
</body>
</html>

index.php content:

<?php
require 'user.php';

// Must be logged in first
if (!isset($_SESSION['username'])) {
    gotoPage('login.php');
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>Main Page</title>
</head>

<body>
    <nav>
        <a href="logout.php">Logout</a>
    </nav>

    <!--Main page goes here-->
</body>
</html>

logout.php content:

<?php
require 'user.php';

logoutCurrentUser();
gotoPage('login.php');
?>
Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
Zakk
  • 1,935
  • 1
  • 6
  • 17
  • 1
    `_SESSION` is a typo, it should be `$_SESSION` – aynber Mar 30 '22 at 19:20
  • @aynber Yes. Fixed it. Thanks. But the problem is still here. – Zakk Mar 30 '22 at 19:25
  • The sanitize() function is most likely a garbage that maims your data. or at least it's misplaced. there must be no data sanitization before storing in the database, only before use, specific to the usage context. – Your Common Sense Mar 31 '22 at 04:14
  • @YourCommonSense _"a garbage that maims your data"_: Can you explain why it is so? – Zakk Mar 31 '22 at 08:09
  • @Zakk sure, I can. garbage because it consists of some random cargo cult code. maims because it does so. why you're asking? – Your Common Sense Mar 31 '22 at 08:41
  • @YourCommonSense I am asking to learn. And use my common sense to tell if it makes sense XD. – Zakk Mar 31 '22 at 08:44
  • @YourCommonSense _"cargo cult code"_... I didn't get that clearly. – Zakk Mar 31 '22 at 08:45
  • @YourCommonSense _"there must be no data sanitization before storing in the database, only before use, specific to the usage context"_ I came across [this anwer](https://stackoverflow.com/a/18573730/16835308) which happens to be yours. You state that there is some big misconceptions about data sanitization. I wish I could learn more about the matter. – Zakk Mar 31 '22 at 08:48
  • https://en.wikipedia.org/wiki/Cargo_cult_programming – Your Common Sense Mar 31 '22 at 09:09
  • You can use your common sense to provide the actual contents of this actual function if you want an explanation on what it does. We should probably move elsewhere with this matter though – Your Common Sense Mar 31 '22 at 09:10
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/243477/discussion-between-zakk-and-your-common-sense). – Zakk Mar 31 '22 at 09:42

1 Answers1

3

You will need session_start() on every page that you want to access $_SESSION variables. login.php does not have session_start(), so isset($_SESSION) will always return false.

Zakk
  • 1,935
  • 1
  • 6
  • 17
jones_admin
  • 46
  • 1
  • 4
  • `user.php` is meant to be included in other php files, it has functions only. – Zakk Mar 30 '22 at 19:35
  • Ok, edited answer to include only login.php – jones_admin Mar 30 '22 at 19:39
  • I have updated the code. the `session_start()` in `index.php` is removed. – Zakk Mar 30 '22 at 19:39
  • Following the logic of the code, `session_start()` is called only when the user was successfully logged in. Just after that, `$_SESSION['username']` is defined. **BUT**, when redirecting to `index.php`, that same `$_SESSION['username']` seems not to be set. I couldn't find out why. – Zakk Mar 30 '22 at 19:43
  • session_start() is required in index.php and login.php. This is what gives access to $_SESSION variables. – jones_admin Mar 30 '22 at 19:44
  • session_start is not the function which is for login. You have to call session_start() at top of the every page then you can access $_SESSION varaible. Otherwise it will not work. https://www.php.net/manual/en/function.session-start.php – Hakan SONMEZ Mar 30 '22 at 19:45
  • But this way, I would have three sessions: one in the start of `index.php`, one in `login.php` and the one I called when logging in... I am wrong? – Zakk Mar 30 '22 at 19:46
  • no session_start does not create session it reads sessions and fills the variable. just put it and you will see what it does. – Hakan SONMEZ Mar 30 '22 at 19:47
  • @HakanSONMEZ Therefore, the `session_start()` in `loginUser()` is useless? – Zakk Mar 30 '22 at 19:49
  • session_start() sets a cookie and will use a session that has previously created. Every time session_start() is called, it will check to see if there is already a session cookie. If so, it will use that session. If not, it will create a new session and set a cookie. – jones_admin Mar 30 '22 at 19:49
  • @jones_admin So, what determines that a user is logged in is the presence of his/her name in the `$_SESSION` variable ?? – Zakk Mar 30 '22 at 19:53
  • 1
    every request needs their own session. login.php runs when user login but when you redirect him index.php it is not the same request it is completely different and you did not call session_start() so the variable is empty. session_start is not what you think. – Hakan SONMEZ Mar 30 '22 at 19:53
  • @Zakk you have to understand your user session is not the same thing with php session_start it does not start a user session – Hakan SONMEZ Mar 30 '22 at 19:54
  • @HakanSONMEZ What determines that a user is logged in then? – Zakk Mar 30 '22 at 19:55
  • Yes. When you check isset($_SESSION['username']), you need the $_SESSION variables to be available. If you don't have session_start() then $_SESSION will have nothing in it. – jones_admin Mar 30 '22 at 19:56
  • Now I understand. Probably that's the reason why the logout fails. – Zakk Mar 30 '22 at 19:58
  • @Zakk you determine the use logged in or not but you have to call session_start() function to use $_SESSION variable. that's all. At the end you should accept jones_admin's answer and upvote it. – Hakan SONMEZ Mar 30 '22 at 19:59
  • @HakanSONMEZ Yes I will. Upvoted and accepted. Thank you very much. – Zakk Mar 30 '22 at 20:00