Laravel REST API request object is empty - php

Environment
I created an application using Laravel 5.7 and implemented a REST API. I have a route in routes/api.php that triggers a middleware which checks if the incoming request has a parameter called api_token.
This is a production environment and here are the specifics:
Linux Ubuntu 18.04 LTS 64-bit
nginx/1.14.0
Laravel 5.7
PHP 7.2
APP_ENV in the .env file is set to 'production' and APP_DEBUG is set to 'false'.
Problem
My problem is that the incoming request object is always empty when it arrives at the server. At least that's what my Laravel application says.
These are my routes in routes/api.php:
Route::middleware('rest')->group(function() {
Route::get('device-location/{deviceID}', 'PositionDataController#getDeviceLocation');
Route::get('device-location/all', 'PositionDataController#getAllLocations');
Route::post('device-location', 'PositionDataController#storeDeviceLocation');
Route::put('device-location/{deviceID}', 'PositionDataController#updateDeviceLocation');
Route::delete('device-location/{deviceID}', 'PositionDataController#deleteDeviceLocation');
});
The routes are in a middleware group called 'rest' as you can see. I'm using the Route::get('device-location/{deviceID}', 'PositionDataController#getDeviceLocation'); route to test the functionality.
Here's the code from the middleware:
public function handle($request, Closure $next)
{
if(request()->all()) {
$deviceID = request()->device_id;
}
else {
return response()->json([
'error' => 'The request object is empty.',
'request' => request(),
'parameters' => request()->all(),
'content' => request()->getContent(),
'input' => request()->input()
], 500);
}
$device = MobileDevice::where('device_id', $deviceID)->first();
if($device) {
$deviceToken = $device->api_token;
if($deviceToken == request()->api_token) {
return $next($request);
}
else {
return response()->json(['error' => 'Token does not match.'], 500);
}
}
else {
return response()->json(['error' => 'The device with the id [' . $deviceID . '] could not be found.'], 500);
}
}
The middleware first checks if there are parameters in the request object and then does some logic to check if the right token was sent. If the request object is empty it returns some data to help me understand what went wrong.
I use Postman (https://www.getpostman.com) to test the API. Here's my Postman setup:
Postman setup
Postman headers
This is the response I get in Postman:
Postman response
I get the same result if I call that route in a browser.
Regardless of if I put in parameters or not the request seems to be always empty.
Here are the things that I've tried to do:
Not using the middleware
Using the $request variable instead of the helper request()
Switching between 'application/json' and 'application/x-www-form-urlencoded' in the Headers of my Postman setup
Calling that route in a browser
Updating to Laravel 5.7
The strange thing is that all of this works perfectly on my local environment and on a test server that has the same specs as the production server.
UPDATE 01:
So it seems to be even worse...
If I add a route like this in web.php:
Route::get('/request-return', function() {
return request()->all();
});
and visit that route like this:
laravel.application/request-return?device_id=1&api_token=XgkQLs8G7OYTqdwsXmjJT5G9bxv20DRi
I get back an empty array [].
So it seems like the parameters don't get to the server itself.

You are getting device id through GET request so use the below line instead of $request()->device_id.
Use this and let me know
$name = $request->input('device_id')

Okay I could solve the problem. It didn't have anything to do with Laravel. It was a nginx configuration problem:
https://serverfault.com/questions/231578/nginx-php-fpm-where-are-my-get-params

Related

Postman GET request not supported on a POST request

I have a simple route in Laravel 8 to return some request data. But when I send the request in Postman with POST selected, I get an error of "The GET method is not supported for this route." Keep in mind, I have POST selected in Postman, not GET.
Here is the route:
Route::post('post-route', 'UserController#postFunction');
Here is is the function being called in UserController:
public function postFunction(Request $request) {
return [
'id1' => $request->id1,
'id2' => $request->id2,
];
}
In Postman I am passing the data as json:
{
'id1': 1234,
'id2': 4321
}
I am simply trying to make sure I am passing the correct data in the request but I am getting this error. Why is it trying to hit a GET request?
You can't test your POST, PUT or DELETE routes with Postman because Laravel uses the CSRF middleware protection.
If you really want to use Postman, you need to comment it to disable this middleware temporarly in app/Http/Kernel.php:
protected $middlewareGroups = [
'web' => [
(...)
//\App\Http\Middleware\VerifyCsrfToken::class,
],
(...)
];
But don't forget to enable it again once you want to deploy your project in production!
If you don't want to disable temporarly the CSRF middleware, you can follow the steps mentioned here https://gist.github.com/ethanstenis/3cc78c1d097680ac7ef0, but it's a little longer.

Problem of environment files with inter-apis calls laravel

Good morning, everyone,
As part of the development of demonstration APIs, I realized two APIs :
HelloWorld
Notify
The first one allows to ask for a HelloWorld to be performed, the second one allows to send e-mails according to defined templates.
In my demonstration I make from Postman (or from a React application) an API call to HelloWorld which then makes an API call to Notifier to send the message.
If from Postman I call directly my Notifier API to send an email, I do not encounter any problem (the .env file is well configured for sending emails in this API).
On the other hand if I call my API from HelloWorld to Notifier (the .env file of HelloWorld is not configured for sending e-mails), I encounter an error:
Expected response code 250 but got code "530", with message "530 5.7.1
Authentication required
On the other hand if I configure the .env file of the HelloWorld API (which does not send an e-mail at any time), I do not have any more error and my e-mail is well sent by Notifier.
This is the API Call in HelloWorld :
$client = new Client();
$response = $client->post("http://vhost-url.local/api/notifier/sendmail", [
'json' => [
'to' => $to,
'template' => $template,
'parameters' => $parameters
],
]);
And this is the action called in Notifier API :
public function sendmail(Request $request)
{
$params = json_decode($request->getContent(), true);
try{
switch ($params['template']) {
case 'HELLO_WORLD':
Mail::to($params['to'])
->send(new HelloWorld([
'message' => $params['parameters']['message']
]));
break;
default:
throw new \Exception("Ce template n'existe pas");
break;
}
} catch(\Exception $e) {
return response()
->json([
'message' => $e->getMessage(),
], 500);
}
return response()
->json([
'message' => 'Le mail a bien été envoyé'
], 200);
}
My question is: During an API call (with Guzzle in my case), is the environment file of the source API used instead of the environment file of the destination API? And if so, how to fix this problem?
I'm not sure if this helps but I have had similar problems. The .env files get messed up when cross-communicating Laravel projects (on Windows only I believe).
See for instance https://github.com/laravel/framework/issues/19454 .
The solution is to run php artisan config:cache to create a cached version of your .env variables. Note that you should never use env('...') in your code, instead you should refer to them using a config file like config('app.env'). .env variables can not be dynamic for this reason.
For custom env variables, I usually create a config/project.php file like so:
return [
'my_custom_var' => env('PROJECT_MY_CUSTOM_VAR')
];
That way you can cache it and call the variable using config('project.my_custom_var');

Can't get Auth object and cookies by consuming my own Laravel API

I'm currently trying to build a secure SPA application in Laravel by using :
Laravel 5.6
Laravel Passport
Guzzle client
To make the whole application secure, I created a proxy to prefix all requests to the API and :
User the password grand type of token
Hide the client ID
Hide the client secret
Add automatic scopes based on the role of the user
This is how the Proxy works :
// The proxify endpoint for all API requests
Route::group(['middleware' => ['web']], function ()
{
Route::any('proxify/{url?}', function(Request $request, $url) {
return Proxify::makeRequest($request->method(), $request->all(), $url);
})->where('url', '(.*)');
});
Each time a request is made, it goes through that package I built to create the access token, refreshing it, or deleting it.
To create the access token for the user I'm using a MiddleWare at loggin :
$response = $http->post('http://myproject.local/proxify/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'username' => $request->get('email'),
'password' => $request->get('password'),
]
]);
This is working well, excepting the fact that I'm setting cookies in the Proxify::makeRequest, so I have to create them in the call, return them in the $response, and then at the end of the Middleware, attaching them to the request (Cookie::queue and Cookie::Make are not working in a Guzzle call it seems).
The access token is created and stored in a cookie.
First problem is that in this call, even in the middleware, and especially in that URL http://myproject.local/proxify/oauth/token, I don't have any access to the Auth trait, even if it's specified as a middleware attached to the route, so impossible to fetch information from the authenticated user.
Then the other problem is that when I'm making a call to get a ressource API such as :
$http = new Client();
$response = $http->get('http://myproject.local/proxify/api/continents');
$continents = $response->getBody()->getContents();
return view('dashboard')->with("continents", $continents);
In that case, when I'm calling the URL, the proxy is not able to get the access_token defined in the cookie with the CookieFacade through the HTTP call, neither the Auth object I'm whiling to use. The $_COOKIE variable is not working neither.
What is wrong with my structure so that I don't have any access to the cookie even if it's set and in the browser ? Any other way to get the info ? I tried to get the cookie from the request in the proxy, not working.
Have you tried using the Illuminate or Symfony Request classes and handling the routing via the Laravel instance? My immediate suspicion is Guzzle is the culprit behind no cookies coming through with the requests. Cookie::queue() is a Laravel specific feature so I wouldn't think Guzzle would know anything about them.
Replace Guzzle in one of the routes where the issue occurs. Start with a new Request instance and make the internal api call like:
// create new Illuminate request
$request = Request::create('/api/users', $action, $data, [], [], [
'Accept' => 'application/json',
]);
// let the application instance handle the request
$response = app()->handle($request);
// return the Illuminate response with cookies
return $response->withCookies($myCookies);
I do something similar to this in a couple applications and never had any problems with cookies or accessing server variables. Granted, it's only for authentication, the rest of the api calls are through axios.

Laravel 5.4 - form/API/view

I created an API (store) that saves the data on the database and returns 201 if successful or 404 if not.
if ($visit->save()){
$visit->view_visit = [
'href' => 'api/v1/visit/' . $visit->id,
'method' => 'GET'
];
$response = [
'msg' => 'Visit created.',
'visit' => $visit
];
return response()->json($response, 201);
}
$response = [
'msg' => 'Error during creation.'
];
return response()->json($response, 404);
It works perfectly. Using postman you can see that the status will be <<201 Created>>.
This API should be used in two ways: called by another application or called by a Laravel form. This is the question:
How do I call it in a way if it successful, it will load a given view on the browsers?
In other words, is there a way to make the form call a route (the api itself, something like ../api/visit/) and in case of success loads the other view? Also, I would like to pass the content of response['msg'] to this new view.
I know I could do it inside the store method by filtering the HTTP referrer, but I would like to keep the controller code strictly to manage the record creation. Besides that, I have to send the 201/404 codes along with the returned data.
I also considered creating another controller to handle the API response and then call the form, but it still sounds too much -- it's supposed to be easy, I guess.
In laravel you can use a helpful method which determines if the request that has been sent is an AJAX request or just a normal request, which is:
$request->wantsJson()
So, Inside your controller in the return function, you will make an if statement:
if ($request->wantsJson()) {
return response()->json();
}else{
return view(...);
}

Laravel 5.2 PHPUnit JSON Api request body not being set

I am testing POSTing data to an API endpoint we've created using Laravel 5.2, but none of the parameters seem to be reaching the application in the test. The endpoint expects json and responds with json and uses a FormRequestValidator which has required rules for active and make parameters. The test fails with status code 422 and the examining the response body it states the active and make parameters are required even though they are being passed in the call so therefore for some reason when the request reaches the the Form Request Validator, the input is not there.
However, when I invoke the endpoint with json body including make and active from Postman or the UI app we've built it works fine, it is only failing in the PHPUnit tests therefore it must be something with PHPUnit or the test setup being incorrect. Here is the test:
public function testItStoresCars()
{
// Arrange
$user = User::first();
//Act
$this->json(Request::METHOD_POST, '/v1/cars', [
'active' => true,
'make' => 'Audi'
],
['Authorization' => 'Bearer '.\JWT::fromUser($user)]));
// Assert
$this->assertResponseOk();
}
I know the Authorisation header is set correctly from other tests passing.
I've tried disabling middleware, using the $this->post helper method and manually setting the headers as well as using the $this->call method with setting the Headers and encoding the data using json_encode but I always get the same 422 response. I'm wondering has anyone encountered this issue or can see an error?
Controller Code
public function store(CreateCarRequest $request)
{
$car = $this->carRepo->save($request->all());
return response()->json(car);
}
FormRequest
class CreateCarRequest extends Request
{
public function rules()
{
return [
'active' => 'required|boolean',
'make' => 'required',
];
}
}
422 is the error response for validation errors.. which means either your data is not posted or it doesn't pass server validation, try
$this->json(Request::METHOD_POST, '/v1/cars', [
'active' => true,
'make' => 'Audi'
],
['Authorization' => 'Bearer '.\JWT::fromUser($user)]))->dump();
dump() should show you the errors

Categories