Making Axios Request Payload work with PHP & What goes behind?

This article is going to be full of problems, not just one but about five or more, who knows. Currently, I am working with three platforms. Full Stack JavaScript - Node JS, React JS, Express JS stack, LAMP Stack and some crazy .NET Core 2 Stack (I am not getting into the details as I have got a macOS and .NET is not something that I would favour, at the moment).

The reason for .NET Core 2 is because my current application's API architecture uses it and since I am working on React JS on a macOS, that too on my personal computer, I thought it would be a good idea to recreate the backend using Node JS instead of .NET Core 2 along with the database and other stuff. Lazy guy, you see? And that was alright, till I wanted to post my experience on my blog.

For explaining something on my blog, I heavily rely on my website and the server that I have got, which runs on a LAMP Stack. For React JS demos, I use CodeSandbox.io and it works charmingly. Having a LAMP Backend with right Front-End is really hard in case of React JS demos as the PHP (in LAMP) doesn't work as the way, Express JS + Node JS does! 😔

Contents

  1. Initial Setup
    1. Securing the Endpoint with HTTPS
    2. Allowing Cross Origin Requests
  2. The way of fetch vs. axios (AJAX Preflight Issue)!
    1. So what went wrong?
    2. Why is this good?
  3. Where's the Request Data?
    1. Solution to get Request Payload
    2. Working of Axios & Express
  4. Demo

Initial Setup

I had my API end-point set up here. It obviously runs on PHP and the basic things I have done are just the few of any API end-point stuff, securing the end-point with HTTPS, route all the requests to a single PHP file and finally opening it up for cross-origin requests.

Securing the Endpoint with HTTPS

That was easy as I had a valid certificate already and all I had to do is when someone reaches unsecurely, I have to secure the route. Simple. I just used a small .htaccess to make it possible.

RewriteEngine on  
RewriteCond %{HTTPS} !=on  
RewriteRule ^.*$ https://%{SERVER_NAME}%{REQUEST_URI} [R,L]  

The above one on its own, can be used for securing the routes and the next one helps me in routing everything to index.php.

RewriteEngine on  
RewriteCond %{REQUEST_FILENAME} !-f  
RewriteCond %{REQUEST_FILENAME} !-d  
RewriteRule ^(.*)$ ./index.php?path=$1 [NC,L,QSA]  

Again, the above can be used all alone by itself for just routing every request to index.php. Having both needed for me, I combined them this way.

RewriteEngine on  
RewriteCond %{HTTPS} !=on  
RewriteRule ^.*$ https://%{SERVER_NAME}%{REQUEST_URI} [R,L]  
RewriteCond %{REQUEST_FILENAME} !-f  
RewriteCond %{REQUEST_FILENAME} !-d  
RewriteRule ^(.*)$ ./index.php?path=$1 [NC,L,QSA]  

Allowing Cross Origin Requests

If you have seen this, the website enable cross-origin resource sharing helps you to configure different clients and servers to allow cross origin requests and make the AJAX calls possible. All I had to do is to set a header that allows cross origin requests possible. It was a bit easy as I am using a single script to process all the requests and it was very quick.

<?php  
  header("Access-Control-Allow-Headers: *");

Talking about the universal allowance being a bit perilous, I decided to lock it down to just the CodeSandbox's demo box URL and that worked well. So the updated code looks like this.

<?php  
  header("Access-Control-Allow-Origin: https://6veek.codesandbox.io");

And although this did solve the issue with accessing over fetch API, there was some other issue with axios in accessing it. If you don't do the above, you get the following error.

Access to XMLHttpRequest at 'https://apps.praveen.science/jwt-react/Auth/SignIn' from origin 'https://6veek.codesandbox.io' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

CORS Error

The way of fetch vs. axios (AJAX Preflight Issue)!

I was shocked to find out that these two APIs behaved differently. After enabling the CORS, the error went off in case of fetch. I was able to get the request success via this simple fetch API code.

fetch("https://apps.praveen.science/jwt-react/Auth/SignIn", {  
    method: "POST",
    body: JSON.stringify({
      username: "praveen",
      password: "rocks"
    })
  }).then(function(response) {
    return response.json();
  }).then(function(data) {
    console.log(data);
  });

But the same code, with axios didn't work.

axios  
  .post("https://apps.praveen.science/jwt-react/Auth/SignIn", {
    username: "praveen",
    password: "rocks"
  }).then(res => console.log(res.data));

It throwed an error saying some issue with the header field being not allowed in the preflight response.

Access to XMLHttpRequest at 'https://apps.praveen.science/jwt-react/Auth/SignIn' from origin 'https://6veek.codesandbox.io' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.

CORS Error Preflight

This made me think that axios is doing more than what fetch does. It's trying to do a preflight check and if the response isn't favourable, it is not allowing the connection.

So what went wrong?

It seems like axios is trying to send a preflight request using HTTP OPTIONS to check if there are any CORS issues. There are three steps to note here.

  1. Axios sends a CORS Preflight check to see if there's any problem with CORS.
  2. It doesn't usually expect any headers here, but if you can't stop sending other headers, you have to make an exception.
  3. Make sure the CORS call is successful. 😇

Why is this good?

This is a good way of exception handling by understanding what are the allowed transactions on the server. A CORS preflight request is a CORS request that checks to see if the CORS protocol is understood.

It is an OPTIONS request, using three HTTP request headers: Access-Control-Request-Method, Access-Control-Request-Headers, and the Origin header.

A preflight request is automatically issued by a browser, when needed. In normal cases, front-end developers don't need to craft such requests themselves.

For example, a client might be asking a server if it would allow a DELETE request, before sending a DELETE request, by using a preflight request:

OPTIONS /Auth/Users/116  
Access-Control-Request-Method: DELETE  
Access-Control-Request-Headers: origin, x-requested-with  
Origin: https://apps.praveen.science  

If the server allows it, then it will respond to the preflight request with an Access-Control-Allow-Methods response header, which lists DELETE:

HTTP/1.1 204 No Content  
Connection: keep-alive  
Access-Control-Allow-Origin: https://apps.praveen.science  
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE  
Access-Control-Max-Age: 86400  

Where's the Request Data?

It was shocking when I tried to get the data from PHP's side. The $_POST global variable was empty. This was definitely concerning for me as all the data that I sent isn't received. I was pretty sure that the problem is somehow with the JavaScript code as fetch worked. When I tried to look into the Network tab, something caught my attention.

Request Payload in Network

I haven't seen something like this in my experience. At this point, I tried to check the $_REQUEST as well and found something totally crazy and different. The var_dump($_POST, $_REQUEST) gave me the following.

array(0) {  
}
array(1) {  
  ["path"]=>
  string(11) "Auth/SignIn"
}

Technically, the $_REQUEST too is empty. It is just a consolidation of GET and POST data. But unfortunately I couldn't find the request payload that I sent in any of them. Moving on, I learnt about Supported Protocols and Wrappers in PHP. PHP comes with many built-in wrappers for various URL-style protocols for use with the filesystem functions such as fopen(), copy(), file_exists() and filesize().

One of the generic I/O stream wrappers in PHP is php://input, which is a read-only stream that allows you to read raw data from the request body. In the case of POST requests, it is preferable to use php://input instead of $HTTP_RAW_POST_DATA as it does not depend on special php.ini directives. Moreover, for those cases where $HTTP_RAW_POST_DATA is not populated by default, it is a potentially less memory intensive alternative to activating always_populate_raw_post_data.

Solution to get Request Payload

Having said that, I was planning to use that for a change and tried something like this.

<?php  
  $rp = json_decode(file_get_contents('php://input'), true);

That technically helped me in getting the JSON data that's sent via Request payload. So, I used the simple json_decode method in PHP and converted the contents into an array. Now when I try to dump the contents of file_get_contents('php://input'), I would neatly get the JSON that I sent to the server through axios.

{
  "username": "praveen",
  "password": "rocks"
}

Working of Axios & Express

It is evident and clear that the way Express JS handles the request payload through its middleware like express.json(). Express tries to combine all the requests and gets us the data that we need instead of we trying to do some crazy work-arounds that we did it in PHP.

Just a quick heads up for those who are wondering why it may not work in older versions of Express JS is that this middleware (express.json()) is available only in Express v4.16.0 onwards. It parses incoming requests with JSON payloads and is based on body-parser.

Demo

Here's a CodeSandbox of what we tried to achieve using React JS and PHP.



comments powered by Disqus