Sécuriser un minimum une authentification par cookie en PHP, code source du site test

Bien que paraissant pas très "sécure", c'est quand même agréable de ne pas avoir à s'identifier systématiquement.

Je souhaite donc placer une information dans le cookie de l'utilisateur qui permettra de l'authentifier par la suite.

Ce n'est pas pour le site de la NASA, c'est pour ça qu'on va tolérer les cookies. Cependant on va tenter de répondre à quelques exigences de sécurité.

Considérations.

En cas de vol du cookies (sniffage des connexion, faille du navigateur, ou tout simplement avec une clé USB en direct sur le poste de l'utilisateur), le cookie ne doit pas permettre une authentification depuis un autre ordinateur que celui pour lequel il a été créé. Je m'appuie sur l'adrese IP. tant pis pour ceux qui n'ont pas une IP fixe, ils s'identifieront à chaque fois.

  • le cookie ne doit pas contenir d'indication sur une méthode d'authentification alternative (le formulaire identifiant mot de passe du site par exemple).
  • Changement du cookie à chaque connexion.

Mise en oeuvre.

Construisons un mini site pour le test.

Trois fichiers:

  • index.php
  • session.php
  • connectetoi.htm

par là: http://blog-du-grouik.tinad.fr/authbycookies

un fichier index.php tout simple:

<?php
 
//démarer une session sur le serveur
 
session_start();
 
if (!isset($_SESSION['is_registered'])){
	//La variable de session $_SESSION['is_registered'] n'existant pas, 
	//on appelle le script qui va lui donner une valeur:
	include("./session.php");
}
 
//$_SESSION['is_registered']=1 si l'utilisateur est authentifié
//$_SESSION['is_registered']=0 si ce n'est pas le cas
 
switch($_SESSION['is_registered']){
	case "1":
		// Il est reconnu, saluons le visiteur
		echo "<center> Bonjour, ".$_SESSION['username']."</center><br />";
		echo "<center><a href=\"./session.php?mod=deco\">se d&eacute;connecter</a></center>";
		break;
	case"0":
		// Il n'est reconu, envoyons le sur la page de loggin et d'inscription
		include("./connectetoi.htm");		
		break;
	default:
		//Si le script session.php appelé ci dessus est bien fait, ce cas est impossible
		echo "<center>Comment que t'as fait ça toi?</center>";
}
 
?>

Nous faisons un super site qui dira bonjour à l'utilisateur authentifié et qui renverra vers une page de login les autres ^^

le fichier connectetoi.htm. Il contient un formulaire de login et un formulaire d'inscription:

<form id="auth" method="POST" action="./session.php?mod=connect">
	<center>	
	<TABLE BORDER="0">
		<CAPTION>Identifiez vous:</CAPTION>
		<TR><TH>Identifiant:</TH><TD><input name="connect_id" type="text"></TD></TR>
		<TR><TH>Mot de passe:</TH><TD><input name="connect_pwd" type="password"></TD></TR>
		<TR><TH></TH><TD><input name="connect_submit" value="Se connecter" type="submit"></TD></TR>
	</table>
	</center>
</form>
 
<br/><br/>
 
<form id="inscription" method="POST" action="./session.php?mod=inscription">
	<center>	
	<TABLE BORDER="0">
		<CAPTION>Inscrivez vous:</CAPTION>
		<TR><TH>Votre identifiant</TH><TD><input name="inscription_id" type="text"></TD></TR>
		<TR><TH>Mot de passe:</TH><TD><input name="inscription_pwd" type="password"></TD></TR>
		<TR><TH>Confirmez votre mot de passe:</TH><TD><input name="inscription_pwd_confirm" type="password"></TD></TR>
		<TR><TH></TH><TD><input name="inscription_submit" value="S'inscrire" type="submit"></TD></TR>
	</table>
	</center>
</form>

Le fichier session.php qui gèrera toutes les ations relatives à l'inscription, la connection, etc...:

<?php
if (!mysql_connect('localhost', 'MySqlUsername', '**********************')) {
	echo 'Impossible de se connecter à MySQL';
	exit;
}
mysql_query("USE blogdugrouik");
 
// Generate a random character string
 
function rand_str($length = 32, $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890')
{
//fonction par kyle dot florence [@t] gmail dot com 08-May-2009 10:23
//publiée là: http://php.net/manual/fr/function.rand.php
    // Length of character list
    $chars_length = (strlen($chars) - 1);
 
    // Start our string
    $string = $chars{rand(0, $chars_length)};
 
    // Generate random string
    for ($i = 1; $i < $length; $i = strlen($string))
    {
        // Grab a random character from our list
        $r = $chars{rand(0, $chars_length)};
 
        // Make sure the same two characters don't appear next to each other
        if ($r != $string{$i - 1}) $string .=  $r;
    }
 
    // Return the string
    return $string;
}
function placecookie($iduser){
	//c'est la fonction qui place un cookie chez l'utilisateur
	$valuecookie = rand_str();
	setcookie('authbycookies[value]',$valuecookie,time()+60*60*24*30,'/authbycookies/','blog-du-grouik.tinad.fr');
 
	mysql_query("UPDATE 
			test_cookies
		SET
			update_time=NOW(),
			unique_key='".md5($valuecookie)."',
			user_ip='".$_SERVER["REMOTE_ADDR"]."'
		WHERE 
		user_id='".$iduser."'");
 
}
session_start();
//par défaut:
$_SESSION['is_registered']=(int)(0);
 
 
 
switch($_GET['mod']){
	case "inscription":
 
		//Vérifier que les trois champs sont remplis
		if (($_POST['inscription_pwd']=="")||($_POST['inscription_pwd_confirm']=="")||($_POST['inscription_id']=="")){
			echo "<center><H1>Vous n'avez pas renseign&eacute; tous les champs pour l'inscription</H1></center>";			
			include("connectetoi.htm");
			die;
		}
		//vérifier la correspondance des mots de passe
		if($_POST['inscription_pwd'] <> $_POST['inscription_pwd_confirm']){
			echo "<center><H1>les mots de passe ne correspondent pas</H1></center>";			
			include("connectetoi.htm");
			die;			
		}
 
		//bon bah on inscrit l'user
		mysql_query("INSERT INTO 
				test_cookies(
					username,
					password,
					update_time)
				VALUES(
					'".mysql_real_escape_string($_POST['inscription_id'])."',
					'".mysql_real_escape_string(md5($_POST['inscription_pwd']))."',
					NOW()");		
		//et arf, on le connecte
		$_SESSION['is_registered']=(int)(1);
		$_SESSION['username']=$_POST['inscription_id'];
 
		//trouve son userid
		$result=mysql_query("SELECT 
						user_id 
					FROM
						test_cookies
					WHERE
						username='".mysql_real_escape_string($_POST['inscription_id'])."'
						AND password='".mysql_real_escape_string(md5($_POST['inscription_pwd']))."'");
 
		$row=mysql_fetch_row($result);
		//cookie
		placecookie($row[0]);
 
		header("Location: index.php");
		die;
		break;
 
	case "connect":
		//on cherche un enregisrement contenant l'username et le md5 du mot de passe
		$result=mysql_query("SELECT 
						user_id 
					FROM
						test_cookies
					WHERE
						username='".mysql_real_escape_string($_POST['connect_id'])."'
						AND password='".mysql_real_escape_string(md5($_POST['connect_pwd']))."'");
 
		$row=mysql_fetch_row($result);
		if (isset($row[0])){
			//ok
			//on enregistre la connection
			mysql_query("UPDATE test_cookies SET update_time=NOW() WHERE user_id='".$row[0]."'");
			$_SESSION['is_registered']=(int)(1);
			$_SESSION['username']=$_POST['connect_id'];
 
			//cookie
			placecookie($row[0]);
 
			//redirection			
			header("Location: index.php");
			die;
		}else{
			//fail
			$_SESSION['is_registered']=(int)(0);
			header("Location: index.php");
			die;
		}
		break;
	case "deco":
		//echo "plop";
		//setcookie('authbycookies[value]','');
		setcookie("authbycookies[value]", false, time() - 3600,'/authbycookies/','blog-du-grouik.tinad.fr'); 
		$_SESSION['is_registered']=(int)(0);
		unset($_COOKIE['authbycookies']['value']);
		session_unset();
 
		header("Location: index.php");
		die;		
		break;
	default:
 
}
 
//test de l'authentification par cookie
 
if (!isset($_COOKIE['authbycookies']['value'])){
	$_SESSION['is_registered']=(int)(0);
}else{
	$result = mysql_query("SELECT 
					user_id, 
					username
				FROM 
					test_cookies
				WHERE 
					unique_key='".md5($_COOKIE['authbycookies']['value'])."'		
					AND user_ip='".$_SERVER["REMOTE_ADDR"]."'");
	$row=mysql_fetch_row($result);
	if (isset($row[0])){	
		//ok
		//on enregistre la connection
		mysql_query("UPDATE test_cookies SET update_time=NOW() WHERE user_id='".$row[0]."'");
		$_SESSION['is_registered']=(int)(1);
		$_SESSION['username']=$_POST['connect_id'];
 
		// renouvellement du cookie
		placecookie($row[0]);
 
	}else{
		$_SESSION['is_registered']=(int)(0);
	}	
 
}
 
?>

la table dans la base de données est faite comme ça:

mysql> describe test_cookies;
+-------------+----------+------+-----+---------+----------------+
| Field       | Type     | Null | Key | Default | Extra          |
+-------------+----------+------+-----+---------+----------------+
| user_id     | int(11)  | NO   | PRI | NULL    | auto_increment | 
| username    | text     | NO   |     | NULL    |                | 
| password    | text     | NO   |     | NULL    |                | 
| update_time | datetime | NO   |     | NULL    |                | 
| unique_key  | text     | NO   |     | NULL    |                | 
| user_ip     | text     | NO   |     | NULL    |                | 
+-------------+----------+------+-----+---------+----------------+
6 rows in set (0.01 sec)

Conclusion

C'est super brouillon, et j'ai la flegme d'écrire un tutoriel là dessus. Mais ça me resservira de l'avoir à porté de main.

Commentaires

1. Le jeudi, août 26 2010, 07:05 par un flémard...

JE trouve que ça fait beaucoup de lignes pour un cookie...
Explique moi un peu l'utilité de tout ça alors que tu pourais faire un truc du genre

if(isset$_SESSION['REMOTE_ADDR'] && $_SERVER['REMOTE_ADDR']==$_SESSION['REMOTE_ADDR'])
{
include('login.php');
//
}

avec les verrifications qui s'imposent et l'affectation de $_SESSION['REMOTE_ADDR'] dans login.php evidement...

2. Le jeudi, août 26 2010, 08:43 par gnieark

Bonjour flémar

Mon titre est mal choisi car ce n'est pas que le cookie, mais aussi l'authentification normale là.

je l'utilise de cette manière au début des fichiers qui sont appelés par des includes. ça fait moins de code que dans votre commentaire:

if($_SESSION['is_registered']!="1")
{
echo "";
die;
}

Dans l'index.php cependant là si l'utilisateur n'est pas authentifié, je le renvoie sur la page de loggin, et ce n'est qu'une fois:

if (!isset($_SESSION['is_registered'])){
include("./session.php");
}

switch($_SESSION['is_registered']){

case "1":
//site
break;
default:
include("./connectetoi.htm"); break;
}

Ce qui revient exactement au même que votre proposition sauf que j'ai appelé votre fichier "login.php" session.php

Page top