Refreshing WP Nonce in admin panel before saving

Hi, I am wondering if someone can help me with this. I have a situation where the wordpress cookie is being updated on the front-end/public pages of the website. This means that if someone has two tabs open, one on an admin page, and one onf the front-end, that the admin page has out of date nonce codes – and therefore cannot be saved.

Is it possible to create a function that fires when the 'update' or 'publish' button is pressed that refreshes the code and allows the page to be saved?

Any help would be much appreciated.

  • Ash
    • Code Norris

    Hello Sanders Web Works

    I don’t think this is possible as the nonce is added on every URL in the admin end. So if you update the nonce, it has to update in all URLs in admin. This is a bit tricky. Maybe there has to be run an ajax on a certain interval, reset the nonce, get the nonce value and replace the nonce in all URLs. But it won’t work if any plugin uses a certain “text” based nonce.

    I am still asking to of our developers if he has any idea, I will get back to you when I will hear back.

    Have a nice day!

    Cheers,

    Ash

  • Sanders Web Works
    • Site Builder, Child of Zeus

    Basically, the problem we have is that an admin can be on an editing screen, and during that time their nonce expires, therefore when they press the ‘update’, ‘publish’ or ‘save’ button a message comes up saying “that link has expired”.

    My thinking was that I could intercept the save function, halt it and run some sort of nonce refresh function first, and then continue with the save.

    But am open to other suggestions if you have any.

  • Panos
    • SLS

    Thanks for your feedback!

    I assume that there is no issue here, and that the nonce expires normally and this is thereason you get the message.

    What you suggested is an idea. Although it sounds tricky. I would recommend to craee a mu-plugin that would override the default wp_verify_nonce() function as I don’t see any filter available. It is safe to use as it first checks if that function exists already. In that function we can check if current user has caps to edit in which case it will always return true.

    If you agree, you could create the wp-content/mu-plugins folder if it doesn’t exist.

    In that folder create a new php file, eg wp_verify_nonce_override.php

    Open to edit that file and insert the following snippet:

    <?php
    if ( !function_exists('wp_verify_nonce') ) :

    function wp_verify_nonce( $nonce, $action = -1 ) {
    $nonce = (string) $nonce;
    $user = wp_get_current_user();
    $uid = (int) $user->ID;

    if ( strpos( $action, "update-post_" ) === 0 ) {
    global $post;

    if ( $post instanceof WP_Post && current_user_can( 'edit_posts', $post->ID ) ) {
    return 1;
    }

    }

    if ( ! $uid ) {
    /**
    * Filters whether the user who generated the nonce is logged out.
    *
    * @since 3.5.0
    *
    * @param int $uid ID of the nonce-owning user.
    * @param string $action The nonce action.
    */
    $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
    }

    if ( empty( $nonce ) ) {
    return false;
    }

    $token = wp_get_session_token();
    $i = wp_nonce_tick();

    // Nonce generated 0-12 hours ago
    $expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce'), -12, 10 );
    if ( hash_equals( $expected, $nonce ) ) {
    return 1;
    }

    // Nonce generated 12-24 hours ago
    $expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
    if ( hash_equals( $expected, $nonce ) ) {
    return 2;
    }

    /**
    * Fires when nonce verification fails.
    *
    * @since 4.4.0
    *
    * @param string $nonce The invalid nonce.
    * @param string|int $action The nonce action.
    * @param WP_User $user The current user object.
    * @param string $token The user's session token.
    */
    do_action( 'wp_verify_nonce_failed', $nonce, $action, $user, $token );

    // Invalid nonce
    return false;
    }
    endif;

    add_filter( 'wp_verify_nonce_failed', function( $nonce, $action, $user, $token ){
    return false;
    }, 20, 4 );

    This is the exact copy of the original wp_verify_nonce() function where we added this part at the beginning :

    if ( strpos( $action, "update-post_" ) === 0 ) {
    global $post;

    if ( $post instanceof WP_Post && current_user_can( 'edit_posts', $post->ID ) ) {
    return 1;
    }
    }

    I would strongly recommend to first test it in a testing/staging site.

    Hope this helps!

    Kind regards!

  • Ash
    • Code Norris

    Hello Sanders Web Works

    Let’s try the following code instead of previous one:

    <?php
    if ( !function_exists('wp_verify_nonce') ) :

    function wp_verify_nonce( $nonce, $action = -1 ) {
    $nonce = (string) $nonce;
    $user = wp_get_current_user();
    $uid = (int) $user->ID;

    if ( ! $uid ) {
    /**
    * Filters whether the user who generated the nonce is logged out.
    *
    * @since 3.5.0
    *
    * @param int $uid ID of the nonce-owning user.
    * @param string $action The nonce action.
    */
    $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
    }

    if ( empty( $nonce ) ) {
    return false;
    }

    $token = wp_get_session_token();
    $i = wp_nonce_tick();

    // Nonce generated 0-12 hours ago
    $expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce'), -12, 10 );
    if ( hash_equals( $expected, $nonce ) ) {
    return 1;
    }

    // Nonce generated 12-24 hours ago
    $expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
    if ( hash_equals( $expected, $nonce ) ) {
    return 2;
    }

    /**
    * Fires when nonce verification fails.
    *
    * @since 4.4.0
    *
    * @param string $nonce The invalid nonce.
    * @param string|int $action The nonce action.
    * @param WP_User $user The current user object.
    * @param string $token The user's session token.
    */
    do_action( 'wp_verify_nonce_failed', $nonce, $action, $user, $token );

    // Invalid nonce
    return false;
    }
    endif;

    add_filter( 'wp_verify_nonce_failed', function( $nonce, $action, $user, $token ){
    return false;
    }, 20, 4 );

    Let us know if that works. Have a nice day!

    Cheers,

    Ash

  • Sanders Web Works
    • Site Builder, Child of Zeus

    Hi Ash,

    Looks like what you have put is just a copy of the original wordpress function? This actually removes the original fix that was working in Panos’ code. Now were back to the issue with both pages and widgets. With Panos’ code, pages were saving but widgets were not.

    Is this what you meant to put?

  • Ash
    • Code Norris

    Oops! Not at all, sorry about that. I saw in the above code that there is a restriction to post only, so I thought removing this should work on all pages.

    Anyway, I am pushing the ball to the @panoskatws’s court, he will be going to reply soon. Thanks for your patience.

    Have a nice day!

    Cheers,

    Ash

  • Panos
    • SLS

    Hi Sanders Web Works ,

    I assume that the user that is trying to save widgets has admin privileges. In the first snippet you can add the following condition :

    if ( 'save-sidebar-widgets' == $action && current_user_can( 'manage_options' ) ) {
    return 1;
    }

    just after this line:

    $uid = (int) $user->ID;

    If the user is not an admin, you need to replace the manage_options capability with one that is specific for that user role. If you think that it is secure for your project, you can remove the capability check completely, but I strongly recommend to keep it.

    Hope this helps :slight_smile:

  • Sanders Web Works
    • Site Builder, Child of Zeus

    Hi,

    We have implimented and have been using the suggested approach, which seems to work ok for pages. However, with the widgets it only seems to allow saving on the second attempt. The first attempt just reverts to the content that was previously saved.

    Also we have the same (original) issue for user pages not saving. Would you be able to give any guidance as to where we might find the nonce ‘slugs’ and required capabilities for this action also?

    Many thanks

  • Panos
    • SLS

    Hi Sanders Web Works ,

    I can’t replicate that. I don’t think it is related to the nonces. If it was the nonce being expired, in the widget’s box, when you click save, the loader should continue spinning for ever.

    Since your member’s are admin’s we can allow the nonce to be valid for all actions. All that it needs is to check if user has admin caps by adding the following condition check:

    if ( current_user_can( 'manage_options' ) ) {
    return 1;
    }

    There is no reason that the nonce should be expired with the above condition check.

    Kind regards!