Search code examples
wordpresscookiesnginxwoocommercepci-compliance

Adding secure attribute to Woocommerce cookies


I am trying to get our server PCI compliant and down to the last issue of setting the Woocommerce cookies to secure. I am running all current versions of Wordpress/Woocommerce and the server is running 100% SSL/HTTPS across the entire site.

The cookies I am trying to secure: woocommerce_recently_viewed

I have tried the following with no luck:

Added to my functions file:

add_filter( 'wc_session_use_secure_cookie', '__return_true' );

Added to index.php:

@ini_set('session.cookie_httponly', 'On');
@ini_set('session.cookie_secure', 'On');
@ini_set('session.use_only_cookies', 'On');

Added to php.ini:

session.cookie_httponly = 1
session.cookie_secure = 1
session.use_only_cookies = 1

My last resort is to adjust the server config (I'm running Nginx) BUT would rather handle this issue on the application level. Any help on this issue would be most appreciated.


Solution

  • First things first: woocommerce_recently_viewed isn't a "session cookie" in the PHP sense. It is a normal cookie created with the setcookie PHP function.

    This means that neither ini_set nor wc_session_use_secure_cookie will change that behaviour.

    I've downloaded the WooCommerce source code and found woocommerce\includes\wc-product-functions.php:

    /**
     * Track product views.
     */
    function wc_track_product_view() {
        if ( ! is_singular( 'product' ) || ! is_active_widget( false, false, 'woocommerce_recently_viewed_products', true ) ) {
            return;
        }
    
        global $post;
    
        if ( empty( $_COOKIE['woocommerce_recently_viewed'] ) )
            $viewed_products = array();
        else
            $viewed_products = (array) explode( '|', $_COOKIE['woocommerce_recently_viewed'] );
    
        if ( ! in_array( $post->ID, $viewed_products ) ) {
            $viewed_products[] = $post->ID;
        }
    
        if ( sizeof( $viewed_products ) > 15 ) {
            array_shift( $viewed_products );
        }
    
        // Store for session only
        wc_setcookie( 'woocommerce_recently_viewed', implode( '|', $viewed_products ) );
    }
    
    add_action( 'template_redirect', 'wc_track_product_view', 20 );
    

    The wc_setcookie is defined as follows (woocommerce\includes\wc-core-functions.php):

    /**
     * Set a cookie - wrapper for setcookie using WP constants.
     *
     * @param  string  $name   Name of the cookie being set.
     * @param  string  $value  Value of the cookie.
     * @param  integer $expire Expiry of the cookie.
     * @param  string  $secure Whether the cookie should be served only over https.
     */
    function wc_setcookie( $name, $value, $expire = 0, $secure = false ) {
        if ( ! headers_sent() ) {
            setcookie( $name, $value, $expire, COOKIEPATH ? COOKIEPATH : '/', COOKIE_DOMAIN, $secure );
        } elseif ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
            headers_sent( $file, $line );
            trigger_error( "{$name} cookie cannot be set - headers already sent by {$file} on line {$line}", E_USER_NOTICE );
        }
    }
    

    As you can see, there isn't any wordpress filter to hook into (should really be a setting, ask a feature request!), so you need to add the $secure parameter to the woocommerce sources...

    ...but there is another way (kinda monkey-patch, but hey, at least we don't break things across updates):

    function custom_wc_track_product_view() {
        if ( ! is_singular( 'product' ) || ! is_active_widget( false, false, 'woocommerce_recently_viewed_products', true ) ) {
            return;
        }
    
        global $post;
    
        if ( empty( $_COOKIE['woocommerce_recently_viewed'] ) )
            $viewed_products = array();
        else
            $viewed_products = (array) explode( '|', $_COOKIE['woocommerce_recently_viewed'] );
    
        if ( ! in_array( $post->ID, $viewed_products ) ) {
            $viewed_products[] = $post->ID;
        }
    
        if ( sizeof( $viewed_products ) > 15 ) {
            array_shift( $viewed_products );
        }
    
        // Store for session only
        wc_setcookie( 'woocommerce_recently_viewed', implode( '|', $viewed_products ), 0, true );
    }
    
    remove_action( 'template_redirect', 'wc_track_product_view', 20 );
    add_action( 'template_redirect', 'custom_wc_track_product_view', 20 );
    

    I've copied the function with another name, did the changes I needed, then I've substituted original hook with mine. Put this code in a new plugin or in theme functions.php.

    Sadly, there isn't a better way without WooCommerce collaboration.