Implementing a Secure Remember Me Login Solution

This one set of function sets took me the better part of the day to get to the point to where I’m happy with it. Enough checks and balances for security, handles efficiently, and doesn’t produce any errors!

I’m developing a site, from scratch PHP, with a lot of drop-in scripts, reference taken from Composer.

I’m using Zebra Sessions for database session handling. I have authentication working beautifully, will be enabling SSL soon, then wanted to implement a “Remember Me” function. I figured that was some part of the Zebra Sessions class, and then learned that it’s a completely whole new system entirely.  Just like authentication, there are ways to do it, and ways to do it well and securely.  This is a TL;DR crash-course in how a “Remember Me” function works and how to implement it…

Things to know…

  • Barely ties into your authentication system.  You can do this without much more than an additional database table and a few lines in your current authentication script.
  • $_SESSION data is used during the current browser session.  If you have Chrome/Firefox/Opera restore your last session automatically, some of this data could be held, but most times you will have an empty $_SESSION data set when you open an instance of your browser.
  • $_COOKIE data can persist over the course of multiple browser sessions, and expire whenever they’re set to.  This is where you’ll store your “Remember Me” data.
  • This class has three main functions:
    • setRememberMe which is run when a successful authentication has occurred and the user had the “Remember Me” checkbox checked AND when you’re regenerating an authentication based on the cookie
    • checkRememberMe which is run when there’s no current logged in and authenticated session present.  Handles a lot of the security measures automatically.
    • logout which is run when the user initiates a Logout function while having a “Remember Me” cookie set which deletes the cookie and the rows from the database.

How this “Remember Me” function works…

  1. User logs in with the “Remember Me” check box marked on log in.
  2. Upon successful authentication, a cookie is set with a security triplet: userID, series token, and signature.  The series token is something that is very large and very random, and will be kept among successful re-authentications to show signs of tampering and track re-authentications. The signature is a hashed value of a secret key, the date of creation, and the other cookie data. This will be verified to check authenticity of all the data sets.
  3. User browses and accesses the application, does what is needed to be done, and shuts down his system or otherwise closes his browser

A short while later…

  1. User comes back a few hours/days later, the authenticated session is no longer available, but the “Remember Me” cookie is still set so long as it hasn’t expired.
  2. Site detects there is no authenticated user session, would have otherwise gone to redirect/restrict access to the supposed guest users. Instead, having failed the authentication check, the application checks for the presence of the “Remember Me” cookie and finds it.
  3. Application verifies the details of the cookie with what is in the database, creates a new session for the previously authenticated user, nulls the old database entry, generates a new cookie, and updates the database with the new cookie details.

And from there it basically just repeats itself till either the user forcefully logs out, thus manually destroying the cookie as well, or till the cookie expires.

Well let’s get down to brass tax…

Front-end Modifications

  1. Modify your Login Screen HTML Form to include a checkbox, set the name/id/type, wrap it in a label.

That’s it!

Back-end Modifications

  1. Create a new class, define a few private variables, and with the construct is where you’ll define those variables. We’ll layout the general functions of the rest of the class too…
    class rememberMe {
    	private $key = null; //Very large, very random key. Defined in construct function
    	private $cookieExpiration; //Time in the future to set the expiration date for the cookies set.
    	private $databaseExpiration; //Time in the future past $this->cookieExpiration where it's safe to remove from the database if it's a used row.
    	private $link; //DB Handler
    	private $table_name; //Table name to use.  Default is 'session_rememberMe_data'
    	private $restrictIP; //Restrict Remember Me cookies to use only by the same IP.  Not preferred for NAT'd networks, which are EVERYWHERE.  Even...in your HOME!  Most likely anyway.  Default set to false.
    	private $restrictUA; //Restrict Remember Me cookies to be able to be used only by the same User Agent.  IE does some stupid shit and changes it as often as it dumps the memory, so your session might cookie might become invalid upon random visits and then destroyed.  Default set to false.
    
    	function __construct($link, $table_name = 'session_rememberMe_data', $restrict_to_ip = false, $restrict_to_user_agent = false) {
    		$this->key = LARGE_RANDOM_PRIVATE_KEY_HERE;
    		$this->cookieExpiration = ( time() + 604800 ); //7d
    		$this->databaseExpiration = ( $this->cookieExpiration + 604800 ); //7d after cookie expiration
    		$this->link = $link;
    		$this->table_name = $table_name;
    		$this->restrictIP = $restrict_to_ip;
    		$this->restrictUA = $restrict_to_user_agent;
    	} //End construct function
    
    	//Logout function used to clean up current cookie from the database...
    	public function logout() {
    		// If cookie is set..
    			// Load and decode Cookie..
    			// Delete/Null matching cookie data from database
    			// Delete cookie from user browser
    	} //End public function logout
    

    Now that we have the class created, we’ll integrate it with the current authentication system

  2. Your authentication system should have a function/method to check the status of a logged in user, assuming the function method is called loggedIn().  Somewhere in your main initial authentication checks…
    if ( !loggedIn() ) {
    	$rememberMe = new rememberMe($databaseHandler);
    	if ( $rememberMe->checkRememberMe() ) {
    		//User remember me data has been verified, session has been set to reauthenticate the user, and new cookie data has already been generated
    		//Redirect, or show welcome back message?
    	}
    }
    

    This loads the kemo_RememberMe class and checks the cookie. The checkRememberMe function either will validate the matching cookie and reauthenticate the user/session, or destroy it.

  3. In your authentication system’s log out function/method, assuming it’s called logOut(), inside that function call the following to destroy session/cookie data…This will destroy the current table row set for the authenticated user if they decide to manually log out. This prevents any sessions understood to be logged out from being hijacked.
    function logOut() {
    	$rememberMe = new rememberMe($databaseHandler);
    	$rememberMe->logout();
    	//Go on to unset/destroy session, restart with blank session
    }
    

    Now whenever the user manually logs out, or otherwise fails a reauthentication check and the logOut()

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>