Proper way to use "Remember me" functionality in PHP
Asked Answered
M

4

16

Short

Working on login system and trying to implement remember me feature.

Recently, l did research about this subject, read bunch of articles, posts, stories, novels, fairy tales (calling them so, because some of them doesn't contain even 1 line of code, just loads of words) about, cookie vulnerabilities such as fixation, hijacking ... etc.

And decided to achieve following targets

  1. To set time delay between login attempts (to prevent bruteforce attacks) and to limit attempts count
  2. To regenerate session id on nearly every operation

But I really confused about my main problem: which way is proper, for "remember me" feature? to use cookies/session/database?

And please explain your idea on code.(I can't understand clearly without code)

Detailed

Currently, my code looks like that

During sign-in I'm using following function to set cookies and session

protected function validateUser($userid, $ckey=0, $rememmber=0) {
    session_start();
    session_regenerate_id(true); //this is a security measure
    $_SESSION['user_id'] = $userid;
    $_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
    if (isset($remember) && $rememmber == 'on') {
        setcookie("user_id", $_SESSION['user_id'], time() + 60 * 60 * 24 * COOKIE_TIME_OUT, "/");
        setcookie("user_key", sha1($ckey), time() + 60 * 60 * 24 * COOKIE_TIME_OUT, "/");
    }
    return true;
}

Then on secure user pages, checking for user_id using user_id to fetch all important data about user from db

public function protect() {
        session_start();

        /* Secure against Session Hijacking by checking user agent */
        if (isset($_SESSION['HTTP_USER_AGENT'])) {
            if ($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT'])) {
                $this->signout();
                exit;
            }
        }

// before we allow sessions, we need to check authentication key - ckey and ctime stored in database

        /* If session not set, check for cookies set by Remember me */
        if (!isset($_SESSION['user_id'])) {
            if (isset($_COOKIE['user_id']) && isset($_COOKIE['user_key'])) {
                /* we double check cookie expiry time against stored in database */

                $cookie_user_id = $_COOKIE['user_id'];
                               $stmt = $this->db->prepare("select `ckey`,`ctime` from `users` where `id` =?") or die($this->db->error);
            $stmt->bind_param("i", $cookie_user_id) or die(htmlspecialchars($stmt->error));
            $stmt->execute() or die(htmlspecialchars($stmt->error));
            $stmt->bind_result($ckey, $ctime) or die($stmt->error);
            $stmt->close() or die(htmlspecialchars($stmt->error));
                // coookie expiry
                if ((time() - $ctime) > 60 * 60 * 24 * COOKIE_TIME_OUT) {
                    $this->signout();
                }
                /* Security check with untrusted cookies - dont trust value stored in cookie.       
                  /* We also do authentication check of the `ckey` stored in cookie matches that stored in database during login */

                if (!empty($ckey) && is_numeric($_COOKIE['user_id']) && $_COOKIE['key'] == sha1($ckey)) {
                    session_regenerate_id(); //against session fixation attacks.

                    $_SESSION['user_id'] = $_COOKIE['user_id'];
                    $_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
                } else {
                    $this->signout();
                }
            } else {
                if ($page != 'main') {
                    header('Location:' . wsurl);
                    exit();
                }
            }
        }
Melbamelborn answered 18/1, 2012 at 8:47 Comment(4)
Just a comment: From a UX perspective I think it's a good point to let your users know whether you mean "keep me logged in" or just "remember my identity".Ammoniacal
A probable duplicate of: #245382Corrianne
@Corrianne ok, you'r cool:)Melbamelborn
Generate unique, very long and random tokens on the server, store them both in your database for later lookups and in the session, which will usually write some session identifier to a cookie behind the scenes. Or use some library like github.com/delight-im/PHP-Auth which does all this for you.Meshwork
D
2

But I really confused about my main problem: which way is proper, for "remember me" feature? to use cookies/session/database?

Http is a stateless protocall. Authentication token must persist to keep the state. Proper way is to use session. Now how do you track the session? It's up to you. But cookies are not bad.

In the session you can save a hash created from browser different criteria(user agent, os, screen resolution etc) to check if the token is from same environment. The more criteria you save the more itll be harder to hijack. Btw you need JavaScript to grab ths extra information every time.

Domiciliary answered 18/1, 2012 at 9:32 Comment(1)
I hate downvoting, but try sending it over URL(I know it looks ugly). - is really target for downvote. It's well known cookie fixation attack vulnerabilityMelbamelborn
W
8

To set time delay between login attempts (to prevent bruteforce attacks) and to limit attempts count

So you're providing a method for DOS by account?

To regenerate session id on nearly every operation

erm, no. That's actually likely to defeat the object. You should always generate a new id when the current id is expired or when the user is authenticated - otherwise leave it alone.

But I really confused about my main problem: which way is proper, for "remember me" feature? to use cookies/session/database?

Since you need to retain a token on the client, that means cookies (unless you fancy writing something really complicated using local storage). Since you don't want to expose data via the cookie / make forgery simple that means it should be a random value. And in order to reconcile the stored random value, that means storing data serverside - probably in a database since it must be possible to reference the data based on the user id or based on the random value.

While you could just use a non-expiring (or very long lived) session, I'd stay away from this - the data will snowball - and it's a good idea to renew the session data once in a while.

You also need to cater for the scenario where a user wants you to remember her on 2 different computers. If you only hold a single 'remember me' token for each account, then either you'll have to use the same value at both clients or delete the old token when you create a new one (i.e. user can only be remembered on one machine).

please explain your idea on code. I can't understand clearly without code

No. I get paid to write code; If you want me to write the code for you then you'll need to pay me. And the code will take up much more space and time than the description above.

Waldner answered 18/1, 2012 at 9:29 Comment(2)
In this community, we're trying to help each other. I'm developer too. Just wanted to get advise and some code example of your idea, (I'm not english speaker. Not all of your post 100% clear to me) Anyway, thank you for wasting your time for free:)Melbamelborn
90% of the users on SO get's paid to write code. Yet only a small fraction holds your attitude.Offering
F
4

If you want an example of a "remember me" function with code, you can check out my PHP library at https://github.com/gbirke/rememberme

Foghorn answered 18/1, 2012 at 8:53 Comment(1)
I got it work on firefox. But on chrome your test files doesn't work: stays on first page, doesn't log in.Melbamelborn
D
2

But I really confused about my main problem: which way is proper, for "remember me" feature? to use cookies/session/database?

Http is a stateless protocall. Authentication token must persist to keep the state. Proper way is to use session. Now how do you track the session? It's up to you. But cookies are not bad.

In the session you can save a hash created from browser different criteria(user agent, os, screen resolution etc) to check if the token is from same environment. The more criteria you save the more itll be harder to hijack. Btw you need JavaScript to grab ths extra information every time.

Domiciliary answered 18/1, 2012 at 9:32 Comment(1)
I hate downvoting, but try sending it over URL(I know it looks ugly). - is really target for downvote. It's well known cookie fixation attack vulnerabilityMelbamelborn
F
-1

Cookies are best solution. Check out the following code:

<form action="" method="post" id="frmLogin">
<div class="error-message"><?php if(isset($message)) { echo $message; } ?></div>    
<div class="field-group">
    <div><label for="login">Username</label></div>
    <div><input name="member_name" type="text" value="<?php if(isset($_COOKIE["member_login"])) { echo $_COOKIE["member_login"]; } ?>" class="input-field">
</div>
<div class="field-group">
    <div><label for="password">Password</label></div>
    <div><input name="member_password" type="password" value="<?php if(isset($_COOKIE["member_password"])) { echo $_COOKIE["member_password"]; } ?>" class="input-field"> 
</div>
<div class="field-group">
    <div><input type="checkbox" name="remember" id="remember" <?php if(isset($_COOKIE["member_login"])) { ?> checked <?php } ?> />
    <label for="remember-me">Remember me</label>
</div>
<div class="field-group">
    <div><input type="submit" name="login" value="Login" class="form-submit-button"></span></div>
</div>       

session_start();
if(!empty($_POST["login"])) {
    $conn = mysqli_connect("localhost", "root", "", "blog_samples");
    $sql = "Select * from members where member_name = '" . $_POST["member_name"] . "' and member_password = '" . md5($_POST["member_password"]) . "'";
    $result = mysqli_query($conn,$sql);
    $user = mysqli_fetch_array($result);
    if($user) {
            $_SESSION["member_id"]         = $user["member_id"];

            if(!empty($_POST["remember"])) {
                setcookie ("member_login",$_POST["member_name"],time()+ (10 * 365 * 24 * 60 * 60));
                setcookie ("member_password",$_POST["member_password"],time()+ (10 * 365 * 24 * 60 * 60));
            } else {
                if(isset($_COOKIE["member_login"])) {
                    setcookie ("member_login","");
                }
                if(isset($_COOKIE["member_password"])) {
                    setcookie ("member_password","");
                }
            }
    } else {
        $message = "Invalid Login";
    }
}
Fraudulent answered 5/6, 2018 at 7:1 Comment(1)
A link to a solution is welcome, but please ensure your answer is useful without it: add context around the link so your fellow users will have some idea what it is and why it’s there, then quote the most relevant part of the page you're linking to in case the target page is unavailable. Answers that are little more than a link may be deleted.Adulterine

© 2022 - 2024 — McMap. All rights reserved.