Forms

Forms must be constructed as usual, using HTML and PHP for dynamic parts. Liaison provides a CSRF utility, and (coming soon) a spam prevention utility. Liaison will help you generate appropriate URLs for your forms and makes it easy to create a route that handles POST requests.

Input sanitization may be added eventually, but is not planned at this time.

Related: Routes, CSRF Class

Docs

  • Example Form
  • CSRF Utility

Example Form

A simple HTML form using Liaison to load the submission path.

public/my-form.php

<form method="POST" action="<?=$package->url("/my-form-handler/");?>">  
    <label>Your Name<br>  
        <input type="text" name="name" placeholder="John Doe" />  
    </label>  
  
    <br>  
    <input type="submit" value="Submit" />  
  
</form>  

Note: add enctype="multipart/form-data" to the <form> if your form handles file uploads

For handling the submission with a POST route, see Routes.

$package is automatically exposed to your public files, and $package->url() ensures the correct path is returned if your package's base url is changed.

CSRF Utility

Warning: The CSRF Utility is newly developed, and may contain bugs not yet discovered.

Liability Waiver: You accept all risks and liability for your use of this tool, which is provided as-is. Review the code.

This utility prevents most Cross Site Request Forgery (CSRF) attacks. A CSRF attack is when an EvilSite has a form that submits to YourSite and causes some unintended behavior on YourSite.

For example, your user might wish to change their password. The password change should only be allowed if the action originated on YourSite, and should be blocked if the password change was initiated from a form on EvilSite.

See my notes and Owasp cheatsheet.

Warning: This tool may fail to prevent CSRF attacks if the user's browser does not properly protect them (such as with outdated browsers), if your server is not propertly configured (if it sends cookies when requests are made from different hosts), if you fail to prevent XSS, or possibly in the case of other unforseen circumstances.

Warning: Cross-Site Scripting (XSS) attacks can bypass CSRF protections. Liaison does not (currently) have built-in tools for validating submitted inputs and preventing XSS. See Types of XSS and Owasp XSS Cheatsheet, and do not print untrusted user data.

Generating CSRF Token

/public/my-form.php

<?php  
    $post_url = $package->url("/my-form-handler/");  
    $num_minutes_csrf_token_remains_valid = 180;  
    // csrf input is a hidden input with a randomly value   
    $csrf_input = \Lia\Utility\CSRF::get_csrf_input('unique-identifier-for-this-form', $post_url, $num_minutes_csrf_token_remains_valid);  
    // the CSRF token will only validate when submitted to the given $post_url  
?>  
<form method="POST" action="<?=$post_url?>">  
    <?=$csrf_input?>  
      
    ... your form ...  
</form>  

Validating CSRF Token

NOTE: CSRF tokens should not be sent via GET

/public/@POST.my-form-handler.php

<?php  
if (!\Lia\Utility\CSRF::is_request_valid('unique-identifier-for-this-form', $_POST)){  
    echo "Form failed to submit";  
    // you might want to redirect the user back to the form.   
    // you might wish to give a better error message  
    // you may wish to log the failure  
    // you may wish to add headers indicating that this request failed  
    return;  
}  
  
// do stuff with the submitted data  
// BE SURE to sanitize all user inputs  

Manually generate/validate CSRF

You can manually generate the csrf token value for any non-standard setups.

<?php  
// send the csrf token to the browser, perhaps to be used by javascript  
$csrf_token = \Lia\Utility\CSRF::get_csrf_value('form_id', $post_url, $mins_til_expiry);  
  
// VALIDATE   
$csrf_key = \Lia\Utility\CSRF::get_csrf_key('form_id');  
$is_valid = \Lia\Utility\is_request_valid('form_id', [$csrf_key => $submitted_csrf_token]);  
  

CSRF Notes

  • During a single request, you can only generate one CSRF token for each form_id.
    • Example: You have an 'Edit Contacts' page that lists 30 different identical forms. Each one is submitted via javascript. You have to use a different form id for each of these forms because they are generated on the same request. You can manually generate incremental form ids and then add the form id as a hidden input to your form, or perhaps use the contact's database id as the numbering for the form id to keep the form id entirely server-side.
  • Across multiple requests, multiple CSRF tokens can be generated for the same form_id - meaning: if a user opens your 'Login' page in three different tabs, there will be three different CSRF tokens and all will be valid.
  • A CSRF token's key is in the format: csrf-form_id, so the hidden input's name will be csrf-form_id
  • A CSRF token can be validated multiple times during one request. It is immediately unset from the $_SESSION on the first is_request_valid() call, and if it passes, the form_id/token combo is stored in a static class variable. If it fails, then it is simply gone from session so future validation requests will fail with a CSRF_TOKEN_NOT_IN_SESSION error being logged
  • When is_request_valid() returns false, we always log a descriptive error. is_request_valid() can throw an exception, which should only happen in the case of a server misconfiguration or inability to start session.