{"id":2792,"date":"2021-10-07T07:34:19","date_gmt":"2021-10-07T07:34:19","guid":{"rendered":"https:\/\/phptutorial.net\/?page_id=2792"},"modified":"2025-04-09T07:52:31","modified_gmt":"2025-04-09T07:52:31","slug":"php-remember-me","status":"publish","type":"page","link":"https:\/\/www.phptutorial.net\/php-tutorial\/php-remember-me\/","title":{"rendered":"PHP Remember Me"},"content":{"rendered":"\n<p><strong>Summary<\/strong>: in this tutorial, you&#8217;ll learn to securely implement the &#8220;Remember Me&#8221; feature in PHP.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id='introduction-to-the-php-remember-me-feature'>Introduction to the PHP remember me feature <a href=\"#introduction-to-the-php-remember-me-feature\" class=\"anchor\" id=\"introduction-to-the-php-remember-me-feature\" title=\"Anchor for Introduction to the PHP remember me feature\">#<\/a><\/h2>\n\n\n\n<p>When users <a href=\"https:\/\/phptutorial.net\/php-tutorial\/php-login\/\">log in<\/a> to a web application and then close web browsers, the <a href=\"https:\/\/phptutorial.net\/php-tutorial\/php-session\/\">session<\/a> <a href=\"https:\/\/phptutorial.net\/php-tutorial\/php-cookies\/\">cookies<\/a> associated with the logins expire immediately. It means that if the users access the web application later, they need to log in again.<\/p>\n\n\n\n<p>The remember me feature allows the users to save their logins for some time, even after closing the web browsers. To implement the remember me feature, you&#8217;ll use <a href=\"https:\/\/phptutorial.net\/php-tutorial\/php-cookies\/\">cookies<\/a> with expiration times in the future.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id='the-common-but-insecure-way'>The common but insecure way <a href=\"#the-common-but-insecure-way\" class=\"anchor\" id=\"the-common-but-insecure-way\" title=\"Anchor for The common but insecure way\">#<\/a><\/h3>\n\n\n\n<p>The insecure way to implement the remember me is to add a user id to the cookie with an expiration time:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">user_id=<span class=\"hljs-number\">120<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When users access the web application, you check if the user id in the cookie is valid before logging them in automatically.<\/p>\n\n\n\n<p>This naive approach relies solely on cookies, which is not secure for the following reasons:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>First, users can change the id to another to log in as another user.<\/li>\n\n\n\n<li>Second, the user id may reveal the number of users in the system.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id='a-more-secure-approach'>A more secure approach <a href=\"#a-more-secure-approach\" class=\"anchor\" id=\"a-more-secure-approach\" title=\"Anchor for A more secure approach\">#<\/a><\/h3>\n\n\n\n<p>A more secure way to implement the remember me feature is to store a random token instead of a user id in both cookies and database server.<\/p>\n\n\n\n<p>The value in the cookies will look like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">remember_me=<span class=\"hljs-number\">6179<\/span>f9c66a9d007e689c7809b5d8320a6692787773488f12a4330cd5ffd25d91<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The following <code><a href=\"https:\/\/www.mysqltutorial.org\/mysql-basics\/mysql-create-table\/\" target=\"_blank\" rel=\"noreferrer noopener\">CREATE TABLE<\/a><\/code> statement create a table that stores the tokens:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"SQL (Structured Query Language)\" data-shcb-language-slug=\"sql\"><span><code class=\"hljs language-sql\"><span class=\"hljs-keyword\">CREATE<\/span> <span class=\"hljs-keyword\">TABLE<\/span> user_tokens\n(\n    <span class=\"hljs-keyword\">id<\/span>               <span class=\"hljs-built_in\">INT<\/span> AUTO_INCREMENT PRIMARY <span class=\"hljs-keyword\">KEY<\/span>,\n    token            <span class=\"hljs-built_in\">VARCHAR<\/span>(<span class=\"hljs-number\">255<\/span>) <span class=\"hljs-keyword\">NOT<\/span> <span class=\"hljs-literal\">NULL<\/span>,\n    expiry           DATETIME <span class=\"hljs-keyword\">NOT<\/span> <span class=\"hljs-literal\">NULL<\/span>,\n    user_id          <span class=\"hljs-built_in\">INT<\/span>      <span class=\"hljs-keyword\">NOT<\/span> <span class=\"hljs-literal\">NULL<\/span>,\n    <span class=\"hljs-keyword\">CONSTRAINT<\/span> fk_user_id\n        <span class=\"hljs-keyword\">FOREIGN<\/span> <span class=\"hljs-keyword\">KEY<\/span> (user_id)\n            <span class=\"hljs-keyword\">REFERENCES<\/span> <span class=\"hljs-keyword\">users<\/span> (<span class=\"hljs-keyword\">id<\/span>) <span class=\"hljs-keyword\">ON<\/span> <span class=\"hljs-keyword\">DELETE<\/span> <span class=\"hljs-keyword\">CASCADE<\/span>\n);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SQL (Structured Query Language)<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">sql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When users access the web application, you match the cookies&#8217; tokens with those stored in the database. Also, you can check the token&#8217;s expiration time. If the tokens match and have not expired, you can get the user id associated with the token and sign the user in automatically.<\/p>\n\n\n\n<p>The <a href=\"https:\/\/www.mysqltutorial.org\/mysql-basics\/mysql-select-from\/\" target=\"_blank\" rel=\"noreferrer noopener\">query<\/a> for matching the token will look like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">SELECT user_id\nFROM user_tokens\nWHERE token = <span class=\"hljs-string\">'6179f9c66a9d007e689c7809b5d8320a6692787773488f12a4330cd5ffd25d91'<\/span> <span class=\"hljs-keyword\">and<\/span>\n      expiry &gt; NOW()<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This approach solves two issues above:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>First, the token is more challenging to guess.<\/li>\n\n\n\n<li>Second, the token doesn&#8217;t reveal the number of users.<\/li>\n<\/ul>\n\n\n\n<p>However, this approach exposes another security issue which is known as a timing attack.<\/p>\n\n\n\n<p>When the database compares the cookie&#8217;s token with the token stored in the database, it returns the different comparison times according to how much two tokens are similar.<\/p>\n\n\n\n<p>For example, if you have the following token stored in the cookie:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-number\">6179<\/span>f9c66a9d007e689c7809b5d8320a6692787773488f12a4330cd5ffd25d91<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And the following token in the database:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-number\">6<\/span>f9a1ef3020bb8351456cd65176e1e62ceeefcdca0a750201886a230f8736cad<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When comparing these tokens, the database compares each character in the tokens and stops matching when it finds a mismatch. In this example, the database stops at the second character:<\/p>\n\n\n\n<p>However, when comparing the following pair of tokens:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-number\">6179<\/span>f9c66a9d007e689c7809b5d8320a6692787773488f12a4330cd5ffd25d91\n<span class=\"hljs-number\">6179<\/span>f9c66a9d007e689c7809b5d8320a6692787773488f12a4330cd5ffd25d92<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The database stops matching after it comparing the second last character.<\/p>\n\n\n\n<p>The comparing time in the second example will always be greater than the second one because the database needs to compare more characters.<\/p>\n\n\n\n<p>By testing different tokens, you can get different response times. In other words, the timing is leaked. To avoid timing leaks, the comparison function needs to return a constant time regardless of the tokens.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id='prevent-timing-attacks'>Prevent timing attacks <a href=\"#prevent-timing-attacks\" class=\"anchor\" id=\"prevent-timing-attacks\" title=\"Anchor for Prevent timing attacks\">#<\/a><\/h3>\n\n\n\n<p>The following shows <a href=\"https:\/\/paragonie.com\/blog\/2015\/04\/secure-authentication-php-with-long-term-persistence\" target=\"_blank\" rel=\"noreferrer noopener\">how to prevent the timing attack as proposed by P.I.E<\/a>. In this approach, instead of storing a single token in the cookie, you store a pair of tokens: <code>selector<\/code> and <code>validator<\/code> with the format: <code>selector:validator<\/code>.<\/p>\n\n\n\n<p>The <code>selector<\/code> is for selecting the <code>validator<\/code> stored in the database. In the database, you store the <code>selector<\/code> and the <code>validator<\/code>&#8216;s hash:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">CREATE TABLE user_tokens\n(\n    id               INT AUTO_INCREMENT PRIMARY KEY,\n    selector         VARCHAR(<span class=\"hljs-number\">255<\/span>) NOT <span class=\"hljs-keyword\">NULL<\/span>,\n    hashed_validator VARCHAR(<span class=\"hljs-number\">255<\/span>) NOT <span class=\"hljs-keyword\">NULL<\/span>,\n    user_id          INT      NOT <span class=\"hljs-keyword\">NULL<\/span>,\n    expiry           DATETIME NOT <span class=\"hljs-keyword\">NULL<\/span>,\n    CONSTRAINT fk_user_id\n        FOREIGN KEY (user_id)\n            REFERENCES users (id) ON DELETE CASCADE\n);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>To hash the validator, you use the password_hash() function.<\/p>\n\n\n\n<p>To get a user id, you match the <code>selector<\/code> from the cookie with the <code>selector<\/code> from the database:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">SELECT id, selector, hashed_validator, user_id, expiry\nFROM user_tokens\nWHERE selector = :selector<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If the query returns a row, you can match the <code>validator<\/code> from the cookie with the <code>hashed_validator<\/code> using the <code>password_verify()<\/code> function.<\/p>\n\n\n\n<p>If the validators match, you can log the user with the <code>user_id<\/code> in automatically.<\/p>\n\n\n\n<p>The following section will enhance the login system by adding the remember me feature using the third approach.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id='create-a-user_tokens-table-to-store-the-tokens'>Create a user_tokens table to store the tokens <a href=\"#create-a-user_tokens-table-to-store-the-tokens\" class=\"anchor\" id=\"create-a-user_tokens-table-to-store-the-tokens\" title=\"Anchor for Create a user_tokens table to store the tokens\">#<\/a><\/h2>\n\n\n\n<p>The following statement creates a <code>user_tokens<\/code> table that stores the <code>selector<\/code>, hashed <code>validator<\/code>, <code>expiry<\/code>, and user id.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">CREATE TABLE user_tokens\n(\n    id               INT AUTO_INCREMENT PRIMARY KEY,\n    selector         VARCHAR(<span class=\"hljs-number\">255<\/span>) NOT <span class=\"hljs-keyword\">NULL<\/span>,\n    hashed_validator VARCHAR(<span class=\"hljs-number\">255<\/span>) NOT <span class=\"hljs-keyword\">NULL<\/span>,\n    user_id          INT      NOT <span class=\"hljs-keyword\">NULL<\/span>,\n    expiry           DATETIME NOT <span class=\"hljs-keyword\">NULL<\/span>,\n    CONSTRAINT fk_user_id\n        FOREIGN KEY (user_id)\n            REFERENCES users (id) ON DELETE CASCADE\n);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\" id='add-the-remember-me-checkbox-to-the-login-form'>Add the remember me checkbox to the login form <a href=\"#add-the-remember-me-checkbox-to-the-login-form\" class=\"anchor\" id=\"add-the-remember-me-checkbox-to-the-login-form\" title=\"Anchor for Add the remember me checkbox to the login form\">#<\/a><\/h2>\n\n\n\n<p>First, add a remember me checkbox to the login form in the <code>public\/login.php<\/code> file:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-meta\">&lt;?php<\/span>\n\n<span class=\"hljs-keyword\">require<\/span> <span class=\"hljs-keyword\">__DIR__<\/span> . <span class=\"hljs-string\">'\/..\/src\/bootstrap.php'<\/span>;\n<span class=\"hljs-keyword\">require<\/span> <span class=\"hljs-keyword\">__DIR__<\/span> . <span class=\"hljs-string\">'\/..\/src\/login.php'<\/span>;\n<span class=\"hljs-meta\">?&gt;<\/span>\n\n<span class=\"hljs-meta\">&lt;?php<\/span> view(<span class=\"hljs-string\">'header'<\/span>, &#91;<span class=\"hljs-string\">'title'<\/span> =&gt; <span class=\"hljs-string\">'Login'<\/span>]) <span class=\"hljs-meta\">?&gt;<\/span>\n\n<span class=\"hljs-meta\">&lt;?php<\/span> <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">isset<\/span>($errors&#91;<span class=\"hljs-string\">'login'<\/span>])) : <span class=\"hljs-meta\">?&gt;<\/span>\n    &lt;div <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">alert<\/span> <span class=\"hljs-title\">alert<\/span>-<span class=\"hljs-title\">error<\/span>\"&gt;\n        &lt;?= $<span class=\"hljs-title\">errors<\/span>&#91;'<span class=\"hljs-title\">login<\/span>'] ?&gt;\n    &lt;\/<span class=\"hljs-title\">div<\/span>&gt;\n&lt;?<span class=\"hljs-title\">php<\/span> <span class=\"hljs-title\">endif<\/span> ?&gt;\n\n    &lt;<span class=\"hljs-title\">form<\/span> <span class=\"hljs-title\">action<\/span>=\"<span class=\"hljs-title\">login<\/span>.<span class=\"hljs-title\">php<\/span>\" <span class=\"hljs-title\">method<\/span>=\"<span class=\"hljs-title\">post<\/span>\"&gt;\n        &lt;<span class=\"hljs-title\">h1<\/span>&gt;<span class=\"hljs-title\">Login<\/span>&lt;\/<span class=\"hljs-title\">h1<\/span>&gt;\n        &lt;<span class=\"hljs-title\">div<\/span>&gt;\n            &lt;<span class=\"hljs-title\">label<\/span> <span class=\"hljs-title\">for<\/span>=\"<span class=\"hljs-title\">username<\/span>\"&gt;<span class=\"hljs-title\">Username<\/span>:&lt;\/<span class=\"hljs-title\">label<\/span>&gt;\n            &lt;<span class=\"hljs-title\">input<\/span> <span class=\"hljs-title\">type<\/span>=\"<span class=\"hljs-title\">text<\/span>\" <span class=\"hljs-title\">name<\/span>=\"<span class=\"hljs-title\">username<\/span>\" <span class=\"hljs-title\">id<\/span>=\"<span class=\"hljs-title\">username<\/span>\" <span class=\"hljs-title\">value<\/span>=\"&lt;?= $<span class=\"hljs-title\">inputs<\/span>&#91;'<span class=\"hljs-title\">username<\/span>'] ?? '' ?&gt;\"&gt;\n            &lt;<span class=\"hljs-title\">small<\/span>&gt;&lt;?= $<span class=\"hljs-title\">errors<\/span>&#91;'<span class=\"hljs-title\">username<\/span>'] ?? '' ?&gt;&lt;\/<span class=\"hljs-title\">small<\/span>&gt;\n        &lt;\/<span class=\"hljs-title\">div<\/span>&gt;\n\n        &lt;<span class=\"hljs-title\">div<\/span>&gt;\n            &lt;<span class=\"hljs-title\">label<\/span> <span class=\"hljs-title\">for<\/span>=\"<span class=\"hljs-title\">password<\/span>\"&gt;<span class=\"hljs-title\">Password<\/span>:&lt;\/<span class=\"hljs-title\">label<\/span>&gt;\n            &lt;<span class=\"hljs-title\">input<\/span> <span class=\"hljs-title\">type<\/span>=\"<span class=\"hljs-title\">password<\/span>\" <span class=\"hljs-title\">name<\/span>=\"<span class=\"hljs-title\">password<\/span>\" <span class=\"hljs-title\">id<\/span>=\"<span class=\"hljs-title\">password<\/span>\"&gt;\n            &lt;<span class=\"hljs-title\">small<\/span>&gt;&lt;?= $<span class=\"hljs-title\">errors<\/span>&#91;'<span class=\"hljs-title\">password<\/span>'] ?? '' ?&gt;&lt;\/<span class=\"hljs-title\">small<\/span>&gt;\n        &lt;\/<span class=\"hljs-title\">div<\/span>&gt;\n\n        &lt;<span class=\"hljs-title\">div<\/span>&gt;\n            &lt;<span class=\"hljs-title\">label<\/span> <span class=\"hljs-title\">for<\/span>=\"<span class=\"hljs-title\">remember_me<\/span>\"&gt;\n                &lt;<span class=\"hljs-title\">input<\/span> <span class=\"hljs-title\">type<\/span>=\"<span class=\"hljs-title\">checkbox<\/span>\" <span class=\"hljs-title\">name<\/span>=\"<span class=\"hljs-title\">remember_me<\/span>\" <span class=\"hljs-title\">id<\/span>=\"<span class=\"hljs-title\">remember_me<\/span>\"\n                    <span class=\"hljs-title\">value<\/span>=\"<span class=\"hljs-title\">checked<\/span>\" &lt;?= $<span class=\"hljs-title\">inputs<\/span>&#91;'<span class=\"hljs-title\">remember_me<\/span>'] ?? '' ?&gt; \/&gt;\n                <span class=\"hljs-title\">Remember<\/span> <span class=\"hljs-title\">Me<\/span>\n            &lt;\/<span class=\"hljs-title\">label<\/span>&gt;\n            &lt;<span class=\"hljs-title\">small<\/span>&gt;&lt;?= $<span class=\"hljs-title\">errors<\/span>&#91;'<span class=\"hljs-title\">agree<\/span>'] ?? '' ?&gt;&lt;\/<span class=\"hljs-title\">small<\/span>&gt;\n        &lt;\/<span class=\"hljs-title\">div<\/span>&gt;\n\n        &lt;<span class=\"hljs-title\">section<\/span>&gt;\n            &lt;<span class=\"hljs-title\">button<\/span> <span class=\"hljs-title\">type<\/span>=\"<span class=\"hljs-title\">submit<\/span>\"&gt;<span class=\"hljs-title\">Login<\/span>&lt;\/<span class=\"hljs-title\">button<\/span>&gt;\n            &lt;<span class=\"hljs-title\">a<\/span> <span class=\"hljs-title\">href<\/span>=\"<span class=\"hljs-title\">register<\/span>.<span class=\"hljs-title\">php<\/span>\"&gt;<span class=\"hljs-title\">Register<\/span>&lt;\/<span class=\"hljs-title\">a<\/span>&gt;\n        &lt;\/<span class=\"hljs-title\">section<\/span>&gt;\n\n    &lt;\/<span class=\"hljs-title\">form<\/span>&gt;\n\n&lt;?<span class=\"hljs-title\">php<\/span> <span class=\"hljs-title\">view<\/span>('<span class=\"hljs-title\">footer<\/span>') ?&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Second, add the code to handle the remember me checkbox to the <code>src\/login.php<\/code> file:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-meta\">&lt;?php<\/span>\n\n<span class=\"hljs-keyword\">if<\/span> (is_user_logged_in()) {\n    redirect_to(<span class=\"hljs-string\">'index.php'<\/span>);\n}\n\n$inputs = &#91;];\n$errors = &#91;];\n\n<span class=\"hljs-keyword\">if<\/span> (is_post_request()) {\n\n    &#91;$inputs, $errors] = filter($_POST, &#91;\n        <span class=\"hljs-string\">'username'<\/span> =&gt; <span class=\"hljs-string\">'string | required'<\/span>,\n        <span class=\"hljs-string\">'password'<\/span> =&gt; <span class=\"hljs-string\">'string | required'<\/span>,\n        <span class=\"hljs-string\">'remember_me'<\/span> =&gt; <span class=\"hljs-string\">'string'<\/span>\n    ]);\n\n    <span class=\"hljs-keyword\">if<\/span> ($errors) {\n        redirect_with(<span class=\"hljs-string\">'login.php'<\/span>, &#91;<span class=\"hljs-string\">'errors'<\/span> =&gt; $errors, <span class=\"hljs-string\">'inputs'<\/span> =&gt; $inputs]);\n    }\n\n    <span class=\"hljs-comment\">\/\/ if login fails<\/span>\n    <span class=\"hljs-keyword\">if<\/span> (!login($inputs&#91;<span class=\"hljs-string\">'username'<\/span>], $inputs&#91;<span class=\"hljs-string\">'password'<\/span>], <span class=\"hljs-keyword\">isset<\/span>($inputs&#91;<span class=\"hljs-string\">'remember_me'<\/span>]))) {\n\n        $errors&#91;<span class=\"hljs-string\">'login'<\/span>] = <span class=\"hljs-string\">'Invalid username or password'<\/span>;\n\n        redirect_with(<span class=\"hljs-string\">'login.php'<\/span>, &#91;\n            <span class=\"hljs-string\">'errors'<\/span> =&gt; $errors,\n            <span class=\"hljs-string\">'inputs'<\/span> =&gt; $inputs\n        ]);\n    }\n\n    <span class=\"hljs-comment\">\/\/ login successfully<\/span>\n    redirect_to(<span class=\"hljs-string\">'index.php'<\/span>);\n\n} <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (is_get_request()) {\n    &#91;$errors, $inputs] = session_flash(<span class=\"hljs-string\">'errors'<\/span>, <span class=\"hljs-string\">'inputs'<\/span>);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the <code>src\/login.php<\/code>, add the remember me checkbox to the filter() function call:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">&#91;$inputs, $errors] = filter($_POST, &#91;\n    <span class=\"hljs-string\">'username'<\/span> =&gt; <span class=\"hljs-string\">'string | required'<\/span>,\n    <span class=\"hljs-string\">'password'<\/span> =&gt; <span class=\"hljs-string\">'string | required'<\/span>,\n    <span class=\"hljs-string\">'remember_me'<\/span> =&gt; <span class=\"hljs-string\">'string'<\/span>\n]);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Also, add the third parameter to the login() function:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">login($inputs&#91;<span class=\"hljs-string\">'username'<\/span>], $inputs&#91;<span class=\"hljs-string\">'password'<\/span>], <span class=\"hljs-keyword\">isset<\/span>($inputs&#91;<span class=\"hljs-string\">'remember_me'<\/span>])<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We&#8217;ll go back to enhance the <code>login()<\/code> function later.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id='define-functions-for-handling-remember-me-feature'>Define functions for handling remember me feature <a href=\"#define-functions-for-handling-remember-me-feature\" class=\"anchor\" id=\"define-functions-for-handling-remember-me-feature\" title=\"Anchor for Define functions for handling remember me feature\">#<\/a><\/h2>\n\n\n\n<p>First, create the <code>remember.php<\/code> file in the <code>src<\/code> folder.<\/p>\n\n\n\n<p>Second, define the following new functions for handling the tokens in the <code>remember.php<\/code> file:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id='generate-tokens'>Generate tokens <a href=\"#generate-tokens\" class=\"anchor\" id=\"generate-tokens\" title=\"Anchor for Generate tokens\">#<\/a><\/h3>\n\n\n\n<p>The following defines the <code>generate_tokens()<\/code> to generate pair of random tokens called selector and validator:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">generate_tokens<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">array<\/span>\n<\/span>{\n    $selector = bin2hex(random_bytes(<span class=\"hljs-number\">16<\/span>));\n    $validator = bin2hex(random_bytes(<span class=\"hljs-number\">32<\/span>));\n\n    <span class=\"hljs-keyword\">return<\/span> &#91;$selector, $validator, $selector . <span class=\"hljs-string\">':'<\/span> . $validator];\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>generate_tokens()<\/code> function returns an array of three elements: <code>selector<\/code>, <code>valdiator<\/code>, and <code>selector:validator<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id='parse-the-token'>Parse the token <a href=\"#parse-the-token\" class=\"anchor\" id=\"parse-the-token\" title=\"Anchor for Parse the token\">#<\/a><\/h3>\n\n\n\n<p>The following <code>parse_token()<\/code> function splits the token stored in the cookie into <code>selector<\/code> and <code>validator<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">parse_token<\/span><span class=\"hljs-params\">(string $token)<\/span>: ?<span class=\"hljs-title\">array<\/span>\n<\/span>{\n    $parts = explode(<span class=\"hljs-string\">':'<\/span>, $token);\n\n    <span class=\"hljs-keyword\">if<\/span> ($parts &amp;&amp; count($parts) == <span class=\"hljs-number\">2<\/span>) {\n        <span class=\"hljs-keyword\">return<\/span> &#91;$parts&#91;<span class=\"hljs-number\">0<\/span>], $parts&#91;<span class=\"hljs-number\">1<\/span>]];\n    }\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">null<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id='insert-a-new-user-token'>Insert a new user token <a href=\"#insert-a-new-user-token\" class=\"anchor\" id=\"insert-a-new-user-token\" title=\"Anchor for Insert a new user token\">#<\/a><\/h3>\n\n\n\n<p>The following <code>insert_user_tokens()<\/code> function adds a new row to the <code>user_tokens<\/code> table:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">insert_user_token<\/span><span class=\"hljs-params\">(int $user_id, string $selector, string $hashed_validator, string $expiry)<\/span>: <span class=\"hljs-title\">bool<\/span>\n<\/span>{\n    $sql = <span class=\"hljs-string\">'INSERT INTO user_tokens(user_id, selector, hashed_validator, expiry)\n            VALUES(:user_id, :selector, :hashed_validator, :expiry)'<\/span>;\n\n    $statement = db()-&gt;prepare($sql);\n    $statement-&gt;bindValue(<span class=\"hljs-string\">':user_id'<\/span>, $user_id);\n    $statement-&gt;bindValue(<span class=\"hljs-string\">':selector'<\/span>, $selector);\n    $statement-&gt;bindValue(<span class=\"hljs-string\">':hashed_validator'<\/span>, $hashed_validator);\n    $statement-&gt;bindValue(<span class=\"hljs-string\">':expiry'<\/span>, $expiry);\n\n    <span class=\"hljs-keyword\">return<\/span> $statement-&gt;execute();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id='find-token-by-a-selector'>Find token by a selector <a href=\"#find-token-by-a-selector\" class=\"anchor\" id=\"find-token-by-a-selector\" title=\"Anchor for Find token by a selector\">#<\/a><\/h3>\n\n\n\n<p>The following <code>find_user_token_by_selector()<\/code> function finds a row in the <code>user_tokens<\/code> table by a selector. It only returns the match selector if the token is not expired by comparing the expiry with the current time.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">find_user_token_by_selector<\/span><span class=\"hljs-params\">(string $selector)<\/span>\n<\/span>{\n\n    $sql = <span class=\"hljs-string\">'SELECT id, selector, hashed_validator, user_id, expiry\n                FROM user_tokens\n                WHERE selector = :selector AND\n                    expiry &gt;= now()\n                LIMIT 1'<\/span>;\n\n    $statement = db()-&gt;prepare($sql);\n    $statement-&gt;bindValue(<span class=\"hljs-string\">':selector'<\/span>, $selector);\n\n    $statement-&gt;execute();\n\n    <span class=\"hljs-keyword\">return<\/span> $statement-&gt;fetch(PDO::FETCH_ASSOC);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id='delete-a-user-token'>Delete a user token <a href=\"#delete-a-user-token\" class=\"anchor\" id=\"delete-a-user-token\" title=\"Anchor for Delete a user token\">#<\/a><\/h3>\n\n\n\n<p>The following <code>delete_user_token()<\/code> function deletes all tokens associated with a user:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">delete_user_token<\/span><span class=\"hljs-params\">(int $user_id)<\/span>: <span class=\"hljs-title\">bool<\/span>\n<\/span>{\n    $sql = <span class=\"hljs-string\">'DELETE FROM user_tokens WHERE user_id = :user_id'<\/span>;\n    $statement = db()-&gt;prepare($sql);\n    $statement-&gt;bindValue(<span class=\"hljs-string\">':user_id'<\/span>, $user_id);\n\n    <span class=\"hljs-keyword\">return<\/span> $statement-&gt;execute();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id='find-a-user-by-a-token'>Find a user by a token <a href=\"#find-a-user-by-a-token\" class=\"anchor\" id=\"find-a-user-by-a-token\" title=\"Anchor for Find a user by a token\">#<\/a><\/h3>\n\n\n\n<p>The following <code>find_user_by_token()<\/code> function returns <code>user_id<\/code> and <code>username<\/code> by a token.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">find_user_by_token<\/span><span class=\"hljs-params\">(string $token)<\/span>\n<\/span>{\n    $tokens = parse_token($token);\n\n    <span class=\"hljs-keyword\">if<\/span> (!$tokens) {\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">null<\/span>;\n    }\n\n    $sql = <span class=\"hljs-string\">'SELECT users.id, username\n            FROM users\n            INNER JOIN user_tokens ON user_id = users.id\n            WHERE selector = :selector AND\n                expiry &gt; now()\n            LIMIT 1'<\/span>;\n\n    $statement = db()-&gt;prepare($sql);\n    $statement-&gt;bindValue(<span class=\"hljs-string\">':selector'<\/span>, $tokens&#91;<span class=\"hljs-number\">0<\/span>]);\n    $statement-&gt;execute();\n\n    <span class=\"hljs-keyword\">return<\/span> $statement-&gt;fetch(PDO::FETCH_ASSOC);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id='check-if-a-token-is-valid'>Check if a token is valid <a href=\"#check-if-a-token-is-valid\" class=\"anchor\" id=\"check-if-a-token-is-valid\" title=\"Anchor for Check if a token is valid\">#<\/a><\/h3>\n\n\n\n<p>The following <code>toke_is_valid()<\/code> function parse the token stored in the cookie (<code>selector:validator<\/code>) and return <code>true<\/code> if the token is valid and not expired:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">token_is_valid<\/span><span class=\"hljs-params\">(string $token)<\/span>: <span class=\"hljs-title\">bool<\/span> <\/span>{ \n    <span class=\"hljs-comment\">\/\/ parse the token to get the selector and validator <\/span>\n    &#91;$selector, $validator] = parse_token($token);\n    $tokens = find_user_token_by_selector($selector);\n    <span class=\"hljs-keyword\">if<\/span> (!$tokens) {\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">false<\/span>;\n    }\n    <span class=\"hljs-keyword\">return<\/span> password_verify($validator, $tokens&#91;<span class=\"hljs-string\">'hashed_validator'<\/span>]);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\" id='modifying-the-function-in-the-auth-php'>Modifying the function in the auth.php <a href=\"#modifying-the-function-in-the-auth-php\" class=\"anchor\" id=\"modifying-the-function-in-the-auth-php\" title=\"Anchor for Modifying the function in the auth.php\">#<\/a><\/h2>\n\n\n\n<p>The following describes the changes to the functions in the <code>auth.php<\/code> file:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id='the-login-function'>The login() function <a href=\"#the-login-function\" class=\"anchor\" id=\"the-login-function\" title=\"Anchor for The login() function\">#<\/a><\/h3>\n\n\n\n<p>The following adds the third parameter <code>$remember<\/code> to the <code>login()<\/code> function:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">login<\/span><span class=\"hljs-params\">(string $username, string $password, bool $remember = false)<\/span>: <span class=\"hljs-title\">bool<\/span>\n<\/span>{\n\n    $user = find_user_by_username($username);\n\n    <span class=\"hljs-comment\">\/\/ if user found, check the password<\/span>\n    <span class=\"hljs-keyword\">if<\/span> ($user &amp;&amp; is_user_active($user) &amp;&amp; password_verify($password, $user&#91;<span class=\"hljs-string\">'password'<\/span>])) {\n\n        log_user_in($user);\n\n        <span class=\"hljs-keyword\">if<\/span> ($remember) {\n            remember_me($user&#91;<span class=\"hljs-string\">'id'<\/span>]);\n        }\n\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">true<\/span>;\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">false<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If the <code>$remember<\/code> is <code>true<\/code>, call the <code>remember_me()<\/code> function.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id='the-log_user_in-function'>The log_user_in() function <a href=\"#the-log_user_in-function\" class=\"anchor\" id=\"the-log_user_in-function\" title=\"Anchor for The log_user_in() function\">#<\/a><\/h3>\n\n\n\n<p>The log_user_in() function logs a user in:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/**\n * log a user in\n * <span class=\"hljs-doctag\">@param<\/span> array $user\n * <span class=\"hljs-doctag\">@return<\/span> bool\n *\/<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">log_user_in<\/span><span class=\"hljs-params\">(array $user)<\/span>: <span class=\"hljs-title\">bool<\/span>\n<\/span>{\n    <span class=\"hljs-comment\">\/\/ prevent session fixation attack<\/span>\n    <span class=\"hljs-keyword\">if<\/span> (session_regenerate_id()) {\n        <span class=\"hljs-comment\">\/\/ set username &amp; id in the session<\/span>\n        $_SESSION&#91;<span class=\"hljs-string\">'username'<\/span>] = $user&#91;<span class=\"hljs-string\">'username'<\/span>];\n        $_SESSION&#91;<span class=\"hljs-string\">'user_id'<\/span>] = $user&#91;<span class=\"hljs-string\">'id'<\/span>];\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">true<\/span>;\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">false<\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id='the-remember_me-function'>The remember_me() function <a href=\"#the-remember_me-function\" class=\"anchor\" id=\"the-remember_me-function\" title=\"Anchor for The remember_me() function\">#<\/a><\/h3>\n\n\n\n<p>The following defines the <code>remember_me()<\/code> function:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">remember_me<\/span><span class=\"hljs-params\">(int $user_id, int $day = <span class=\"hljs-number\">30<\/span>)<\/span>\n<\/span>{\n    &#91;$selector, $validator, $token] = generate_tokens();\n\n    <span class=\"hljs-comment\">\/\/ remove all existing token associated with the user id<\/span>\n    delete_user_token($user_id);\n\n    <span class=\"hljs-comment\">\/\/ set expiration date<\/span>\n    $expired_seconds = time() + <span class=\"hljs-number\">60<\/span> * <span class=\"hljs-number\">60<\/span> * <span class=\"hljs-number\">24<\/span> * $day;\n\n    <span class=\"hljs-comment\">\/\/ insert a token to the database<\/span>\n    $hash_validator = password_hash($validator, PASSWORD_DEFAULT);\n    $expiry = date(<span class=\"hljs-string\">'Y-m-d H:i:s'<\/span>, $expired_seconds);\n\n    <span class=\"hljs-keyword\">if<\/span> (insert_user_token($user_id, $selector, $hash_validator, $expiry)) {\n        setcookie(<span class=\"hljs-string\">'remember_me'<\/span>, $token, $expired_seconds);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>remember_me()<\/code> function saves the login for a user for a specified number of days. By default, it remembers the login for 30 days.<\/p>\n\n\n\n<p>The <code>remember_me()<\/code> function does the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>First, generate <code>selector<\/code>, <code>validator<\/code>, and token (<code>selector:validator<\/code>)<\/li>\n\n\n\n<li>Second, insert a new row into the <code>user_tokens<\/code> table.<\/li>\n\n\n\n<li>Third, set a cookie with the specified expiration time.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id='the-logout-function'>The logout() function <a href=\"#the-logout-function\" class=\"anchor\" id=\"the-logout-function\" title=\"Anchor for The logout() function\">#<\/a><\/h3>\n\n\n\n<p>If users log out, besides deleting the session, you need to delete the records in the <code>user_tokens<\/code> table and remove the <code>remember_me<\/code> cookie:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">logout<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">void<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">if<\/span> (is_user_logged_in()) {\n\n        <span class=\"hljs-comment\">\/\/ delete the user token<\/span>\n        delete_user_token($_SESSION&#91;<span class=\"hljs-string\">'user_id'<\/span>]);\n\n        <span class=\"hljs-comment\">\/\/ delete session<\/span>\n        <span class=\"hljs-keyword\">unset<\/span>($_SESSION&#91;<span class=\"hljs-string\">'username'<\/span>], $_SESSION&#91;<span class=\"hljs-string\">'user_id`'<\/span>]);\n\n        <span class=\"hljs-comment\">\/\/ remove the remember_me cookie<\/span>\n        <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">isset<\/span>($_COOKIE&#91;<span class=\"hljs-string\">'remember_me'<\/span>])) {\n            <span class=\"hljs-keyword\">unset<\/span>($_COOKIE&#91;<span class=\"hljs-string\">'remember_me'<\/span>]);\n            setcookie(<span class=\"hljs-string\">'remember_user'<\/span>, <span class=\"hljs-keyword\">null<\/span>, <span class=\"hljs-number\">-1<\/span>);\n        }\n\n        <span class=\"hljs-comment\">\/\/ remove all session data<\/span>\n        session_destroy();\n\n        <span class=\"hljs-comment\">\/\/ redirect to the login page<\/span>\n        redirect_to(<span class=\"hljs-string\">'login.php'<\/span>);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id='the-is_user_logged_in-function'>The is_user_logged_in() function <a href=\"#the-is_user_logged_in-function\" class=\"anchor\" id=\"the-is_user_logged_in-function\" title=\"Anchor for The is_user_logged_in() function\">#<\/a><\/h3>\n\n\n\n<p>The following <code>is_user_logged_in()<\/code> function verifies if the user is currently logged in:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">is_user_logged_in<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">bool<\/span>\n<\/span>{\n    <span class=\"hljs-comment\">\/\/ check the session<\/span>\n    <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">isset<\/span>($_SESSION&#91;<span class=\"hljs-string\">'username'<\/span>])) {\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">true<\/span>;\n    }\n\n    <span class=\"hljs-comment\">\/\/ check the remember_me in cookie<\/span>\n    $token = filter_input(INPUT_COOKIE, <span class=\"hljs-string\">'remember_me'<\/span>, FILTER_SANITIZE_SPECIAL_CHARS);\n\n    <span class=\"hljs-keyword\">if<\/span> ($token &amp;&amp; token_is_valid($token)) {\n\n        $user = find_user_by_token($token);\n\n        <span class=\"hljs-keyword\">if<\/span> ($user) {\n            <span class=\"hljs-keyword\">return<\/span> log_user_in($user);\n        }\n    }\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">false<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>How it works<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>First, the function returns <code>true<\/code> if the session has the key <code>username<\/code>.<\/li>\n\n\n\n<li>Then, verify the token in the cookies and log the user in if the token is valid.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id='summary'>Summary <a href=\"#summary\" class=\"anchor\" id=\"summary\" title=\"Anchor for Summary\">#<\/a><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The remember me feature saves the login for some time even after the web browsers are closed.<\/li>\n\n\n\n<li>Use cookies to implement the remember me feature.<\/li>\n<\/ul>\n<div class=\"helpful-block-content\" data-title=\"\">\n\t<header>\n\t\t<div class=\"wth-question\">Did you find this tutorial useful?<\/div>\n\t\t<div class=\"wth-thumbs\">\n\t\t\t<button\n\t\t\t\tdata-post=\"2792\"\n\t\t\t\tdata-post-url=\"https:\/\/www.phptutorial.net\/php-tutorial\/php-remember-me\/\"\n\t\t\t\tdata-post-title=\"PHP Remember Me\"\n\t\t\t\tdata-response=\"1\"\n\t\t\t\tclass=\"wth-btn-rounded wth-yes-btn\"\n\t\t\t>\n\t\t\t\t<svg\n\t\t\t\t\txmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\tstroke-width=\"2\"\n\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\tclass=\"feather feather-thumbs-up block w-full h-full\"\n\t\t\t\t>\n\t\t\t\t\t<path\n\t\t\t\t\t\td=\"M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3\"\n\t\t\t\t\t><\/path>\n\t\t\t\t<\/svg>\n\t\t\t\t<span class=\"sr-only\"> Yes <\/span>\n\t\t\t<\/button>\n\n\t\t\t<button\n\t\t\t\tdata-response=\"0\"\n\t\t\t\tdata-post=\"2792\"\n\t\t\t\tdata-post-url=\"https:\/\/www.phptutorial.net\/php-tutorial\/php-remember-me\/\"\n\t\t\t\tdata-post-title=\"PHP Remember Me\"\n\t\t\t\tclass=\"wth-btn-rounded wth-no-btn\"\n\t\t\t>\n\t\t\t\t<svg\n\t\t\t\t\txmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\tstroke-width=\"2\"\n\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t>\n\t\t\t\t\t<path\n\t\t\t\t\t\td=\"M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17\"\n\t\t\t\t\t><\/path>\n\t\t\t\t<\/svg>\n\t\t\t\t<span class=\"sr-only\"> No <\/span>\n\t\t\t<\/button>\n\t\t<\/div>\n\t<\/header>\n\n\t<div class=\"wth-form hidden\">\n\t\t<div class=\"wth-form-wrapper\">\n\t\t\t<div class=\"wth-title\"><\/div>\n\t\t\t\n\t\t\t<textarea class=\"wth-message\"><\/textarea>\n\n\t\t\t<button class=\"btn btn-primary wth-btn-submit\">Send<\/button>\n\t\t\t<button class=\"btn wth-btn-cancel\">Cancel<\/button>\n\t\t\n\t\t<\/div>\n\t<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>In this tutorial, you&#8217;ll learn to securely implement the remember me feature in PHP.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":15,"menu_order":105,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-2792","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.phptutorial.net\/wp-json\/wp\/v2\/pages\/2792","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.phptutorial.net\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.phptutorial.net\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.phptutorial.net\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.phptutorial.net\/wp-json\/wp\/v2\/comments?post=2792"}],"version-history":[{"count":5,"href":"https:\/\/www.phptutorial.net\/wp-json\/wp\/v2\/pages\/2792\/revisions"}],"predecessor-version":[{"id":3343,"href":"https:\/\/www.phptutorial.net\/wp-json\/wp\/v2\/pages\/2792\/revisions\/3343"}],"up":[{"embeddable":true,"href":"https:\/\/www.phptutorial.net\/wp-json\/wp\/v2\/pages\/15"}],"wp:attachment":[{"href":"https:\/\/www.phptutorial.net\/wp-json\/wp\/v2\/media?parent=2792"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}