WordPress preview support

Description

We’d like to support the WordPress preview functionality for both the decoupled and the embedded mode, so it the CMS experience remains the same.

Possible solution

We could use the Embedded mode to support the preview in the decoupled. We’d need a WordPress plugin for that. This was our first idea:

Open Image

The JWT generated contains information in its payload about:

  • The expiring time
  • The post ID

The expiring time for the normal preview is 60 seconds, enough time to send the request to Frontity and Frontity send the request back to the REST API. After those 60 seconds it is not valid anymore.

A new token is generated for each request (each time the user clicks on the Preview button). That is because each time the expiring time changes, that means that the payload is different and the token is different.

The expering time for the publicly sharable link is infinite. That means that the token is always the same. To avoid having to save token in the database, just a post meta "public-share" setting is saved. The non-expiring token is only valid if that post meta is true. Disabling the sharable link simply turns the "public-share" meta to false.

The secret key used is a constant that the user needs to define in wp-config.php named PREVIEW_AUTH_KEY but it defaults to SECURE_AUTH_KEY if missing.

We are going to do a proof of concept of the preview in our next sprint. This is the proposal:

Dev - Preview mode PoC overview

If this approach works well, we will do a proper implementation proposal.

I started with the research, and the first thing I tried to solve was how to allow a REST API request to get revisions from a specific post, mocking the content of a JWT.

My first approach was to use the rest_authenticate_errors filter hook to check the JWT and authenticate somehow but that didn’t work because we don’t want authentication but authorization as the JWT won’t be associated to any WordPress user (correct me here if I’m wrong).

Later I found a filter called user_has_cap that allows WordPress to modify capabilities for a user during runtime, and I think it is a good solution. With that filter, you can allow any user you want (even the anonymous user if there’s no user authenticated) to access specific entities without any other check (I wrote the following code in my local environment and it works fine!). Only the entity specified in the mocked JWT can be fetched.

/**
 * Modify user capabilities on run time.
 */
add_filter( 'user_has_cap', function ( $allcaps, $caps, $args, $user ) {
  // Simulate the content of a JWT.
  $jwt = array(
    // Allow only GET requests so nothing can be modified or deleted.
    'allow_methods' => array( 'GET' ),
    // Capabilities needed to get revisions from the REST API.
    'capabilities' => array( 'edit_post', 'delete_post' ),
    // Post ID from which we want to get revisions.
    'post_id' => 2003
  );

  // REST API check.
  $is_rest_request = defined( 'REST_REQUEST' ) && REST_REQUEST;

  // This is not a REST API request so do not change capabilities.
  if ( ! $is_rest_request ) return $allcaps;
  
  // TOKEN CHECKS.

  // If it is not an allowed HTTP method do not change capabilities.
  if ( ! in_array( $_SERVER[ 'REQUEST_METHOD' ], $jwt['allow_methods'] ) ) {
    return $allcaps;
  }

  // If the capability being check doesn't match do not change capabilities.
  if (! in_array( $args[0], $jwt['capabilities'] ) ) {
    return $allcaps;
  }

  // If it is not the post ID do not change capabilities.
  if ( $args[2] !== $jwt['post_id'] ) {
    return $allcaps;
  }

  // Add capabilities.
  foreach ( $caps as $cap ) {
    $allcaps[ $cap ] = true;
  }

  // Return capabilities.
  return $allcaps;
}, 9999, 4);
1 Like