Drupal for Privacy Buffs: Configuring Your Drupal 6 Site to Work Without Cookies for Anonymous Users

A Drupal 6 site we are currently building has a requirement to not set cookies for anonymous users. In this blog post, I describe the hack-free solution we implemented for them that prevents Drupal from setting cookies.

There are three steps we took to put this solution into place:

  1. Override Drupal’s session.inc file to disable cookies and prevent writing to the sessions table.
  2. Implement a hook_form_alter to re-enable cookies when a user is trying to log in.
  3. Add a bit of javascript to remove the has_js cookie.

Step 1

Drupal makes it really easy to override its PHP session handling functions. First, copy the default include file from includes/session.inc to an accessible location of your choice — we used sites/all/includes/mysite_session.inc. Then edit that file and make the following changes:

mysite_session.inc

@@ -24,6 +23,8 @@ function sess_read($key) {
// Handle the case of first time visitors and clients that don't store cookies (eg. web crawlers).
if (!isset($_COOKIE[session_name()])) {
+ // Custom modification: turn off session cookies
+ if (substr($_GET['q'], 0, 11) != 'user/reset/') {
+ ini_set('session.use_cookies', 0);
+ }
$user = drupal_anonymous_user();
return '';
}
@@ -61,7 +62,12 @@ function sess_write($key, $value) {
// the session table. This reduces memory and server load, and gives more useful
// statistics. We can't eliminate anonymous session table rows without breaking
// the throttle module and the "Who's Online" block.
- if (!session_save_session() || ($user->uid == 0 && empty($_COOKIE[session_name()]) && empty($value))) {
+ // Custom modification: We won't use throttle nor "Who's Online", so we are
+ // eliminating anonymous sessions.
+ if (!session_save_session() || $user->uid == 0) {
return TRUE;
}

EDIT: The code above has been changed since this blog post was originally posted. Namely, we now don’t turn off cookies on the user/reset path, which allows one-time logins to work correctly (and anyone visiting that page has the intention to log in anyway).

The first change we made was to the sess_read function, which by default checks to see if there’s already a cookie set, and if not, considers the user anonymous. Since the user is anonymous, we can turn off all future cookie setting here by setting the session.use_cookies PHP variable to 0. The second change is to sess_write, which writes to the sessions database table. Usually, Drupal will write a new session row for a first-time anonymous user, but we modify it here to never write to the table if the user is anonymous.

Now we need to tell Drupal to use our custom session functions instead of the default ones. To do that, we just add a variable override to our settings.php file, which we can do in the section titled “Variable overrides.” Simply add the following line in that section, or add it to any already existing overrides you may have:

settings.php
‘./sites/all/includes/mysite_session.inc’,
);
?>

And that’s all we need to do to prevent session cookies from being written! Of course, the problem here is that when a user tries to log in, Drupal won’t be able to start a session. So we have to let Drupal know when it should allow cookies again.

Step 2

To do this, we only need to put a simple hook_form_alter function in a custom utility module:

custommodule.module

EDIT: The code above has been tweaked since the blog was originally posted. We now handle one-time login above in the session file, since a form_alter on ‘user_pass_reset’ doesn’t work reliably, due to how the one-time login form is implemented.

All we are doing here is re-enabling the PHP session.use_cookies variable when a user is submitting a user login form. You may have to add more form_id’s here if you have other ways a user can log in. You could also use this technique to let a user enable cookies at their choosing when taking a particular action.

With this in place, a user will now get a cookie only when logging in (and as such we can warn them in advance). To minimize the intrusion, we could set the cookie_lifetime variable to 0 in settings.php so that cookie expires at the end of the session, but since only staff members can log in on our client’s site, we did not need to do that.

Step 3

Session cookies have been taken care of, so we’re all done, right? Not exactly. After implementing the above and doing a test drive, I noticed a pesky “has_js” cookie was still being set, and the guilty party was the default drupal.js file. This cookie is added by javascript as a way to let code know that the user has javascript enabled — in core, it’s used only by the batch processing API so that it can know when to show a fancy progress bar while processing batch jobs.

Unfortunately, we can’t circumvent the setting of this cookie without hacking drupal.js, so our solution here was to immediately make the cookie expire using our own javascript. The first step is to add our custom remove_has_js.js function only when drupal.js is included on a page requested by an anonymous user:

template.php
uid) {
drupal_add_js(drupal_get_path(‘theme’, ‘customtheme’) . ‘/js/remove_has_js.js’);
$vars[‘scripts’] = drupal_get_js();
}
}
?>

The remove_has_js.js file itself is fairly simple, setting the expiration date of the has_js cookie in the past:

remove_has_js.js

if (Drupal.jsEnabled) {
// remove 'js enabled' cookie
document.cookie = 'has_js=1; expires=Fri, 19 Nov 1978 05:00:00 GMT; path=/';
}

And that’s it! Now when we browse around the site as an anonymous user, we are consistently cookie-free.

Qualifications

I should note that a consequence of this solution is that anonymous users will not have a Drupal session. There are solutions out there that replace cookies with URL sessions, but we did not want to burden our URL’s with long strings and, since the site is mostly informational with user interaction handled externally, we had no great need to maintain anonymous sessions. Nevertheless, if you implement this, be sure to test all anonymous functionality on your site. This solution isn’t compatible with the throttle module nor with the “Who’s Online” block that comes with core, and could potentially conflict with contributed module functionality. Also, this solution doesn’t prevent contributed modules from setting cookies, so be aware of that possibility as well.

On the other hand, not having to write to the sessions table could be a big plus for site performance, especially if your site gets heavy anonymous traffic.

External Cookies

Even with this solution in place, we still need to be careful of external sites setting cookies via Drupal. For example, 3rd party video providers often set a cookie even if the user does not choose to play a video. Fortunately, Advomatic’s Aaron Winborn modified the Embedded Media Field module to allow the use of a thumbnail display that doesn’t add the embedded code until the thumbnail is clicked, before which the user can be properly warned.

You may have to add similar functionality to other modules you are using that depend on 3rd party functionality that set cookies, or if there’s a contributed module that you are relying on that doesn’t have this feature, submit an issue asking for the maintainer to add it in.