phphttpsymfony4

Symfony 4.3 PHP: How to read the cookie from a 302 redirection response?


What I want to do (in the controller)

  1. Receive a request, based on which
  2. Send an external HTTP request
  3. Parse the response, based on which
  4. Return a response

I plan to have multiple functions that all follow this pattern. They're called one by one, in succession.

Symfony 4.3 introduces the HttpClient component and the BrowserKit component which at first sight seem to fit the bill. But as it turns out, things get more complicated. The external HTTP requests are sent to a server that tracks sessions with cookies, which shouldn't be anything too unusual. If the request is sent without cookies, the server always responds with the same landing page content. So we need the cookie. No worries, the response to the initial request comes with a "Set-Cookie" header that contains the cookie that is sent in the "Cookie" header in the subsequent requests. The HTTP status of the response with the cookie is "302 Found" which in a normal web environment, using a web browser, redirects to the next page with the same cookie in the request. Replicating this action in my Symfony controller seems to be very tricky. Expanding on step 2. of what I want to do now:

2.a. Send an external HTTP request

2.b. Read the cookie from the 302 response (without automatic redirection)

2.c. Set the "Cookie" header of the subsequent request, the value coming from 2.b.

2.d. Send the request to the new target (set in the "Location" header of the 302 response)

Here's some of the code from the controller:

use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Exception\RedirectionException;
use Symfony\Component\HttpFoundation\JsonResponse;

public function init(Request $request): JsonResponse
{
    $url = "external url";
    $client = HttpClient::create();
    try {
        $response = $client->request('GET', $url, [
            'max_redirects' => -1
        ]);
    } catch (RedirectionException $redirectionException) {

    }
    return new JsonResponse([], 200);
}

Not setting max_redirects means that the client follows the redirects and ends up returning the 200 response without any cookies. Setting the value to -1 will throw a redirection exception and again, the 302 response with the cookie seems to be lost.

I've also tried the HttpBrowser component:

$cookieJar = new CookieJar();
$client = new HttpBrowser(HttpClient::create(), null, $cookieJar);
$client->request('GET', $url);

But I get a 200 response without the cookie as it automatically follows the redirects.

I understand that the components mentioned here are new to Symfony 4.3 and "experimental" but I also don't think my task is overly complicated or unusual. But maybe there are other http clients for Symfony that would be better suited for the task?


Solution

  • As it turns out, this task was easier to complete by trying out other HTTP client components for Symfony than the native ones. So I've put Symfony HttpClient and HttpBrowser aside for now. I managed to implement a solution for this problem with guzzlehttp/guzzle version 6.3.3. Here's what it looks like in my controller function:

    use GuzzleHttp\Client;
    use Symfony\Component\HttpFoundation\Cookie;
    use Symfony\Component\HttpFoundation\JsonResponse;
    use Symfony\Component\HttpFoundation\Request;
    
    public function init(Request $request): JsonResponse {
        $client = new Client([
            'base_uri' => 'an external domain',
            'cookies' => true
        ]);
        // get the 302 response, without following
        $response = $client->get('path',[
            'allow_redirects' => false
        ]);
        // get the cookie from the 302 response
        $jar = new \GuzzleHttp\Cookie\CookieJar(true,[$response->getHeader('Set-Cookie')]);
        // follow the redirect manually, with the cookie from the 302 response
        $redirectResponse = $client->request('GET', $response->getHeader('Location')[0], [
            'cookies' => $jar
        ]);
    
        $jsonResponse = new JsonResponse(['message' => 'my message'],200);
        $originalCookie = Cookie::fromString($response->getHeader('Set-Cookie')[0]);
        $newCookie = Cookie::create(
            $originalCookie->getName(),
            $originalCookie->getValue(),
            $originalCookie->getExpiresTime(),
            '/',
            'my domain',
            false,
            true,
            $originalCookie->isRaw(),
            $originalCookie->getSameSite()
        );
    
        $jsonResponse->headers->setCookie($newCookie);
        return $jsonResponse;
    }
    

    The cookie cannot be taken from the response as such or it will never be sent back because the domain or the path doesn't match. So I create a new cookie which is very similar to the original one, then send it back to the browser. This way the cookie comes back in the subsequent requests and the data content can be sent forward.

    Other issues that arise here are the same-origin requirements which have to be taken care of on the external server.

    I'm still interested in how to do all this with the native Symfony 4.3 components, or also, if anyone can confirm that it's not possible.