Como contornar as restrições de CORS em solicitações entre domínios? (com proxy)

Olá, quero desenvolver um servidor web proxy em PHP que permita aos usuários acessar outros sites (de domínios diferentes) através dele, contornando o erro de CORS (Compartilhamento de Recursos de Origem Cruzada). O objetivo é criar uma camada intermediária que faça solicitações em nome do usuário, permitindo assim o acesso a conteúdos que possam estar bloqueados em determinados domínios devido às restrições de CORS.

Segue um exemplo de código que já elaborei:

<?php
// Verifica se a URL de destino foi fornecida na query string
if (isset($_GET['url'])) {
$url = $_GET['url'];

// Configura opções de stream para o contexto de file_get_contents
$options = array(
    'http' => array(
        'header' => "User-Agent: PHP\r\n",
        'method' => $_SERVER['REQUEST_METHOD'],
        'follow_location' => false // Evita redirecionamentos no proxy
    )
);

// Cria o contexto para a requisição
$context = stream_context_create($options);

// Realiza a requisição para a URL de destino usando file_get_contents
$result = file_get_contents($url, false, $context);

// Pega os headers da resposta para enviar ao cliente
$headers = $http_response_header;

// Envia os headers para o cliente
foreach ($headers as $header) {
    header($header);
}

echo $result;

} else {
// Caso a URL de destino não tenha sido fornecida, retorna um erro
header("HTTP/1.0 400 Bad Request");
echo "URL de destino não especificada.";
}

Embora esse código aborde parte do problema, ainda há a questão pendente das solicitações de origem cruzada (CORS). Gostaria de entender como desenvolver uma solução que possa resolver completamente o problema do CORS. Agradeço antecipadamente por qualquer orientação, ajuda ou exemplo de código que possam direcionar-me para a solução adequada. Em relação ao tópico: "Como contornar as restrições de CORS em solicitações entre domínios?"

Basicamente, vc precisa adicionar alguns cabeçalhos especificos.

Antes daquele seu if, adicione isto:

$method = $_SERVER['REQUEST_METHOD'];
$origin = $_SERVER['HTTP_ORIGIN'];

if ($method === 'OPTIONS') {
  $requestMethod = $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'];
  $requestHeaders = $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'];
  header("Access-Control-Allow-Methods: $requestMethod");
  header("Access-Control-Allow-Headers: $requestHeaders");
}

header("Access-Control-Allow-Origin: $origin");
header('Access-Control-Allow-Credentials: true');

// ...

Antes de alguns tipos de requisições o navegador faz uma requisição extra com o método OPTIONS chamada de pre-flight request.

Na pre-flight request ele espera que o servidor responda quais métodos e quais cabeçalhos ele permite. O cabeçalho "Access-Control-Allow-Headers' é importante porque seu cliente pode querer enviar cabeçalhos diferentes e vc precisa permiti-los.

O cabeçalho 'Access-Control-Allow-Origin' deve estar presente em qualquer requisição. Seu valor pode ser '*' indicando que qualquer origin é permitida, mas o asterisco é inválido quando vc está usando 'Access-Control-Allow-Credentials: true', por isso temos que usar a origin exata do cliente.

O cabeçalho 'Access-Control-Allow-Credentials: true' é importante, pois permite que o cliente envie cookies junto com a requisição.

Não tenho certeza se isso cobre todos os casos de uso, mas acredito que é um bom começo para o que vc precisa. Para entender melhor o protocolo CORS, recomendo a especificação: https://fetch.spec.whatwg.org/#http-cors-protocol

Não sei se entendi muito bem, mas **não tenho como mudar o cabeçalho do outro domínio já que não tenho acesso a eles**. O meu código acima faz a requisição do conteúdo da página de domínio diferente (no qual não tenho acesso), e assim exibe no meu domínio, por exemplo: examplo.com.br/proxy.php?url=https://outrodominio.com.br. Mas suponhamos que o outro domínio https://outrodominio.com.br faça uma chamada para uma folha de estilo na url https://outrodominio.com.br/style.css então ainda vou ter problema com o Compartilhamento de Recursos de Origem Cruzada. **Queria saber como posso resolver isso? No entanto, já agradeço pela ajuda e referência.**
Seguindo o exemplo para deixar mais claro, não tem como eu simplesmente fazer uma requisição para https://outrodominio.com.br/ senão teria restrição pelo CORS. Por isso faço pelo PHP e a página e exibida no meu domínio como no exemplo examplo.com.br/proxy.php?url=https://outrodominio.com.br, assim posso fazer a requisição para na minha origem examplo.com.br. Os links apresentados são apenas ilustrativos.
Moço, eu entendi perfeitamente sua situação, por isso eu disse "adicine isso antes daquele seu `if`". Aquele código que vc mostrou onde tem o trecho `if (isset($_GET['url']))` não está no seu arquivo `proxy.php`? Pois então, é a esse `if` que eu me referia e é antes dele que vc tem que adicionar o que eu sugeri.
Sim, ao adicionar o código acima não muda nada, recebo apenas os erros:
O código que gerou estes erros está no seu GitHub? Eu poderia vê-lo?
Testei no chrome, edge e firefox. Claro o código etá todo aqui: ```php array( 'header' => "User-Agent: PHP\r\n", 'method' => $_SERVER['REQUEST_METHOD'], 'follow_location' => false // Evita redirecionamentos no proxy ) ); // Cria o contexto para a requisição $context = stream_context_create($options); // Realiza a requisição para a URL de destino usando file_get_contents $result = file_get_contents($url, false, $context); // Pega os headers da resposta para enviar ao cliente $headers = $http_response_header; //Envia os headers para o cliente foreach ($headers as $header) { header($header); } echo $result; } else { // Caso a URL de destino não tenha sido fornecida, retorna um erro header("HTTP/1.0 400 Bad Request"); echo "URL de destino não especificada."; } ```
Testei seu código e não aparece aquele erro. Será que estamos falando da mesma coisa? Preparei um repositório no GitHub. Segue as instruções do README e me diz se tiver algum problema, tá bom? https://github.com/wldomiciano/cors-proxy-com-php
Testei, me retorna isso no console (No localhost:8001): localhost/:1 Access to fetch at 'http://localhost:8003/destino.php' from origin 'http://localhost:8001' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. (índice):12 Uncaught (in promise) TypeError: Failed to fetch at doRequest ((índice):12:9) at (índice):21:7 doRequest @ (índice):12 (anônimo) @ (índice):21 Promise.then (assíncrono) doRequest @ (índice):18 (anônimo) @ (índice):21 (índice):18 Warning: file_get_contents(http://localhost:8003/destino.php): Failed to open stream: Cannot assign requested address in /var/www/html/proxy.php on line 28 Warning: Undefined variable $http_response_header in /var/www/html/proxy.php on line 29 Warning: foreach() argument must be of type array|object, null given in /var/www/html/proxy.php on line 31 Está usando qual navegador?
Coloquei online em https://inteligenciaplena.com.br/teste/proxy.php e não me retornou mais esse erro, talvez seja a verão do php local! Mas como pode ver no console ainda tenho erros relacionados a Compartilhamento de Recursos de Origem Cruzada: https://inteligenciaplena.com.br/teste/proxy.php?url=https://joveminventor.com.br/ São referentes a recursos do site, como css e js... Como poderia resolvê-los? Talvez mudar o url para mandar para o proxy, isso seria viável?
Agora eu entendi aquele erro HTTP_ORIGIN ser indefinido. Realmente não estávamos falando da mesma coisa. Veja bem, se vc está acessando do seu navegador o endereço `https://inteligenciaplena.com.br/teste/proxy.php?url=https://joveminventor.com.br/` vc não está sujeito ao CORS e usar seu proxy é desnecessário. Na verdade vc está criando um problema de CORS que antes não existia como vc mesmo notou naqueles erros relacionados com CSS, JS e etc... Pensa comigo: Por que eu iria acessar `https://inteligenciaplena.com.br/teste/proxy.php?url=https://joveminventor.com.br/` se eu posso acessar `https://joveminventor.com.br/` diretamente? O CORS ocorre quando eu tento acessar `https://joveminventor.com.br/` a partir de um endereço não autorizado usando o `fetch`, como eu fiz naquele exemplo do repositório. É nesta situação que seu proxy faz sentido. Então eu imaginei que vc queria usar o `fetch` para fazer uma requisição para alguma API que retornaria um JSON. Claro que vc pode usar o `fetch` para requisitar um arquivo HTML, mas aí vc vai ter que impelementar uma lógica para substituir a URL de todos os CSS, JS e qualquer outro arquivo que o HTML desejado esteja referenciando. Veja novamente o meu repositório. Eu o atualizei com a lógica que lida com arquivos CSS. Mas é só um exemplo, pois está incompleto. Veja com atenção o README também porque eu modifiquei alguns comandos.
Entendi, bom exemplo! Muito obrigado pela direção.
Maravilha, então! Eu pretendo excluir aquele repositório de exemplo, vc ainda vai precisar dele?
Já salvei o código e estou avançando, pode apagar caso queira. Agradeço mais uma vez
Ah, uma dúvida: A requisição que gerou este erro foi feita a partir de um navegador? Porque é muito estranho que o HTTP_ORIGIN não esteja presente, pois o navegador sempre envia este cabeçalho sem a gente precisar fazer nada.