85

I would like to check server-side if a request to my php page is an ajax request or not.

I saw two ways to do this:

First way: sending a GET parameter in the request which tells the page that this is an AJAX request (=mypage.php?ajax)

mypage.php:

if(isset($_GET['ajax'])) {
    //this is an ajax request, process data here.
}

Second way: set a header to the xmlHttpRequest:

client-side js:

xmlHttpRequestObject.open(“GET”,url,true);
xmlHttpRequestObject.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

mypage.php:

if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest' ) {
    //request is ajax
}

The fact is, those two ways of doing it can easily be hacked, so it's not secure to check if i get an AJAX request like this.

How can i check if i'm receiving an AJAX request?

tshepang
  • 12,111
  • 21
  • 91
  • 136
BackSlash
  • 21,927
  • 22
  • 96
  • 136
  • 10
    Just keep in mind: "NEVER trust the client side". You cannot know from where a request is made. You could spoof/disguise/fake (almost) everything. – DAG Aug 15 '13 at 19:48
  • Possible duplicate of http://stackoverflow.com/questions/4301150/how-do-i-check-if-the-request-is-made-via-ajax-with-php – JRizz Aug 15 '13 at 19:49
  • @ChristianGärtner I know that, that's why i posted the question. I don't think there is a 100% safe way to do it, but i think there must be a way to make it tricky for "hackers" to fake the request (maybe managing it server-side) – BackSlash Aug 15 '13 at 19:50
  • @J.Robertson As written in the question, i tried to do it as is answered in the question you linked, but it can easily be spoofed. – BackSlash Aug 15 '13 at 19:51
  • "hackers" would see through any "tricky" stuff. there is literally NOTHING you can do to 100% reliably tell is an http request is "legit" or "faked". – Marc B Aug 15 '13 at 19:59
  • @MarcB Yes, but if it's tricky it will be harder for them to fake it. It's not 100% safe, there isn't a 100% safe way, but this could be a good start. – BackSlash Aug 15 '13 at 20:02
  • 1
    at best you can put a very minor speedbump in the "hacker's" path. just remember that ajax is just a perfectly normal http request. other than the fact that it tens to have been initiated by some javascsript as a background request within a web page, there is absolutely no standard method of deciding if it's ajax or something the user clicked on/submitted. the presence/abscence of the X headers is not a guarantee of anything – Marc B Aug 15 '13 at 20:04

11 Answers11

79

Example from an archived tutorial:

if(strtolower($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') == 'xmlhttprequest')
{    
    // We can assume that request was made with AJAX   
}

This checks if the HTTP_X_REQUESTED_WITH parameter is set and its value is equal to xmlhttprequest.

Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
Volodymyr
  • 1,557
  • 2
  • 14
  • 21
  • 6
    This answer is EXTREMELY relevant now because the facebook android app sends with `HTTP_X_REQUESTED_WITH=com.facebook.katana.` Adding in the last check `== 'xmlhttprequest'` You need to implement this check of type witht he new facebook browser, otherwise strange things can happen. – Tschallacka Mar 17 '16 at 09:28
  • $_SERVER['HTTP_X_REQUESTED_WITH'] and be undefined – boctulus Jan 16 '19 at 20:04
  • 2
    I often use this as an additional hurdle for scrapers to contend with, it's not foolproof but it's effective in some cases... As `$_SERVER['HTTP_X_REQUESTED_WITH']` may be undefined I recommend a slight edit. `if(!isset($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest')`... – Andy Gee Nov 10 '20 at 02:22
  • 5
    Hello from 2021, This code is working fine if user is using jQuery ajax such as `.ajax()`, `.post()` but with vanilla JS `XMLHttpRequest()`, or `.fetch()` they don't send this header anymore. – vee Jun 13 '21 at 14:27
42

There is no sure-fire way of knowing that a request was made via Ajax. You can never trust data coming from the client. You could use a couple of different methods but they can be easily overcome by spoofing.

Wayne Whitty
  • 19,513
  • 7
  • 44
  • 66
  • 1
    Yes. I'm pretty sure there isn't a 100% safe way to do it. But i think that there must be a way to make it tricky for "hackers" to fake the request (maybe managing it server side?) – BackSlash Aug 15 '13 at 19:52
  • 5
    @BackSlash You could make it tricky, but that would be security through obscurity. Personally, I wouldn't rely on it. Your best bet would probably be tokens that change per-request. Or a captcha. – Wayne Whitty Aug 15 '13 at 19:55
  • I was thinking about cookies, are they sent with an ajax request? – BackSlash Aug 15 '13 at 19:58
  • An Ajax request is basically just a HTTP request that is sent without the user having to leave the page. Anything you can do with a HTTP request can also be done in an Ajax request :) Wouldn't rely on cookies either though! – Wayne Whitty Aug 15 '13 at 20:00
  • @BackSlash: I found a nice article, have a look: http://abhinavsingh.com/blog/2009/10/web-security-using-crumbs-to-protect-your-php-api-ajax-call-from-cross-site-request-forgery-csrfxsrf-and-other-vulnerabilities/ – Qarib Haider Dec 01 '14 at 12:28
  • `if(isset($_SERVER['HTTP_CF_CONNECTING_IP'])) { $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CF_CONNECTING_IP']; }` – Krii Feb 10 '16 at 15:18
  • 1
    but what is the security risks here? the headers can be spoofed ... so what? it is just an ajax request that is accessible from the public ... spoof or not... – yeahman Nov 04 '18 at 07:10
  • @yeahman That would depend on the purpose of the check. e.g. Trying to prevent non-Ajax requests. – Wayne Whitty Aug 20 '20 at 12:19
15

From PHP 7 with null coalescing operator it will be shorter:

$is_ajax = 'xmlhttprequest' == strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ?? '' );
luckydonald
  • 5,976
  • 4
  • 38
  • 58
Emeszenes
  • 151
  • 1
  • 2
9

Set a session variable for every page on your site (actual pages not includes or rpcs) that contains the current page name, then in your Ajax call pass a nonce salted with the $_SERVER['SCRIPT_NAME'];

<?php
function create_nonce($optional_salt='')
{
    return hash_hmac('sha256', session_id().$optional_salt, date("YmdG").'someSalt'.$_SERVER['REMOTE_ADDR']);
}
$_SESSION['current_page'] = $_SERVER['SCRIPT_NAME'];
?>

<form>
  <input name="formNonce" id="formNonce" type="hidden" value="<?=create_nonce($_SERVER['SCRIPT_NAME']);?>">
  <label class="form-group">
    Login<br />
    <input name="userName" id="userName" type="text" />
  </label>
  <label class="form-group">
    Password<br />
    <input name="userPassword" id="userPassword" type="password" />
  </label>
  <button type="button" class="btnLogin">Sign in</button>
</form>
<script type="text/javascript">
    $("form.login button").on("click", function() {
        authorize($("#userName").val(),$("#userPassword").val(),$("#formNonce").val());
    });

    function authorize (authUser, authPassword, authNonce) {
        $.ajax({
          type: "POST",
          url: "/inc/rpc.php",
          dataType: "json",
          data: "userID="+authUser+"&password="+authPassword+"&nonce="+authNonce
        })
        .success(function( msg ) {
            //some successful stuff
        });
    }
</script>

Then in the rpc you are calling test the nonce you passed, if it is good then odds are pretty great that your rpc was legitimately called:

<?php
function check_nonce($nonce, $optional_salt='')
{
    $lasthour = date("G")-1<0 ? date('Ymd').'23' : date("YmdG")-1;
    if (hash_hmac('sha256', session_id().$optional_salt, date("YmdG").'someSalt'.$_SERVER['REMOTE_ADDR']) == $nonce || 
        hash_hmac('sha256', session_id().$optional_salt, $lasthour.'someSalt'.$_SERVER['REMOTE_ADDR']) == $nonce)
    {
        return true;
    } else {
        return false;
    }
}

$ret = array();
header('Content-Type: application/json');
if (check_nonce($_POST['nonce'], $_SESSION['current_page']))
{
    $ret['nonce_check'] = 'passed';
} else {
    $ret['nonce_check'] = 'failed';
}
echo json_encode($ret);
exit;
?>

edit: FYI the way I have it set the nonce is only good for an hour and change, so if they have not refreshed the page doing the ajax call in the last hour or 2 the ajax request will fail.

Muhammad Hassaan
  • 7,296
  • 6
  • 30
  • 50
GovCoder
  • 117
  • 1
  • 2
  • 2
    This is more a CSRF Protection than a way to decide if it was a regular GET/POST request or an ajax request. – Sebi2020 Sep 20 '18 at 01:10
9

This function is used from the Yii PHP Framework to check for an AJAX call.

public function isAjax() {
  return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
}
Nanhe Kumar
  • 15,498
  • 5
  • 79
  • 71
5
$headers = apache_request_headers();
$is_ajax = (isset($headers['X-Requested-With']) && $headers['X-Requested-With'] == 'XMLHttpRequest');
revoke
  • 529
  • 4
  • 9
  • 1
    Some headers can easily be spoofed. `X-Requested-With` is one of them, as it's set by the client. That's why I don't want to use it. – BackSlash Jun 25 '14 at 13:41
  • 1
    Yes, it's not safe, but if you simply need to decide on the format of the answer, you can use it. – revoke Jul 02 '14 at 10:17
  • This NOT work with android facebook app, set the x-request-with to com.facebook.katana ! – Blackfire Aug 17 '17 at 23:42
1

Try below code snippet

if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) 
   && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') 
  {
    /* This is one ajax call */
  }
Sani Kamal
  • 1,208
  • 16
  • 26
1

try to use HTTP_SEC

  if (!array_key_exists("HTTP_SEC_FETCH_SITE",$_SERVER) || $_SERVER["HTTP_SEC_FETCH_SITE"] != "same-origin")
    die(header("HTTP/1.1 404 Not Found"));
  if (!array_key_exists("HTTP_SEC_FETCH_MODE",$_SERVER) || $_SERVER["HTTP_SEC_FETCH_MODE"] != "cors")
    die(header("HTTP/1.1 404 Not Found"));
  if (!array_key_exists("HTTP_SEC_FETCH_DEST",$_SERVER) || $_SERVER["HTTP_SEC_FETCH_DEST"] != "empty")
    die(header("HTTP/1.1 404 Not Found"));
Smar ts
  • 49
  • 5
0

The code below is able to detect AJAX request in three different ways:

  • using X-Requested-With header
  • using Accept: application/json
  • fall-back to ?ajax=1 in the URL (make sure to add that manually)
    /**
     * http://php.net/manual/en/function.apache-request-headers.php#70810
     * @return bool
     */
    public function isAjax()
    {
        $headers = function_exists('apache_request_headers') ? apache_request_headers() : [];
        if (!$headers) {
            $headers = [
                'X-Requested-With' => ifsetor($_SERVER['HTTP_X_REQUESTED_WITH'])
            ];
        }
        $headers = array_change_key_case($headers, CASE_LOWER);

        $isXHR = false;
        if (isset($headers['x-requested-with'])) {
            $isXHR = $headers['x-requested-with'] === 'XMLHttpRequest';
        }

        $acceptJson = str_contains($_SERVER['HTTP_ACCEPT'] ?? '', 'application/json');

        return $this->getBool('ajax') || $isXHR || $acceptJson;
    }
Slawa
  • 1,141
  • 15
  • 21
-2

if ($request->ajax()) { //write code here }

-7

You could try using a $_SESSION variable to make sure that a request was made from a browser. Otherwise, you could have the request sent through a database or file [server-side].