PHPBoek();
11 Cookies
Cookies zijn kleine tekstbestandjes die in de webbrowser van de bezoeker kunnen worden opgeslagen, om ze op een later tijdstip weer uit te lezen. Ze maken het mogelijk informatie te onthouden en te gebruiken op verschillende pagina's van je website. Cookies kunnen voor vele doeleinden worden gebruikt, waaronder het realiseren van een login-gedeelte op een website.
Theorie
In dit hoofdstuk bekijken we hoe een cookie wordt opgeslagen in de webbrowser van de bezoeker en hoe het weer uitgelezen kan worden. Ook kijken we naar wat technische kanten van cookies die handig zijn om in het achterhoofd te houden.
Beperkingen en veiligheid
Cookies kennen een instelbare geldigheid die zelf gekozen kan worden. Als de geldigheid verloopt wordt het cookie automatisch bij de bezoeker verwijderd. Hierbij zijn er twee mogelijkheden: een cookie laten verwijderen als de bezoeker zijn browser sluit, of een specifieke einddatum opgeven. In het laatste geval wordt het cookie door de browser bewaard, ook als deze tussendoor wordt gesloten. Hou er rekening mee dat een bezoeker altijd de mogelijkheid heeft zijn cookies te verwijderen. Ook zijn cookies gebonden aan één specifieke browser op één specifiek gebruikersaccount op één specifieke computer (sommige browsers kunnen cookies meenemen tussen verschillende installaties door koppeling aan een gebruikersaccount, maar dat laten we hier even buiten beschouwing). Er kan dus nooit van uit worden gegaan dat een geplaatst cookie altijd blijft bestaan. Informatie die niet verloren mag gaan kan dus beter niet in een cookie worden opgeslagen.
Omdat cookies tekstbestandjes zijn die op de computer van de bezoeker worden opgeslagen, zijn ze vrij eenvoudig te vervalsen. Zo kan een bezoeker de inhoud en de geldigheid van een cookie wijzigen. Informatie in een cookie is dus niet per definitie te vertrouwen! Dit is een belangrijk gegeven als het gaat om het maken van beveiligde gedeeltes op een website, waarover meer in hoofdstuk 30.
Cookie plaatsen
De functie setcookie()
maakt het mogelijk om een cookie op de computer van de bezoeker op te slaan:
$name
[, string $value
= ""
[, int $expire
= 0
[, string $path
= ""
[, string $domain
= ""
[, bool $secure
= FALSE
[, bool $httponly
= FALSE
]]]]]] )
Belangrijk: een cookie kan alleen worden opgeslagen als nog geen ander output naar de browser is verzonden. De functie setcookie()
moet dus worden gebruikt voor je doctype, je <html>
, je <head>
, etc. Er mag zelfs geen spatie voor de PHP-openingstag <?php
staan!
De parameters van setcookie()
zijn achtereenvolgens $name
voor de naam van het cookie. De hier gekozen naam gebruik je ook weer voor het uitlezen van het cookie. Wat je wil opslaan in het cookie wordt gegeven in $value
. De geldigheid van het cookie wordt bepaald door $expire
. Geef hier waarde 0
(nul) om aan te geven dat het cookie moet worden verwijderd als de bezoeker zijn browser sluit. Voor een langere geldigheid wordt de verloopdatum gegeven als UNIX tijdstempel. De laatste interessante parameter is $path
. Standaard geldt een cookie voor de huidige map en alle mappen die zich daar in bevinden. Om een cookie geldig te maken voor de hele website (domein) wordt $path
ingesteld op waarde '/'
.
Laten we eens een eenvoudig voorbeeld bekijken. Onderstaande code plaatst een cookie met de naam phpboek_cookie en Hallo wereld! als inhoud. De geldigheid van het cookie is ingesteld op 0, dus het cookie is weg als de bezoeker de browser sluit.
$c_naam = 'phpboek_cookie';
$c_waarde = 'Hallo wereld!';
setcookie($c_naam, $c_waarde, 0, '/');
?>
In onderstaand voorbeeld geven we het cookie een zelf gekozen geldigheid mee.
$c_tijd = time() + 604800;
setcookie($c_naam, $c_waarde, $c_tijd, '/');
?>
Hierbij gebruiken we de functie time()
.
Deze functie geeft het UNIX tijdstempel van dit moment (in seconden). De tijd die het cookie geldig moet blijven wordt hier bij opgeteld. Om een cookie een week geldig te laten moet er dus 604800 seconden bij opgeteld worden.
Cookie lezen
Een eenmaal geplaatst cookie kan worden uitgelezen via de globale variabele $_COOKIE[]
. Deze array bevat alle cookies die door de website geplaatst zijn. De zelf gekozen naam van een cookie komt terug als sleutel in de array $_COOKIE[]
. Hou er rekening mee dat een geplaatst cookie pas bij de volgende pagina die de bezoeker opvraagt beschikbaar is in $_COOKIE[]
. Plaatsen en direct uitlezen in hetzelfde script is dus niet mogelijk.
Het eerder geplaatste cookie kunnen we dus als volgt uitlezen:
$c_naam = 'phpboek_cookie';
echo htmlspecialchars($_COOKIE[$c_naam]);
?>
Omdat niet zeker is dat de inhoud van een cookie hetzelfde is als wat er in is gezet, gebruiken we htmlspecialchars()
om eventuele code-injecties onschadelijk te maken.
Cookie verwijderen
Er is geen functie om een cookie te verwijderen. In plaats daarvan wordt een nieuw cookie gezet met dezelfde naam. Dit keer echter zonder inhoud en een geldigheidsdatum in het verleden. Het eerder geplaatste cookie kan als volgt worden verwijder:
$c_naam = 'phpboek_cookie';
setcookie($c_naam, '', 1, '/');
?>
Als tijdstip voor geldigheid is het UNIX tijdstempel 1
gekozen. Dit is het tijdstip 01-01-1970 00:00:01, zo ver in het verleden als mogelijk is. Er kan ook een ander tijdstempel worden gekozen, zo lang dit maar in het verleden ligt is het effect hetzelfde.
Array opslaan in cookie
Soms is het nodig om meerdere verschillende waarden in een cookie op te slaan. Het is mogelijk om voor iedere waarde een apart cookie te maken, maar het is netter om alles wat je nodig hebt voor je website in één cookie te bewaren. Plaats hiervoor alle waarden die je wilt bewaren in een array, en sla de array op in het cookie. Echter, als we terug kijken naar de parameters van de functie setcookie()
, dan moet de informatie die in het cookie wordt opgeslagen een string zijn en geen array!
Om de array te converteren naar een string kan de functie serialize()
gebruikt worden.
$value
)
Deze functie converteert (onder andere) een array op een dusdanige manier naar een string zodat er later weer een array van gemaakt kan worden. Hiermee kunnen we een array in een cookie opslaan.
$gebruiker['id'] = 21;
$gebruiker['token'] = "3492080e224e475afd747dd89fb01607";
//serialize de array
$cookie_value = serialize($gebruiker);
//zet geserializede array in cookie
setcookie("cookie", $cookie_value, time() + 3600, '/');
?>
Als het cookie wordt opgehaald kan van de geserializeerde array weer een echte array gemaakt worden met hulp van de functie unserialize()
. De werking hiervan is gelijk als die van zijn tegenhanger.
$str
[, array $options
] )
De oorspronkelijke array kan hiermee als volgt uit het cookie van het vorige voorbeeld worden teruggehaald:
$gebruiker = unserialize($_COOKIE['cookie']);
?>
Praktijkvoorbeeld: loginscript
Eén van de meest voorkomende toepassingen van cookies zijn beveiligde gedeeltes van een website. Daarbij wordt een cookie gebruikt om de bezoeker te identificeren en eventueel bij een volgend bezoek automatisch in te loggen. Een dergelijk loginscript bestaat feitelijk uit drie delen: een script om de gebruiker te laten aanmelden met gebruikersnaam en wachtwoord, een script om te bepalen of gebruiker daadwerkelijk is aangemeld, en een script om de gebruiker af te melden. Dit praktijkvoorbeeld gaat in op een heel eenvoudig loginscript. In geval van een website met veel gebruikers wordt meestal een database gebruikt, maar dat doen we hier nog niet.
Het wordt ten strengste afgeraden om het navolgende praktijkvoorbeeld in een productieomgeving te gebruiken. Het is bedoeld om de basisprincipes van een loginscript uit te leggen en gaat voorbij aan verschillende noodzakelijke beveiligingsmaatregelen. Zie hoofdstuk 30 voor meer informatie.
Aanmeldscript
Het script om aan te melden bestaat uit een HTML-formulier voor het invoeren van gebruikersnaam en wachtwoord, een deel dat controleert of gebruikersnaam en wachtwoord correct zijn en een deel dat het cookie plaatst. Als basis gebruiken we het volgende HTML-formulier:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<p>Vul gebruikersnaam en wachtwoord in om toegang te krijgen tot het beveiligde gedeelte van deze website</p>
<form method="post">
<table>
<tr><td>Gebruikersnaam:</td><td><input type="text" name="username"></td></tr>
<tr><td>Wachtwoord:</td><td><input type="password" name="password"></td></tr>
<tr><td></td><td><input type="submit" value="Login"></td></tr>
</table>
</form>
</body>
</html>
Het formulier is eenvoudig en bevat velden voor gebruikersnaam en wachtwoord. Er is geen formulier-actie opgegeven, dus post naar zichzelf (zie ook het praktijkvoorbeeld van hoofdstuk 10). Omdat een cookie alleen geplaatst kan worden als nog geen andere uitvoer naar de browser is gestuurd, betekent dit dat we de aanmeld controle helemaal bovenaan het bestand moeten toevoegen.
Voordat we dat doen maken we eerst een apart bestand met alle gebruikersnamen en bijbehorende wachtwoorden. Deze slaan we op in een array, waarbij de sleutel gelijk is aan de gebruikersnaam en het wachtwoord de bijbehorende waarde. Het bestand slaan we op als users.inc.php en gaan we gebruiken voor zowel het aanmeldscript als het script dat gaat controleren of iemand is ingelogd.
//Lijst met gebruikersnamen en wachtwoorden.
$gebruikers = array(
'jantje' => 'FHASSVAE',
'pietje' => 'DSFHEASD',
'keesje' => 'SJSAFSAE'
);
?>
De controle voor de gebruikersnaam en wachtwoord is als volgt: eerst kijken we of er een gebruikersnaam en wachtwoord zijn ingevuld; zo ja, dan wordt de ingevulde gebruikersnaam gebruikt als sleutel voor de array $gebruikers
en het hierbij gevonden wachtwoord vergeleken met het ingevulde wachtwoord. Hierbij wordt het eerder gemaakte bestand users.inc.php met behulp van include()
opgenomen in het script.
include('users.inc.php');
//als formulier verzonden
if (!empty($_POST['username']) && !empty($_POST['password'])) {
//controleer wachtwoord
if ($gebruikers[$_POST['username']] == $_POST['password']) {
$cookie['username'] = $_POST['username'];
$cookie['password'] = $_POST['password'];
//zet cookie
setcookie('login', serialize($cookie), time() + 60*60*24*7*2, '/');
//login is gelukt
$login_correct = TRUE;
}
//wachtwoord niet correct
else {
$login_error = TRUE;
}
}
?>
Als gebruikersnaam en wachtwoord correct zijn, dan worden beide als array in een cookie opgeslagen. Hierdoor kan de gebruikersnaam/wachtwoord controle uitgevoerd worden op andere pagina's van de website zonder dat de gebruiker steeds gebruikersnaam en wachtwoord moet opgeven. Omdat cookies als tekst worden opgeslagen op de computer van de gebruiker is het niet slim het wachtwoord open en bloot in het cookie te zetten. Dat maakt het voor kwaadwillenden heel eenvoudig om een wachtwoord uit een cookie te vissen.
Het in een cookie opslaan van een wachtwoord is om verschillenden redenen onverstandig. Het wordt ten strengste afgeraden om dit praktijkvoorbeeld in een productieomgeving te gebruiken. Zie hoofdstuk 30 voor meer informatie.
In het script wordt er gebruik gemaakt van twee hulpvariabelen $login_correct
en $login_error
om de bezoeker te laten weten of de ingevulde informatie goed of fout was. Hiervoor moet de HTML pagina met het loginformulier nog wat worden aangepast:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Login</title>
</head>
<body>
<?php if ($login_correct === TRUE) { ?>
<h1>Login gelukt!</h1>
<p>Welkom in het beveiligde gedeelte van deze website!</p>
<?php } else { ?>
<h1>Login</h1>
<p>Vul gebruikersnaam en wachtwoord in om toegang te krijgen tot het beveiligde gedeelte van deze website</p>
<?php
if ($login_error === TRUE) {
echo '<p class="error">De gebruikersnaam/wachtwoord combinatie bestaat niet.</p>';
}
?>
<form method="post">
<table>
<tr><td>Gebruikersnaam:</td><td><input type="text" name="username"></td></tr>
<tr><td>Wachtwoord:</td><td><input type="password" name="password"></td></tr>
<tr><td></td><td><input type="submit" value="Login"></td></tr>
</table>
</form>
<?php } ?>
</body>
</html>
Nu wordt bij een correcte login getoond dat dit het geval is. Voor praktisch gebruik kunnen hier wat links opgenomen worden naar beveiligde onderdelen van de website. Als de login niet gelukt is wordt het formulier getoond met foutmelding. Het totale script wordt daarmee als volgt:
include('users.inc.php');
//als formulier verzonden
if (!empty($_POST['username']) && !empty($_POST['password'])) {
//controleer wachtwoord
if ($gebruikers[$_POST['username']] == $_POST['password']) {
$cookie['username'] = $_POST['username'];
$cookie['password'] = $_POST['password'];
//zet cookie
setcookie('login', serialize($cookie), time() + 60*60*24*7*2, '/');
//login is gelukt
$login_correct = TRUE;
}
//wachtwoord niet correct
else {
$login_error = TRUE;
}
}
?>
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Login</title>
</head>
<body>
<?php if ($login_correct === TRUE) { ?>
<h1>Login gelukt!</h1>
<p>Welkom in het beveiligde gedeelte van deze website!</p>
<?php } else { ?>
<h1>Login</h1>
<p>Vul gebruikersnaam en wachtwoord in om toegang te krijgen tot het beveiligde gedeelte van deze website</p>
<?php
if ($login_error === TRUE) {
echo '<p class="error">De gebruikersnaam/wachtwoord combinatie bestaat niet.</p>';
}
?>
<form method="post">
<table>
<tr><td>Gebruikersnaam:</td><td><input type="text" name="username"></td></tr>
<tr><td>Wachtwoord:</td><td><input type="password" name="password"></td></tr>
<tr><td></td><td><input type="submit" value="Login"></td></tr>
</table>
</form>
<?php } ?>
</body>
</html>
Login controle
We kunnen nu inloggen waarbij een cookie wordt geplaatst. Maar zolang het cookie nergens wordt gecontroleerd is er nog niets beveiligd. In deze paragraaf bekijken we een eenvoudig controle-script dat alleen maar hoeft te worden toegevoegd bovenaan de broncode van de pagina's die moeten worden beveiligd. Hiervoor moeten we onze lijst met gebruikersnamen en wachtwoorden hebben, de inhoud van het cookie lezen en controleren of de gegevens in het cookie overeen komen met de lijst. De code daarvoor ziet er als volgt uit:
include('users.inc.php');
$cookie = unserialize($_COOKIE['login']);
if ($gebruikers[$cookie['username']] != $cookie['password']) {
echo 'Niet ingelogd!';
exit;
}
?>
De controle werkt als volgt: met behulp van de gebruikersnaam uit het cookie wordt het hierbij behorende wachtwoord gelezen uit de array $gebruikers
. Dit wachtwoord wordt vergeleken met het wachtwoord dat in het cookie is opgeslagen. Zijn deze niet gelijk, dan is de gebruiker niet ingelogd! Als de bezoeker niet is ingelogd wordt dit gemeld en wordt het uitvoeren van het script gestopt met behulp van exit
:
$status
] )
exit
zorgt ervoor dat de uitvoering van het script direct wordt gestopt. Alles wat na exit
staat wordt genegeerd en ook niet meer naar de bezoeker gestuurd (omdat exit
geen echte functie maar een taalconstructie is hoeven er geen haakjes gebruikt te worden, mag wel). De te beveiligen pagina is hiermee dus afgeschermd; enkel bovenaan dit stukje script toevoegen.
Afmeldscript
Nu dat bezoekers kunnen inloggen, is het ook handig om ze de mogelijkheid te geven om ze te laten uitloggen. Zeker omdat het cookie twee weken geldig is. De code hiervoor is bijzonder simpel: het cookie verwijderen wat eerder gezet is. Slechts één regel code!
setcookie('login', '', 1, '/');
?>
Tot besluit nog maar een keer. Het in een cookie opslaan van een wachtwoord is om verschillenden redenen onverstandig. Het wordt ten strengste afgeraden om dit praktijkvoorbeeld in een productieomgeving te gebruiken. Zie hoofdstuk 30 voor meer informatie. Als je het toch wil gebruiken, wijs dan zelf willekeurige wachtwoorden toe aan je gebruikers, laat ze deze niet zelf wijzigen en vertel dat ze het toegekende wachtwoord nergens anders voor moeten hergebruiken.
Zelftest
- De inhoud van een cookie kan worden uitgelezen via:
setcookie()
getcookie()
$_COOKIE[]
$COOKIE[]
- Wanneer is de inhoud van een cookie betrouwbaar?
- Nooit.
- Alleen als de geldigheid niet is verstreken.
- Alleen als een cookie wordt gezet dat niet automatisch met het sluiten van de browser verwijderd wordt.
- Altijd.
- Welke van onderstaande stellingen is/zijn waar?
I Het is mogelijk een cookie te plaatsen met een onbeperkte geldigheid.
II Om een cookie te verwijderen wordt een geldigheid in het verleden ingesteld.- Alleen stelling I is waar.
- Alleen stelling II is waar.
- Beide stellingen zijn waar.
- Beide stellingen zijn onwaar.
- De geldigheid van een cookie wordt uitgedrukt in:
- seconden vanaf het moment van plaatsen.
- seconden vanaf 1 januari 1970.
- minuten vanaf het moment van plaatsen.
- minuten vanaf 1 januari 1970.
Antwoorden
- c
- a
- b
- b
Oefening: Cookie plaatsen en lezen
De volgende array wordt gegeven:
$array['laatste_bezoek'] = time();
Opdracht 1: cookie plaatsen
Schrijf het script om de array op te slaan in een cookie. Het cookie moet twee weken geldig blijven.
Opdracht 2: cookie lezen
Schrijf een afzonderlijk script om het cookie uit te lezen en weer te geven zoals het volgende voorbeeld:
Laatste bezoek: 1370727753
Uitwerking
opdracht1.php
<?php
$array['naam'] = "phpboek";
$array['laatste_bezoek'] = time();
setcookie('phpboek', serialize($array), time() + 1209600, '/');
?>
opdracht2.php
<?php
$array = unserialize($_COOKIE['phpboek']);
echo 'Naam: '.$array['naam'];
echo '<br />';
echo 'Laatste bezoek: '.$array['laatste_bezoek'];
?>