Archive for the ‘mysqli’ Category

User Account Class in PHP 2

Wednesday, February 10th, 2010 by Phillip Napieralski

I started up an account class in my previous post, but I left out a way for the user to authenticate! Let’s fix that…

Authenticate

	public function authenticate($group_id = 0) {
		$dbconn = @Database::grab();

		// They must have ALL three cookies set from the login function, otherwise something went wrong!
		if( !isset($_COOKIE['id']) && !isset($_COOKIE['cookie']) && !isset($_COOKIE['name']) )
		{
			return false;
		}

		// Sanitize
		$id = safe($dbconn, $_COOKIE['id']);
		$cookie = safe($dbconn, $_COOKIE['cookie']);
		$username = safe($dbconn, $_COOKIE['name']);

		// Make sure they have a valid cookie value first! (64 characters long in our case)
		if( strlen($cookie) > 63 )
		{
			// Check the values in the DB with a quick query
			$query = "SELECT id, username, cookie, group_id FROM users WHERE
					id = ".$id." AND
					cookie = '".$cookie."' AND username = '".$username."' AND group_id >= $group_id LIMIT 1";
			$queryData = $dbconn->query($query);
		}
		if( $queryData )
		{
			if( $queryData->num_rows > 0) {
				$data = $queryData->fetch_object();
				$this->group_id = $data->group_id;
				$this->user_id = $data->id;
				$this->username = $data->username;
				return true;
			}
		}

		// They failed to authenticate! Wrong username/pass?
		$this->logout();
		return false;
	}

There you have it. Basically, check to make sure they have all the cookies you set in your login function. If any are missing, fail. Then, we write a query to check the credentials from the cookies to see if they match what’s in the database. If not, clean up their cookies with $this->logout() and return false. That reminds me, here’s the code for logout:

Logout

	public function logout() {
		$dbconn = @Database::grab();
		
		if(isset($_COOKIE["id"]) && isset($_COOKIE["cookie"]))
		{
			$id = safe($dbconn, $_COOKIE['id']);
			$cookie = safe($dbconn, $_COOKIE['cookie']);

			// Set the cookie to null in the database
			$query = "UPDATE users SET cookie = 0 WHERE id = ".$id." AND cookie = '".$cookie."'";
			$dbconn->query($query);
		}
		// Expire our cookies by setting the time to 1 second after the EPOCH
		setcookie("hpname", "", 1, "/" );
		setcookie("hpid", "", 1,"/" );
		setcookie("hpcookie", "", 1,"/" );
		
		// Unset them immediately
		unset($_COOKIE['hpname']);
		unset($_COOKIE['hpid']);
		unset($_COOKIE['hpcookie']);
	}

There you go. What’s missing now? We just need a forgot password function and we’re good to go.

Lost on this tutorial? Try going back to part 1

User Account Class in PHP 1

Wednesday, February 3rd, 2010 by Phillip Napieralski

I’m going to describe the class I created to handle signup/login/authentication at happypolack.com.

If you’d like a demo, create an account at Happy Polack 🙂.

The Database Structure

First, what is it going to look like in the database? Well, here’s what I used:

Generated using MySQL Workbench

Take note of the indexes used. I have a foreign key relating the users and the groups table (1-1 for now). I also have a unique index on the username to prevent the same username. Lastly, I have a regular index on the email address to make searching that field faster (useful if they forgot their password).

The other thing to note is that both password and cookie have a datatype of CHAR(64). This is because I encrypted the password using a SHA256 algorithm, which is WAY stronger than md5, before placing it in the database (this is PHPs hash(“sha256”,$unenc_password) function). For the cookie field, I use that algorithm on the uniq_id() function built into PHP. I’ll get to that in a minute.

The Class Structure

class AccountController {
        // Initialize variables to something arbitrary
	private $group_id = -1;
	private $user_id = -1;
	private $username = '';

	public function signup($username, $password, $email){...}
	public function login($username, $password){...}
	public function logout(){...}
	public function authenticate($group_id = 0){...}
	public function is_logged_in(){...}
	public function is_admin(){...}
}

Excellent! We got the basic functionality out of the way. We are able to signup/login/logout/authenticate. We can even pass a group_id to authenticate to make sure they are part of a specific group.

Signup

public function signup($username, $password, $email) {
	$dbconn = @Database::grab(); // This WILL be different for you. I use a simple MySQLi wrapper.

	$origpassword =  $password; // store original password to send to them
	$password = hash('sha256', $password);
	if(!$dbconn->query($query))
		return false;
	else
	{
		// Send a nice email
		$message = '<b>Account Information:</b>';
		$message .= '<p>Username: '.$username;
		$message .= '</p><p>Password: '.$origpassword;
		$message .= '</p><p>Your profile: http://happypolack.com/user/'.$username.'</p>;

		send_mail( $email, "Welcome to Happy Polack!", "Hello and Welcome to Happy Polack!", $message );
	}
	return true;
}

There you have it! Now we can signup, well… except for the send_mail piece, let me give you the code for that:

function send_mail($to, $subject, $title, $msg)
{
	$headers = 'From: Happy Polack <you@example.com>'. "\r\n".
			'Reply-To: you@example.com' . "\r\n" .
			'X-Mailer: PHP/' .phpversion(). "\r\n";

	$headers .= 'MIME-Version: 1.0' . "\r\n";
	$headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";

	$message =  '<h1>'. $title.'</h1>';
	$message .= $msg;

	mail( $to, $subject, $message, $headers);
}

Basically, this nifty function automatically sets up the headers so we can use HTML in our e-mails and it automatically formats various features (this implementation could be spruced up with some neat css too).

Login

public function login($username, $password) {
	$dbconn = @Database::grab();

	$epassword = hash("sha256",$password); // Encrypt using SHA256. We could also "salt" the password before hashing for added security.

	$query = "SELECT id, group_id FROM users WHERE username = '$username' AND password = '$epassword' LIMIT 1";
	$qData = $dbconn->query($query) ;
	if( $qData->num_rows > 0 )
	{
		$userData = $qData->fetch_array();
		$cookie = generate_token();
		$userID = $userData['id'];

		$query = "UPDATE users SET cookie = '$cookie' WHERE username = '$username' AND password = '$epassword'";

		if($dbconn->query($Query))
		{
			$this->user_id = $userID;
			$this->group_id = $userData['group_id'];
			$expire_time = 60*60*24*7+time(); // About one week
			// When we create the cookies, we pass the cookie's name, the value, the expire time and what piece of the site the cookie is valid on. I specify "/" to allow it to be usable throughout the whole website.
			setcookie("id", $userID, $expire_time, "/");
			setcookie("cookie", $cookie, $expire_time, "/");
			setcookie("username", $username, $expire_time, "/");

			// This piece is so that the COOKIE values are set immediately. When I left this piece out, I noticed it would take an extra page load for the cookies to register. This is because the cookies are generally sent with everyday headers and *it seems* that they are not processed right away.
			$_COOKIE['id'] = $userID;
			$_COOKIE['cookie'] = $cookie;
			$_COOKIE['username'] = $username;

			return true; // SUCCESS, now logged in!
		}
	}
	return false; // Bummer!
}

What’s happening here? Well, let’s get a pretty picture to clarify the order a little bit:

Generated with yUML - Does this help?

In easier terms: Check if the user is in the database -> if he is, put a random value into his cookie field for later authentication and set his cookies.

That reminds me, here is one last function, generate_token(), that is pretty essential, and simple, to get a random string of length 64.

// Return a random string of length 64
function generate_token() {
	return hash('sha256', uniqid(mt_rand(), true));
}

Next Time

Wow! Progress has been made. But there’s more to be had. We still need an authenticate function as well as a way to logout!

Phew.