Contents
Introducción
En WordPress, proteger llamadas AJAX y peticiones REST contra CSRF (Cross-Site Request Forgery) y usos no autorizados es fundamental. Las nonces (números usados una vez) son la manera estándar de WordPress para añadir una capa de protección basada en la sesión/usuario. Este artículo explica en detalle cómo generar, transportar y verificar nonces tanto para llamadas a admin-ajax.php (AJAX clásico) como para la REST API, y ofrece ejemplos prácticos en PHP y JavaScript.
¿Qué es una nonce en WordPress y cómo funciona?
Nonce en WordPress no es una clave secreta fuerte: es un token con validez limitada y asociado a un usuario y a una acción. Sirve principalmente para prevenir CSRF y operaciones accidentales. Características importantes:
- Se genera con wp_create_nonce(action).
- Se valida con check_ajax_referer() para AJAX o wp_verify_nonce() para usos generales.
- Tienen una vida útil (por defecto 24 horas, calculada en ticks).
- No sustituyen la autorización: siempre combine validación de nonce con comprobaciones de capacidades (current_user_can()).
Protección para AJAX clásico (admin-ajax.php)
1) Encolar el script y pasar la nonce
En el lado servidor (functions.php o plugin) encola tu script y crea una nonce. Se puede usar wp_localize_script o wp_add_inline_script para inyectar la nonce:
// functions.php o plugin principal
function mi_enqueue_scripts() {
wp_enqueue_script(
mi-script,
plugin_dir_url(__FILE__) . assets/js/mi-script.js,
array(jquery),
1.0,
true
)
// Crear una nonce para mi_accion_ajax
nonce = wp_create_nonce(mi_accion_ajax)
// Pasar datos al script
wp_localize_script(mi-script, MiData, array(
ajax_url =gt admin_url(admin-ajax.php),
nonce =gt nonce,
))
}
add_action(wp_enqueue_scripts, mi_enqueue_scripts)
2) Lado cliente: enviar la nonce con fetch/jQuery
En el JS, envía la nonce como campo en el body (o encabezado) para que el servidor la pueda comprobar.
// assets/js/mi-script.js
(function() {
// Usando fetch
function enviarDato(dato) {
const form = new FormData()
form.append(action, mi_accion_ajax) // nombre de la acción
form.append(valor, dato)
form.append(security, MiData.nonce) // nombre del campo puede ser security o el que quieras
fetch(MiData.ajax_url, {
method: POST,
body: form,
credentials: same-origin
})
.then(response =gt response.json())
.then(data =gt console.log(Respuesta AJAX:, data))
.catch(err =gt console.error(err))
}
// Ejemplo de uso
// enviarDato(hola)
})()
3) Lado servidor: manejar la petición y validar la nonce
Registra los hooks AJAX y valida la nonce con check_ajax_referer(). Además, verifica capacidades si es necesario y sanea entradas antes de procesarlas o devolverlas.
// functions.php o plugin
add_action(wp_ajax_mi_accion_ajax, mi_accion_ajax_handler)
// Si también quieres que usuarios no autenticados puedan usarlo:
// add_action(wp_ajax_nopriv_mi_accion_ajax, mi_accion_ajax_handler)
function mi_accion_ajax_handler() {
// Verificar la nonce: el primer parámetro es la acción que usaste en wp_create_nonce
// el segundo parámetro es el nombre del campo en POST/GET: security
check_ajax_referer(mi_accion_ajax, security)
// Validación adicional
if (! current_user_can(edit_posts)) {
wp_send_json_error(array(message =gt No tienes permisos.), 403)
}
// Recuperar y sanitizar parámetros
valor = isset(_POST[valor]) ? sanitize_text_field(_POST[valor]) :
// Procesar
resultado = Procesado: . valor
wp_send_json_success(array(resultado =gt resultado))
}
Notas prácticas para AJAX
- check_ajax_referer al fallar mata la ejecución y envía un error 403 puedes usar su tercer parámetro a false para manejar el error manualmente.
- Para peticiones desde usuarios no autenticados, las nonces no proporcionan la misma seguridad considere alternativos (tokens, claves públicas/privadas, validación referer, etc.).
- Siempre sanea entradas y verifica capacidades con current_user_can() antes de realizar acciones sensibles.
Protección para la REST API
Para la REST API de WordPress la forma habitual es enviar un encabezado X-WP-Nonce con el valor de wp_create_nonce(wp_rest). WordPress, por defecto, valida este encabezado para sesiones basadas en cookies y usuarios autenticados.
1) Pasar nonce para REST API
// functions.php o plugin
function mi_enqueue_rest_script() {
wp_enqueue_script(mi-rest-script, plugin_dir_url(__FILE__) . assets/js/mi-rest.js, array(), 1.0, true)
// Nonce especial para la REST API
rest_nonce = wp_create_nonce(wp_rest)
wp_localize_script(mi-rest-script, MiREST, array(
root =gt esc_url_raw(rest_url()),
nonce =gt rest_nonce
))
}
add_action(wp_enqueue_scripts, mi_enqueue_rest_script)
2) Cliente: llamar a un endpoint REST con la nonce
// assets/js/mi-rest.js
(function() {
function llamadaREST() {
const url = MiREST.root mi-plugin/v1/mi-endpoint
fetch(url, {
method: POST,
headers: {
Content-Type: application/json,
X-WP-Nonce: MiREST.nonce
},
body: JSON.stringify({ dato: valor }),
credentials: same-origin // importante para cookies
})
.then(res =gt {
if (!res.ok) throw new Error(HTTP error res.status)
return res.json()
})
.then(data =gt console.log(REST respuesta:, data))
.catch(err =gt console.error(err))
}
// llamadaREST()
})()
3) Registrar endpoint REST y validar la nonce
Al registrar rutas en la REST API, se utiliza el parámetro permission_callback para autorizar la petición. En muchos casos basta verificar capacidades de usuario (current_user_can). Si quieres validar la nonce explícitamente, lee X-WP-Nonce con wp_verify_nonce().
// functions.php o plugin
add_action(rest_api_init, function () {
register_rest_route(mi-plugin/v1, /mi-endpoint, array(
methods =gt POST,
callback =gt mi_rest_handler,
permission_callback =gt mi_rest_permissions_check,
))
})
function mi_rest_permissions_check(request) {
// Método recomendado: comprobar capacidades
if (! current_user_can(edit_posts)) {
return new WP_Error(rest_forbidden, No tienes permisos para acceder., array(status =gt 403))
}
// Opcional: comprobar nonce directamente (ejemplo)
headers = getallheaders()
nonce = isset(headers[X-WP-Nonce]) ? headers[X-WP-Nonce] : null
if (nonce ! wp_verify_nonce(nonce, wp_rest)) {
return new WP_Error(rest_nonce_invalid, Nonce inválida, array(status =gt 403))
}
return true
}
function mi_rest_handler(request) {
params = request->get_json_params()
dato = isset(params[dato]) ? sanitize_text_field(params[dato]) :
return rest_ensure_response(array(resultado =gt Recibido: . dato))
}
Uso con endpoints públicos (no autenticados)
Si tu endpoint debe ser accesible por usuarios no autenticados, no puedes confiar en X-WP-Nonce para autenticación. Para APIs públicas considera:
- Autenticación por token (JWT, OAuth) o clave única del plugin.
- Comprobaciones adicionales (rate limiting, validación de origen, captchas).
- No exponer acciones destructivas sin una capa de autenticación sólida.
Buenas prácticas y consideraciones de seguridad
- Usa check_ajax_referer() / wp_verify_nonce() correctamente: para AJAX utiliza check_ajax_referer, y para REST valida headers o confía en permission_callback con current_user_can.
- Complementa con autorización: la nonce protege contra CSRF, pero no sustituye comprobaciones de permisos. Usa current_user_can() para autorizar acciones sensibles.
- Sanea todas las entradas: sanitize_text_field, intval, esc_url_raw, etc., según el tipo de dato.
- No expongas nonces innecesariamente: pásalas solo al script que las necesita (wp_localize_script o data-attributes), y evita imprimirlas en HTML de forma insegura.
- Comprende sus limitaciones: las nonces caducan y no son secretas un atacante con acceso al navegador del usuario puede reutilizarlas hasta que expiren.
- Evita incluir nonces en URLs públicas: mejor en POST o headers.
- Logs y respuestas: en entorno de producción no devuelvas información sensible en errores.
Depuración y problemas comunes
- Nonce inválida al usar REST: asegúrate de enviar X-WP-Nonce y de que la página y script estén en el mismo dominio (credentials: same-origin).
- Chequeo falla sólo en usuarios no autenticados: recuerda que las nonces están asociadas a usuarios para visitantes anónimos considera otra estrategia.
- Cache agresiva rompe nonces: no caches páginas con nonces dinámicas (o genera nonces por petición y evita almacenar en caché su valor visible).
Ejemplo completo mínimo
Resumen mínimo de todas las piezas: encolar, enviar y validar (AJAX).
// 1) Encolar y crear nonce
function ejemplo_enqueue() {
wp_enqueue_script(ejemplo-js, plugin_dir_url(__FILE__) . ejemplo.js, array(), 1.0, true)
wp_localize_script(ejemplo-js, EjemploData, array(
ajax_url =gt admin_url(admin-ajax.php),
nonce =gt wp_create_nonce(ejemplo_action),
))
}
add_action(wp_enqueue_scripts, ejemplo_enqueue)
// 2) Handler AJAX
add_action(wp_ajax_ejemplo_action, ejemplo_handler)
function ejemplo_handler() {
check_ajax_referer(ejemplo_action, security)
val = isset(_POST[val]) ? sanitize_text_field(_POST[val]) :
wp_send_json_success(array(ok =gt true, val =gt val))
}
// ejemplo.js
(function() {
function enviar() {
const f = new FormData()
f.append(action, ejemplo_action)
f.append(val, hola mundo)
f.append(security, EjemploData.nonce)
fetch(EjemploData.ajax_url, { method: POST, body: f, credentials: same-origin })
.then(r =gt r.json())
.then(console.log)
.catch(console.error)
}
// enviar()
})()
Resumen final
Las nonces de WordPress son una herramienta eficaz para mitigar CSRF en llamadas AJAX y REST cuando se usan correctamente. Siempre combínalas con comprobaciones de permisos, saneado de entradas y buenas prácticas de seguridad (no cachear, no exponer nonces en URLs, etc.). Para APIs públicas, utilice mecanismos de autenticación más adecuados (JWT, OAuth o claves de API).
|
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |
