Contents
Introducción
En este tutorial se explica de forma detallada cómo crear en WordPress un endpoint proxy que actúe como intermediario entre el navegador y APIs externas para evitar problemas de CORS. La técnica consiste en que el navegador solicite a tu propio dominio (sin CORS) y tu servidor (WordPress) solicite a la API externa, devolviendo la respuesta al navegador. Se incluyen ejemplos completos en PHP para registrar la ruta REST, validar y sanitizar peticiones, manejar headers, caché, errores, y un ejemplo de uso desde JavaScript.
¿Por qué usar un proxy en WordPress?
- Evita restricciones CORS: El navegador hace la petición a tu mismo dominio (sin política CORS), y tu servidor hace la petición a la API externa.
- Oculta claves y credenciales: Los tokens o claves se mantienen en el servidor y no viajan al cliente.
- Control y seguridad: Puedes imponer lista blanca de dominios, limitar tasas, filtrar respuestas y aplicar caché.
- Compatibilidad: Funciona con APIs que no permiten CORS o que requieren cabeceras complejas.
Consideraciones de seguridad antes de empezar
- Nunca implementar un proxy abierto que permita cualquier URL: obliga siempre a una allowlist (lista blanca) o reglas precisas.
- Sanitiza y valida la URL y parámetros.
- Limita tamaño de respuesta y tiempo de conexión para evitar abuso y consumo de memoria.
- Usa caching y rate-limiting para controlar tráfico hacia APIs externas y mitigar costes o bloqueos.
- Si necesitas exponer credenciales para la API externa, guárdalas en constantes, opciones seguras o variables de entorno y no en el cliente.
Implementación paso a paso (plugin o functions.php)
Ejemplo completo para registrar un endpoint REST en WordPress. Este código puede colocarse en un plugin o en functions.php de un child theme (recomendado: plugin para mayor control).
1) Registro de la ruta REST y permisos
WP_REST_Server::READABLE . , . WP_REST_Server::CREATABLE . , . WP_REST_Server::EDITABLE . , . WP_REST_Server::DELETABLE, callback => wp_proxy_request_handler, permission_callback => wp_proxy_permission_check, )) }) / Permission callback: aquí se controla quién puede usar el proxy. - Devuelve true para permitir acceso público (riesgo mayor). - Mejor: implementar controles (token, usuario logueado, referer, etc.). / function wp_proxy_permission_check(WP_REST_Request request) { // Ejemplo seguro: permitimos peticiones públicas pero solo a hosts de la allowlist. // La validación de la URL real se hace en el handler. return true } ?>
2) Handler principal: validación, petición a la API externa, respuesta
Este handler realiza:
- Recoge la URL objetivo (param url).
- Valida y checa la lista blanca de hosts.
- Prepara argumentos para wp_remote_request: método, headers seguros, body.
- Aplica caché para GET usando transients.
- Devuelve la respuesta con el mismo Content-Type y status code de la API externa y añade cabeceras CORS si es necesario.
get_param(url) if (empty(url)) { return new WP_REST_Response(array(error => missing_url), 400) } // Sanitizar url = esc_url_raw(url) if (empty(url)) { return new WP_REST_Response(array(error => invalid_url), 400) } // Lista blanca de hosts (ajusta a tus necesidades) allowed_hosts = array( api.example.com, another.service.net, raw.githubusercontent.com, ) parsed = wp_parse_url(url) if (empty(parsed[host]) ! in_array(parsed[host], allowed_hosts, true)) { return new WP_REST_Response(array(error => host_not_allowed), 403) } // Determinar método HTTP y preparar args method = strtoupper(request->get_method()) // GET, POST, PUT, DELETE, etc. args = array( method => method, timeout => 20, redirection => 5, httpversion => 1.1, ) // Copiar headers seguros desde la petición original incoming_headers = request->get_headers() forward_headers = array() allowed_forward = array(authorization, content-type, accept) // ajustar lista foreach (incoming_headers as key => value) { k = strtolower(key) if (in_array(k, allowed_forward, true)) { // WP REST headers vienen en arrays forward_headers[k] = is_array(value) ? implode(, , value) : value } } if (! empty(forward_headers)) { args[headers] = forward_headers } // Pasar body para métodos no GET if (method !== GET) { body = request->get_body() if (! empty(body)) { args[body] = body } else { // Para formularios params = request->get_params() if (! empty(params)) { args[body] = params } } } // CACHÉ para GET (opcional) cache_key = proxy_ . md5(url . serialize(args)) if (method === GET) { cached = get_transient(cache_key) if (cached !== false) { // Devolver respuesta cacheada (asumimos array con body,headers,status) resp = new WP_REST_Response(cached[body], cached[status]) if (! empty(cached[headers]) is_array(cached[headers])) { foreach (cached[headers] as hk => hv) { resp->header(hk, hv) } } // Permitir CORS desde todas las fuentes o ajustar dominio resp->header(Access-Control-Allow-Origin, ) return resp } } // Petición hacia la API externa remote = wp_remote_request(url, args) if (is_wp_error(remote)) { error = remote->get_error_message() return new WP_REST_Response(array(error => request_failed, message => error), 502) } status = wp_remote_retrieve_response_code(remote) body = wp_remote_retrieve_body(remote) response_headers = wp_remote_retrieve_headers(remote) // array of headers // Construir respuesta REST con mismo body y Content-Type resp = new WP_REST_Response(body, status) // Establecer Content-Type si viene de la API externa if (! empty(response_headers[content-type])) { resp->header(Content-Type, response_headers[content-type]) } else { // Forzar tipo si no viene resp->header(Content-Type, application/octet-stream) } // Reenviar otras cabeceras útiles (solo algunas seguras) pass_through = array(cache-control, expires, etag) foreach (pass_through as hk) { if (! empty(response_headers[hk])) { resp->header(hk, response_headers[hk]) } } // Permitir CORS (ajusta a tu dominio en producción) resp->header(Access-Control-Allow-Origin, ) resp->header(Access-Control-Allow-Methods, GET, POST, OPTIONS) resp->set_status(status) // Guardar en caché si es GET y status 200 if (method === GET status === 200) { cache_value = array( body => body, headers => array_intersect_key(response_headers, array_flip(pass_through)), status => status, ) // TTL configurable, ejemplo 5 minutos set_transient(cache_key, cache_value, MINUTE_IN_SECONDS 5) } return resp } ?>
3) Limitar tasa por IP (rate limit simple)
Ejemplo sencillo que bloquea si hay más de X peticiones en Y segundos por IP usando transients:
1, t => time()) set_transient(key, data, 60) // ventana 60s return true } data[count] set_transient(key, data, 60) // límite: 30 peticiones por minuto if (data[count] > 30) { return false } return true } // Uso dentro del handler: if (! wp_proxy_rate_limit_check()) { return new WP_REST_Response(array(error => rate_limited), 429) } ?>
Ejemplo de uso desde JavaScript (cliente)
Ejemplo para consumir el proxy desde el navegador usando fetch. Nota: el navegador se conecta a tu dominio /wp-json/… y no hay problemas de CORS si el frontend y backend comparten origen.
// GET simple (esperando JSON) const target = https://api.example.com/data?param=value fetch(/wp-json/proxy/v1/request?url= encodeURIComponent(target), { method: GET, credentials: same-origin // opcional según si necesitas cookies }) .then(response => { const ct = response.headers.get(content-type) if (ct.includes(application/json)) { return response.json() } return response.text() }) .then(data => { console.log(Proxy result:, data) }) .catch(err => console.error(Proxy error:, err))
Enviar POST con JSON y recibir respuesta
const target = https://api.example.com/resource fetch(/wp-json/proxy/v1/request, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ url: target, payload: { key: value } }) }) .then(r => r.json()) .then(json => console.log(json))
Detalles, buenas prácticas y casos especiales
Sobre evitar CORS
La mecánica del proxy evita necesitar que la API externa permita CORS: el navegador nunca habla directamente con la API externa, lo hace con tu dominio. Aun así, el proxy puede querer añadir cabeceras Access-Control-Allow-Origin para que clientes en otros orígenes puedan consumir tu endpoint si publicas una web en otro dominio (pero normalmente el frontend y el WP están en el mismo dominio).
Encabezados y seguridad
- No reenvíes indiscriminadamente cookies ni headers que puedan comprometer tu sistema.
- Reenvía solo headers necesarios (Authorization opcional si controlas su uso).
- Controla y filtra URLs: permitiendo solo hosts concretos o patrones.
Archivos binarios y streaming
Si necesitas descargar imágenes/archivos grandes, la implementación con WP_REST_Response y wp_remote_request puede consumir memoria. Para contenido grande puedes:
- Usar wp_remote_request y devolver directamente el body si cabe en memoria.
- Para streaming real (sin cargar todo en memoria), usar un endpoint PHP que abra un stream desde la URL remota y haga echo de bloques, ajustando Content-Type y Content-Length y terminando con exit esto suele hacerse fuera del sistema REST o con cuidado en un endpoint propio.
Autenticación hacia APIs externas
Si la API externa requiere token, almacénalo en opciones seguras o constantes y aplícalo desde el servidor al construir args[headers][Authorization] = Bearer … así el token nunca llega al cliente.
Manejo de errores y códigos HTTP
Devuelve siempre el código HTTP real recibido de la API externa (o uno representativo) para que el cliente pueda reaccionar correctamente. Encapsula errores de wp_remote_request en 5xx o 502 para indicar fallo del proxy.
Logs y monitorización
Registra errores importantes en los logs para auditar problemas: utiliza error_log() o plugins de logging. Monitorea latencias y tasas de error para detectar problemas de la API externa o abusos del proxy.
Tabla resumen: qué reenviar del servicio remoto y qué bloquear
Reenviar | Bloquear / No reenviar |
---|---|
Content-Type, Cache-Control, Expires | Set-Cookie, Server (información interna), X-Powered-By |
ETag (opcional, para caching) | Cabeceras que contengan tokens sensibles |
Resumen final
Crear un endpoint proxy en WordPress es una solución práctica y potente para esquivar problemas CORS, proteger credenciales y controlar el flujo hacia APIs externas. La clave está en validar/limitar las URLs permitidas, sanitizar datos, aplicar caché, y proteger el endpoint contra abuso (rate-limiting, logging y listas blancas). El ejemplo proporcionado ofrece una base sólida que puedes adaptar: mejorar el permiso de acceso, implementar un proxy de streaming para archivos grandes, añadir autenticación por token o integrar Redis para caché a mayor escala.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |