[php/laravel] Http client met bearer lijkt niet te werken

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

  • Saven
  • Registratie: December 2006
  • Laatst online: 02-10 16:35

Saven

Administrator

Topicstarter
Hi all,

Ik loop tegen een vaag probleem aan, ik wil een kleine wrapper maken voor communicatie met een externe api. Laravel heeft daar tegenwoordig een mooie Http client (Guzzle wrapper) voor.

Het issue: ik moet eerst een authenticatie token (Bearer) opvragen. Dat gaat prima. Die token gebruik ik vervolgens voor alle calls die daarna volgen. Kleine class:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php

namespace App\Service;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

class MyApiClient
{
    private const AUTH_ENDPOINT = '/auth/token';

    private string $url;

    private string $token;

    public function __construct()
    {
        $this->url = config('apps.service.api_rest_url');
        
        // Token opvragen en instellen
        $this->token = $this->generateAuthToken('username', 'password');
    }

    public function generateAuthToken($username, $password)
    {
        $response = Http::acceptJson()->post($this->url.self::AUTH_ENDPOINT, [
            'username' => $username,
            'password' => $password
        ]);

        // Deze call werkt altijd, geeft altijd een geldige token terug.
        return $response->json();
    }

    private function sendRequest($endpoint)
    {
        $url = $this->url.$endpoint;

        // $this->token geeft ALTIJD correct de token terug, ook bij calls waarbij de end server een (fout)melding geeft
        $response = Http::withToken($this->token)->get($url);
        
        return $response->json();
    }

    public function getProducts()
    {
        return $this->sendRequest('/products');
    }
}


En vervolgens simpel een call naar getProducts
PHP:
1
2
3
$client = new MyApiClient();

$products = $client->getProducts();


Exact deze opzet geeft een vage response (html body met foutmelding) van de end server terug.Het aanvragen van de token werkt echter goed. De token wordt óók correct als Authorization: Bearer XXX meegegeven als header bij de getProducts/sendRequest call.

Ik zou eerst denken dat de token wordt niet goed meegestuurd werd oid met de getProducts call. Dus handmatig een token ingevoerd:
PHP:
1
2
3
4
5
6
7
8
9
10
    public function __construct()
    {
        $this->url = config('apps.service.api_rest_url');
        
        // Token opvragen/instellen
        $this->token = $this->generateAuthToken('username', 'password');

        // Handmatig overriden
        $this->token = '2342jrfsgnjdgkkdf' 
    }

Gek genoeg werkt dit niet. Als ik de regel met generateAuthToken verwijder, werkt het wel :X :X

Het lijkt dus dat die Http call in de generateAuthToken een soort van delay veroorzaakt?

Na een beetje aanklooien lijkt dit gek genoeg te werken met de originele opzet:
PHP:
1
2
3
4
5
$client = new MyApiClient();

sleep(1);

$products = $client->getProducts();


Maar dat is natuurlijk geen doen :D

Ik heb heel de documentatie uitgepluisd en ook nog de interne source code bekeken, maar ik snap niet waar dit probleem vandaan komt. Een snelle oplossing is natuurlijk gewoon zelf cURL te gebruiken, maar ik wil gewoon weten waar dit probleem vandaan komt 8)7

Iemand van jullie die hier meer van weet toevallig?

Alle reacties


Acties:
  • 0 Henk 'm!

  • Pred
  • Registratie: November 2001
  • Laatst online: 02-10 13:35
Wat is de return value van generateAuthToken? Wat returnt die ->json()? Is dat eigenlijk wel een string?

Acties:
  • 0 Henk 'm!

  • eamelink
  • Registratie: Juni 2001
  • Niet online

eamelink

Droptikkels

Als ik mag gokken: tijdverschil tussen de authorization server en de resource server, waardoor de token nog niet geldig is op de resource server als je hem te snel gebruikt :)

Acties:
  • 0 Henk 'm!

  • Saven
  • Registratie: December 2006
  • Laatst online: 02-10 16:35

Saven

Administrator

Topicstarter
Pred schreef op zondag 19 september 2021 @ 20:32:
Wat is de return value van generateAuthToken? Wat returnt die ->json()? Is dat eigenlijk wel een string?
Een geldige token (string). Dat is echt het probleem niet :)
eamelink schreef op zondag 19 september 2021 @ 20:33:
Als ik mag gokken: tijdverschil tussen de authorization server en de resource server, waardoor de token nog niet geldig is op de resource server als je hem te snel gebruikt :)
Omfg, dat lijkt dan inderdaad het meest waarschijnlijk :X Dus eigenlijk gewoon een issue bij de de externe dienst zelf :D Lekker dan, hele dag aan verspild :') Zal het eens navragen.

Is daar een andere oplossing voor dan een dirty sleep/usleep? :+

Edit: nu ik er over nadenk. Als ik handmatig de token override met een andere geldige token, dan nog lijkt het niet te werken :X Er lijkt dus wel een soort delay in te zitten.

[ Voor 9% gewijzigd door Saven op 19-09-2021 20:41 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Ik zie geen “acceptJson” in je andere calls, gaan die ook met Accept: application/json de deur uit?

Acties:
  • 0 Henk 'm!

  • Saven
  • Registratie: December 2006
  • Laatst online: 02-10 16:35

Saven

Administrator

Topicstarter
Verwijderd schreef op zondag 19 september 2021 @ 20:40:
Ik zie geen “acceptJson” in je andere calls, gaan die ook met Accept: application/json de deur uit?
Ah sorry misschien onduidelijk, maar de end server geeft in dit geval altijd json terug. Heb het wel getest met acceptsJson, maar in het voorbeeld staat het idd niet meer. De workarounds werken echter wel gewoon.

Ook als ik de token override zoals in het voorbeeld, dan nog werkt het niet. In de dump() van de call, zie ik echter wel dat mijn handmatig overridden token in de Bearer header werd meegestuurd. Het lijkt dus niet een kwestie van een probleem bij de end server, maar echt in de Http Client 8)7


Dit werkt niet:
PHP:
1
2
3
4
5
6
7
8
9
10
    public function __construct()
    {
        $this->url = config('apps.service.api_rest_url');
        
        // Token opvragen/instellen
        $this->token = $this->generateAuthToken('username', 'password');

        // Handmatig overriden
        $this->token = '2342jrfsgnjdgkkdf' 
    }


Dit werkt wel:
PHP:
1
2
3
4
5
6
7
    public function __construct()
    {
        $this->url = config('apps.service.api_rest_url');

        // De generateAuthToken() is nu verwijderd
        $this->token = '2342jrfsgnjdgkkdf' 
    }


De foutmelding van de server wanneer ik de getProducts call doe, is overigens een vage '429 Too Many Requests'. Als ik de genAuthToken verwijder uit de constructor werkt het dus gek genoeg wel

[ Voor 35% gewijzigd door Saven op 19-09-2021 20:50 ]


Acties:
  • +1 Henk 'm!

Verwijderd

Nee ik bedoel, stuur jij die header mee?

Acties:
  • 0 Henk 'm!

  • Pred
  • Registratie: November 2001
  • Laatst online: 02-10 13:35
Voeg anders op een paar plekken Log::info(...) toe om te kijken wat de token op dat moment is?

[ Voor 12% gewijzigd door Pred op 20-09-2021 08:58 ]


Acties:
  • 0 Henk 'm!

  • Barryvdh
  • Registratie: Juni 2003
  • Laatst online: 02-10 16:36
Pred schreef op zondag 19 september 2021 @ 20:32:
Wat is de return value van generateAuthToken? Wat returnt die ->json()? Is dat eigenlijk wel een string?
Dit heb je getest door die value te dumpen/dd()'en ook? Niet dat het stiekem een array is ofzo.

Je kan ook je calls loggen met Laravel Telescope, dan zie je wat hij verzend precies.

Acties:
  • 0 Henk 'm!

  • DukeBox
  • Registratie: April 2000
  • Laatst online: 22:22

DukeBox

loves wheat smoothies

Zoals @Verwijderd aangeeft, de bearer moet in de header niet in de post. Aangezien je in $this beide hebt staan, trek je die later weer uit elkaar ?

Duct tape can't fix stupid, but it can muffle the sound.


Acties:
  • 0 Henk 'm!

Verwijderd

Wat ik vooral wil weten is of je uitgaande berichten de Accept: application/json header hebben.

Acties:
  • 0 Henk 'm!

  • Barryvdh
  • Registratie: Juni 2003
  • Laatst online: 02-10 16:36
Maar als de call met handmatige token wel werkt, lijkt het probleem niet in die request te zitten dus.

Maar jij zegt dat als je handmatig het token instelt dit werkt, maar als je daarvoor de authentication call doet, het niet meer werkt met diezelfde token? En met sleep er tussen wel?

Wat voor error krijg je dan precies? Krijg je een 429 Too Many Requests? Dan loop je wellicht tegen een rate limiter aan.

Acties:
  • 0 Henk 'm!

  • Voutloos
  • Registratie: Januari 2002
  • Niet online
Is het wel stateless? Weet je zeker dat je vorige token niet ongeldig maakt als je een generateAuthToken() doet? Want dan werken daarom je tests met geforceerde tokenwaardes niet.

En tenzij je écht iets met polling implementeert (en dan nog is het vaak niet beste idee), is sleep() echt never nooit niet de oplossing, dus je hebt gelijk dat je dat niet wil. :)

[ Voor 6% gewijzigd door Voutloos op 20-09-2021 10:14 ]

{signature}


Acties:
  • 0 Henk 'm!

  • Saven
  • Registratie: December 2006
  • Laatst online: 02-10 16:35

Saven

Administrator

Topicstarter
Verwijderd schreef op maandag 20 september 2021 @ 09:13:
Wat ik vooral wil weten is of je uitgaande berichten de Accept: application/json header hebben.
Ja en nee. Dat hebben ze wel gehad, in mijn voorbeeldcode hebben ze dat niet meer. Maar maakt voor de end server niets uit omdat die endpoint sowieso altijd json terugstuurt. Maar daar zit de fout niet in, want zelfs als ik de token handmatig override, zolang de Auth() method wordt aangeroepen werkt het niet meer :X Wel als ik de token handmatig set en de Auth method niet aanroep :)
Barryvdh schreef op maandag 20 september 2021 @ 09:04:
[...]

Dit heb je getest door die value te dumpen/dd()'en ook? Niet dat het stiekem een array is ofzo.
Jep, ik set 'm dus zelfs ook nog handmatig..
Barryvdh schreef op maandag 20 september 2021 @ 09:39:
Maar als de call met handmatige token wel werkt, lijkt het probleem niet in die request te zitten dus.

Maar jij zegt dat als je handmatig het token instelt dit werkt, maar als je daarvoor de authentication call doet, het niet meer werkt met diezelfde token? En met sleep er tussen wel?

Wat voor error krijg je dan precies? Krijg je een 429 Too Many Requests? Dan loop je wellicht tegen een rate limiter aan.
Precies dat :) Als ik vóór het handmatig setten nog een Auth call doe, krijg ik idd die 429 melding. Moet daarbij wel zeggen dat ik op een andere server voor dezelfde dienst ook een kleine applicatie heb draaien, die werkt in principe op dezelfde manier maar dan met curl() functies. Dus lijkt me bijna sterk dat de requests elkaar te snel opvolgen. Ik zal t eens testen met curl() functies ipv de Http Client
Voutloos schreef op maandag 20 september 2021 @ 10:11:
Is het wel stateless? Weet je zeker dat je vorige token niet ongeldig maakt als je een generateAuthToken() doet? Want dan werken daarom je tests met geforceerde tokenwaardes niet.

En tenzij je écht iets met polling implementeert (en dan nog is het vaak niet beste idee), is sleep() echt never nooit niet de oplossing.
Ja, gewoon plain nginx/php. Dat dacht ik zelf in eerste instantie ook idd, maar tokens zijn gewoon een uur oid geldig - ook als ik nieuwe genereer, dan blijven oude tokens gewoon geldig. Dat is het probleem dus niet. Sleep leek me ook zeer zeker niet een oplossing nee :D

Acties:
  • 0 Henk 'm!

  • Saven
  • Registratie: December 2006
  • Laatst online: 02-10 16:35

Saven

Administrator

Topicstarter
Nou, vage zooi dus met die Http Client in Laravel. @Barryvdh @Verwijderd met deze snel gemaakte curl wrapper werkt het wel prima.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?php

namespace App\Service;

class MyApiClientCurl
{
    private const AUTH_ENDPOINT = '/auth/token';

    private string $url;

    private ?string $token = null;

    public function __construct()
    {
        $this->url = config('apps.service.api_rest_url');

        $this->token = $this->generateAuthToken('username', 'password');
    }

    public function generateAuthToken($username, $password)
    {
        return $this->sendRequest(self::AUTH_ENDPOINT, [
            'username' => $username,
            'password' => $password
        ]);
    }

    private function sendRequest($endpoint, array $data=[])
    {
        $url = $this->url.$endpoint;

        $process = curl_init($url);

        $headers = [
            'Content-Type: application/json'
        ];

        // Token header alleen meesturen als we al geauthenticeerd zijn.
        if( $this->token )
        {
            $headers[] = 'Authorization: Bearer ' . $this->token;
        }

        curl_setopt($process, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($process, CURLOPT_RETURNTRANSFER, true);

        // Quickie, maak er een POST van wanneer data array gevuld is..
        if( !empty($data) )
        {
            curl_setopt($process, CURLOPT_POST, true);
            curl_setopt($process, CURLOPT_POSTFIELDS, json_encode($data));
        }

        $response = json_decode(curl_exec($process));

        curl_close($process);

        return $response;
    }

    public function getProducts()
    {
        return $this->sendRequest('/products');
    }
}


Hiermee genereer ik in feite dezelfde calls 8)7 Mja, een antwoord/fix zoeken is hopeloos denk ik. Dus vooral ter info. ;)

Dank allen voor het meedenken :)

Acties:
  • +1 Henk 'm!

  • Voutloos
  • Registratie: Januari 2002
  • Niet online
Subjectief, maar amper meer code nodig hebben en direct met de curl dependency werken is misschien überhaupt zo gek nog niet. :)

(Er mist nog wat error handling, maar dat deed je in je eerste implementatie ook niet :+ )

{signature}

Pagina: 1