Como añadir cabeceras CORS correctas a la REST API en PHP en WordPress

Contents

Introducción

Cuando desarrollas aplicaciones que consumen la REST API de WordPress desde otro origen (otro dominio, subdominio, puerto o protocolo), el navegador aplica la política de CORS (Cross-Origin Resource Sharing). Si las cabeceras CORS no están configuradas correctamente, las peticiones desde el frontend externo serán bloqueadas por el navegador aunque el servidor responda correctamente. Este artículo explica en detalle cómo añadir cabeceras CORS correctas a la REST API de WordPress usando PHP, con ejemplos listos para aplicar en functions.php o en un plugin.

Conceptos clave de CORS (resumen)

  • Access-Control-Allow-Origin: controla qué orígenes pueden acceder al recurso. Nunca combines Access-Control-Allow-Origin: con Access-Control-Allow-Credentials: true.
  • Access-Control-Allow-Methods: métodos HTTP permitidos (GET, POST, PUT, DELETE, OPTIONS, …).
  • Access-Control-Allow-Headers: cabeceras permitidas en la petición (por ejemplo Authorization, Content-Type, X-WP-Nonce).
  • Access-Control-Allow-Credentials: si se permiten cookies o autenticación basada en credenciales.
  • Access-Control-Max-Age: tiempo (en segundos) que el resultado del preflight puede ser cacheado.
  • Vary: Origin: recomendado cuando la cabecera Allow-Origin se establece dinámicamente por origen, para evitar caches incorrectos.

Consideraciones de seguridad y buenas prácticas

  • Preferible utilizar una lista blanca de orígenes en lugar de responder con . Validar y comparar el origen recibido con una lista controlada.
  • No usar Access-Control-Allow-Credentials: true salvo que sea necesario (cookies o autenticación basada en sesión). Si lo usas, el origen debe ser exacto (no ).
  • Añadir Vary: Origin cuando resuelves dinámicamente el origen para evitar que caches compartidos devuelvan cabeceras incorrectas.
  • Permitir solo las cabeceras que realmente vas a necesitar (por ejemplo Authorization, Content-Type, X-WP-Nonce).
  • Limitar métodos permitidos y mantener Access-Control-Max-Age razonable.

Cómo detectar si necesitas añadir cabeceras CORS

  • En la consola del navegador verás errores tipo: Access to XMLHttpRequest at … from origin … has been blocked by CORS policy.
  • La petición preflight (OPTIONS) devuelve 403 o 404 o no devuelve las cabeceras CORS.

¿Dónde inyectar las cabeceras en WordPress?

Opciones comunes:

  • Hook a la REST API usando rest_pre_serve_request (recomendado para interceptar respuestas REST antes de enviarlas).
  • Comprobar la constante REST_REQUEST en hooks como init para enviar cabeceras solo cuando se solicite la REST API.
  • Crear un pequeño plugin que haga esto para no ensuciar functions.php.

Ejemplo 1 — Implementación recomendada usando rest_pre_serve_request (plugin o functions.php)

Este ejemplo añade cabeceras CORS de forma dinámica basándose en una lista blanca de orígenes y maneja las peticiones preflight (OPTIONS). Colócalo en functions.php o mejor en un plugin propio.

lt?php
// Añade esto en functions.php o en un plugin
add_filter( rest_pre_serve_request, mi_rest_allow_cors, 10, 4 )

function mi_rest_allow_cors( served, result, request, route ) {
    // Lista blanca de orígenes permitidos
    allowed_origins = array(
        https://example.com,
        https://app.example.com,
    )

    // Obtener origen de la petición
    origin = isset( _SERVER[HTTP_ORIGIN] ) ? trim( wp_unslash( _SERVER[HTTP_ORIGIN] ) ) : 

    if ( origin  in_array( origin, allowed_origins, true ) ) {
        header( Access-Control-Allow-Origin:  . origin )
        header( Vary: Origin ) // importante cuando la respuesta depende del Origin
    }

    // Cabeceras y métodos permitidos
    header( Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS )
    header( Access-Control-Allow-Headers: Authorization, Content-Type, X-WP-Nonce )
    header( Access-Control-Allow-Credentials: true )
    header( Access-Control-Max-Age: 86400 )

    // Si es preflight (OPTIONS), terminamos con 200
    if ( OPTIONS === _SERVER[REQUEST_METHOD] ) {
        // Evitar que WordPress haga más trabajo para la petición OPTIONS
        status_header( 200 )
        exit
    }

    return served
}

Explicación del snippet

  • Se usa el filtro rest_pre_serve_request que permite añadir cabeceras justo antes de enviar la respuesta REST.
  • Se valida el origen contra una lista blanca y se devuelve el origen exacto en Access-Control-Allow-Origin para permitir el uso de cookies/credenciales.
  • Se responde inmediatamente para peticiones OPTIONS con código 200 esto es necesario para el preflight.

Ejemplo 2 — Versión simple usando REST_REQUEST en init

Si prefieres comprobar la constante REST_REQUEST, esta variante puede funcionar. Es menos WP REST hook y más general.

lt?php
add_action( init, mi_cors_headers_init )

function mi_cors_headers_init() {
    if ( defined( REST_REQUEST )  REST_REQUEST ) {
        origin = isset( _SERVER[HTTP_ORIGIN] ) ? trim( wp_unslash( _SERVER[HTTP_ORIGIN] ) ) : 

        // Lista blanca (ejemplo)
        allowed = array( https://example.com )

        if ( origin  in_array( origin, allowed, true ) ) {
            header( Access-Control-Allow-Origin:  . origin )
            header( Vary: Origin )
        }

        header( Access-Control-Allow-Methods: GET, POST, OPTIONS )
        header( Access-Control-Allow-Headers: Authorization, Content-Type, X-WP-Nonce )
        header( Access-Control-Allow-Credentials: true )

        if ( OPTIONS === _SERVER[REQUEST_METHOD] ) {
            status_header( 200 )
            exit
        }
    }
}

Ejemplo 3 — Permitir cabeceras para JWT o Authorization

Si tu aplicación usa tokens JWT o Authentication via Authorization header, añade explícitamente esa cabecera. Ejemplo:

lt?php
add_filter( rest_pre_serve_request, function( served ) {
    header( Access-Control-Allow-Origin: https://example.com )
    header( Vary: Origin )
    header( Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE )
    header( Access-Control-Allow-Headers: Authorization, Content-Type, X-WP-Nonce )
    header( Access-Control-Allow-Credentials: true )
    if ( OPTIONS === _SERVER[REQUEST_METHOD] ) {
        status_header(200)
        exit
    }
    return served
}, 10 )

Pruebas prácticas (curl y navegador)

Comprobar la respuesta directamente con curl:

# Petición normal GET (simula Origin)
curl -i -H Origin: https://example.com https://tudominio.com/wp-json/wp/v2/posts
# Petición preflight (OPTIONS)
curl -i -X OPTIONS -H Origin: https://example.com -H Access-Control-Request-Method: POST -H Access-Control-Request-Headers: Authorization, Content-Type https://tudominio.com/wp-json/

En el navegador observa en DevTools (Network) las cabeceras de respuesta y errores en consola. Si la respuesta preflight no devuelve las cabeceras necesarias, el navegador bloqueará la petición real.

Problemas comunes y soluciones

  • Cabeceras no aparecen: puede que otro plugin o tema sobrescriba o que haya salida previa que impida añadir cabeceras. Revisa errores PHP y buffering. Usa ob_start() con precaución.
  • Cabeceras duplicadas: si se inyectan en el servidor web (Apache/Nginx) y también desde PHP, se duplica. Elimina duplicados o centraliza en un sitio.
  • Cache interfiere: proxies o CDNs pueden cachear respuestas con un solo Origin. Usa Vary: Origin y config de CDN para variar por origen o desactiva cache para rutas API.
  • Credenciales no enviadas: recuerda que el frontend debe usar fetch/axios con credentials: include o withCredentials: true además el servidor debe devolver Access-Control-Allow-Credentials: true y no usar como Allow-Origin.

Resumen de recomendaciones

  1. Usa una lista blanca de orígenes y devuelve el origen exacto en Access-Control-Allow-Origin.
  2. Añade Vary: Origin cuando la respuesta depende del Origin.
  3. Permite solo las cabeceras y métodos necesarios.
  4. Responde al preflight OPTIONS con las cabeceras necesarias y estado 200.
  5. Coloca la lógica en un plugin para mantener separados el tema y la funcionalidad y facilitar despliegues y mantenimiento.

Ejemplo completo de plugin minimal

Archivo: wp-cors-rest.php

lt?php
/
  Plugin Name: WP CORS REST Helper
  Description: Añade cabeceras CORS seguras a la REST API.
  Version: 1.0
  Author: Tu Nombre
 /

add_filter( rest_pre_serve_request, wpcors_rest_pre_serve, 10, 4 )

function wpcors_rest_pre_serve( served, result, request, route ) {
    // Configuración: ajustar orígenes permitidos
    allowed_origins = array(
        https://example.com,
        https://app.example.com,
    )

    origin = isset( _SERVER[HTTP_ORIGIN] ) ? trim( wp_unslash( _SERVER[HTTP_ORIGIN] ) ) : 

    if ( origin  in_array( origin, allowed_origins, true ) ) {
        header( Access-Control-Allow-Origin:  . origin )
        header( Vary: Origin )
    }

    header( Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS )
    header( Access-Control-Allow-Headers: Authorization, Content-Type, X-WP-Nonce )
    header( Access-Control-Allow-Credentials: true )
    header( Access-Control-Max-Age: 86400 )

    if ( OPTIONS === _SERVER[REQUEST_METHOD] ) {
        status_header( 200 )
        exit
    }

    return served
}

Conclusión

Añadir cabeceras CORS correctamente a la REST API de WordPress es fundamental para integraciones cross-origin seguras y funcionales. La mejor práctica es responder dinámicamente con el origen exacto (lista blanca), manejar las peticiones OPTIONS adecuadamente y no usar comodines junto con credenciales. Colocar esta lógica en un plugin facilita el mantenimiento y evita mezclar presentación y comportamiento.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *