- Notifications
You must be signed in to change notification settings - Fork 655
Document effect of not providing nonce #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
I made endpoints for getting and verifying nonces. I used an arbitrary $_GET['nonce'] parameter for the verify endpoint and wondered why it didn't work. For too long. https://wordpress.stackexchange.com/questions/295471/nonce-retrieved-from-the-rest-api-is-invalid-and-different-from-nonce-generated/295547
Related to #19 I like the wording of #19 (comment), but I think that the Authentication page requires a separate paragraph on this. |
Please do not ever do this (generate nonces via the REST API). Nonces are the fundamental protection in WordPress against cross-site request forgery (CSRF), and supplying them via the REST API will break the protection it offers unless you secure the code against these attacks in a different way (which is quite difficult to get right). You should to generate the nonce out-of-band, ideally using For verifying nonces, you can access The nonce checking code does technically reset the user, but a better way to think about it is that without the nonce, your requests are treated as non-authenticated. The resetting of the user is just the way we accomplish that. We can certainly clarify this, as I do think it can be confusing right now. |
I can't use wp_localize_script, as I'm not creating a plugin or a theme. I'm building a single page app. I'm going to need the nonces as I'm going to have to create posts from the application frontend and modify existing ones. How should I get the nonce? Other authentication methods such as JWT or OAuth aren't going to work for this case. And if it's a terrible idea, why isn't that in big bold letters, also on the authentication page? 😉 It's only said that you need nonces, but doesn't help you if you're not building a theme or plugin. Also, I'm not entirely sure how a site at different origin could use this as an attack vector. The nonce generated is different for every user, except the anonymous user. But anonymous users can't do anything anyway that would've required nonces. When the site at a different origin queries the endpoint, it gets the anonymous user nonce, right? I get that that site could generate a bunch of post delete links like Third party code from GTM & ads is what I'm actually concerned about. Those have access to the nonce and could basically do whatever they want. If I'm not missing something here, cross-origin sites couldn't possibly have the nonce, unless the user actually sent the nonce to them via parameters or form data? |
Fundamentally, cookie authentication is only designed for plugins and themes, and should not be used outside of that. I'd recommend using OAuth personally, but with pre-authorised keys; that is, the user never has to manually authorise the key, so it's essentially invisible to them apart from two redirects. For single-page apps hosted on the domain that really need to use cookies, I can think of two solutions: either load your single page app via WordPress as a sort of dummy theme (this is how I usually use it, in combination with something like react-wp-scripts), or load the nonce in via a cross-site-secured method. Your code follows the second pattern, which will somewhat work, but you should avoid doing this via the REST API. The REST API sends cross-origin headers allowing access from any domain by default, so you'll have to undo that sending, and it's possible (although unlikely) that this could break in the future. You may also need to disable things like enveloping and method overrides, which can break cross-origin protection. (Personally, I'd have a rewrite rule that generates a JS script dynamically, and lock that down with additional headers.)
You're right, we should clarify that nonces are for authentication within WordPress only. It's one of those things that seems obvious when you're writing the docs, but isn't actually. :)
Nonces aren't about authentication, they're about intent. You could load up a URL which, unbeknownst to you, uses your authentication in the background due to the lack of intent checks. For example, if your site is at // Get the nonce via the insecure endpoint. const { nonce } = await fetch( 'http://wordpress.local/wp-json/nonce/v1', { credentials: 'include' } ).json(); // Fetch all posts, including private ones. const posts = await fetch( `http://wordpress.local/wp-json/wp/v2/posts?status=any&_wpnonce=${ nonce }`, { credentials: 'include' } ); // Delete all the posts. posts.forEach( post => { fetch( `http://wordpress.local/wp-json/wp/v2/posts/${ post.id }?_wpnonce=${ nonce }`, { credentials: 'include', method: 'DELETE', } ) } ); // Fetch the current user's data, including email. const user = fetch( `http://wordpress.local/wp-json/wp/v2/users/me?_wpnonce=${ nonce }`, { credentials: 'include' } ); In a typical (non-WordPress) setup, cross-origin policy would prevent against this. We intentionally made the decision to allow cross-origin requests in order to allow browser-based apps, but this means we need the second layer of protection (nonces) instead. |
I'll look into if we could use OAuth in the way you described it. Dynamically created script (using a // register_rest_route('', '', [ if ($_SERVER['HTTP_ORIGIN'] !== get_site_url()) { return new WP_Error('stop_being_evil', 'Stop being evil', ['status' => 403]); } return [ 'nonce' => wp_create_nonce('wp_rest'), 'user' => $user, ]; // ]) Using a dummy theme comes with the cost of loading WordPress every time, which is what we'd like to skip. Right now it looks like https://wp-oauth.com/ provides what we need, which is username & password authentication, so I'm probably going to end up scratching this, but for future reference and so on. |
Origin checks are best left to the browser, since they're a fair bit more complex than that. The |
I added a commit to remove the function line reference and to bridge the comment about what happens without a nonce into the prior paragraph. I agree with Ryan's points above but it still feels useful to explicitly call out what happens if you don't pass a nonce with your cookie. |
I made endpoints for getting and verifying nonces. I used an arbitrary $_GET['nonce'] parameter for the verify endpoint and wondered why it didn't work. For too long.
https://wordpress.stackexchange.com/questions/295471/nonce-retrieved-from-the-rest-api-is-invalid-and-different-from-nonce-generated/295547