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 🙂 |
