4
\$\begingroup\$

I'm looking for a good approach to AJAX communication using only pure JS and PHP. I would like it to be as safe as possible and compatible with most modern browsers and IE 10+. I did some research and found some code examples for writing a vanilla JS function to handle AJAX communication, but I was not sure how safe and up to date those were. I started coding my own solution based on what I've found. I need to receive and send data using AJAX. There will be a login system and also a text posting system.

index.html

<input type="checkbox" id="agreement"/> I agree with terms.

<input type="text" id="user"/> User

<input type="password" id="password"/> Password

<textarea id="text"></textarea> Text

<button id="submit" onclick="submit_form()">Submit</button>

<span id="result"></span>

ajax.php

if($_GET['action'] == 'submit_form')
{
    $agreement = $_POST['agreement'];
    $user = $_POST['user'];
    $password = $_POST['password'];
    $text = $_POST['text'];

    echo 'Data received: <br/>';
    echo 'Agreement: '.$agreement.'<br/>';
    echo 'User: '.$user.'<br/>';
    echo 'Password: '.$password.'<br/>';
    echo 'Text: '.$text.'<br/>';
}

scrip.js

function submit_form()
{
    var ajax = new XMLHttpRequest();

    ajax.open('POST', 'ajax.php?action=submit_form', true);

    ajax.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');

    var content = '';

    content = content + 'agreement=' + document.querySelector('#agreement').checked;
    content = content + '&user=' + document.querySelector('#user').value;
    content = content + '&password=' + document.querySelector('#password').value;
    content = content + '&text=' + document.querySelector('#text').value;

    ajax.send(content);

    ajax.onreadystatechange = function()
    {
        if (ajax.readyState == 4 && ajax.status == 200)
        {
            document.querySelector('#result').innerHTML = ajax.responseText;
        }
    }
}

Can you spot any problems with this approach? Are there better, simpler or more cross-browser compatible ways for doing the same? Are there any special concerns I should have when sending and receiving passwords?

\$\endgroup\$
5
\$\begingroup\$
var ajax = new XMLHttpRequest();

ajax.open('POST', 'ajax.php?action=submit_form', true);

You can replace XHR with fetch. While XHR is widely supported, fetch is easier to work with. It uses promises instead of callbacks, which means you can readily use async/await with it. It's inspired from jQuery's $.ajax(). So if you're familiar with that, fetch will just click.

var content = '';

content = content + 'agreement=' + document.querySelector('#agreement').checked;
content = content + '&user=' + document.querySelector('#user').value;
content = content + '&password=' + document.querySelector('#password').value;
content = content + '&text=' + document.querySelector('#text').value;

Do not build query strings manually. The issue is that in query strings, certain characters must be escaped correctly for it to be valid. Otherwise, I could stick characters in there which can make the query string invalid. Use URLSearchParams() to construct the query string.

<button id="submit" onclick="submit_form()">Submit</button>

Instead of using onclick on the button, use onsubmit on the form instead. This way, you can also catch someone submitting the form by pressing enter. Also, instead of inline event properties, use element.addEventListener(). This way, you can, among other things, add more than just one handler and avoid defining a global function for your event handler.

if($_GET['action'] == 'submit_form')
{
    $agreement = $_POST['agreement'];
    $user = $_POST['user'];
    $password = $_POST['password'];
    $text = $_POST['text'];

    echo 'Data received: <br/>';
    echo 'Agreement: '.$agreement.'<br/>';
    echo 'User: '.$user.'<br/>';
    echo 'Password: '.$password.'<br/>';
    echo 'Text: '.$text.'<br/>';
}

Not exactly sure why you'd put action in a GET parameter while everything else is in POST. Use POST all the way if you intend to POST. Also, if I recall correctly, it's up to the server to parse GET parameters in a non-GET operation. The server may choose to ignore it. This might work in PHP, but it might not work on another platform.

Also, sanitize/escape the data before printing it on the page. This looks like debugging to me, so that's fine. But never trust user input, never print arbitrary values. This can easily be used in a reflected XSS, a security issue.

\$\endgroup\$
  • \$\begingroup\$ Great answer, thank you! When it comes to using fetch(), it seems I may not be able to, because I need support for IE 10. About URLSearchParams(), great advice. I'll definitely look more on how to use it and implement it into my code. I'm already using element.addEventListener() now and got rid of the $_GET. I also added another setRequestHeader() to pass a specific header so I can check if the PHP file is being requested from AJAX or direct acess (I know it can be faked from client side, but still ...). What else can I do to improve it? Anything on sending and receiving passwords? \$\endgroup\$ – sutt_ Aug 14 at 11:38
  • \$\begingroup\$ Also, any comments on security (XSS)? \$\endgroup\$ – sutt_ Aug 14 at 12:35
1
\$\begingroup\$

In regards to security for yourself, I would be using filter_var() to sanitize any input strings. Mainly the name and text inputs; If its a major concern or for your own sanity you could go as far as adding a preg_match($regex, $string) condition to check for characters you just don't want in those inputs.

For your passwords and user security I strongly recommend using password_hash() before sending them, and password_verify() to check them the user password against the hash you receive back. Its relatively simple to use and is pretty much the best way to handle passwords in php.

I wouldn't get too involved unless you REALLY know what you are doing with password hashes, I suggest just sticking with the PASSWORD_DEFAULT algorithm. Then for comparison you just receive the hash and compare it against the users password input inside the password_verify() function. You won't have to worry about escaping user input with these as far as I'm aware.

Just to be clear on the password_hash(), you only hash it one time before it gets sent to the database, you do not hash the password when you use the verify function, you just check the plain password input against the hash you received from the database.

In an absolute quick way to show you how here's an example; in the top we create the hash and then you store it however you want. Then in the bottom example you get that hash and check it against a post of the password.

Create a Password Hash

$password = password_hash($_POST['password'], PASSWORD_DEFAULT);

// Send $password

Receive & Verify the Hash

$password = $_POST['password'];
$hash = // Receive password hash from earlier

if(password_verify($password, $hash)){
 // TRUE
}else{
 // FALSE
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.