Nginx – Is it really possible to use PHP sessions for authentication with nginx fastcgi cache

cachefastcginginxphp-fpmsession

I have recently switched an opencart instance from Apache+mod_php to nginx+fastcgi+php-fpm. I have been trying to leverage caching for most pages through fastcgi-cache.

Unfortunately, many users began reporting ghost orders or taking over others accounts (weeee!!!!) From exhaustive digging, it appears that pages were cached with set-cookie! So subsequent users who did not send a pre-existing session cookie were getting the cache-initiator's session cookie. Bad!

According to all the documentation out there, the following settings are supposed to prevent this from happening (to the best of my understanding at least:)

 fastcgi_pass_header Set-Cookie;
 fastcgi_pass_header Cookie;
 fastcgi_ignore_headers Cache-Control Expires Set-Cookie;

When I looked through the individual caches I noticed several pages with set-cookie: [somerandomsessionid] According to nginx documentation under fastcgi_cache_valid…

If the header includes the “Set-Cookie” field, such a response will not be cached.

By including Set-Cookie with fastcgi_ignore_headers, am I telling it to cache set-cookie? In many examples, Set-Cookie is part of the arguments to fastcgi_ignore_headers. Or is it supposed to prevent Set-Cookie from being processed even though it's obviously in the cached files?

Here are the pertinent parts of my configuration:

location ~ .php$ { …

fastcgi_next_upstream error timeout invalid_header http_500 http_503;
fastcgi_cache OPENCART;
fastcgi_cache_bypass $no_cache;
fastcgi_no_cache $no_cache;
fastcgi_cache_purge $purge_method;
fastcgi_cache_methods GET HEAD;
fastcgi_cache_valid 200 5m;
fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_pass_header Set-Cookie;
#fastcgi_hide_header Set-Cookie;
fastcgi_pass_header Cookie;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;

My cache bypass rules (called in /etc/conf.d)…

################## Fast CGI Cache Settings

# if we find a PHP session cookie, let's cache it's contents

map $http_cookie $php_session_cookie {
    default "";
    ~PHPSESSID=(?<sessionkey>[a-zA-Z0-9]+) $sessionkey; # PHP session cookie
}

fastcgi_cache_path /var/nginx/cache levels=1:2 keys_zone=OPENCART:5m max_size=10000m inactive=15m;
fastcgi_cache_key "$scheme$request_method$host$request_uri$is_mobile$php_session_cookie";

map $request_method $purge_method {
    PURGE   1;
    default 0;
}

################## Cache Header

add_header X-Cache $upstream_cache_status;

################## Cache Bypass Maps

#Don't cache the following URLs
map $request_uri $no_cache_uri {
    default 0;
    ~*/admin/ 1;
    ~*/dl/ 1;
}

# ~*/music/mp3_[^/]+/[0-9]+/.+$ 1;

map $query_string $no_cache_query {
    default 0;
    ~*route=module/cart$ 1;
    ~*route=account/ 1; #exclude account links
    ~*route=checkout/ 1; #exclude checkout links
    ~*route=module/founders 1;
    ~*route=module/cart 1;
    ~*route=product/product/captcha 1;
    ~*nocache=1 1; # exclude ajax blocks and provide for manual cache override
}

map $http_cookie $no_cache_cookie {
    default 0;
}  

map $http_x_requested_with $no_cache_ajax {
    default 0;
    XMLHttpRequest 1; # Don't cache AJAX
}

map $sent_http_x_no_cache $no_no_cache {
    default 0;
    on 1; # Don't cache generic header when present and set to "on"
}

## Combine all results to get the cache bypass mapping.
map $no_cache_uri$no_cache_query$no_cache_cookie$no_cache_ajax$no_no_cache $no_cache {
        default 1;
        00000 0;
}

Session setting in php.ini

session.auto_start = 1
session.cache_expire = 180
session.cache_limiter = nocache
session.cookie_lifetime = 0
session.cookie_path = /
session.cookie_secure = 1
session.gc_divisor = 1000
session.gc_maxlifetime = 3600
session.gc_probability = 0
session.hash_function = "sha256"
session.name = PHPSESSID
session.serialize_handler = php
session.use_cookies = 1
session.use_only_cookies = 1
session.use_strict_mode = 1
session.use_trans_sid = 0

Opencart using session_start() on every page load so bypassing php session does me no good for the most part. If there were a way to prevent Set-Cookie headers from ending up in the cache, this would probably work for me.

Can anyone point me in the right direction?

Best Answer

You should also check what is in your cached files.

I had for example I had in "Set-Cookie" in some cached files.

vi /var/www/cache/prod/a/05/9671214cbf3a27f79135a52cbd5b305a

Set-Cookie: Mywebsite=arj4m9egloj9jhrlsps7cu29ec; expires=Fri, 08-Jun-2018 14:39:21 GMT; Max-Age=2592000; path=/

I deleted all my cached files.

rm -rf /var/www/cache/prod/*

And now I check if there is any new Set-Cookie in a new cached file:

grep -rn  "Set-Cookie" /var/www/cache/prod/

The best solution I found is to prevent in the PHP part from setting a session Cookie when the page will be cached :

if( (strpos($_SERVER['REQUEST_URI'], 'include/php/render') === FALSE) &&
    (!isBot()) ) { 
    // No session Cookie for PHP render Images
    // No session Cookie Bots (They create one for each page visisted!)
    $session_lifetime = 30*24*3600; //30 days
    session_set_cookie_params($session_lifetime,"/");
    ini_set('session.gc_maxlifetime', $session_lifetime);
    ini_set('session.name', 'Blackart');
    session_start();
}
Related Topic