简体   繁体   中英

advanced user authentication using sessions

I am setting up a website that has 3 types of users:

-those who are registered and are subscribed for current month
-those that are registered, but are not subscribed for current month
-users that are not registered (you cant be subscribed if you are not regitered)

I've created code that identifies these 3 kinds of users and acts appropriately. My question is, is this the way to go? I've never done similar thing before. Or should I reprogram my approach?

//login.php

//connect to database and see if a user and password combination exists. Store $exists=0 if not, and $exists=1 if it exists.
session_start();

$conn = new mysqli($hn,$un,$pw,$db);
if ($conn->connect_error){
    die($conn->connect_error);
}
$query = "SELECT COUNT(1) as 'exists',expiration_date FROM table WHERE email = ? AND password = ?;";
$stmt = $conn->prepare($query);
$stmt->bind_param("ss", $email, $password);

$email = $_POST["email"];
$password = hash("hashingalgorithm", "salt".$_POST["password"]."salthere");

$stmt->execute();
   /* Get the result */
$result = $stmt->get_result();
$num_of_rows = $result->num_rows;
$row = $result->fetch_assoc();
$exists = $row["exists"];
$expiration_date = $row["expiration_date"];

/* free results */
$stmt->free_result();

/* close statement */
$stmt->close();
$conn->close();

date_default_timezone_set('Europe/Berlin');


if ($exists==0){
    echo "Wrong email or password";
    $_SESSION['loginerror'] = 2;
    header('Location: https://www.homepage.com/login'); 
}else if ($exists){
    if (strtotime($expiration_date) < (strtotime("now"))){//logged in, but not subscribed
        session_destroy();
        session_start();
        $_SESSION["authenticated"] = true;
        header('Location: https://www.homepage.com');
    }else{//logged in and ready to go
        $_SESSION["authenticated"] = true;
        $_SESSION["email"] = $email;
        header('Location: https://www.homepage.com');
    }
}else{
    echo "An error with has occured.";
} 

Then on every single page on my website I use this code, to see what kind of user has visited me

session_start();
if(isset($_SESSION["authenticated"]) && isset($_SESSION["email"])){ 
    $email = $_SESSION["email"];

    //connect to database and fetch expiration_date for a user with $email. Store it in $expiration_date

$conn = new mysqli($hn,$un,$pw,$db);
    if ($conn->connect_error){
        die($conn->connect_error);
    }
    $query = "SELECT expiration_date FROM table WHERE email = ?;";
    $stmt = $conn->prepare($query);
    $stmt->bind_param("s", $email);

    $email = $_SESSION["email"];
    $stmt->execute();
    /* Get the result */
    $result = $stmt->get_result();
    $num_of_rows = $result->num_rows;
    $row = $result->fetch_assoc();
    $expiration_date = $row["expiration_date"];
    /* free results */
    $stmt->free_result();
    /* close statement */
    $stmt->close();
    $conn->close();
    date_default_timezone_set('Europe/Berlin');

    if (strtotime($expiration_date) < (strtotime("now"))){//logged in, but not subscribed
        session_destroy();
        session_start();
        $_SESSION["authenticated"] = true;
        header('Location: https://www.homepage.com');
    }else{  //html for subsribed and registered user
    echo <<<_END
    //html here
    _END;
    }
}else if(isset($_SESSION["authenticated"]) && !isset($_SESSION["email"])){
        // user is logged in, but not subscribed;
        echo <<<_END
        //htmlhere
        _END;
}else{// user is not registered nor is subscribed
        echo <<<_END
        //htmlhere
        _END;
}

The code works, but I am worried about accessing database on every single page once the user registers and is subscribed. I am in effect penalizing users for registering and subscribing. Is there a better, performance wise, way to handle this kind of problem?

The solution here is to check for subscribed users (only on the first page where they login to your website) ie inside login.php you could use,

// ...your previous code
session_start();
// initialize session variables
$_SESSION['subscribe_date'] = $_SESSION['authenticated'] = false;
if ($exists == 0){
    echo "Wrong email or password";
    $_SESSION['loginerror'] = 2;
    header('Location: https://www.homepage.com/login'); 
} else if ($exists){
    $_SESSION["authenticated"] = true;
    if (strtotime($expiration_date) > (strtotime("now"))){ //logged in,& subscribed
        $_SESSION["email"] = $email;
        $_SESSION['subscribe_date'] = $expiration_date;
    } else {  //logged in and not subscribed, do nothin!
    }
    header('Location: https://www.homepage.com');
} else {
    echo "An error has occured.";
} 

Then, on every other page, all you need to do is only check for $_SESSION['subscribe_date'] instead of firing a query each time

if(!empty($_SESSION["subscribe_date"]) && strtotime($_SESSION["subscribe_date"]) > (strtotime("now"))) {
 // html for subscribed and registered user
} else if (!empty($_SESSION["authenticated"])) {
 // html for registered user
} else {
 // html for unregistered user
}

Also, note that I have removed session_destroy(); A very bad idea to call it on every page. You can unset session variables instead if needed. ie unset($_SESSION["email"]); You should call session_destroy(); only at the end when the user logs out. Check out the warning-section for session_destroy()

If it was me, I would separate and refactor these codes into external classes/functions. But if we are talking about minor tweeks to your code, here is what would I do:

login.php

//connect to database and see if a user and password combination exists. Store $exists=0 if not, and $exists=1 if it exists.
session_start();

$conn = new mysqli($hn,$un,$pw,$db);
if ($conn->connect_error){
    die($conn->connect_error);
}
$query = "SELECT COUNT(1) as 'exists',expiration_date FROM table WHERE email = ? AND password = ?;";
$stmt = $conn->prepare($query);
$stmt->bind_param("ss", $email, $password);

$email = $_POST["email"];
$password = hash("hashingalgorithm", "salt".$_POST["password"]."salthere");

$stmt->execute();
/* Get the result */
$result = $stmt->get_result();
$num_of_rows = $result->num_rows;
$row = $result->fetch_assoc();
$exists = $row["exists"];
$expiration_date = $row["expiration_date"];

/* free results */
$stmt->free_result();

/* close statement */
$stmt->close();
$conn->close();

date_default_timezone_set('Europe/Berlin');


if ($exists==0){
    //removed echo, you shouldn't output anything before headers
    $_SESSION['loginerror'] = 2;
    header('Location: https://www.homepage.com/login');
    //use exit after each header redirect
    exit;
}else if ($exists){
    //moved to the top to minimize code redundancy
    $_SESSION["authenticated"] = true;
    //it's good to have user identifier (e.g. email) accessible for all authenticated users
    $_SESSION["email"] = $email;
    if (strtotime($expiration_date) <= (strtotime("now"))){//logged in, but not subscribed
        //unset only subscribed sessions, do not destroy all sessions
        unset($_SESSION["subscribed"]);
        header('Location: https://www.homepage.com');
        exit;
    }else{
        //logged in with active subscribtion
        $_SESSION["subscribed"] = true;
        header('Location: https://www.homepage.com');
        exit;
    }
}else{
    echo "An error with has occured.";
} 

included code on every page

session_start();
if(isset($_SESSION["authenticated"])){
    //merged if-statement for both subscribed and unsubscribed user
    $email = $_SESSION["email"];

    //connect to database and fetch expiration_date for a user with $email. Store it in $expiration_date

    $conn = new mysqli($hn,$un,$pw,$db);
    if ($conn->connect_error){
        die($conn->connect_error);
    }
    //check if subscribed user is still subscribed and unsubscribed user is still unsubscribed
    $query = "SELECT expiration_date FROM table WHERE email = ?;";
    $stmt = $conn->prepare($query);
    $stmt->bind_param("s", $email);

    $email = $_SESSION["email"];
    $stmt->execute();
    /* Get the result */
    $result = $stmt->get_result();
    $num_of_rows = $result->num_rows;
    $row = $result->fetch_assoc();
    $expiration_date = $row["expiration_date"];
    /* free results */
    $stmt->free_result();
    /* close statement */
    $stmt->close();
    $conn->close();
    date_default_timezone_set('Europe/Berlin');

    //I have separated expiration verification and template echoing
    //first - expiration verification
    if (strtotime($expiration_date) <= (strtotime("now")) && isset($_SESSION["subscribed"])){
        //had been subscribed a second ago, but now he's not
        unset($_SESSION["subscribed"]);
        header('Location: https://www.homepage.com');
        exit;
    }else if (strtotime($expiration_date) > (strtotime("now")) && !isset($_SESSION["subscribed"])){
        //had been unsubscribed, but renewed subscription in the meantime (maybe in another browser with this session still active)
        $_SESSION["subscribed"] = true;
        header('Location: https://www.homepage.com');
        exit;
    }

    //user successfully passed expiraton verification, maybe was few times redirected
    //second - template echoing
    if (isset($_SESSION["subscribed"])) {
        // user is logged in and subscribed;
        echo '';
    }else{
        // user is logged in, but not subscribed;
        echo '';
    }
}else{
    // user is not registered
    echo '';
}

There are comments before each change in your code. Here is short list of changes I've made:

  • use exit; after each header redirect: php - Should I call exit() after calling Location: header?
  • do not echo output before headers
  • use 3 session verification variables for every authenticated user - authenticated, subscribed and email
  • do not destroy whole session if user is not subscribed, use unset instead
  • I have merged both authenticated users (subscribed and unsubscribed) into one if statement
  • I have separated expiration verification and output echoing in two subsequent steps
  • on every page I am checking if subscribed user is still subscribed (subscription can expire during his session) and if unsubscribed did not become subscribed in meantime (he can use two browsers with active sessions)

There are some other minor tweaks I would do (move date_default_timezone_set on top of every file etc.), but they are not topic of your question.

In my understanding, You already have a working code. And what you are asking is opinion. You want to remove duplication in each page of checking into database for authentication and subscription.

In my opinion, you need to change how you use sessions ,

 $_session['email']        // email address of user 
 $_session['auth_type']    // holds authentication type
 $_session['auth_till']    // subscription expire date

Then lets create function to check subscription. This function can be put into a separate file for example: init.php. Here we can put session start mechanism so that in any case sessions will be available.

if(!isset($_SESSION)) 
    session_start();       // start session if not already started

// lets define global vars to hold authentication type of visitor
define("SUBSCRIBED",1); 
define("UN_SUBSCRIBED",2);
define("REGISTERED",3);
function checkSubscription():bool{
    $return = false;
    if(($_session['auth_type']==SUBSCRIBED)&&(strtotime($_session['auth_till']) < strtotime("now")))
        $return= true;
    return $return
}

And on login.php use same technique, while setting up sessions authentication type.

Now any other page can simply use function to check subscription

for example:

<?php
// file: product.php
include_once("init.php");
if(!checkSubscription()){
    // subscription finished. do what you need to do. otherwise continue.
}

There are many improvements that can be done to your codes. But i think this would meet your needs. Please let me know if you need any further assistant. Please visit Scape and let me know if any useful coding available there.

Use a PHP session variable to store information about the current user before making SQL queries on every page load.

For example, when the user logs in, save the user id to that users session in a session variable:

$_SESSION['user_id'] = <userid>;

On the next page load, check for that session variable before running the database query.

if ($_SESSION['user_id']) {
    // normal operations
} else {
    // Make your database call
}

Generally speaking, when a user logs in for the first time, set two state flags in $_SESSION :

 $_SESSION['loggedIn']   = true
 $_SESSION['subscribed'] = bool //true or false

I assume that only registered users can login. If a user does something to change his or her state, update $_SESSION accordingly.

Take note, be sure to check that a session is active before checking values. Also, use session_regenerate_id to deter session fixation.

Truly sophisticated types might try serializing a User object and storing it in $_SESSION . Then, on each page load, the User object's properties can be marshaled (unserialized) and made to come alive once more in a new instance of a User object. In that world, you would just check the properties of the User object to see if he or she is (a) logged in and (b) subscribed. The state of the User object would be your concern, not just isolated values in the $_SESSION superglobal.

PHP Manual: Object Serialization

Book: The Object Oriented Thought Process (4th Edition) : See Chapter 12

on login.php you already getting expiration_date from the DB, you could store it in session variable

$_SESSION['expiration_date'] = $row['expiration_date'];

and on every page you could use the above value to check instead of querying the DB

if (isset($_SESSION['expiration_date']) && (strtotime($_SESSION['expiration_date']) < (strtotime("now") ))

PHP session model allows to start the session and fetch later all data, concerning to the session that was serialized and stored in $_SESSION array.

It is right to worry about database calls that cause each page access. The session data can be set with cookie or given back inside POST or GET method. So, you need to define suitable method of PHP session id retreival and session lifetime.

$_SESSION['name'] = 'value'; // Stores Session values to avoid database requests
...
session_id($sid); // sets the id of existing session
session_start(); // retreive stored session data;

You can set once such flags as registered and subscribed for beinh used later with no database requests during session lifetime or special actions:

  • Subscribed - no actions expected. User already satisfied all expectations.
  • Registered - can only subscribe. So, before subscribe action met, user environment can keep database idle until subscribe request.
  • Not Registered - Can only register. No stored data. No database requests.

When session starts, both subscribed and registered flags will be set to FALSE until authentication passed. So, my opinion - database must be called only three times per session: when user authenticates, registers or subscribes. As code shows, you transfer data via POST . Add sid hidden field to all forms to keep session id. And SELECT from database all needed data (not password, of course) that can be useful during the session to interact with user.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM