Wp-calypso: Gutenframe: Atomic/Jetpack sites redirect to WP Admin editor without "SameSite" cookies

Created on 13 Nov 2019  ยท  14Comments  ยท  Source: Automattic/wp-calypso

When using Chrome Canary (so cross-site cookies are disabled by default), Calypso is unable to load Gutenframe on Atomic and Jetpack sites and it ends up falling back to the WP Admin block editor.

This is the error displayed on the browser console:

A cookie associated with a cross-site resource at <domain> was set without the `SameSite` attribute. It has been blocked, as Chrome now only delivers cookies with cross-site requests if they are set with `SameSite=None` and `Secure`. You can review cookies in developer tools under Application>Storage>Cookies and see more details at https://www.chromestatus.com/feature/5088147346030592 and https://www.chromestatus.com/feature/5633521622188032.

We can avoid this by setting SameSite:None on those cookies and setting the secure attribute on the cookie ( meaning it will only be usable over HTTPS ). See: pb6Nl-daR-p2.

Editor [Pri] High [Type] Task

Most helpful comment

Jeez, I should have checked for your comments earlier ๐Ÿ™ƒ Ha, yes; I was blaming JP, but now I also think it's core cookies during auth. And even before that, there are some cookies in wp-login.php that could be problematic.

There are ample available hooks within wp_set_auth_cookie(); that part shouldn't be too bad (it's all also within pluggable.php, but I'm assuming full function overrides would be red flags for inclusion in Jetpack). We may be stuck with core hacks for wp-login.php though, if necessary.

Testing today with local Calypso, a local JP ngrok install, and forcing get_editing_flow() from WP.com to return gutenberg-iframe, I was able to determine all of this, and get the correct auth cookies set, but had to quit before sorting the wp-login.php piece, which I think is happening before the auth cookies. I basically used the same approach that Joseph has in place for WP.com in wp-content/mu-plugins/pluggable.php#108. He also leaves the original cookies, I noticed.

Of course, this test env is no good for a full PoC, since it's all insecure (and the new cookies rely on secure transport). Now that I have (I believe) a better understanding of what is happening where, I can try again with the env from p9o2xV-Fw-p2, which should satisfy the secure aspect and get us close to a working solution.

All 14 comments

To ensure this fails properly in Canary, explicitly set the needed flags to enable at chrome://flags. These flags are apparently only set to 50% of Canary users.

Screen Shot 2019-12-12 at 11 11 12 AM

I started digging into this today. I could confirm that Simple sites work fine, and an Atomic site did not (testing on https://wordpresss.com/block-editor/post/:atomic_id between latest stable Chrome and Canary with flags set as above). It would be helpful if Chrome said _which_ cookie is being blocked ๐Ÿ™ƒ

Looking at instances of setcookie in Jetpack, its use in SSO is notable, as I think we rely on that when attempting to load the iframed site (but I'm not sure what is meant by "Atomic handles this"). Both the failing and succeeding cases have a 302 for the iframed site's post-new.php request, but at that point, I start getting lost and will need to dig deeper.

I was able to do some testing on a new environment (p9o2xV-Fw-p2), but haven't gotten far. I confirmed that SSO wasn't involved, and then realized I didn't have it activated anyways ๐Ÿ™ƒ. This _does_ make some sense though, since Gutenframe is capable of handling JP sites without SSO enabled (I believe we redirect to the login window).

I never got very far on this, finding it tough to observe what is calling what and creating what cookies for what. I can pick it up in the new year if others have no progress before that ๐Ÿ‘

After eliminating SSO from the mix, I confirmed that I can still make Gutenframe work without SSO from current Chrome. When attempting the same from Chrome Canary, I can see that the cookie errors are immediate, from the JP site's domain, and happening _before_ I get sent to the JP site's login page.

When looking through instances of setcookie in Jetpack, I thought I was on to something with the three instances in class.jetpack.php. However, tailing a log demonstrated that none of the setcookie instances were called as part of the Gutenframe exchange.

Side note: my test environment (p9o2xV-Fw-p2) is PHP 7.2, so I added a shim_setcookie function by @josephscott that has the same behaviour as PHP 7.3 (obviously this didn't make a difference, as they were never called). This was helpful since it makes setting the needed samesite and secure options easier:

/**
 * A PHP 7.2 compatible version of the array argument version of PHP 7.3's setcookie(). Useful for setting SameSite cookies in PHP 7.2.
 *
 * @param string $name Name of the cookie
 * @param string $value Value of the cookie
 * @param array $options Options to include with the cookie
 * @return bool False when error happens, other wise true
 */
function shim_setcookie( string $name, string $value, array $options = [] ) {
    $not_allowed_chars = ",; \t\r\n\013\014";

    if ( strpbrk( $name, $not_allowed_chars ) !== false ) {
        return false;
    }

    $cookie = "Set-Cookie: $name=" . urlencode( $value ) . "; ";

    foreach ( $options as $k => $v ) {
        if ( $k === 'expires' && !empty( $v ) ) {
            $cookie_date = gmdate( 'D, d M Y H:i:s \G\M\T', $v );
            $cookie .= sprintf( 'expires=%s', $cookie_date ) . ';';
        } elseif ( $k === 'secure' && $v === true ) {
            $cookie .= 'secure; ';
        } elseif( $k === 'httponly' && $v === true ) {
            $cookie .= 'HttpOnly; ';
        } elseif( $k === 'domain' && is_string( $v ) && !empty( $v ) ) {
            if ( strpbrk( $v, $not_allowed_chars !== false ) ) {
                return false;
            }
            $cookie .= sprintf( "domain=%s", $v . '; ' );
        } elseif( $k === 'path' && is_string( $v ) && !empty( $v ) ) {
            if ( strpbrk( $v, $not_allowed_chars !== false ) ) {
                return false;
            }
            $cookie .= sprintf( "path=%s", $v . '; ' );
        } elseif ( $k == 'samesite' && is_string( $v ) && !empty( $v ) ) {
            $cookie .= sprintf( "SameSite=%s", $v . '; ' );
        }
    }

    $cookie = trim( $cookie );
    $cookie = trim( $cookie, ';' );
    header( $cookie, false ); 
    return true;
}

@kwight maybe the problematic cookies are the ones that register Core during the authentication?

https://github.com/WordPress/wordpress-develop/blob/086ae28f0e77e01286d699478aaaa652d0eb3a69/src/wp-includes/pluggable.php#L941

I can see those cookies are correctly registered when using Gutenframe on Chrome, but they never appear on Chrome Canary:

Screenshot 2020-01-10 at 17 38 07

Jeez, I should have checked for your comments earlier ๐Ÿ™ƒ Ha, yes; I was blaming JP, but now I also think it's core cookies during auth. And even before that, there are some cookies in wp-login.php that could be problematic.

There are ample available hooks within wp_set_auth_cookie(); that part shouldn't be too bad (it's all also within pluggable.php, but I'm assuming full function overrides would be red flags for inclusion in Jetpack). We may be stuck with core hacks for wp-login.php though, if necessary.

Testing today with local Calypso, a local JP ngrok install, and forcing get_editing_flow() from WP.com to return gutenberg-iframe, I was able to determine all of this, and get the correct auth cookies set, but had to quit before sorting the wp-login.php piece, which I think is happening before the auth cookies. I basically used the same approach that Joseph has in place for WP.com in wp-content/mu-plugins/pluggable.php#108. He also leaves the original cookies, I noticed.

Of course, this test env is no good for a full PoC, since it's all insecure (and the new cookies rely on secure transport). Now that I have (I believe) a better understanding of what is happening where, I can try again with the env from p9o2xV-Fw-p2, which should satisfy the secure aspect and get us close to a working solution.

there are some cookies in wp-login.php that could be problematic.

Seems they are only checking the browser has enabled cookies, so I don't expect them to affect the auth and therefore Gutenframe.

Did you still get refused cookies after overriding the auth cookies?

Seems they are only checking the browser has enabled cookies, so I don't expect them to affect the auth and therefore Gutenframe.

My quick thinking was that if these cookies fail to set because of samesite issues, everything after it will fail too.

Did you still get refused cookies after overriding the auth cookies?

Yep. Hopefully I'll get more clarity today ๐Ÿ‘

We should also make sure to check Atomic without internal proxy active; I'm not sure what difference this could make, but it wouldn't hurt to verify once a proposed solution is ready.

Working with a JN site, I've been able to get Canary to load Gutenframe โ€“ yay! However, this involved an mu plugin with an override of wp_set_auth_cookie, as I didn't get very far using just the filters within it. There's still the possibility of working from the wp_login hook in users.php, since it executes after the original cookies are set, but getting required data might be tricky.

It might be worth check this plugin to see if we can re-use something: https://wordpress.org/plugins/samesite/

It might be worth check this plugin to see if we can re-use something

Ha, it's exactly what I have running now: an override to the wp_set_auth_cookie, with a shim/polyfill function for setting the cookie itself.

I ended up building a prototype that works (with some gotchas, i.e. requires already authenticated users to login again so cookies are overwritten): https://github.com/Automattic/jetpack/pull/14349

@kwight Let's pair today and compare it with your solution, so we can identify what are next steps

Was this page helpful?
0 / 5 - 0 ratings