Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

jaracas's avatar

In Laravel 9, the authorization header gets dropped on some requests using the HTTP Client (GuzzleHTTP)

I'm using Laravel 9, and using the HTTP Client (GuzzleHTTP) to make calls to an API. Previously this just worked, however the last few days something has changed (On their end, I believe) and my requests are having the authorization header stripped - whether I use withHeaders() or withToken().

Here's the relevant code:

private function buildRequest($options = []): PendingRequest {
    $request = Http:: 
    // withToken($this->accessToken ?? $this->getAccessToken()) // <- this doesn't work either
    withHeaders([
        'Accept' => 'application/json',
        'Content-Type' => 'application/json',
        'Authorization' => 'Bearer ' . $this->accessToken ?? $this->getAccessToken()
    ])->baseUrl($this->baseUrl)
        ->timeout($this->timeout)
        ->withOptions($options);

    if (!is_null($this->retryTimes) && !is_null($this->retryMilliseconds)) {
        $request->retry($this->retryTimes, $this->retryMilliseconds);
    }

    return $request;
}

The first request works, however when I try to request the second page (Using the below options) the request object has the authorization header completely stripped.

array:2 [ "limit" => "100" "page" => "2" ]

Request object:
-request: GuzzleHttp\Psr7\Request {#969
        -method: "GET"
        -requestTarget: null
        -uri: GuzzleHttp\Psr7\Uri {#984
          -scheme: "https"
          -userInfo: ""
          -host: "api.APIPLACE.io"
          -port: null
          -path: "/v4/stuff"
          -query: "limit=100?page=2"
          -fragment: ""
          -composedComponents: "https://api.APIPLACE.io/v4/stuff?limit=100?page=2"
        }
        -headers: array:4 [
          "Host" => array:1 [
            0 => "api4.APIPLACE.io"
          ]
          "User-Agent" => array:1 [
            0 => "GuzzleHttp/7"
          ]
          "Accept" => array:1 [
            0 => "application/json"
          ]
          "Content-Type" => array:1 [
            0 => "application/json"
          ]
        ]
        -headerNames: array:4 [
          "host" => "Host"
          "user-agent" => "User-Agent"
          "accept" => "Accept"
          "content-type" => "Content-Type"
        ]

What's going on? Why is the bearer token being stripped from the request? Obviously the response from the API Is:

access_denied: Must define bearer access token in header or request.

Some more code (The code to make multiple requests depending on how many pages there are):

private function getAllPages($response, $options) {
    $return = array();
    $links = $this->returnHeaderArray($response->headers());

    try {
        while (@isset($links) && @isset($links['next']) && !$response->failed()) {
            $this->checkToken();

            $options = array_merge($options, $links['next_page_options']);
            $request = $this->buildRequest($options);
            $response = $request->get($links['next']);
            dump($response, $options, $request);
            $links = $this->returnHeaderArray($response->headers(), $links['total']);
            if ($response->successful())
                $return = array_merge($return, $response->json());
        }
    } catch (\Exception $e) {
        dd($e);
        return $return;
    }

    return $return;
}
0 likes
5 replies
gych's avatar

Can you show the request object of your first request that works?

Issue might be related to the way the options are added in the second request. The two options are separated by '?' which seems odd, its more likely that they need to be separated by '&'

https://api.APIPLACE.io/v4/stuff?limit=100&page=2
jaracas's avatar

@gych Yeah I noticed that too, but that's how the "next page" response has it.

Here's the complete request object from the first request that works:

Illuminate\Http\Client\PendingRequest {#878 
  #factory: Illuminate\Http\Client\Factory {#875
    #dispatcher: Illuminate\Events\Dispatcher {#27 …5}
    #stubCallbacks: Illuminate\Support\Collection {#876
      #items: []
      #escapeWhenCastingToString: false
    }
    #recording: false
    #recorded: []
    #responseSequences: []
    #preventStrayRequests: false
  }
  #client: null
  #handler: null
  #baseUrl: "https://api.APIPLACE.io/v4/"
  #urlParameters: []
  #bodyFormat: "json"
  #pendingBody: null
  #pendingFiles: []
  #cookies: GuzzleHttp\Cookie\CookieJar {#913
    -cookies: []
    -strictMode: false
  }
  #transferStats: GuzzleHttp\TransferStats {#945
    -request: GuzzleHttp\Psr7\Request {#931
      -method: "GET"
      -requestTarget: null
      -uri: GuzzleHttp\Psr7\Uri {#928
        -scheme: "https"
        -userInfo: ""
        -host: "api.APIPLACE.io"
        -port: null
        -path: "/v4/stuff"
        -query: "limit=100"
        -fragment: ""
        -composedComponents: "https://api.APIPLACE.io/v4/stuff?limit=100"
      }
      -headers: array:5 [
        "User-Agent" => array:1 [
          0 => "GuzzleHttp/7"
        ]
        "Host" => array:1 [
          0 => "api.APIPLACE.io"
        ]
        "Accept" => array:1 [
          0 => "application/json"
        ]
        "Content-Type" => array:1 [
          0 => "application/json"
        ]
        "Authorization" => array:1 [
          0 => "Bearer asdfioja9ifdjrandomlettersandnumbers"
        ]
      ]
      -headerNames: array:5 [
        "user-agent" => "User-Agent"
        "host" => "Host"
        "accept" => "Accept"
        "content-type" => "Content-Type"
        "authorization" => "Authorization"
      ]
      -protocol: "1.1"
      -stream: GuzzleHttp\Psr7\Stream {#930
        -stream: stream resource {@831
          wrapper_type: "PHP"
          stream_type: "TEMP"
          mode: "w+b"
          unread_bytes: 0
          seekable: true
          uri: "php://temp"
          options: []
        }
        -size: 0
        -seekable: true
        -readable: true
        -writable: true
        -uri: "php://temp"
        -customMetadata: []
      }
    }
    -response: GuzzleHttp\Psr7\Response {#944
      -reasonPhrase: "OK"
      -statusCode: 200
      -headers: array:8 [
        "Date" => array:1 [
          0 => "Sat, 27 Jan 2024 22:08:16 GMT"
        ]
        "Content-Type" => array:1 [
          0 => "application/json; charset=UTF-8"
        ]
        "Content-Length" => array:1 [
          0 => "27822"
        ]
        "Connection" => array:1 [
          0 => "keep-alive"
        ]
        "server" => array:1 [
          0 => "Apache/2.4.56 (Debian)"
        ]
        "requestid" => array:1 [
          0 => "SJDFIOSDJFOIJ="
        ]
        "X-Total-Count" => array:1 [
          0 => "163"
        ]
        "link" => array:1 [
          0 => "<http://api.APIPLACE.io/v4/stuff?limit=100?page=2>; rel="next",<http://api.APIPLACE.io/v4/stuff?limit=100?page=2>; rel="last""
        ]
      ]
      -headerNames: array:8 [
        "date" => "Date"
        "content-type" => "Content-Type"
        "content-length" => "Content-Length"
        "connection" => "Connection"
        "server" => "server"
        "requestid" => "requestid"
        "x-total-count" => "X-Total-Count"
        "link" => "link"
      ]
      -protocol: "1.1"
      -stream: GuzzleHttp\Psr7\Stream {#940
        -stream: stream resource {@833
          wrapper_type: "PHP"
          stream_type: "TEMP"
          mode: "w+b"
          unread_bytes: 0
          seekable: true
          uri: "php://temp"
          options: []
        }
        -size: null
        -seekable: true
        -readable: true
        -writable: true
        -uri: "php://temp"
        -customMetadata: []
      }
    }
    -transferTime: 0.736455
    -handlerStats: array:38 [
      "url" => "https://api.APIPLACE.io/v4/stuff?limit=100"
      "content_type" => "application/json; charset=UTF-8"
      "http_code" => 200
      "header_size" => 384
      "request_size" => 213
      "filetime" => -1
      "ssl_verify_result" => 0
      "redirect_count" => 0
      "total_time" => 0.736455
      "namelookup_time" => 0.001043
      "connect_time" => 0.140699
      "pretransfer_time" => 0.28894
      "size_upload" => 0.0
      "size_download" => 27822.0
      "speed_download" => 37778.0
      "speed_upload" => 0.0
      "download_content_length" => 27822.0
      "upload_content_length" => 0.0
      "starttransfer_time" => 0.594494
      "redirect_time" => 0.0
      "redirect_url" => ""
      "primary_ip" => "IP-ADDRESS"
      "certinfo" => []
      "primary_port" => 443
      "local_ip" => "LOCAL-IP"
      "local_port" => 56298
      "http_version" => 2
      "protocol" => 2
      "ssl_verifyresult" => 0
      "scheme" => "HTTPS"
      "appconnect_time_us" => 288905
      "connect_time_us" => 140699
      "namelookup_time_us" => 1043
      "pretransfer_time_us" => 288940
      "redirect_time_us" => 0
      "starttransfer_time_us" => 594494
      "total_time_us" => 736455
      "appconnect_time" => 0.288905
    ]
    -handlerErrorData: 0
  }
  #options: array:4 [
    "connect_timeout" => 10
    "http_errors" => false
    "timeout" => 10
    "headers" => array:3 [
      "Accept" => "application/json"
      "Content-Type" => "application/json"
      "Authorization" => "Bearer asdfioja9ifdjrandomlettersandnumbers"
    ]
  ]
  #throwCallback: null
  #throwIfCallback: null
  #tries: 3
  #retryDelay: 1000
  #retryThrow: true
  #retryWhenCallback: null
  #beforeSendingCallbacks: Illuminate\Support\Collection {#897
    #items: array:1 [
      0 => Closure(Request $request, array $options, PendingRequest $pendingRequest) {#881
        class: "Illuminate\Http\Client\PendingRequest"
        this: Illuminate\Http\Client\PendingRequest {#878}
      }
    ]
    #escapeWhenCastingToString: false
  }
  #stubCallbacks: Illuminate\Support\Collection {#889
    #items: []
    #escapeWhenCastingToString: false
  }
  #preventStrayRequests: false
  #middleware: Illuminate\Support\Collection {#880
    #items: []
    #escapeWhenCastingToString: false
  }
  #async: false
  #promise: null
  #request: Illuminate\Http\Client\Request {#942
    #request: GuzzleHttp\Psr7\Request {#931}
    #data: array:1 [
      "limit" => 100
    ]
  }
  #mergableOptions: array:6 [
    0 => "cookies"
    1 => "form_params"
    2 => "headers"
    3 => "json"
    4 => "multipart"
    5 => "query"
  ]
}
jaracas's avatar

I just tested it in Postman, and the ?'s work just fine - I mean it grabs the first page rather than whatever page number it's requesting, but it doesn't error out or anything

jaracas's avatar

I added ->dump() to the request and it's showing page 2 is being requested with the auth bearer token... But somewhere between buildRequest() and getAllPages() it gets stripped. I even tried adding withToken to the get() call in getAllPages and it's still being stripped off somewhere.

If I add withHeaders(['Authorization' => 'Bearer '.$this->accessToken ?? $this->getAccessToken()]) to getAllPages' get() request, it shows TWO Authorization headers in the dump() of the request object

I'm seeing this:

 "allow_redirects" => array:5 [
    "max" => 5
    "protocols" => array:2 [
      0 => "http"
      1 => "https"
    ]
    "strict" => false
    "referer" => false
    "track_redirects" => false
  ]
  "decode_content" => true
  "verify" => true
  "idn_conversion" => false
  "__redirect_count" => 1

In a dump() of the get request

jaracas's avatar
jaracas
OP
Best Answer
Level 1

I figured it out - the API changed their response for 'next' and 'last' pages to http:// instead of https://, and guzzle strips the Authorization header when there's a redirect - which there was a redirect because the API's servers have one set to redirect all http to https.

I know it's an edge case but hopefully this will help someone in the future.

Always make sure to add dump() to your requests if you're having issues with the authorization header being stripped, and if you see redirects or http:// when your initial URL was https... Well, that's the issue. I fixed it by just searching for http:// and replacing it with https:// before calling the 'Next' page.

1 like

Please or to participate in this conversation.