Unique URL Every Time

You could use a partial implementation of JWTs to pass a unique token including identifying information and the requested post ID (or slug) as the endpoint, and check the identifying info upon validation.

URLs containing a unique token can be rewritten to pass the token as a specific variable. A 'parse_query' action hook can then check for the presence of the token variable, and replace the query with one that will return the proper post if the token is valid – or an error if it isn’t.

In this manner, only the visitor issued the token could use it to access the post (unless someone else both acquires the token and spoofs the original visitor’s IP – this could be further secured with a cookie or session ID of some sort). Forging tokens is impossible without your secret.

$secret="{insert randomly generated string here}";
$custom_post_type="my_cpt";
$unique_url_base="cpt";

add_action( 'init', 'wpse_212309_rewrite_unique_token_url' );

/**
 * Adds a rewrite rule to forward URLs in the format /cpt/{unique token} to
 * index.php?post_type=my_cpt&unique_token={unique token}. Supersedes WordPress
 * rewrites for the endpoint.
 **/
function wpse_212309_rewrite_unique_token_url(){
    add_rewrite_rule(
        trailingslashit( $unique_url_base ) . '([^\.]*.[^\.]*)$',
        'index.php?post_type=" . $custom_post_type . "&unique_token=$matches[1]',
        'top'
    );
}

add_action( 'parse_query', 'wpse_212309_decode_unique_token_query' );

/**
 * Replaces queries for the 'my_cpt' post-type containing a unique token with
 * the appropriate 'my_cpt' post if the token is valid (i.e., passed to the
 * server from the client IP to which it was assigned).
 **/
function wpse_212309_decode_unique_token_query( $wp ) {
    if( is_admin() )
        return;

    if( isset( $wp->query_vars[ 'p' ] ) || $custom_post_type != $wp->query_vars[ 'post_type' ] || empty( $_GET[ 'unique_token' ] ) )
        return;

    $post_id = wpse_212309_get_post_id_from_unique_slug( $_GET[ 'unique_token' ] );

    if( ! $post_id ) {
        $wp->set_404();
        status_header( 404 );
        return;
    }

    $wp->parse_request( 'p=' . $post_id );
}

/**
 * Encodes data into a URL-friendly JWT-esque token including IP information
 * for the requesting party, as well as an optional expiration timestamp.
 **/
function wpse_212309_encode_token( $payload, $expiration = null ) {    
    $payload[ 'aud' ] = hash( 'md5', $_SERVER[ 'REMOTE_ADDR' ] . $_SERVER[ 'HTTP_X_FORWARDED_FOR' ] );
    $payload[ 'iss' ] = time();

    if( isset( $expiration ) )
        $payload[ 'exp' ] = $expiration;

    $payload = base64_encode( json_encode( $payload ) );
    $hash    = hash( 'md5', $payload . $secret );

    return urlencode( $payload . '.' . $hash );
}

/**
 * Decodes a token generated by 'wpse_212309_encode_token()', returning the
 * payload if the token is both unaltered and sent by the original client IP
 * or false otherwise.
 **/
function wpse_212309_decode_token( $token ) {
    if( empty( $token ) || -1 === strpos( $token, '.' ) )
        return false;

    $token   = urldecode( $token );
    $token   = explode( '.', $token );
    $hash    = $token[1];
    $payload = $token[0];

    // If the payload or the hash is missing, the token's invalid.
    if( empty( $payload ) || empty( $hash ) )
        return false;

    $hash_check = hash( 'md5', $payload . $secret );

    // Has the payload and/or hash been modified since the token was issued?
    if( $hash_check !== $hash )
        return false;

    $payload = base64_decode( $payload );

    if( ! $payload )
        return false;

    $payload = json_decode( $payload, true );

    if( ! $payload )
        return false;

    $audience_check = hash( 'md5', $_SERVER[ 'REMOTE_ADDR' ] . $_SERVER[ 'HTTP_X_FORWARDED_FOR' ] );

    // Was this token passed to the server by the IP that it was issued to?
    if( $audience_check != $payload[ 'aud' ] )
        return false;

    // Does the payload have an expiration date - if so, has it expired?
    if( ! empty( $payload[ 'exp' ] ) && $payload[ 'exp' ] > time() )
        return false;

    // Token validated - return the payload as legitimate data.
    return $payload;
}

/**
 * Produces a token associating a post ID with a particular client, suitable
 * for inclusion in a URL. Optionally takes a "time to live" argument, in
 * in seconds, before the token should expire.
 **/
function wpse_212309_generate_unique_slug( $post_id, $ttl = null ) {
    $expiration = null;

    if( $ttl )
        $expiration = time() + $ttl * 1000;

    return wpse_212309_encode_token( array( 'pid' => $post_id ), $expiration );
}

/**
 * Returns a post ID from a token if the token was in fact issued to the 
 * requesting client IP, or false otherwise.
 **/
function wpse_212309_get_post_id_from_unique_slug( $token ) {
    $payload = wpse_212309_decode_token( $token );

    if( ! $payload )
        return false;

    return $payload[ 'pid' ];
}

How you actually send visitors to the unique URLs containing a token depends on your application (i.e. how you set up your “search tool”), but using the implementation above you can retrieve a visitor-unique slug for a post ID like this:

// A slug that only works for the visitor it was issued to:
$unique_slug = wpse_212309_generate_unique_slug( $post_id );

// OR, for one that additionally expires in an hour:
$unique_slug = wpse_212309_generate_unique_slug( $post_id, 3600 )

Leave a Comment