TD;LR: I'm trying to grasp the idea behind middleware implementation. It appears to be working but how do I properly handle the response so it shows a Basic Authorization login prompt to the browser?
--
I'm using:
equip/dispatch
for PSR-15 middleware dispatcher
guzzlehttp/psr7
for PSR-7 ServerRequestInterface
middlewares/http-authentication
sample basic authorization middleware
middlewares/response-time
additional dummy middleware (to check if also gets executed in the pipeline)
Running the code below:
$middleware = [
new \Middlewares\ResponseTime(),
(new \Middlewares\BasicAuthentication([
'username1' => 'password1',
'username2' => 'password2'
]))->attribute('username')
];
// Default handler for end of collection
$default = function (\GuzzleHttp\Psr7\ServerRequest $request) {
// Any implementation of PSR-7 ResponseInterface
return new \GuzzleHttp\Psr7\Response();
};
$collection = new \Equip\Dispatch\MiddlewareCollection($middleware);
// Any implementation of PSR-7 ServerRequestInterface
$request = \GuzzleHttp\Psr7\ServerRequest::fromGlobals();
$response = $collection->dispatch($request, $default);
print_r($response);
Returns the following:
GuzzleHttp\Psr7\Response Object
(
[reasonPhrase:GuzzleHttp\Psr7\Response:private] => Unauthorized
[statusCode:GuzzleHttp\Psr7\Response:private] => 401
[headers:GuzzleHttp\Psr7\Response:private] => Array
(
[WWW-Authenticate] => Array
(
[0] => Basic realm="Login"
)
[X-Response-Time] => Array
(
[0] => 16.176ms
)
)
[headerNames:GuzzleHttp\Psr7\Response:private] => Array
(
[www-authenticate] => WWW-Authenticate
[x-response-time] => X-Response-Time
)
[protocol:GuzzleHttp\Psr7\Response:private] => 1.1
[stream:GuzzleHttp\Psr7\Response:private] =>
)
It appears middlewares/response-time and middlewares/http-authentication were executed just fine. However, I am under the impression that middlewares/http-authentication will show an actual login prompt like this:
But it didn't. I am suppose to implement that myself? If yes, how can I do that properly?
Please change the realm to 'My realm'.
$middleware = [
new \Middlewares\ResponseTime(),
(new \Middlewares\BasicAuthentication([
'username1' => 'password1',
'username2' => 'password2'
]))->attribute('username')->realm('My realm')
];
The GuzzleHttp request happens in the backend so it won't work like it normally would work in a browser e.g. prompting for username and password. You are basically imitating a request when you use Guzzle without using any browsers. As such if you want the prompt you will have to implement this logic without using GuzzleHttp.
You can still test it with Guzzle as per below.
The below code will work.
$request = \GuzzleHttp\Psr7\ServerRequest::fromGlobals()->withHeader('Authorization', 'Basic '.base64_encode('username1:password1'));
$response = $collection->dispatch($request, $default);
echo $response->getStatusCode(); //200
echo $response->getReasonPhrase(); // OK
The below code will fail.
$request = \GuzzleHttp\Psr7\ServerRequest::fromGlobals()->withHeader('Authorization', 'Basic '.base64_encode(''IncorrectUser:IncorrectPassword''));
$response = $collection->dispatch($request, $default);
echo $response->getStatusCode(); //401
echo $response->getReasonPhrase(); // Unauthorized
The best way to implement the BasicAuthentication would be within a middleware framework.
Zend Expressive
//Example using your library
$app->pipe((new \Middlewares\BasicAuthentication([
'username1' => 'password1',
'username2' => 'password2'
])
)->attribute('username')->realm('My realm'));
//Example using other library
$app->pipe(new Tuupola\Middleware\HttpBasicAuthentication([
"users" => [
"root" => "t00r",
"user" => "passw0rd"
]
]));
Both of the above code work as expected. For e.g. prompting for username and password in the browser and allowing user to view the content when correct credentials are sent. Guzzle is causing issues for you.
I hope this helps.
Related
I use Guzzle6 in the PSR7 flavor because it integrates nicely with Hawk authentication. Now, I face problems adding a body to the request.
private function makeApiRequest(Instructor $instructor): ResponseInterface
{
$startDate = (new CarbonImmutable('00:00:00'))->toIso8601ZuluString();
$endDate = (new CarbonImmutable('00:00:00'))->addMonths(6)->toIso8601ZuluString();
$instructorEmail = $instructor->getEmail();
$body = [
'skip' => 0,
'limit' => 0,
'filter' => [
'assignedTo:user._id' => ['email' => $instructorEmail],
'start' => ['$gte' => $startDate],
'end' => ['$lte' => $endDate],
],
'relations' => ['reasonId']
];
$request = $this->messageFactory->createRequest(
'POST',
'https://app.absence.io/api/v2/absences',
[
'content_type' => 'application/json'
],
json_encode($body)
);
$authentication = new HawkAuthentication();
$request = $authentication->authenticate($request);
return $this->client->sendRequest($request);
}
When I var_dump the $request variable, I see no body inside the request. This is backed by the fact that the API responds as if no body was sent. I cross-checked this in Postman. As you can see, the body specifies filters and pagination, so it is easy to see that the results I get are actually not filtered.
The same request in Postman (with body) works flawlessly.
As the parameter be can of type StreamInterface I created a stream instead and passed the body to it. Didn't work either.
Simple JSON requests can be created without using json_encode()... see the documentation.
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://app.absence.io/api/v2',
'timeout' => 2.0
]);
$response = $client->request('POST', '/absences', ['json' => $body]);
Found the problem, actually my POST body is NOT empty. It just turns out that dumping the Request will not hint anything about the actual body being enclosed in the message.
I can recommend anyone having similar problems to use http://httpbin.org/#/HTTP_Methods/post_post to debug the POST body.
Finally, the problem was that my content_type header spelling was wrong as the server expects a header Content-Type. Because of this, the JSON data was sent as form data.
Using Laravel 5 and trying to send some data from my site to another one, which provides me with the REST API. But they use cookies as a authorization. For this moment, I've passed auth successfully. And stuck on how should I send this cookie to API interface via POST method? Here is my listing.
Thanx in advance.
P.S. All things are going on inside the controller.
if (Cookie::get('amoauth') !== null) {
//COOKIE IS HERE
$client = new Client();
$newlead = $client->post('https://domain.amocrm.ru/private/api/v2/json/leads/set', [
'add' => [
'add/name' => 'TEST LEAD',
'add/date_create' => time(),
'add/last_modified' => time(),
'add/status_id' => '1',
'add/price' => 5000
]
]);
} else {
$client = new Client();
$auth = $client->post('https://domain.amocrm.ru/private/api/auth.php',[
'USER_LOGIN' => 'login',
'USER_HASH' => 'hash',
'type' => 'json'
]);
$auth = $auth->getHeaders('Set-Cookie');
Cookie::queue('amoauth', $auth, 15);
return redirect('/test');
}
Now it returns me the following:
Client error: `POST https://domain.amocrm.ru/private/api/v2/json/leads/set` resulted in a `401 Unauthorized` response.
Found the solution: switched to ixudra/curl.
Having difficulties authorizing php SoapClient with MS Dynamic Great Plains. I can connect through SoapUI. However, it only successfully connects on 3rd attempt. Also, the auth token progressively gets longer. See pastebin link below.
I made use of the following package (https://github.com/mlabrum/NTLMSoap) to setup a NTLM stream, but it doesn't seem to be sending a correct token. The token length is shorter than what is sent through SoapUI.
$wsdlUrl = 'http://example.org:48620/Metadata/Legacy/Full/DynamicsGP.wsdl';
$options = [
'ntlm_username' => 'Domain\username',
'ntlm_password' => 'password'
];
$soapClient = new \NTLMSoap\Client($wsdlUrl, $options);
$params = array(
criteria => array(
'ModifiedDate' => array(
'GreaterThan' => '2016-04-18',
'LessThan' => '2016-04-19'
)
),>
'context' => array(
'OrganizationKey' => array(
'type' => 'CompanyKey',
'Id' =
)
)
);
$soapClient->__setLocation('http://example.org:48620/DynamicsGPWebServices/DynamicsGPService.asmx');
$response = $soapClient->GetPurchaseOrderList(array($params));
I had to set use ___setLocation() because client was being forwarded to http://localmachine:48620/DynamicsGPWebServices/DynamicsGPService.asmx
I have been trying to get Charles Web Proxy to work to show the actual the request/response, buts its crapped out on me.
This is the SoapUI output. http://pastebin.com/7zg4E3qD
Is there a way to globally add form_params to all requests with guzzle 6?
For example:
$client = new \GuzzleHttp\Client([
'global_form_params' => [ // This isn't a real parameter
'XDEBUG_SESSION_START' => '11845',
'user_token' => '12345abc',
]
]);
$client->post('/some/web/api', [
'form_params' => [
'some_parameter' => 'some value'
]
]);
In my ideal world, the post would have the result of array_merge-ing global_form_params and form_params:
[
'XDEBUG_SESSION_START' => '11845',
'user_token' => '12345abc',
'some_parameter' => 'some value',
]
I can see also wanting something like this for query or json
According to Creating a client you can set "any number of default request options" and on the GuzzleHttp\Client Source Code
$client = new Client['form_params' => [form values],]);
would apply your form_params to every request.
This could create issues with GET requests due to the Content-Type header being changed within Client::applyOptions. It would ultimately depend on server configuration.
If your intentions are to have the client make both GET and POST requests then you might be better served by moving the form_params into middleware. For example:
$stack->push(\GuzzleHttp\Middleware::mapRequest(function (RequestInterface $request) {
if ('POST' !== $request->getMethod()) {
// pass the request on through the middleware stack as-is
return $request;
}
// add the form-params to all post requests.
return new GuzzleHttp\Psr7\Request(
$request->getMethod(),
$request->getUri(),
$request->getHeaders() + ['Content-Type' => 'application/x-www-form-urlencoded'],
GuzzleHttp\Psr7\stream_for($request->getBody() . '&' . http_build_query($default_params_array)),
$request->getProtocolVersion()
);
});
$response = $facebook->api(
'me/objects/namespace:result',
'POST',
array(
'app_id' => app_id,
'type' => "namespace:result",
'url' => "http://samples.ogp.me/370740823026684",
'title' => "Sample Result",
'image' => "https://fbstatic-a.akamaihd.net/images/devsite/attachment_blank.png",
'description' => ""
)
);
I get the following error.
The parameter object is required
I don't know where to add the parameter object.
I have been facing the same issue for the past couple of days, in the end I stopped trying to use the Facebook PHP SDK to send the request and resorted to using just sending a HTTP Request.
I am creating an object rather than updating one, but the implementation shouldn't differ much from your needs. I was getting the same error (The parameter object is required) and the below implementation resolved that issue.
I used the vinelab HTTP client composer package and built a request like this:
$request = [
'url' => 'https://graph.facebook.com/me/objects/namespace:object',
'params' => [
'access_token' => $fbAccessToken,
'method' => 'POST',
'object' => json_encode([
'fb:app_id' => 1234567890,
'og:url' => 'http://samples.ogp.me/1234567890',
'og:title' => 'Sample Object',
'og:image' => 'https://fbstatic-a.akamaihd.net/images/devsite/attachment_blank.png',
'og:description' => 'Sample Object'
])
]
];
// I'm using Laravel so this bit might look different for you (check the vine lab docs if you use that specific HTTP client)
$response = HttpClient::post($request);
// raw content
$response->content();
// json
$response->json();
As I said, I used the vinelab HTTP package in my implementation, you should be able to use any similar package or directly use curl in PHP instead.