Sessions in PHP. Pitfalls of using sessions in PHP By user inaction in PHP


.
In this article we will add to our registration verification of e-mail address, automatic login and recovery of forgotten password. Before starting the lesson, make sure that the mail() function is running on your server.

Let's start by adding fields to the users table. We need a field to store the e-mail address, a field for the user status (0 - inactive, 1 - activated) and a field with the registration date.


Your E-mail *:



Then you need to edit save_user.php by adding a check for the correctness of the e-mail address and sending a letter to confirm registration. The letter contains a link with two variables passed by the get method: login and a generated code, unique for each user. We need the code so that the user cannot activate his account without a letter, and this will give us confidence that the entered email address really belongs to him. Let's add the following code after extracting the sent data from the global variables:

If (isset($_POST["email"])) ( $email = $_POST["email"]; if ($email == "") ( unset($email);) )
if (empty($login) or empty($password)or empty($code) or empty($email))
//add a variable with e-mail address
//if the user has not entered a login or password, then we issue an error and stop the script
{
exit("You have not entered all the information, go back and fill out all the fields!"); //stop script execution
}
if (!preg_match("/+@+\.(2,3)/i", $email)) //checking the e-mail address using regular expressions for correctness
(exit("Invalid e-mail entered!");)

In the request to add a new user, you must also add the date of registration. If the data has been successfully inserted into the database and the user is registered, then you need to send a message to the entered address:

// if this is not the case, then save the data
$result2 = mysql_query("INSERT INTO users (login,password,avatar,email,date) VALUES("$login","$password","$avatar","$email",NOW())");
// Check if there are errors
if ($result2=="TRUE")
{
$result3 = mysql_query ("SELECT id FROM users WHERE login="$login"",$db);//retrieving the user id. Thanks to him, we will have a unique activation code, because there cannot be two identical identifiers.
$myrow3 = mysql_fetch_array($result3);
$activation = md5($myrow3["id"]).md5($login);// account activation code . Let's encrypt the identifier and login using the md5 function. The user is unlikely to be able to select this combination manually through the address bar.
$subject = "Registration confirmation";//message subject
$message = "Hello! Thank you for registering on citename.ru\nYour login: ".$login."\n
Follow the link to activate your account:\nhttp://localhost/test3/activation.php?login=".$login."&code=".$activation."\nRegards,\n
Administration citename.ru";//message content
mail($email, $subject, $message, "Content-type:text/plane; Charset=windows-1251\r\n");//send a message

Echo "A letter with a link has been sent to you by email to confirm your registration. Attention! The link is valid for 1 hour. Home page"; //talk about a letter sent to the user
}

Message sent! Now the user will open it and follow the specified link to a page that will check the activation code. Having made sure that the code is correct, we confirm the registration by changing the value of the activation field in the database from “0” to “1”.

Create the activation.php file

The email address has been confirmed, now we know that this address belongs to this user, it will be possible to send a password to it if the user forgets it, or other notifications. But what is the difference between activated users and non-activated ones? Both of them can access the site, therefore, we need to restrict access to those who are not activated. Let's open the testreg file. php and add one more condition to the database request:

$result = mysql_query("SELECT * FROM users WHERE login="$login" AND password="$password" AND activation="1"",$db); //retrieve from the database all data about the user with the entered login
//we added "AND activation ="1"", that is, the user will be searched only among the activated ones. It is advisable to add this condition to other similar checks of user data

If the user forgets the password, then in this case you need to display a link on the main page through which he can recover it, and you can also immediately set a checkbox for automatic login.

Automatic login.






Register


Forgot your password?

There is a link, but no file. Let's write send _ pass . php. In it we will ask the user for their login and email address. If the entered e-mail and login are in the database, we will send a new password to it, if the user has forgotten the old one, because we made sure during registration that the e-mail address is valid.

Next we will make an automatic login. It will work like this: if you successfully log in with the checkbox pressed, auto = “yes” will be entered in the cookie. If the server sees that auto = “yes” in the browser, it will launch a session and take the variables there, in cookies. Then there is a check of running sessions in the database.

Let's open testreg . php and add the following code after successful login:

If (isset($_POST["save"]))(
//If the user wants his data to be saved for subsequent login, then we save it in his browser cookies

setcookie("id", $myrow["id"], time()+9999999);)
if (isset($_POST["autovhod"]))(
//If the user wants to log in to the site automatically
setcookie("auto", "yes", time()+9999999);
setcookie("login", $_POST["login"], time()+9999999);
setcookie("password", $_POST["password"], time()+9999999);
setcookie("id", $myrow["id"], time()+9999999);)

Now you need to start a session in the right place if automatic login is enabled. Let's open index.php and write at the very beginning of the page:

First we call the session_start() function that I described above. Then we check whether the variable "name" exists in the session. If it exists, then we read data from it and write it to the name variable. If it does not exist (that is, the user came for the first time), then set the “name” variable in the session to the value “15St.”. The next line displays the value of the $name variable. Obviously, the first time you run it, you will see an empty line, but the second time you see the line “15St.” read from the session.

I advise you to now enter in the address bar: "javascript:document.cookie" (enter in the same tab as you ran the script). As a result, you will see something like this: "PHPSESSID=". The PHPSESSID value is precisely the unique identifier.

And to make everything completely clear, I advise you to even find the session file. If you use Denwer, then it is located in the "tmp" folder. Look at the files that start with "sess_" - these are the same session files. You can open them in a simple notepad.

Another very important property is the temporary nature of sessions in PHP. That is, if cookies are stored until they are deleted by the browser. And the browser never deletes them by default. The sessions are stored for the time specified in the PHP settings. By default, this is 15 minutes. That is, if you use session-based authentication, then after 15 minutes of user inactivity, he will have to log in again. Of course, this is good, because if the user forgets to “Log out”, then nothing bad will happen. An attacker will not be able to use the user account. Moreover, when using cookies, they can be stolen, substituted in your browser, and as a result, the attacker is authorized using someone else’s data, without even knowing the password. But it will not be possible to steal a session, since all parameters are stored on the server, and there is no way to find out about them.

Therefore, try to primarily use sessions rather than pure cookies in your practice.

And finally, I would like to warn you about a very common mistake. Never output data to browsers before using the session_start() function, otherwise it will throw an error. That is, you cannot write like this:

An error will occur when running this script. The same rule applied to cookies (setcookie() function). So, I think everything is clear here.

As for sessions in PHP, then, of course, they can be used to store statistics, authentication, personal user settings and other similar things.

Greetings, dear community.

First of all, I want to thank you for a very useful resource. More than once I have found many interesting ideas and practical advice here.

The purpose of this article is to highlight the pitfalls of using sessions in PHP. Of course, there is PHP documentation and plenty of examples, and this article is not intended to be a complete guide. It is designed to reveal some of the nuances of working with sessions and protect developers from unnecessary waste of time.

The most common example of using sessions is, of course, user authorization. Let's start with the most basic implementation in order to gradually develop it as new tasks arise.

(In order to save space and time, we will limit our examples to only the session functions themselves, instead of building here a full-fledged test application with a beautiful class hierarchy, comprehensive error handling and other good stuff).

Function startSession() ( // If the session has already been started, stop executing and return TRUE // (the session.auto_start parameter in the php.ini settings file must be disabled - the default value) if (session_id()) return true; else return session_start(); // Note: Prior to version 5.3.0, the session_start() function returned TRUE even if an error occurred. // If you are using a version prior to 5.3.0, perform an additional check for session_id() // after calling session_start() ) function destroySession() ( if (session_id()) ( // If there is an active session, delete the session cookies, setcookie(session_name(), session_id(), time()-60*60*24); // and destroy the session session_unset( ); session_destroy(); ) )

Note: It is assumed that the reader has basic knowledge of PHP sessions, so we will not cover the principle of operation of the session_start() and session_destroy() functions here. The tasks of layout of the login form and user authentication are not related to the topic of the article, so we will also omit them. Let me just remind you that to identify the user in each subsequent request, at the moment of successful login, we need to store the user identifier in a session variable (named userid, for example), which will be available in all subsequent requests within the life of the session. It is also necessary to implement processing the result of our startSession() function. If the function returns FALSE, display the login form in the browser. If the function returned TRUE, and a session variable containing the identifier of the authorized user (in our case - userid), exists - display the page of the authorized user (for more information about error handling, see the addition dated 2013-06-07 in the section on session variables).

So far everything is clear. Questions begin when you need to implement user inactivity control (session timeout), enable multiple users to work simultaneously in one browser, and also protect sessions from unauthorized use. This will be discussed below.

Controlling user inactivity using built-in PHP tools The first question that often arises among developers of various consoles for users is automatic termination of the session in the event of inactivity on the part of the user. There is nothing easier than to do this using the built-in capabilities of PHP. (This option is not particularly reliable or flexible, but we will consider it for completeness).

Function startSession() ( // User inactivity timeout (in seconds) $sessionLifetime = 300; if (session_id()) return true; // Set the cookie lifetime ini_set("session.cookie_lifetime", $sessionLifetime); // If user inactivity timeout is set, set the session lifetime on the server // Note: For a production server, it is recommended to preset these parameters in the php.ini file if ($sessionLifetime) ini_set("session.gc_maxlifetime", $sessionLifetime); if (session_start( )) ( setcookie(session_name(), session_id(), time()+$sessionLifetime); return true; ) else return false; )

A few clarifications. As you know, PHP determines which session needs to be launched by the cookie name sent by the browser in the request header. The browser, in turn, receives this cookie from the server, where the session_start() function places it. If the browser cookie has expired, it will not be sent in the request, which means PHP will not be able to determine which session to start and will treat this as creating a new session. The PHP settings parameter session.gc_maxlifetime, which is set equal to our user inactivity timeout, sets the lifetime of a PHP session and is controlled by the server. Controlling the session lifetime works as follows (here we consider an example of storing sessions in temporary files as the most common and default option in PHP).

When a new session is created, a file named sess_ is created in the directory set as the directory for storing sessions in the PHP settings parameter session.save_path, where is the session identifier. Next, in each request, at the time of launching an already existing session, PHP updates the modification time of this file. Thus, in each subsequent request, PHP, by the difference between the current time and the time of the last modification of the session file, can determine whether the session is active or its lifetime has already expired. (The mechanism for deleting old session files is discussed in more detail in the next section.)

Note: It should be noted here that the session.gc_maxlifetime parameter applies to all sessions within one server (more precisely, within one main PHP process). In practice, this means that if several sites are running on the server, and each of them has its own user inactivity timeout, then setting this parameter on one of the sites will lead to its setting for other sites. The same applies to shared hosting. To avoid this situation, separate session directories are used for each site within the same server. Setting the path to the sessions directory is done using the session.save_path parameter in the php.ini settings file, or by calling the ini_set() function. After this, the sessions of each site will be stored in separate directories, and the session.gc_maxlifetime parameter set on one of the sites will only be valid for its session. We will not consider this case in detail, especially since we have a more flexible option for monitoring user inactivity.

Controlling user inactivity using session variables It would seem that the previous option, for all its simplicity (just a couple of additional lines of code), gives everything we need. But what if not every request can be regarded as the result of user activity? For example, a page has a timer that periodically makes an AJAX request to receive updates from the server. Such a request cannot be regarded as user activity, which means that automatically extending the session lifetime is not correct in this case. But we know that PHP updates the modification time of the session file automatically every time the session_start() function is called, which means that any request will lead to an extension of the session lifetime, and the user inactivity timeout will never occur. In addition, the last note from the previous section about the intricacies of the session.gc_maxlifetime parameter may seem too confusing and difficult to implement for some.

To solve this problem, we will abandon the use of built-in PHP mechanisms and introduce several new session variables that will allow us to control the time of user inactivity ourselves.

Function startSession($isUserActivity=true) ( ​​$sessionLifetime = 300; if (session_id()) return true; // Set the cookie lifetime before closing the browser (we will control everything on the server side) ini_set("session.cookie_lifetime", 0) ; if (! session_start()) return false; $t = time(); if ($sessionLifetime) ( // If the user inactivity timeout is set, // check the time elapsed since the last user activity // (last request time when the lastactivity session variable was updated) if (isset($_SESSION["lastactivity"]) && $t-$_SESSION["lastactivity"] >= $sessionLifetime) ( // If the time elapsed since the user's last activity, / / is greater than the inactivity timeout, which means the session has expired and you need to terminate the session destroySession(); return false; ) else ( // If the timeout has not yet occurred, // and if the request came as a result of user activity, // update the lastactivity variable with the value of the current one time, // thereby extending the session time by another sessionLifetime seconds if ($isUserActivity) $_SESSION["lastactivity"] = $t; ) ) return true; )

Let's summarize. In each request, we check whether the timeout has been reached since the last user activity until the current moment, and if it has been reached, we destroy the session and interrupt the execution of the function, returning FALSE. If the timeout has not been reached, and the $isUserActivity parameter with the value TRUE is passed to the function, we update the time of the user’s last activity. All we have to do is determine in the calling script whether the request is the result of user activity, and if not, call the startSession function with the $isUserActivity parameter set to FALSE.

Addition from 2013-06-07 Processing the result of the sessionStart() function

The comments pointed out that returning FALSE does not provide a complete understanding of the cause of the error, and this is absolutely fair. I did not publish detailed error handling here (the length of the article is already quite large), since this is not directly related to the topic of the article. But given the comments, I’ll clarify.

As you can see, the sessionStart function can return FALSE in two cases. Either the session could not be started due to some internal server errors (for example, incorrect session settings in php.ini), or the session lifetime has expired. In the first case, we must redirect the user to a page with an error stating that there are problems on the server and a form for contacting support. In the second case, we must transfer the user to the login form and display a corresponding message in it stating that the session has expired. To do this, we need to enter error codes and return the corresponding code instead of FALSE, and in the calling method, check it and act accordingly.

Now, even if a session on the server still exists, it will be destroyed the first time it is accessed if the user's inactivity timeout has expired. And this will happen regardless of what session lifetime is set in the global PHP settings.

Note: What happens if the browser was closed and the session name cookie was automatically destroyed? The request to the server the next time the browser is opened will not contain the session cookies, and the server will not be able to open the session and check the user's inactivity timeout. For us, this is equivalent to creating a new session and does not affect functionality or security in any way. But a fair question arises - who will then destroy the old session, if until now we have destroyed it after the timeout has expired? Or will it now hang in the sessions directory forever? To clean up old sessions in PHP, there is a mechanism called garbage collection. It runs at the time of the next request to the server and clears all old sessions based on the last modification date of the session files. But the garbage collection mechanism does not start with every request to the server. The frequency (or rather, the probability) of launching is determined by two settings parameters session.gc_probability and session.gc_divisor. The result of dividing the first parameter by the second is the probability of launching the garbage collection mechanism. Thus, in order for the session clearing mechanism to be launched with each request to the server, these parameters must be set to equal values, for example “1”. This approach guarantees a clean session directory, but is obviously too expensive for the server. Therefore, on production systems, the default value of session.gc_divisor is set to 1000, which means that the garbage collection mechanism will run with a probability of 1/1000. If you experiment with these settings in your php.ini file, you may notice that in the case described above, when the browser closes and clears all its cookies, there are still old sessions left in the sessions directory for a while. But this should not worry you, because... as already stated, this does not in any way affect the safety of our mechanism.

Update from 2013-06-07 Preventing scripts from freezing due to session file locking

The comments raised the issue of simultaneously running scripts freezing due to the session file being blocked (the most striking option is long poll).

To begin with, I note that this problem does not directly depend on the server load or the number of users. Of course, the more requests, the slower the scripts are executed. But this is an indirect dependence. The problem appears only within one session, when the server receives several requests on behalf of one user (for example, one of them is long poll, and the rest are regular requests). Each request tries to access the same session file, and if the previous request did not unlock the file, then the subsequent one will hang waiting.

To keep session file locking to a minimum, it is strongly recommended to close the session by calling the session_write_close() function immediately after all actions with session variables have been completed. In practice, this means that you should not store everything in session variables and access them throughout the execution of the script. And if you need to store some working data in session variables, then read them immediately when the session starts, save them in local variables for later use and close the session (meaning closing the session using the session_write_close function, and not destroying it using session_destroy).

In our example, this means that immediately after opening a session, checking its lifetime and the existence of an authorized user, we must read and save all additional session variables required by the application (if any exist), then close the session using a call to session_write_close() and continue execution of a script, be it a long poll or a regular request.

Protecting sessions from unauthorized use Let's imagine a situation. One of your users gets a Trojan that robs the browser cookies (in which our session is stored) and sends it to the specified email. The attacker obtains the cookie and uses it to spoof a request on behalf of our authorized user. The server successfully accepts and processes this request as if it came from an authorized user. If additional verification of the IP address is not implemented, such an attack will lead to a successful hacking of the user's account with all the ensuing consequences.

Why was this possible? Obviously, because the name and session identifier are always the same for the entire lifetime of the session, and if you receive this data, you can easily send requests on behalf of another user (of course, within the lifetime of this session). This may not be the most common type of attack, but theoretically it seems quite feasible, especially considering that such a Trojan does not even need administrator rights to rob the user's browser cookies.

How can you protect yourself from attacks of this kind? Again, obviously, by limiting the lifetime of the session identifier and periodically changing the identifier within the same session. We can also change the name of the session by completely deleting the old one and creating a new session, copying all the session variables from the old one into it. But this does not affect the essence of the approach, so for simplicity we will limit ourselves to only the session identifier.

It is clear that the shorter the session ID lifetime, the less time an attacker will have to obtain and use cookies to forge a user request. Ideally, a new identifier should be used for each request, which will minimize the possibility of using someone else's session. But we will consider the general case when the session identifier regeneration time is set arbitrarily.

(We will omit the part of the code that has already been discussed).

Function startSession($isUserActivity=true) ( ​​// Session identifier lifetime $idLifetime = 60; ... if ($idLifetime) ( // If the session identifier lifetime is set, // check the time elapsed since the session was created or the last regeneration // (time of the last request when the session variable starttime was updated) if (isset($_SESSION["starttime"])) ( if ($t-$_SESSION["starttime"] >= $idLifetime) ( // Time the life of the session identifier has expired // Generate a new identifier session_regenerate_id(true); $_SESSION["starttime"] = $t; ) ) else ( // We get here if the session has just been created // Set the time for generating the session identifier to the current time $_SESSION["starttime"] = $t; ) ) return true; )

So, when creating a new session (which occurs when the user successfully logs in), we set the session variable starttime, which stores for us the time of the last generation of the session identifier, to a value equal to the current server time. Next, in each request, we check whether enough time (idLifetime) has passed since the last generation of the identifier, and if so, we generate a new one. Thus, if during the set lifetime of the identifier the attacker who received the cookie of the authorized user does not have time to use it, the fake request will be regarded by the server as unauthorized, and the attacker will be taken to the login page.

Note: The new session ID gets into the browser's cookie when the session_regenerate_id() function is called, which sends the new cookie, similar to the session_start() function, so we don't need to update the cookie ourselves.

If we want to make our sessions as secure as possible, it is enough to set the lifetime of the identifier to one or even remove the session_regenerate_id() function from brackets and remove all checks, which will lead to the regeneration of the identifier in each request. (I have not tested the impact of this approach on performance, and I can only say that the session_regenerate_id(true) function essentially performs only 4 actions: generating a new identifier, creating a header with the session cookie, deleting the old one and creating a new session file).

Lyrical digression: If the Trojan turns out to be so smart that it does not send cookies to the attacker, but organizes the sending of a pre-prepared fake request immediately upon receiving the cookie, the method described above most likely will not be able to protect against such an attack, because between the time the Trojan receives the cookie and sending a fake request there will be practically no difference, and there is a high probability that at this moment the session identifier will not be regenerated.

Possibility of simultaneous work in one browser on behalf of several users The last task that I would like to consider is the possibility of simultaneous work in one browser by several users. This feature is especially useful at the testing stage, when you need to emulate the simultaneous work of users, and it is advisable to do this in your favorite browser, rather than using the entire available arsenal or opening several instances of the browser in incognito mode.

In our previous examples, we did not explicitly specify a session name, so the default PHP name (PHPSESSID) was used. This means that all the sessions we have created so far have sent a cookie to the browser under the name PHPSESSID. Obviously, if the cookie name is always the same, then there is no way to organize two sessions with the same name within the same browser. But if we used our own session name for each user, the problem would be solved. Let's do so.

Function startSession($isUserActivity=true, $prefix=null) ( ... if (session_id()) return true; // If the user prefix is ​​passed in the parameters, // set a unique session name that includes this prefix, // otherwise set common name for all users (for example, MYPROJECT) session_name("MYPROJECT".($prefix ? "_".$prefix: "")); ini_set("session.cookie_lifetime", 0); if (! session_start()) return false; ... )

Now all that remains is to make sure that the calling script passes a unique prefix for each user to the startSession() function. This can be done, for example, by passing a prefix in the GET/POST parameters of each request or through an additional cookie.

Conclusion In conclusion, I will provide the complete final code of our functions for working with PHP sessions, including all the tasks discussed above.

Function startSession($isUserActivity=true, $prefix=null) ( $sessionLifetime = 300; $idLifetime = 60; if (session_id()) return true; session_name("MYPROJECT".($prefix ? "_".$prefix: "")); ini_set("session.cookie_lifetime", 0); if (! session_start()) return false; $t = time(); if ($sessionLifetime) ( if (isset($_SESSION["lastactivity"] ) && $t-$_SESSION["lastactivity"] >= $sessionLifetime) ( destroySession(); return false; ) else ( if ($isUserActivity) $_SESSION["lastactivity"] = $t; ) ) if ($idLifetime ) ( if (isset($_SESSION["starttime"])) ( if ($t-$_SESSION["starttime"] >= $idLifetime) ( session_regenerate_id(true); $_SESSION["starttime"] = $t; ) ) else ( $_SESSION["starttime"] = $t; ) ) return true; ) function destroySession() ( if (session_id()) ( session_unset(); setcookie(session_name(), session_id(), time() -60*60*24); session_destroy(); ) )

I hope this article will save some time for those who have never delved too deeply into the session mechanism, and give enough insight into this mechanism for those who are just starting to get acquainted with PHP.

Good day, friends! Let's look at user registration in PHP. First, let's define the conditions for our user registration:

  • The password is encrypted using the MD5 algorithm
  • We will salt the password
  • Checking if your login is busy
  • User activation by letter.
  • Recording and storing data in the MySQL DBMS

To write this script, we need to understand what user registration is. User registration means obtaining real user data, processing and storing data.

To explain in simple words, registration is just recording and storing certain data by which we can authorize the user in our case - this is Login and Password.

Authorization is the granting of rights to a certain person or group of persons to perform certain actions, as well as the process of verifying these rights when attempting to perform these actions. Simply put, with the help of authorization, we can limit access to certain content on our website.

Let's look at the structure of script directories for implementing our registration with authorization. We need to break the scripts into logical components. We placed the registration and authorization modules in a separate directory. We will also place the connection to the MySQL database, a file with user functions, a CSS style file and our HTML template in separate directories. This structure allows you to quickly navigate through scripts. Imagine that you have a large website with a bunch of modules, etc. and if there is no order, it will be very difficult to find something in such a mess.

Since we will store all the data in the MySQL DBMS, let's create a small table in which we will store registration data.

First you need to create a table in the database. Let's call the table bez_reg where bez is the table prefix, and reg is the name of the table.

Table structure: bez_reg -- -- Table structure `bez_reg` -- CREATE TABLE IF NOT EXISTS `bez_reg` (`id` int(11) NOT NULL AUTO_INCREMENT, `login` varchar(200) NOT NULL, `pass` varchar( 32) NOT NULL, `salt` varchar(32) NOT NULL, `active_hex` varchar(32) NOT NULL, `status` int(1) NOT NULL, PRIMARY KEY (`id`)) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; Now let's create the main scripts for further work. File INDEX.PHP

CONFIG.PHP file

less/reg/?mode=auth">Login





  • 

    2024 gtavrl.ru.