Why Cloudfront returning 403 with Guzzle? - php

I'm consuming a API that uses the CloudFront service,
And I'm getting a 403 error for the requests with Guzzle, but if uses for example PHP Curl or call via Postman or Browser works.
Here a log of the Guzzle:
Log of guzzle
And here part of code:
/**
* #return void
*/
public function __construct()
{
$this->client = new Client([
'base_uri' => env('API_HOST'),
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
'timeout' => 30,
'debug' => true,
]);
}
/**
* #param string $method
* #param string $url
* #param array $body
* #param bool $isMultipart
*
* #return ResponseInterface
*/
private function request(string $method, string $url, array $body = [], bool $isMultipart = false): ResponseInterface
{
if ($isMultipart) {
$params['multipart'] = [$body];
} else {
$params['json'] = $body;
}
$url = $this->appedAuthTokensToUrl($url);
return $this->client->request($method, $url, $params);
}

Finally found solution. If had a body in the GET request the Cloudfront will return 403.
So in my case the problem is this if (always setting a body):
if ($isMultipart) {
$params['multipart'] = [$body];
} else {
$params['json'] = $body;
}
Here link of Cloudfront docs

Related

Laravel Socialite: This authorization code has been used (Facebook)

Description: I have implemented the laravel socialite stateless, because I am using Laravel as a backend app with REST APIs and my frontend is in Angular. I get the correct redirect URL, however, when I enter my Facebook credentials and agree to proceed with the application I get redirected to my site with the following issue:
Client error: `POST https://graph.facebook.com/v3.3/oauth/access_token` resulted in a `400 Bad Request` response: {"error":{"message":"This authorization code has been used.","type":"OAuthException","code":100,"error_subcode":36009,"f (truncated...)
Here are the routes in my api.php
Route::get('/auth/redirect/{provider}', [AuthController::class, 'redirectToProvider'])
->where('provider', '[A-Za-z]+');
Route::get('/auth/{provider}/callback', [AuthController::class, 'handleProviderCallback'])
->where('provider', '[A-Za-z]+');
And the following functions in my controller AuthController.php
/**
* #param $provider
* #return JsonResponse
*/
public function redirectToProvider($provider): JsonResponse
{
$response = $this->authService->redirectToProvider($provider);
return response()->json($response);
}
/**
* #param $provider
* #param Request $request
* #return JsonResponse
* #throws \App\Exceptions\Custom\CustomValidationException
*/
public function handleProviderCallback($provider, Request $request): JsonResponse
{
ValidationUtils::validate($request->all(), [
'code' => 'required',
]);
$response = $this->authService->handleProviderCallback($provider);
return response()->json($response);
}
And this is where it resolves in the AuthServiceImpl.php
/**
* #param $provider
* #return array[]
*/
public function redirectToProvider($provider): array
{
if (!in_array($provider, self::PROVIDERS)) {
throw new CustomNotFoundException(trans('errors.not_found.provider'));
}
$success['provider_redirect'] = Socialite::driver($provider)->stateless()->redirect()->getTargetUrl();
return [
'data' => $success
];
}
/**
* #param $provider
* #return array[]|void
*/
public function handleProviderCallback($provider)
{
if (!in_array($provider, self::PROVIDERS)) {
throw new CustomNotFoundException(trans('errors.not_found.provider'));
}
try {
$providerUser = Socialite::driver($provider)->stateless()->user();
if ($providerUser) {
$user = $this->socialAccountsService->findOrCreate($providerUser, $provider);
$user->markEmailAsVerified();
$token = $user->createToken(env('API_AUTH_TOKEN_PASSPORT_SOCIAL'))->accessToken;
return [
'data' => [
'tokens' => [
'token_type' => 'Bearer',
'expires_in' => 5400,
'access_token' => $token
],
'user' => new UserResource($user)
],
'message' => trans('auth.login')
];
}
} catch (\Exception $e) {
throw new CustomUnauthorizedException($e->getMessage());
}
}
You can try it yourself by logging in with Facebook on the following link: https://afillix.common.mk/login

What are the best practices when redirecting users after OAuth 2.0 token renew?

I have implemented an Mautic API in a website. I use OAuth 2.0 to authenticate the communications between the two. The problem that I have is that I must renew the token from time to time, and in order to do that I have to provide a callback URL, I figured that I just have to use http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI] as my callback URL, that way, when the authentication or renew is done the user would be redirected to the last called URL. The problem is that sometimes the user is being redirected to the login page of the API to authorize the integration. That, to the best of my knowledge, should be done by me, only once.
In short, how can I avoid my users being shown the API authentication screen?
I haven't finished the integration yet; I still must work on some security issues.
The class responsible for doing that is right below:
<?php
use Mautic\Auth\ApiAuth;
use Mautic\MauticApi;
class Mautic
{
private static $instance;
private $publicKey;
private $secretKey;
private $callback;
private $baseURL;
private $Api;
private $ApiURL;
private $auth;
private $token;
private $companyName;
public function __construct()
{
$config = $this->getConfig();
$this->publicKey = $config['publicKey'];
$this->secretKey = $config['secretKey'];
$this->baseURL = $config['baseURL'];
$this->companyName = $config['companyName'];
$this->Api = new MauticApi();
$this->ApiURL = $this->baseURL . '/api/';
if (!$this->isTokenValid()) {
$this->getToken();
}
}
/**
* Read the config file "mautic.json" located in the root directory and returns an array with config values
*
* #return array
*/
private function getConfig(): array
{
return $this->getJSON('mautic.json');
}
/**
* Instantiates a new API class
*
* #param string $apiName
* #return object
*/
private function setApi(string $apiName): object
{
if(!$this->auth){
$this->getToken();
}
return $this->Api->newApi($apiName, $this->auth, $this->ApiURL);
}
/**
* Retorna la instancia de la clase actual
*
* #return object
*/
public static function getInstance(): object
{
if (!self::$instance)
self::$instance = new self();
return self::$instance;
}
public function isTokenValid(): bool
{
$oldTokenExpiration = $this->checkForToken()['expires'];
if (time() >= $oldTokenExpiration) {
return false;
}
return true;
}
private function getToken($accessToken = null, $tokenExpiration = null, $refreshToken = null)
{
if ($previousToken = $this->checkForToken()) {
$settings['accessToken'] = $previousToken['access_token'];
$settings['accessTokenExpires'] = $previousToken['expires'];
$settings['refreshToken'] = $previousToken['refresh_token'];
}
$settings = [
'baseUrl' => $this->baseURL, // Base URL of the Mautic instance
'version' => 'OAuth2', // Version of the OAuth
'clientKey' => $this->publicKey, // Client/Consumer key from Mautic
'clientSecret' => $this->secretKey, // Client/Consumer secret key from Mautic
'callback' => "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"
];
if (isset($accessToken) && isset($tokenExpiration) && isset($refreshToken)) {
}
$initAuth = new ApiAuth();
$auth = $initAuth->newAuth($settings);
// Initiate process for obtaining an access token; this will redirect the user to the authorize endpoint and/or set the tokens when the user is redirected back after granting authorization
if ($auth->validateAccessToken()) {
if ($auth->accessTokenUpdated()) {
$accessTokenData = $auth->getAccessTokenData();
$this->storeToken($accessTokenData);
$this->auth = $auth;
$this->token = $accessTokenData['access_token'];
return $this->auth;
}
}
}
private function storeToken($accessTokenData)
{
$tokenInfo = json_encode($accessTokenData);
file_put_contents("token.json", $tokenInfo);
}
/**
* Read the file "token.json" located in the root directory and returns an array with any passed token values
*
* #return array
*/
private function checkForToken(): array
{
return $this->getJSON('token.json');
}
/**
* Reads a JSON file and returns its key and values as an array
*
* #param string $filePath
* #return array
*/
private function getJSON($filePath): array
{
if (!file_exists($filePath)) {
return false;
}
$oldToken = file_get_contents($filePath);
$oldToken = json_decode($oldToken);
return (array) $oldToken;
}
/**
* Creates a new contact
*
* #param string $name
* #param string $phone
* #param string $email
* #param string $companyName
* #return array
*/
public function createContact(string $name, string $phone, string $email, int $companyName = null): array
{
if ($companyName == null) {
$companyName = $this->getConfig()['companyName'];
}
$data = array(
'firstname' => $name,
'phone' => $phone,
'email' => $email,
'company' => $companyName,
'ipAddress' => $_SERVER['REMOTE_ADDR'],
'overwriteWithBlank' => true,
);
$contactApi = $this->setApi('contacts');
$newContact = $contactApi->create($data);
return $newContact;
}
/**
* Retorna los datos de un contacto
*
* #param int $contactId
* #return object
*/
public function getContact(int $contactId): object
{
return json_decode($this->curlGET("contacts", array($contactId)));
}
/**
* Ejecuta una requisición GET al servidor de la API
*
* #param string $APIMethod
* #param array $dataToSend
* #return string
*/
private function curlGET(string $APIMethod, array $dataToSend = array()): string
{
$dataToSend["access_token"] = $this->token;
$baseURL = $this->ApiURL . $APIMethod;
$curl = curl_init();
$curlOptions = array(
CURLOPT_URL => $baseURL . '?' . http_build_query($dataToSend),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_SSL_VERIFYPEER => false
);
curl_setopt_array($curl, $curlOptions);
$returnedData = curl_exec($curl);
if (!$returnedData) {
return curl_error($curl);
} else {
curl_close($curl);
return $returnedData;
}
}
}
The problem seems to be re-authentication. Once you authenticate successfully you should not need to do that again and again.
You get Token, Token Expires and Refresh Token when the process is complete. Here's complete example(https://tutorialsjoint.com/mautic-rest-api/).
Once you have the token and you are checking if token is expired, you should use refresh token to obtain fresh access token. For some reason if your refresh token becomes invalid then only you need to re-authenticate, that usually happens when you change the client credentials.
In your code I see you are doing authentication But can't see refresh token call, that should be your issue here.

How to pass storeUrl to default Magento oauth script?

I'm trying to create my first Magento 2 extension and integration and I've been following the guide in their docs here. All good so far, I've completed the auth handshake, stored all the required keys for api requests and can make requests back to my extension fine.
Looking at the OauthClient.php script provided at the foot of the tutorial, the url is hardcoded like so:
return new Uri('http://magento.host/oauth/token/request'); and the tutorial advises you to "Change the instances of http://magento.host in this example to a valid base URL."
<?php
use OAuth\Common\Consumer\Credentials;
use OAuth\Common\Http\Client\ClientInterface;
use OAuth\Common\Http\Exception\TokenResponseException;
use OAuth\Common\Http\Uri\Uri;
use OAuth\Common\Http\Uri\UriInterface;
use OAuth\Common\Storage\TokenStorageInterface;
use OAuth\OAuth1\Service\AbstractService;
use OAuth\OAuth1\Signature\SignatureInterface;
use OAuth\OAuth1\Token\StdOAuth1Token;
use OAuth\OAuth1\Token\TokenInterface;
class OauthClient extends AbstractService
{
/** #var string|null */
protected $_oauthVerifier = null;
public function __construct(
Credentials $credentials,
ClientInterface $httpClient = null,
TokenStorageInterface $storage = null,
SignatureInterface $signature = null,
UriInterface $baseApiUri = null
) {
if (!isset($httpClient)) {
$httpClient = new \OAuth\Common\Http\Client\StreamClient();
}
if (!isset($storage)) {
$storage = new \OAuth\Common\Storage\Session();
}
if (!isset($signature)) {
$signature = new \OAuth\OAuth1\Signature\Signature($credentials);
}
parent::__construct($credentials, $httpClient, $storage, $signature, $baseApiUri);
}
/**
* #return UriInterface
*/
public function getRequestTokenEndpoint()
{
return new Uri('http://magento.host/oauth/token/request');
}
/**
* Returns the authorization API endpoint.
*
* #throws \OAuth\Common\Exception\Exception
*/
public function getAuthorizationEndpoint()
{
throw new \OAuth\Common\Exception\Exception(
'Magento REST API is 2-legged. Current operation is not available.'
);
}
/**
* Returns the access token API endpoint.
*
* #return UriInterface
*/
public function getAccessTokenEndpoint()
{
return new Uri('http://magento.host/oauth/token/access');
}
/**
* Parses the access token response and returns a TokenInterface.
*
* #param string $responseBody
* #return TokenInterface
*/
protected function parseAccessTokenResponse($responseBody)
{
return $this->_parseToken($responseBody);
}
/**
* Parses the request token response and returns a TokenInterface.
*
* #param string $responseBody
* #return TokenInterface
* #throws TokenResponseException
*/
protected function parseRequestTokenResponse($responseBody)
{
$data = $this->_parseResponseBody($responseBody);
if (isset($data['oauth_verifier'])) {
$this->_oauthVerifier = $data['oauth_verifier'];
}
return $this->_parseToken($responseBody);
}
/**
* Parse response body and create oAuth token object based on parameters provided.
*
* #param string $responseBody
* #return StdOAuth1Token
* #throws TokenResponseException
*/
protected function _parseToken($responseBody)
{
$data = $this->_parseResponseBody($responseBody);
$token = new StdOAuth1Token();
$token->setRequestToken($data['oauth_token']);
$token->setRequestTokenSecret($data['oauth_token_secret']);
$token->setAccessToken($data['oauth_token']);
$token->setAccessTokenSecret($data['oauth_token_secret']);
$token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
unset($data['oauth_token'], $data['oauth_token_secret']);
$token->setExtraParams($data);
return $token;
}
/**
* Parse response body and return data in array.
*
* #param string $responseBody
* #return array
* #throws \OAuth\Common\Http\Exception\TokenResponseException
*/
protected function _parseResponseBody($responseBody)
{
if (!is_string($responseBody)) {
throw new TokenResponseException("Response body is expected to be a string.");
}
parse_str($responseBody, $data);
if (null === $data || !is_array($data)) {
throw new TokenResponseException('Unable to parse response.');
} elseif (isset($data['error'])) {
throw new TokenResponseException("Error occurred: '{$data['error']}'");
}
return $data;
}
/**
* #override to fix since parent implementation from lib not sending the oauth_verifier when requesting access token
* Builds the authorization header for an authenticated API request
*
* #param string $method
* #param UriInterface $uri the uri the request is headed
* #param \OAuth\OAuth1\Token\TokenInterface $token
* #param $bodyParams array
* #return string
*/
protected function buildAuthorizationHeaderForAPIRequest(
$method,
UriInterface $uri,
TokenInterface $token,
$bodyParams = null
) {
$this->signature->setTokenSecret($token->getAccessTokenSecret());
$parameters = $this->getBasicAuthorizationHeaderInfo();
if (isset($parameters['oauth_callback'])) {
unset($parameters['oauth_callback']);
}
$parameters = array_merge($parameters, ['oauth_token' => $token->getAccessToken()]);
$parameters = array_merge($parameters, $bodyParams);
$parameters['oauth_signature'] = $this->signature->getSignature($uri, $parameters, $method);
$authorizationHeader = 'OAuth ';
$delimiter = '';
foreach ($parameters as $key => $value) {
$authorizationHeader .= $delimiter . rawurlencode($key) . '="' . rawurlencode($value) . '"';
$delimiter = ', ';
}
return $authorizationHeader;
}
}
My Question is how do I pass the url from the store that the integration has been set up on back as a variable in here?(I have it stored in my db)
Thanks for taking the time to take a look.
For anyone who might come across this in future after getting the saved data from the table in my db, I added the Url into the call to make the new class on the checklogin.php script in the documentation:
$oAuthClient = new OauthClient($credentials,$magentoBaseUrl);
And then in OauthClient.php, I added the url to the construct and updated the getRequestTokenEndpoint method and the getAcessTokenEndpoint:
public function __construct(
Credentials $credentials,
$magentoBaseUrl = null,
ClientInterface $httpClient = null,
TokenStorageInterface $storage = null,
SignatureInterface $signature = null,
UriInterface $baseApiUri = null
) {
if (!isset($httpClient)) {
$httpClient = new \OAuth\Common\Http\Client\CurlClient();
}
if (!isset($storage)) {
$storage = new \OAuth\Common\Storage\Session();
}
if (!isset($signature)) {
$signature = new \OAuth\OAuth1\Signature\Signature($credentials);
}
if (!isset($magentoBaseUrl)) {
die();
}
$this->magentoBaseUrl = $magentoBaseUrl;
parent::__construct($credentials, $httpClient, $storage, $signature, $baseApiUri);
}
...
/**
* #return UriInterface
*/
public function getRequestTokenEndpoint()
{
return new Uri($this->magentoBaseUrl.'/oauth/token/request');
}
...
/**
* Returns the access token API endpoint.
*
* #return UriInterface
*/
public function getAccessTokenEndpoint()
{
return new Uri($this->magentoBaseUrl.'/oauth/token/access');
}

PHP: Guzzle Client - Proxy request to different server

So, I am just trying to proxy all requests to a different server.
I want everything to be the same, except I want to change the base uri.
<?php
namespace App\MicroServices\Example\Clients;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
/**
* Class ExampleClient
* #package App\MicroServices\Example\Clients
*/
class ExampleClient extends Client implements ClientInterface
{
/**
* ExampleClient constructor.
*/
public function __construct()
{
parent::__construct([
'base_uri' => config('example.base.uri'),
'timeout' => config('example.client.timeout'),
]);
}
/**
* #param Request $request
*
* #return string
*
* #throws \GuzzleHttp\Exception\GuzzleException
*/
public function proxyToExampleServer(Request $request)
{
$method = $request->getMethod();
$body = json_decode($request->getContent(), true);
$path = $request->path() . ($request->getQueryString() ? ('?' . $request->getQueryString()) : '');
$headers = [];
foreach ($request->header() as $key => $value)
{
$headers[$key] = $value[0];
}
$headers['Authorization'] = $request->bearerToken();
return $this->request($method, $path, ['headers' => $headers, 'body' => $body])->getBody()->getContents();
}
}
Response in Postman is 400 Bad Request, I have tried changing to 'form_params' and 'json'
Any thoughts?
So, I got it working with this function
/**
* #param Request $request
*
* #return string
*
* #throws \GuzzleHttp\Exception\GuzzleException
*/
public function proxyToExampleServer(Request $request)
{
$method = $request->getMethod();
$body = json_decode($request->getContent(), true);
$path = $request->path() . ($request->getQueryString() ? ('?' . $request->getQueryString()) : '');
$headers = $request->header();
try {
return $this->request($method, $path, ['headers' => $headers, 'json' => $body])->getBody()->getContents();
} catch (ClientException $exception) {
return $exception->getResponse();
}
}

Why http request with Guzzle doesn't work?

I'm developing a website with Laravel 5.7 that has a registration form. When the form is submitted, its params are used to create another user via API.
These API have a .cloudfunctions.net endpoint and the web application is developed using Angular + Firebase.
When I make a GuzzleHttp request to that endpoint, I receive back an HTML response: the google account login page.
The strange thing is that when I run the corresponding cURL command from my vagrant console, or I run the same request with Postman, it returns me a correct json response.
This is a cURL example command:
curl -d '{DATA}' -H "Content-Type: application/json" -u test:test -X POST https://{endpoint}.cloudfunctions.net/api/{function}
And this is my ApiManager class in Laravel project:
namespace App\Utils;
use GuzzleHttp\Client;
/**
* API Manager class.
*/
class ApiManager
{
protected $client;
protected $params;
protected $body;
public function __construct()
{
$this->setupClient();
}
/**
* Setup GuzzleHttp client.
* #return void
*/
private function setupClient()
{
$this->client = new Client([
'base_uri' => env('REMOTE_ENDPOINT'),
'auth' => [env('REMOTE_USERNAME'), env('REMOTE_PASSWORD')],
'headers' => [
'Accept' => 'application/json'
],
'strict' => true
]);
}
/**
* Setup request body as json.
* http://docs.guzzlephp.org/en/stable/request-options.html#json
*
* #param array $params
* #return void
*/
protected function setupBody($params)
{
$this->body = [
'debug' => env('GUZZLE_DEBUG', false),
'json' => $params
];
}
/**
* Decode raw json body to associative array
*
* #param mixed $response
* #return void
*/
protected function getResponseBody($response)
{
$body = json_decode($response->getBody(), true);
if ($body != null && array_key_exists('error', $body)) {
return $body['error'];
}
return $body;
}
/**
* Create user request.
*
* #param array $params
* #return mixed $response
*/
public function createUser($params)
{
$this->setupBody($params);
$response = $this->client->request('POST', '/create-user', $this->body);
if ($response->getStatusCode() == 200) {
return $this->getResponseBody($response);
}
return false;
}
/**
* Update user request.
*
* #param array $params
* #return mixed $response
*/
public function updateUser($params)
{
$this->setupBody($params);
$response = $this->client->request('POST', '/update-user', $this->body);
if ($response->getStatusCode() == 200) {
return $this->getResponseBody($response);
}
return false;
}
}
Anyone can help me find why Guzzle returns the Google account login page, while Postman or cURL from command line work?
Thanks in advance

Categories