Attach header to all response()->json()->send() laravel; - json

For some reasons I send JSON response from my helper/library instead of controller using below code:
response()->json(
[
"status" => false,
"message" => "This product is out of stock",
],
200
)->send();
exit;
my problem is no middleware header if that response sent. How to attach header to all response()->json()->send();exit; function?
Below us my response header of default controller ):
Above response has all header from all middleware and below is my header response from response()->json()->send();exit;:
above not showing headers from the middleware.
I know I can send the header manually by add ->header('X-Header-One', 'Header Value') like code below:
response()->json(
[
'status' => false,
'message' => 'This voucher is not for selected products',
]
)->header('X-Header-One', 'Header Value')->send();
exit;
But I already have so many that responses, and I dont want to try to WETing my code.

After doing some digging, you could also create a Response Macro
https://laravel.com/docs/8.x/responses#response-macros
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;
class ResponseMacroServiceProvider extends ServiceProvider
{
/**
* Register the application's response macros.
*
* #return void
*/
public function boot()
{
Response::macro('custom', function ($value) {
return Response::json($value)->headers();
});
}
}
Then in your code, just use
return response()->custom($data);

In your Controller.php
Add a function and call it whatever you want
EG
public function MyCustomResponse()
{
...
}
Then allow that to take in the params you want, in your case it is an array and an int, (data and status)
public function MyCustomResponse(array $data, int $status)
{
...
}
Then handle the logic in there
public function MyCustomResponse(array $data, int $status)
{
response()->json($data, $status)->header('X-Header-One', 'Header Value')->send();
}
Now when you want to use it, ensure that you are extending the controller where you have placed this code and just do
return $this->myCustomResponse($data, 200);
A better option depending on your need is to use a middleware
public function handle($request, Closure $next)
{
$request->headers->set( ... );
return $next($request);
}
And apply to your route

Create a middleware SetHeader.php
then
<?php
namespace App\Http\Middleware;
use Closure;
class setHeader
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$request->headers->set('X-Header-One', 'Header Value');
return $next($request);
}
}
like that you can add as may as headers and apply to those routes which you want to send

Well, the answer to this question is already given, But I suggest using the power of the middleware concept. Middleware is not just working for requests but also works for the response.
By using the response macro we have to change the reference to use a custom function instead of json.
Here is the middleware code.
SetResponseHeaders.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class SetResponseHeaders
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
$response = $next($request);
$response->header('X-Header-One', 'XValue');
return $response;
}
}
and don't forget to register the middleware.
Http/Kernel.php
protected $middleware = [
// other middlewares
\App\Http\Middleware\SetResponseHeaders::class,
]
Special notes:- If you are using the CORS concept then you have the cors.php file under the config folder. In my case, I am using fruitcake/laravel-cors package. so you have to expose the header otherwise you will not get the value.
cors.php
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
// other config
'exposed_headers' => ['X-Header-One'],
]

Related

How to read JSON data in laravel controller?

Before this, I was getting 405 methods not allowed error in live server
ajax call to upload files:
$('#form-repeater').on('submit',function(e){
e.preventDefault();
$.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } });
let thiss=$(this);
let form = document.getElementById('form-repeater');
let data =new FormData(form);
$.ajax({
type:thiss.attr('method'),
url:thiss.attr('action'),
data:data,
dataType:'JSON',
// ContentType:'application/json',
cache: false,
processData: false,
success:function(response){
if(response.message=='1'){
Swal.fire(
'Product Added Successfully',
'',
'success'
)
setTimeout(function(){
window.location.href="/banner";
}, 2000);//wait 2 seconds
}
else{
error = response.errors;
if(error.staff){
$('#form-repeater .invalid-staff').html(error.staff);
}else{
$('#form-repeater .invalid-staff').html('');
}
if(error.customerNumber){
$('#form-repeater .invalid-cust_numb').html(error.customerNumber);
}else{
$('#form-repeater .invalid-cust_numb').html('');
}}});
This is my response from ajax call:enter image description here
normally I can't retrieve data in my controller:
I debug using dd($request->all());
When sending JSON requests to your application, you may access the JSON data via the input method as long as the Content-Type header of the request is properly set to application/json. You may even use "dot" syntax to dig into JSON arrays: $name = $request->input('user.name');
Then on your controller you could do something like this:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* Store a new user.
*
* #param Request $request
* #return Response
*/
public function store(Request $request)
{
$name = $request->input('user.name');
// Manage your data the way you want
}
}

Slim and different response type based on url

So I want to allow users to either request .xml or .json responses when they run a rest API request to the server. (much like twitter)
But I don't belive that the following way is the best way, as it means duplicate code, surely there is a better way to allow .xml or .json response.
$app->get('/books/:id.xml', function ($id) use ($app) {
$app->render('/xml/books.xml', array('id' => $id));
});
$app->get('/books/:id.json', function ($id) use ($app) {
$app->render('json/books.json', array('id' => $id));
});
OR
// Define app routes
$app->get('/hello/{name}.{type}', function ($request, $response, $args) {
//return $response->write("Hello " . $args['name']);
if($args['type'] == 'xml')
{
return 'this is xml';
}
var_dump(parse_url($_SERVER['REQUEST_URI']));
});
if anyone knows how to do this, that would be great.
Consider using value of Accept HTTP header of request instead of file extension in the end of the URI.
I'd say using header is more reliable and more "proper" way to determine the format in which the data should be returned.
URI should be used to point to specific resource. Accept header should be sent by client to tell you what format the data should be returned.
Apart from being standard way to implement a RESTful service, it removes the headache of altering routes (like in your second example).
If you agree to such implementation, there's an excellent library to solve your problem.
It has lots of uses, and here is an example for your use case:
<?php
use Negotiation\Negotiator;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
class YourController
{
public function __construct(Negotiator $negotiator, DataProvider $someDataProvider)
{
$this->negotiator = $negotiator;
$this->someDataProvider = $someDataProvider;
}
/**
* Processing request.
*
* We get data, then we use Negotiator to detect what format the requestor prefers.
* Then we return data in requested format or in case format is not supported,
* fall back to JSON.
*
*
* #param Request $request
* #param Response $response
* #return Response
*/
public function __invoke(Request $request, Response $response)
{
$data = $this->someDataProvider->getSomeData();
$mediaType = $this->determineMediaType($request);
switch ($mediaType) {
case 'application/json':
default:
// $data = $data->asJson();
// transform data to JSON...
break;
case 'application/xml':
$data = $data->asXml();
// transform data to XML...
break;
}
// Write data to body of response
$response->getBody()->write($data);
// Set appropriate response header
return $response->withHeader('Content-Type', $mediaType);
}
/**
* Find preferred data format from Accept header.
*
* Uses Negotiator to determine whether JSON or XML should be returned.
*
* #param Request $request
* #return string
*/
private function determineMediaType(Request $request)
{
$acceptHeader = $this->extractAcceptHeader($request);
// Set list of "known" formats, i.e. formats that your service supports
$known = ['application/json', 'application/xml'];
// Let negotiator determine what format should be used
$mediaType = $this->negotiator->getBest($acceptHeader, $known);
if ($mediaType) {
return $mediaType->getValue();
} else {
return 'application/json'; # if request has unexpected value of accept header, default to JSON
}
}
/**
* Extract Accept header value from Request object
* #param Request $request
* #return string
*/
private function extractAcceptHeader(Request $request)
{
return $request->getHeaderLine('Accept');
}
}
This class is an example of a callback to a route. Such implementation allows you to easily extend the list of supported formats without tampering routes.

How to validate params comming from GET request in Yii2 RESTful service

I have a language parameter that needs to be sent to my documents endpoint. So I have to validate that user has sent this parameter in his GET request.
Making rule in my model didn't do anything:
public function rules()
{
return [
[['language'], 'required'],
];
}
Because of that I have tried this:
1) I have created ParamsValidator class:
<?php
namespace app\modules\v1\components;
use yii\web\UnprocessableEntityHttpException;
use yii\base\Component;
use Yii;
/**
* Class that is responsible for validating input params.
*/
class ParamsValidator extends Component
{
public function validate($params)
{
if (!isset($params['language'])) {
throw new UnprocessableEntityHttpException("Language parameter is required");
}
}
}
I am invoking its validate() method inside my controllers init() method:
public function init()
{
$this->_params = Yii::$app->request->queryParams;
$validator = new ParamsValidator();
$validator->validate($this->_params);
}
And this sort of work. Code works, but I get ugly response back. Instead of nice JSON response, I get bunch of html starting like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Unprocessable entity (#422)</title>
<style>
body {
font: normal 9pt "Verdana";
color: #000;
background: #fff;
}
Instead of this html, I would like some nice JSON response like this:
{
"name": "Forbidden",
"message": "You are not authorized to do this.",
"code": 0,
"status": 403,
"type": "yii\\web\\ForbiddenHttpException"
}
This nice JSON error that you see is made by:
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
'auth' => [$this, 'authenticate']
];
But obviously my Validator is not doing this.
Questions:
1) How to validate params that comes via GET request ?
2) If my method is right, how to get this nice JSON error response ?
A simple option is by overriding the ActiveController::checkAccess method by adding this inside controller:
public function checkAccess($action, $model = null, $params = [])
{
if ($action === 'index' or $action === 'view')
{
$params = Yii::$app->request->queryParams;
if (!isset($params['language'])) {
throw new \yii\web\ForbiddenHttpException('You are not authorized to do this.');
}
}
}
In case you need to do it at model level you'll need to use the addError() method instead of directly throwing the error. Your model instance will hold it when invoking its validate method. Then inside your action you can simply return it. it will be serialized and errors will be outputted in the right format. There is many ways to achieve this. A simple example may be by using the load method to pass both query and body params to the model before validating it (scenarios may be required):
$queryParams = Yii::$app->request->queryParams;
$bodyParams = Yii::$app->request->bodyParams;
$params = array_merge($queryParams,$bodyParams );
$model = new $modelClass;
$model->load($params , '');
if ($model->validate() === false) return $model;
else {
// do whatever you need
}

Laravel setup for basic API and AJAX-calls

Laravel POST return type?
I'm setting up an API alpha version using basic auth single sign on.
Cant debug with postman/js because its not returning JSON but redirecting me instead. From what Ive read AJAX calls shouldnt be redirected? Do I have errors in the configuration?
Routes:
Route::post('/test1', 'tradesCtrl#test1']);
//Route::post('/test1', ['middleware' => 'auth.basic.once', 'uses' => 'tradesCtrl#test1']);
AJAX call:
$.post(
"http://localhost:8000/test1",
{
p1:"100a",
p2:80
},
function(data, status,jqXHR){
console.log(data);
});
Controller (this will echo HTTP (!):
public function test1(Request $request)
{
if($request->ajax()) {
echo "AJAX!";
} else {
echo "HTTP!";
}
}
Allow cross domain for now in App.php
// allow origin
header('Access-Control-Allow-Origin: *');
Kernel.php disable csrf protection by commeting out
app\Http\Middleware\VerifyCsrfToken::class
If I try to insert validation for parameters
public function test1(Request $request)
{
$this->validate($request, [
'p1' => 'Integer',
'p2' => 'Integer'
]);
echo "Serving stuff";
}
This immediately return in a 404 page not found when failing validation, probably a redirect to something else that's not working??

CodeIgniter CSRF token in JSON request

I ran into an issue with CodeIgniter / CSRF / JSON.
I am sending http POST requests to my PHP backend with the Content-Type "application/json. The payload is JSON data. Along with the data, I pass the CSRF token that is generated and stored in the CSRF cookie. With a standard POST FORM request, it works just fine, but when sending as JSON it fails.
As $_POST array is empty because of the JSON content-type, CodeIgniter fails to validate the cookie and throws an error.
How can I have CodeIgniter check JSON payload and validate my CSRF token ?
To fix that issue, I had to change the code of the "Security.php" file located in "system/core/".
In function "csrf_verify", replace that code:
// Do the tokens exist in both the _POST and _COOKIE arrays?
if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name]))
{
$this->csrf_show_error();
}
// Do the tokens match?
if ($_POST[$this->_csrf_token_name] != $_COOKIE[$this->_csrf_cookie_name])
{
$this->csrf_show_error();
}
By that code:
// Do the tokens exist in both the _POST and _COOKIE arrays?
if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name])) {
// No token found in $_POST - checking JSON data
$input_data = json_decode(trim(file_get_contents('php://input')), true);
if ((!$input_data || !isset($input_data[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name])))
$this->csrf_show_error(); // Nothing found
else {
// Do the tokens match?
if ($input_data[$this->_csrf_token_name] != $_COOKIE[$this->_csrf_cookie_name])
$this->csrf_show_error();
}
}
else {
// Do the tokens match?
if ($_POST[$this->_csrf_token_name] != $_COOKIE[$this->_csrf_cookie_name])
$this->csrf_show_error();
}
That code first checks $_POST then if nothing has been found, it checks the JSON payload.
The ideal way of doing this would be to check the incoming request Content-Type header value. But surprisingly, it's not straight forward to do ...
If someone has a better solution, please post it here.
Cheers
Alternatively, you can skip the CSRF checking by adding following code on application/config/config.php below Line No. 351 (based on CI 2.1.4).
$config['csrf_expire'] = 7200; // This is line no. 351
/* If the REQUEST_URI has method is POST and requesting the API url,
then skip CSRF check, otherwise don't do. */
if (isset($_SERVER["REQUEST_URI"]) &&
(isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] == 'POST') ))
{
if (stripos($_SERVER["REQUEST_URI"],'/api/') === false ) { // Verify if POST Request is not for API
$config['csrf_protection'] = TRUE;
}
else {
$config['csrf_protection'] = FALSE;
}
} else {
$config['csrf_protection'] = TRUE;
}
If this needs to be overridden, best to extend the Security library rather than editing the core file directly.
Create the file My_Security.php in application/core/ and add the following (from the solution above):
<?php
class My_Security extends CI_Security {
/**
* Verify Cross Site Request Forgery Protection
*
* #return object
*/
public function csrf_verify()
{
// If it's not a POST request we will set the CSRF cookie
if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST')
{
return $this->csrf_set_cookie();
}
// Do the tokens exist in both the _POST and _COOKIE arrays?
if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name])) {
// No token found in $_POST - checking JSON data
$input_data = json_decode(trim(file_get_contents('php://input')), true);
if ((!$input_data || !isset($input_data[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name])))
$this->csrf_show_error(); // Nothing found
else {
// Do the tokens match?
if ($input_data[$this->_csrf_token_name] != $_COOKIE[$this->_csrf_cookie_name])
$this->csrf_show_error();
}
}
else {
// Do the tokens match?
if ($_POST[$this->_csrf_token_name] != $_COOKIE[$this->_csrf_cookie_name])
$this->csrf_show_error();
} // We kill this since we're done and we don't want to
// polute the _POST array
unset($_POST[$this->_csrf_token_name]);
// Nothing should last forever
unset($_COOKIE[$this->_csrf_cookie_name]);
$this->_csrf_set_hash();
$this->csrf_set_cookie();
log_message('debug', 'CSRF token verified');
return $this;
}
}
As Brian write, you have to put your custom class into /application/core/ ex. My_Security.php
This is mine solution, work for me, i check the application/json content_type and request cookies.
defined('BASEPATH') OR exit('No direct script access allowed');
class MY_Security extends CI_Security {
public function csrf_verify()
{
// If it's not a POST request we will set the CSRF cookie
if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST')
{
return $this->csrf_set_cookie();
}
/**
* mine implementation for application/json
*/
$reqHeaders = getallheaders();
$content_type = $reqHeaders["Content-Type"];
#it's a json request?
if(preg_match("/(application\/json)/i",$content_type))
{
#the check the cookie from request
$reqCookies = explode("; ",$reqHeaders["Cookie"]);
foreach($reqCookies as $c)
{
if(preg_match("/(".$this->_csrf_cookie_name."\=)/", $c))
{
$c = explode("=",$c);
if($_COOKIE[$this->_csrf_cookie_name] == $c[1])
{
return $this;
}
}
}
}
//< end
// Check if URI has been whitelisted from CSRF checks
if ($exclude_uris = config_item('csrf_exclude_uris'))
{
$uri = load_class('URI', 'core');
foreach ($exclude_uris as $excluded)
{
if (preg_match('#^'.$excluded.'$#i'.(UTF8_ENABLED ? 'u' : ''), $uri->uri_string()))
{
return $this;
}
}
}
// Do the tokens exist in both the _POST and _COOKIE arrays?
if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name])
OR $_POST[$this->_csrf_token_name] !== $_COOKIE[$this->_csrf_cookie_name]) // Do the tokens match?
{
$this->csrf_show_error();
}
// We kill this since we're done and we don't want to polute the _POST array
unset($_POST[$this->_csrf_token_name]);
// Regenerate on every submission?
if (config_item('csrf_regenerate'))
{
// Nothing should last forever
unset($_COOKIE[$this->_csrf_cookie_name]);
$this->_csrf_hash = NULL;
}
$this->_csrf_set_hash();
$this->csrf_set_cookie();
log_message('info', 'CSRF token verified');
return $this;
}
}