📜 ⬆️ ⬇️

PHP for beginners. Session

ElePHPant. PHP for beginners. Session

Have a nice day, everyone. Here is the first article in the PHP series for novice developers. This will be an unusual series of articles, there will be no echo "Hello World" , there will be a hardcore from the life of PHP programmers with a small touch of "homework" to consolidate the material.

I'll start with the sessions - this is one of the most important components with which you have to work. Not understanding the principles of his work - bloat up business. So in order to avoid problems, I will try to tell you about all the possible nuances.

But for a start, in order to understand why we need a session, let us turn to the sources - to the HTTP protocol.

HTTP Protocol


The HTTP protocol is the HyperText Transfer Protocol - the “hypertext transfer protocol” —that is in fact, a text protocol, and it is easy to understand.
Initially it was meant that under this protocol only HTML would be transmitted, the name and the title, and now they just don’t send and = ^. ^ = And (• _ ㅅ _ •)

In order not to beat around the bush, let me give you an example of communication over the HTTP protocol.
Here is an example of the request your browser sends it when you request the page http://example.com :

 GET / HTTP/1.1 Host: example.com Accept: text/html <пустая строка> 

Here is an example answer:

 HTTP/1.1 200 OK Content-Length: 1983 Content-Type: text/html; charset=utf-8 <html> <head>...</head> <body>...</body> </html> 

These are very simplified examples, but even here you can see what the HTTP request and response consist of:

  1. the starting line - for the request contains the method and path of the requested page, for the response - the protocol version and the response code
  2. headers - have a key-value format separated by a colon, each new header is written on a new line
  3. body of the message - directly HTML or data is separated from the headers by two line breaks, may be absent, as in the above request

So, sort of dealt with the protocol - it is simple, it has its history since 1992, so you can’t call it ideal, but what is - send a request - get an answer, and that's all, the server and the client are not connected in any way. But such a scenario is by no means the only possible one, we can have authorization, the server should somehow understand that this request came from a specific user, i.e. client and server must communicate within a certain session. And yes, for this, they came up with the following mechanism:

  1. When authorizing a user, the server generates and remembers a unique key - the session identifier, and informs its browser
  2. The browser saves this key, and with each subsequent request, it is sent

To implement this mechanism, cookies were created (cookies, cookies) - simple text files on your computer, by file for each domain (although some browsers are more advanced, and use SQLite to store the database), while the browser imposes a limit on the number of records and the size of the stored data (for most browsers it is 4096 bytes, see RFC 2109 from 1997)
Those. If you steal a cookie from your browser, then you can go to your facebook page on your behalf? Do not worry, you can’t do that, at least from facebook, and then I'll show you one of the possible ways to protect against this type of attack on your users.

Let's now see how our request-response changes, be there authorization:

Request
 POST /login/ HTTP/1.1 Host: example.com Accept: text/html login=Username&password=Userpass 


Our method has changed to POST, and in the request body we have passed the login and password. If you use the GET method, the query string will contain a login and password, which is not very correct from an ideological point of view, and has a number of side effects in the form of logging (for example, in the same access.log ) and caching of passwords in open form.

Response
 HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Set-Cookie: KEY=VerySecretUniqueKey <html> <head>...</head> <body>...</body> </html> 

The server’s response will contain the Set-Cookie: KEY=VerySecretUniqueKey header Set-Cookie: KEY=VerySecretUniqueKey , which will force the browser to save this data into cookies, and the next time you access the server, they will be sent and recognized by the server:

Request
 GET / HTTP/1.1 Host: example.com Accept: text/html Cookie: KEY=VerySecretUniqueKey <пустая строка> 

As you can see, the headers sent by the browser (Request Headers) and the server (Response Headers) are different, although there are also common ones for both requests and answers (General Headers)

The server has recognized our user by the sent cookies, and will further provide him with access to personal information. So, well, sort of dealt with HTTP and sessions, you can now return to PHP and its features.

PHP and session


I hope you already have PHP installed on your computer, because then I will give examples, and they will need to run

PHP language was created to become the HTTP protocol - i.e. Its main task is to respond to an HTTP request and "die" by freeing memory and resources. Consequently, the session mechanism works in PHP not in automatic mode, but in manual mode, and you need to know what to call, and in what order.
Here you have an article on the topic of PHP is meant to die , or here it is in Russian , but it is better to set it aside for “later”.

First of all, you need to "start" the session - for this we use the function session_start () , create a file session.start.php with the following contents:

 <?php session_start(); 

Run the PHP built-in web server in the folder with your script:

 php -S 127.0.0.1:8080 

Launch the browser and open the Developer Tools (or whatever you have ) in it, then go to http://127.0.0.1:8080/session.start.php - you should see only a blank page, but don’t rush to close it - look on the headers that the server sent us:

Cookie

There will be a lot of things, we are only interested in this line in the server’s response (clean the cookies, if there is no such line, and refresh the page):

 Set-Cookie: PHPSESSID=dap83arr6r3b56e0q7t5i0qf91; path=/ 

After seeing this, the browser will retain a cookie with the name `PHPSESSID`:

Browser session cookie

PHPSESSID is the default session name, regulated from the php.ini config by session.name directive, if necessary, the name can be changed in the configuration file itself or using the session_name () function

And now - we are updating the page, and we see that the browser sends this cookie to the server, you can try to refresh the page a couple of times, the result will be identical:

Browser request with cookie

Total, what we have - the theory coincided with practice, and this is just fine.

The next step is to save an arbitrary value in the session, for this PHP uses the super-global variable $_SESSION , we will save the current time - for this we call the date () function:

 session_start(); $_SESSION['time'] = date("H:i:s"); echo $_SESSION['time']; 

Update the page and see the server time, update again - and the time is updated. Let's now make sure that the set time does not change with each page refresh:

 session_start(); if (!isset($_SESSION['time'])) { $_SESSION['time'] = date("H:i:s"); } echo $_SESSION['time']; 

Update - time does not change, what you need. But at the same time, we remember that PHP is dying, which means that it stores this session somewhere, and we will find this place ...

All secret becomes clear


By default, PHP stores the session in files - the session.save_handler directive is responsible for this, the path by which the files are saved look for in the session.save_path directive, or use the session_save_path () function to get the required path.
In your configuration, the path to the files may not be specified, then the session files will be stored in temporary files of your system - call the sys_get_temp_dir () function and find out where this hidden place is.

So, go along this path and find your session file (I have this file sess_dap83arr6r3b56e0q7t5i0qf91 ), open it in a text editor:

 time|s:8:"16:19:51"; 

As you can see, this is our time, this is how tricky our session is stored, but we can make edits, change time, or we can simply enter any string, why not:

 time|s:13:"\m/ (@.@) \m/"; 

To convert this string to an array, you need to use the session_decode () function, for the inverse transformation - session_encode () - this is called serialization, that's just in PHP for sessions - it has its own - special, although you can use standard PHP serialization - write in the session configuration directive .serialize_handler value is php_serialize and you will be happy, and $_SESSION can be used without restrictions - now you can use numbers and special characters as an index | and ! in the name (for all 10+ years of work, I never had to :)

The task
Write your function similar to the function session_decode() , here's a test data set for the session (to solve the knowledge of regular expressions is not required), take the text for conversion from the file of your current session:

 $_SESSION['integer var'] = 123; $_SESSION['float var'] = 1.23; $_SESSION['octal var'] = 0x123; $_SESSION['string var'] = "Hello world"; $_SESSION['array var'] = array('one', 'two', [1,2,3]); $object = new stdClass(); $object->foo = 'bar'; $object->arr = array('hello', 'world'); $_SESSION['object var'] = $object; $_SESSION['integer again'] = 42; 


So that we have not tried? That's right - to steal cookies, let's launch another browser and add the same cookie to it. I wrote you a simple javascript for this, copy it into the browser console and run it, just do not forget to change the session ID to your own:

 javascript:(function(){document.cookie='PHPSESSID=dap83arr6r3b56e0q7t5i0qf91;path=/;';window.location.reload();})() 

Now you have both browsers looking at the same session. I mentioned above that I will talk about the ways of protection, consider the easiest way - we will tie the session to the browser, or rather to how the browser is presented to the server - we will memorize the User-Agent and check it every time:

 session_start(); if (!isset($_SESSION['time'])) { $_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT']; $_SESSION['time'] = date("H:i:s"); } if ($_SESSION['ua'] != $_SERVER['HTTP_USER_AGENT']) { die('Wrong browser'); } echo $_SESSION['time']; 

It is more difficult to fake, but it is still possible, add $_SERVER['REMOTE_ADDR'] and $_SERVER['HTTP_X_FORWARDED_FOR'] save and check here, and this will more or less be like protection from intruders encroaching on our cookies.

The keyword in the previous paragraph seems to be running in HTTPS protocol in real projects for a long time, so no one can steal them without physical access to your computer or smartphone


It is worth mentioning the session.cookie-httponly directive , thanks to it the session cookie will be inaccessible from JavaScript. In addition, if you look at the manual of the setcookie () function, you will notice that the last parameter is also responsible for HttpOnly. Keep this in mind - this setting allows you to effectively deal with XSS attacks in almost all browsers .

The task
Add to the code a check on the user's IP; if the check fails, delete the compromised session.

Steps


And now I will explain step by step the algorithm, how the session works in PHP, using the example of the following code (default settings):

 session_start(); $_SESSION['id'] = 42; 

  1. after calling session_start() PHP looks for a session identifier in the cookie by the name specified in session.name - this is PHPSESSID
  2. if there is no identifier, then it is created (see session_id () ), and creates an empty session file on the path session.save_path named sess_{session_id()} , headers will be added to the server’s response, to set the cookie {session_name()}={session_id()}
  3. if the identifier is present, then look for the session file in the folder session.save_path :
    • don't find - create an empty file with the name sess_{$_COOKIE[session_name()]} (the identifier can contain only characters from the ranges az , AZ , 0-9 , a comma and a minus sign)
    • we find, read the file and unpack the data (see session_decode () ) into the super-global variable $_SESSION (the file is locked for reading / writing)
  4. when the script has finished its work, all the data from $_SESSION packed using session_encode() in a file by the path session.save_path named sess_{session_id()} (the lock is released)

The task
Set in your browser an arbitrary cookie value with the name PHPSESSID , let it be 1234567890 , refresh the page, check that you have created a new file sess_1234567890

Is there life without cookies?


PHP can work with the session even if cookies in the browser are disabled, but then all the URLs on the site will contain a parameter with the ID of your session, and yes - do you need to configure this, but do you need it? I did not have to use it, but if I really want to - I’ll just say where to dig:


And if you need to store the session in the database?


To store the session in the database, you will need to change the session repository and tell PHP how to use it, the SessionHandlerInterface interface and the session_set_save_handler function have been created for this purpose.
Separately, I note that you do not need to write your own session handlers for redis and memcache - when you install these extensions, the corresponding handlers go along with them, so RTFM is our everything. Well, yes, the handler must be specified before calling session_start() ;)

The task
Implement SessionHandlerInterface to store session in MySQL, check if it works.
This task with an asterisk, for those who have already met with databases.


When does a session die?


During the session lifetime, the session.gc_maxlifetime directive is responsible. By default, this directive is equal to 1440 seconds (24 minutes), it should be understood so that if the session was not addressed within a specified time, the session will be considered “spoiled” and will wait for its turn for deletion.

Another question is interesting, can you ask it to the hardcore developers - when does PHP delete the files of expired sessions? The answer is in the official manual, but not in an explicit form - so remember:

The garbage collection can be started when the session_start() function is called, the launch probability depends on two session.gc_probability and session.gc_divisor directives, the first acts as a dividend, the second is the divisor, and by default these values ​​are 1 and 100, tons. e. the probability that the collector will be launched and the session files will be deleted - approximately 1%.

The task
Change the value of the session.gc_divisor directive so that the garbage collector runs every time, check that this is the case.


The most trivial error


Error with more than half a million results in the issuance of Google:

Cannot send session cookie - headers already sent by
Cannot send session cache limiter - headers already sent

To get one, create a file session.error.php with the following contents:

 echo str_pad(' ', ini_get('output_buffering')); session_start(); 

In the second line, the strange "magic" is the focus with the output buffer, I will tell about it in one of the following articles, so far consider it only a string of 4096 characters long, in this case - these are all spaces

Start by deleting the cookie and get the errors, though the error text is different, but the essence is the same - the train is gone - the server has already sent the page contents to the browser, and sending the headers is too late, it will not work, and the coveted session identifier does not appear in the cookies. If you stumbled with this error - look for a place where the text is displayed ahead of time, it can be a space before the characters <?php , or after ?> In one of the included files, and it’s okay if it is a space, there can be a thread non-printing character like BOM , so be careful, and this contagion will not touch you (as ... homeric laughter).

The task
To test the knowledge gained, I want you to implement your own session mechanism and make the above code work:

 require_once 'include/sess.php'; sess_start(); if (isset($_SESS["id"])) { echo $_SESS["id"]; } else { $_SESS["id"] = 42; } 

To accomplish this, you will need the register_shutdown_function () function.



Lock


Another common mistake for newbies is an attempt to read the session file while it is blocked by another script. Actually, this is not quite a mistake, this is a misunderstanding of the principle of blocking :)

But let's take another step:

  1. session_start() not only creates / reads a file, but also blocks it, so that no one can make edits at the time of the script execution, or read non-consistent data from the session file
  2. blocking is removed at the end of the script


“Sticking” into this error is very easy, create two files:

 // start.php session_start(); echo "OK"; 


 // lock.php session_start(); sleep(10); echo "OK"; 


Now, if you open the lock.php page in the browser, and then open start.php in a new tab, you will see that the second page will open only after the first script, which blocks the session file for 10 seconds, runs.

There are a couple of options for how to avoid this phenomenon - “clumsy” and “thoughtful”.

"Clumsy"
Use a self-written session handler in which to “forget” to implement the lock :)
A slightly better option is to take ready and disable the lock (for example, memcached has this option - memcached.sess_locking ) O_o
Spending hours debugging the code in search of a rarely pop-up error ...

"Thoughtful"
It’s much better to follow the session lock yourself and take it off when it is not needed:

- If you are sure that you do not need to make changes to the session data, use the read_and_close option when starting the session:

 session_start([ 'read_and_close' => true ]); 


Thus, the lock will be released immediately after reading the session data.

- If you still need to make changes to the session, after making these, close the session from the recording:

 session_start(); // some changes session_write_close(); 


The task
Just above was a listing of two files start.php and lock.php , create more files read-close.php and write-close.php , in which you will control the blocking in these ways. Check how the lock works (or does not work).


Finally


In this article, you are given seven tasks, while they concern not only working with sessions , but also introduce you to MySQL and the functions of working with strings . To assimilate this material - a separate article is not needed, the manual on the links provided is enough - no one will read it for you. Dare!

PS If you have learned something new from the article - thank the author - zasharte article in the social networks;)
PPS Yes, this is a cross-post article from my blog , but it is still relevant today :)

A series of articles "PHP for beginners":

Source: https://habr.com/ru/post/437972/