After testing and reading the codes of WordPress. Here is the explanation of the phenomenon.
Firstly, the following is nonce explanation and how it is going to affect the process.
According to WordPress Nonces,
A nonce is a “number used once” to help protect URLs and forms from
certain types of misuse, malicious or otherwise. WordPress nonces
aren’t numbers, but are a hash made up of numbers and letters. Nor are
they used only once, but have a limited “lifetime” after which they
expire. During that time period the same nonce will be generated for a
given user in a given context. The nonce for that action will remain
the same for that user until that nonce life cycle has completed.
In short, it is a checking reference for security reason to ensure things are not being tampered.
Come to the login case. Your login logic is right. A form + a form handler.
The process of the login:
- you click the login after input username password
- it submit itself to the same page
- page seems refreshed, logout link appear, logout link is generated by wp_logout_url() (default template) , inside this, nonce is being generated together, you may refer to the source code for details.
- because nonce is based on a few things including time reference and a cookie value. If you follow the 1-3 sequence and click logout at this time. It will ask you for confirmation page.
However, if instead try immediately, you go to the URL bar, click enter to force a loading of the page as if input a new url. Then if you click logout again. It does not ask for confirmation. It prompts confirmation box because it thinks that the logout nonce is invalid.
It is because the cookie value is not available yet with a value Null. Thus, the nonce is invalid.
Once the cookies have been set, they can be accessed on the next page load with the $_COOKIE or $HTTP_COOKIE_VARS arrays.
^^^^^^^^^^^^^^
You may refer to this related post and php manual on cookie
So there is a solution that, WordPress login also uses but not documented (maybe documented but I haven’t read through) that adding a “redirect” parameter to handle this. By using redirect, it will be a new page load so the cookie is updated and thus nonce is valid.
So, here is the a solution:
Login form
<form method="post" action="<?= get_permalink(); ?>">
<p><input type="text" name="my_username" placeholder="Username" /></p>
<p><input type="password" name="my_password" placeholder="Password" /></p>
<p><input type="submit" name="submit" value="Login" /></p>
<!-- change to your url -->
<input type="hidden" name="redirect_to" value="somewhere_or_same_link" />
</form>
Form handler
add_action('after_setup_theme', 'my_login');
function my_login()
{
if(!empty($_POST['my_username']) && !empty($_POST['my_password']))
{
$user = wp_signon([
'user_login' => $_POST['my_username'],
'user_password' => $_POST['my_password'],
'remember' => true],
is_ssl());
if(is_wp_error($user))
{
echo $user->get_error_message();
}
else
{
if ( ! empty( $_POST['redirect_to'] ) ) {
wp_set_current_user($user->ID);
$redirect_to = wp_unslash( $_POST['redirect_to'] );
wp_safe_redirect( $redirect_to );
// or directly use wp_redirect( $redirect_to );
}
exit(); // it is important to exit the current script so that the redirect start a new one completely or it will remain the same, the cookie will not be refreshed
}
}
}