Lesson 6: Sessions & Cookies
Objectives
- Understand the concept of cookies and how they work
- Use a cookie to detect repeat visitors
- Use a session to track a user's login status
- Experience a game that uses a session to retain a user's progress
What are cookies?
The Web was invented to be "stateless". Every page you visit would be a clean slate, with no memory of what came before.
By the mid-1990s, it became clear that websites might benefit from some way of being able to "track" a user from one page load to another, to enable things like login sessions and shopping carts. The solution was cookies.
When you visit a website, it can optionally send a string to your browser, called a cookie. If your browser accepts the cookie, it will send it back for every page it subsequently requests from that site. It'll do this until either the cookie expires, the user deletes it, or the website asks the browser to delete it.
Before cookies became mainstream, imaginative developers came up with other ways to approximate their functionality, e.g. appending GET parameters to every internal link to "pass on" information to the next page, but this was not always reliable (e.g. if the visitor opened multiple browser windows pointing to the same website, for example).
Detecting repeat visitors
Let's have a go at implementing the thing from the diagram above - a web page that welcomes you with a different
message if you've visited the page before. Here's welcome-back.php:
<?php
// See if a cookie is set:
if( isset( $_COOKIE['visited'] ) ) {
// The cookie is set, so we welcome the user back.
echo "👋 Welcome back! You've visited this page before!";
// We could optionally set the cookie again here to renew it/
// extend its lifespan.
} else {
// Cookie is not set: welcome them!
echo "🎉 Welcome! This is your first visit.";
// We need to specify the number of SECONDS the cookie will
// last for, so we need to do some arithmetic:
$cookie_lifespan = 60 /* seconds in a minute */
* 60 /* minutes in an hour */
* 24 /* hours in a day */
* 7; /* days (a week) */
// Now we can set the cookie, which will last for a week.
// Our cookie just sets visited=1 and will be accessible
// on subsequent requests via $_COOKIE['visited'].
setcookie( 'visited', '1', time() + $cookie_lifespan );
}
Visit the page and you'll see the message saying it's your first visit. Refresh the page and you'll see it says "welcome back"!
Try opening the page in a private browsing window and you'll see that it doesn't recognise you. A private browsing window keeps a separate cookie store from your regular browsing window, and usually deletes all cookies when it's closed.
Could you extend this program so that it counts how many times the visitor has returned to the page? (🏁 view solution)
There are lots of options for the setcookie() function.
You've seen the first three (name, value, and lifespan). The others, in order, are:
-
path: which part of your website the cookie belongs to, by default it'll be the current directory your code is in, but if you'd like your cookie to be available throughout your entire site, you can set it to'/'. -
domain: if your site spans multiple subdomains and you want your cookie to span them all, you'll need to se this. E.g. if your site spanscool.example.comandfunny.example.com, a cookie will need to set adomainofexample.comto be accessed from both of them. -
secure: setting this totruetells browsers only to send the cookie over HTTPS connections. -
httponly: cookies can also be manipulated using JavaScript, but if you set this totruethen browsers won't let JavaScript access this cookie. That's useful if there's a chance that malicious JavaScript might end up being run on your site. -
options: if you want to do advanced things with cookies, like configuring how they behave when people hotlink to your page, you'll need this.
$_COOKIEs are example of user input, which means that
users can tamper with them.
Try it for yourself: after
visiting the "visit counter" page,
open your browser's Developer Tools (F12 or Ctrl+Shift+I in most browsers), go to the "Storage" tab,
and change the value of the "visited" cookie to a different number. Then visit the page again to see the change!
(How can cookies be secured?)
Using what you learned in the previous lesson, could you ask the user for their name and then use it whenever they come back? (🏁 view solution)
Logging in (and staying logged in)
The easiest way to use cookies in PHP is with PHP's session functionality. Sessions are a tamper-proof implementation of cookies (with, usually, a short life: until the user closes their browser) that provide easy-to-access variables to developers.
Just call session_start() near the top of your script, and you can use $_SESSION to store and retrieve data.
Let's make an admin section of a website that can be logged-into with a password. Here's login.php:
<?php
// Start the session:
session_start();
// Check if the user has requested to log out (?logout=true):
if( isset( $_GET['logout'] ) && $_GET['logout'] === 'true' ) {
// Log the user out:
session_destroy();
// Refresh the page so we start a new session!
header( 'Location: login.php' );
die();
}
// Check if a password was submitted ($_POST['password']):
if( isset( $_POST['password'] ) ) {
// Check if the password is correct:
if( $_POST['password'] === 'secret' ) {
// Password is correct: log the user in!
$_SESSION['logged_in'] = true;
} else {
// Password is incorrect: show an error message:
echo "<h2>Login failed</h2><p>Wrong password!</p>";
}
}
// Check if the user is logged in:
if( $_SESSION['logged_in'] ) {
// They are logged-in!
?>
<h1>You're logged in!</h1>
<ul>
<li>
<a href="members.php">Go to the members area</a>
</li>
<li>
<a href="login.php?logout=true">Log out</a>
</li>
</ul>
<?php
} else {
// They're not logged in!
?>
<h1>Login</h1>
<p>
Once you log in, you'll be able to access
<a href="members.php">the members area</a>.
</p>
<form method="post" action="login.php">
<label for="password">Password:</label>
<input type="password" id="password" name="password" autofocus>
<button type="submit">Login</button>
</form>
<p>
<small>
🤫 Shh! The password is "secret".
</small>
</p>
<?php
}
Let's also make a members area page that's only accessible if you've logged in - members.php:
<?php
// Start the session:
session_start();
// Check if the user is logged in:
if( ! $_SESSION['logged_in'] ) {
// They're not logged in - send them a HTTP 403 error code
// ("forbidden") and information about logging in:
header( 'HTTP/1.1 403 Forbidden' );
?>
<h1>Forbidden</h1>
<p>
You're not logged in, so you can't access this page.
<a href="login.php">Log in</a> to continue.
</p>
<?php
die();
}
?>
<h1>Members area</h1>
<p>
Welcome to the members area!
</p>
<p>
If this was a real members area, there'd be all kinds of secret things here!
</p>
<ul>
<li>
<a href="login.php?logout=true">Log out</a>
</li>
</ul>
Have a try at logging in and accessing the members area. Try accessing the members area without logging in, too (it shouldn't let you!).
If you use a session for authentication, be sure to check it on every page that needs protection! A common mistake is to e.g. check it on the main "members area" page, but then not check it on sub-pages of the members area. This means that anybody who can guess the address, or who has ever been logged-in before, can access those sub-pages!
Look at the new cookie using your browser's Developer Tools (F12 or Ctrl+Shift+I in most browsers). It'll be
called PHPSESSID and will contain a long random string. If you tamper with it, your session will be invalidated: you'd
have to successfully guess the random string belonging to somebody already-logged-in! The actual information (e.g. whether you're
logged in or not) is kept securely on the server, in a file whose name is referenced by that cookie.
Potion Finder game
Let's make a game! I've hidden four different-coloured potions on different pages of the site. Your job is to find them all. We'll use sessions to keep track of which potions you've found. Here's a page with more details, and a link to the source code.
Here's the first one, to get you started:
You found the blue potion! Click on it to collect it!
(What is this?)
Here's how it works:
-
You may remember from Lesson 4 that this site includes a file called
inc/functions.phpon every page. Well that file includes another, calledinc/potion-finder.php, which contains all of the logic for this game. -
It uses
session_start()to start a session, and a variable called$_SESSION['potions']to track which potions have been found. -
It also provides a convenience function called
draw_potion(), which I've used throughout the site. This function checks if you already have the specified potion and, if not, draws a picture of it with a link to collect it. - To prevent tampering with the links to instantly collect all the potions, each potion has a unique password which is included in its link and checked when the link is clicked.
-
inc/potion-finder.phpincludes logic for showing a page with the details of the potions collected so far, if you put?show_potions=trueon the end of a URL.