Programster's Blog

Tutorials focusing on Linux, programming, and open-source

Cookies in PHP

PHP

Introduction

Cookies get a bad rap because of the way they have been used by corporations in order to track users across the internet in order to build profiles for better targeted advertising. However, they are just a tool, which can be used effectively in order to provide better experiences for users. This tutorial will be about how one can set/use cookies, and will not focus on how they are abused.

Cookie Storage

Before diving into how to set cookies, it is important to know that cookie information is stored on the client's browser. Thus the information will not be available if the user switches device or browser. This can be frustrating for a user if they use a laptop when at a desk, but then switch to a tablet/phone when they are mobile. They would probably want their settings to persist across both devices. Thus it is best to store things like user's profile settings such as language choice in a database, which is then retrieved whenever they log in. This way all devices are kept "in-sync". However, a cookie is a fabulous place to store information that would be useful to have before the user is logged in.

Setting Cookies

Believe it or not, you can use PHP to set a cookie on the client's machine with the setcookie function. The following example will set a cookie with the name user_language with a value of fr to indicate the user would like to view the site in French in future.

<?php
setcookie("user_language", "fr");

However, we only provided the two mandatory fields out of a possible 7. Based on the defaults, this means that:

  • The cookie will expire at the end of the session (when the browser is closed).
  • The path will be set to the current directory that the cookie is set in, thus if the user was on the page: blog.programster.org/admin then only routes with /admin or /admin/something-else will be able to retrieve the cookie.
  • We will only be able to access the cookie from the current subdomain, rather than any subdomain within our domain.
  • The cookie will be sent from the client back to the server even if they aren't using HTTPS.
  • The cookie will be accessible by Javascript.

Expirey

Most of the time, you would prefer the cookie to not expire when the user closes their browser, but last for perhaps a week. In such a case you would do something like this:

<?php
$secondsInWeek = 7 * 24 * 60 * 60;
$expirey = time() + $secondsInWeek;
setcookie("user_language", "fr", $expirey);

Path

If you want the cookie to be available across your entire site then manually set the path to / like so:

<?php
$path = '/';
setcookie("user_language", "fr", 0, $path);

Domain

If you wish for a cookie to be viewable across all of your subdomains, maybe part of some sort of single-sign-on functionality, then you want to manually set the domain. E.g. for the cookie to be accessible on blog.programster.org and sign-in.programster.org use:

<?php
$domain = 'programster.org';
setcookie("user_language", "fr", 0, "/", $domain);

Secure

These days everyone should always be using HTTPS to prevent information being leaked as it travels over the internet. To force cookie information to only be sent to the server if the client is connected over HTTPS set this to true:

<?php
$secure = TRUE;
setcookie("user_language", "fr", 0, "/", "programster.org", $secure);

HTTP Only

This will force the cookie to only be available through the http protocol. This does not mean that it's the opposite of secure, sending the cookie information only if the user is not using SSL. It actually just means that the cookie won't be accessible via Javascript etc. This could prevent XSS exploits from stealing cookies. I would suggest overriding this setting to TRUE whenever possible, unless you know that you need to access it with your own Javascript. Unfortunately, since it is the last setting this would mean having to fill in all the other default parameter variables each time as well.

<?php
$httpOnly = TRUE; // not accessible by Javascript for XSS security

setcookie(
    "user_language", 
    "fr", 
    0, 
    "/", 
    "programster.org", 
    TRUE, 
    $httpOnly
);

Retrieving Cookies

Cookies can be accessed using the $_COOKIE superglobal (meaning it can be accessed from any scope). In the following example, we will return the language that we set via cookie if it exists, and if not default back to english:

<?php

function getLanguagePreference() : string
{
    $language = "en-gb";

    if (isset($_COOKIE('user_language')))
    {
        $language = $_COOKIE('user_language');
    }

    return $language;
}

Security

You can't read a cookie that belongs to another domain. However, you can read it across sub-domains though (if you set the domain setting). For example, if I set a cookie for this site, blog.programster.org, I could read it in another of my sites at something like pigs-can-fly.programster.org

Persistent Sessions

PHP uses a cookie to store the session identifier. This session identifier is sent to the server with every request, and from that the server can access and retreive the user's $_SESSION data. By default, cookies have an expiry value of 0, which means they are removed as soon as the browser is closed. Thus, even if the session still exists on the server, the user cannot access it if they closed the browser. If you wish to have persistent sessions that last across the browser being closed and re-opened, then you just need to to update the expirey of the session cookie like so:

<?php
$oneDayInSeconds = 24 * 60 * 60;
$expirey = time() + $oneDayInSeconds;
session_start();
setcookie(session_name(), session_id(), $expirey);

If you have this at the entry point to your application, then the cookie will continue to be refreshed to 1 day after the page was visited. However, this only changes the cookie settings, you may need to tweak the max lifetime setting in the php.ini file, and possibly set your own session handler in order to ensure your sessions last long enough on the server itself.

References

Last updated: 26th August 2018
First published: 16th August 2018