Cross Site request Forgery (CSRF) Protection With PHP and Angular.js

This post will demonstrate how to submit a form and email a message to the desired recipient. This lesson will use ajax with PHP and Angular.js. In addition to the form submission, a new class and a one time token are used to ensure that the form is actually submitted from the website and not through some script on a foreign machine.

How It Works

Whenever the page is loaded, the csrf class is called and a new hash, the one time token is created. That value is set as a session variable and added to a session array. Since browsers like Firefox and Chrome can handle sessions differently, creating the array ensures that if a token is created a second time, its value is stored no matter what.

The form itself is has typical angular attributes like ng-click and ng-model. As you may or may not already know, ng-model values come from the $scope.itemname created in the Javascript. Thus, when you see a tag like ng-model="{{message}}", you know its value comes from $scope.message in the Javascript.

Now for the action. When the form is submitted, the check_credentials() function is called.

If you look in the angular code, you will see that all form inputs, including the hidden csrf input are passed into the ajax post.

Once this code arrives to the php file, variables are set and sessions are checked to ensure that the one time token passes the test. Since the one time token is created on the server, this ensures email is not sent unless the csrf is valid.

Once the email is sent, all session variables are emptied due to the last line that shows $_SESSION = array();

Now, back to the angular.js code. Once all is successful, the original form is hidden, and a success message pops up with an image and a success message. Meanwhile, the page is not refreshed and the user experience is quite satisfying.

 

CSRF Class

class csrf
{
    public $csrf;
    public $session;
    public $csrf_array = array();

    public function __construct()
    {
        $csrf = hash("sha1", rand() . time() . rand());
        $_SESSION['csrf'] = $csrf;
        $session = $_SESSION['csrf'];
        $this->MakeToken($csrf, $session);
    }

    public function MakeToken($csrf, $session)
    {
        $this->csrf = $csrf;
        $this->session = $session;

        array_push($this->csrf_array, $this->csrf, $this->session);
        return $this->csrf_array;
    }

}

 

Top of File

<?php
    session_start();
    include("classes/csrf.php");
    $csrf = new csrf();
    $_SESSION['csrf'] = $csrf->session;
    $_SESSION['csrfs'][] = $csrf->session;
?><!DOCTYPE html>

 

Form

<div class="footer-widget newsletter-widget" id="message-received" ng-controller="ProjectsListCtrl" style="color:white">
    <div id="message"></div>
    <form name="myForm">
        <p><input type="text" size="40" ng-model="name" value="{{name}}" placeholder="Name"></p>
        <p><input type="text" size="40" ng-model="phone" placeholder="Phone"></p>
        <p><input type="text" size="40" name="email" ng-model="email" placeholder="Email" value="{{email}}" ng-pattern="/^[_a-zA-Z0-9]+(\.[_a-zA-Z0-9]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,4})$/" required></p>
        <p><textarea id="mess" placeholder="Your Message" ng-model="message" value="{{message}}"></textarea></p>
        <input type="hidden" name="csrf" ng-model="csrf" value="{{csrf}}"/>
        <button ng-click="check_credentials()">Send Message</button>
    </form>

</div>

 

Angular Code

<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.js"></script>

<script>
    angular.module('admin-projects', []);

    angular.module('admin-projects').controller('ProjectsListCtrl', function($scope, $http) {

        $scope.check_credentials = function () {

            if ($scope.email === undefined) { alert("Webmaster says email is undefined because it did not match pattern!") }

            if($scope.email.length < 5){
                alert("Email is invalid");
            }else{
                //alert("Email is valid");
                //alert($scope.csrf);
            }

            $scope.csrf = "<?php echo $csrf->session; ?>";

            var request = $http({

                method: "post",
                url: "post.php",
                data: {
                    email: $scope.email,
                    name: $scope.name,
                    message: $scope.message,
                    csrf: $scope.csrf
                },
                headers: {'Content-Type': 'application/x-www-form-urlencoded'}
            });

            request.success(function (data) {
                        
                    $('#message-received').hide();
                    $('#message-received').html('<em><span style="font-size:12px; color:white">Your message has been successfully sent!</span></em>&nbsp;<span style="color:#FB3F43" class="glyphicon glyphicon-ok"></span><br/><img style="width:100%; border:1px solid #FB4848; border-radius:10px;" src="images/clients/myimage.jpg">').fadeIn(3000);
     
                $scope.email = '';
                $scope.name = '';
                $scope.phone = '';
                $scope.message = '';

            });

        }

    });

</script>

 

PHP Code

session_start();
if ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
    //Request identified as ajax request
} else {
    die("No direct access");
}
$postdata = file_get_contents("php://input");
$request = json_decode($postdata);
$email = $request->email;
$password = $request->name;
$subject = $request->subject;
$content = $request->message;
$csrf = $request->csrf;

$session_array = $_SESSION['csrfs'];

//echo "CSRF: ". $csrf . " and CSRF Session: " . $_SESSION['csrf'] . print_r($_SESSION['csrfs']) . print_r($_SESSION);

if ($csrf == $_SESSION['csrf'] || in_array($_SESSION['csrf'], $session_array)) {

    $to = "test@example.com";    
    $headers = "From: $email" . "\r\n";
    mail($to, $subject, $content, $headers);
}

$_SESSION = array();