Note: Yes i do realise that inherently over non-ssl connections exposing a token like this may be hazardous, however the point of this test was have the form generated without PHP.
The Structure:
Code: Select all
index.html
req.php
js/main.js
css/style.css
lib/loader.php
lib/stdOut.php
lib/token.php- index.html contains a form with X number of fields plus a submit button.
- When the user finishes filling in all of the fields, they press the submit button to process the form.
- jQuery intercepts the form's onsubmit event and handles it manually.
- Submit validates to make sure that data exists in the fields (plus any html5 validation patterns)
- Submit calls the javascript function 'getToken()' and assigns the output to the variable 'nToken'
- getToken() submits an ajax GET request to 'req.php?gettoken' to obtain a new token
- req.php creates a 'new token;' class and requests a new token.
- token generates a new token on __construct as there is no $_POST['token'] to check.
- New token is also stored to $_SESSION['token'] with a variable key name and the token as the value ( array('token_random'=>'thetoken') )
- req.php returns the generated token in the json format {response:'thetoken',status:'success'}
- Submit then checks to see if the form contains a field with the jQuery selector $('#formModToken'), if it exists it updates with nToken, else it creates a new dom element and appends it to the form.
- Submit (now with the token field), submits the form (via ajax) to req.php (handling the original for submission)
- req.php finds there to be a $_POST, and when 'new token;' is reconstructed, finds a $_POST['token'] field and assigns that as the current token.
- req.php then calls token::checkToken()
- token checks current stored token against the session variable.
- token returns a boolean to req.php
- req.php deals with the actions/response
- Submit removes the token field from the form after a response
1. Storing the token inside a predefined session variable as an array (mashed keyboard example below). Given that the token is always stored inside $_SESSION['token'], is there a more appropriate way to handle this? This also comes back to how i accessed the token by using array_keys() on $_SESSION['token'] to retrieve the current token.
Code: Select all
$_SESSION['token'] = array('token_asdfa87g63g83a' => 'fawbf97w9f982');Are there any other issues that I'm just not seeing in the implementation or way in which it operates?
Feedback would be appreciated
Code:
Here is the actual code used in the above.
index.html
Code: Select all
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<link rel="stylesheet" href="css/bootstrap-dialog.min.css">
<link rel="stylesheet" href="css/pack.css"/>
</head>
<body>
<div class='container'>
<div class='row'>
<div class='col-md-6'>
<h2>Pack Info</h2>
<div id='pack-info'></div>
<h2>Request</h2>
<form method='post' onsubmit="return false;">
<div class='form-group'>
<label for='formName'>Name</label>
<input type='text' name='formName' class='form-control' id='formName' placeholder='Joe Blogs' required aria-required="true" />
</div>
<div class='form-group'>
<label for='formURL'>URL</label>
<input type='url' pattern="https?://.+" name='formUrl' class='form-control' id='formURL' placeholder='http://myurl.com' required $
</div>
<div class='form-group'>
<label for='formDesc'>Description</label>
<textarea id='formDesc' name='formDesc' class='form-control' rows='5' required aria-required="true"></textarea>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
</div>
<script src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script src="js/bootstrap-dialog.min.js"></script>
<script src="js/main.js"></script>
</body>
</html>
Code: Select all
jQuery(document).ready(function(){
$("form").submit(function (e) {
e.preventDefault();
// Silly validation to make sure form isn't empty
var n = 0;
if( $('#formName').val() == "" ){ n++; }
if( $('#formURL').val() == "" ){ n++; }
if( $('#formDesc').val() == "" ){ n++; }
if( n > 0 ){
dialog('Form Validation', 'Please fill in all fields correctly', BootstrapDialog.TYPE_DANGER);
return false;
}
var nToken = getToken();
if( $('#formToken').length == 0 ){
var t = $('<input>');
t.attr({id:'formToken',name:'token',value:nToken,type:'hidden'});
$("form").append(t);
} else {
$('#formToken').val(nToken);
}
$.ajax({method:'post',url:'req.php',data:$("form").serialize()})
.done(function(data){
if( data.status == "success" ){
dialog('Request', data.response, BootstrapDialog.TYPE_SUCCESS);
}else if( data.status == "Token.SUCCESS" ){
dialog('Request', data.response, BootstrapDialog.TYPE_SUCCESS);
$('#formToken').remove();
$("form")[0].reset();
}else if( data.status == "error" || data.status == 'Token.ERROR' ){
dialog('Request', data.response, BootstrapDialog.TYPE_DANGER);
}
})
.fail(function(data){ dialog('Request', data.response, BootstrapDialog.TYPE_DANGER); });
});
});
var getToken = function(){
var token = '';
$.ajax({method:'get',url:'req.php?gettoken',async:false}).done(function(data){token=data.response;});
return token;
}
var dialog = function(title, body, type){
BootstrapDialog.show({
type: type,
title: title,
message: body,
buttons: [
{ label: 'Close', action: function(dialog){ dialog.close(); } }
]
});
}
Code: Select all
<?php
session_start();
define("_EXEC",true);
require_once 'lib/loader.php';
loader::load('token.php');
$token = new token;
if( isset($_GET['gettoken']) )
{
$obj = new stdClass;
$obj->response = $token->getToken();
$obj->status = 'success';
}
if(isset($_POST) && !empty($_POST)){
$obj = new stdClass;
if(!$token->checkToken()) {
$obj->response = "Invalid Token";
$obj->status = 'Token.ERROR';
} else {
/**
* Now we add the request to the database
*/
if( database result ) {
$obj->response = "Thanks for adding your request for a modpack!";
$obj->status = 'Token.SUCCESS';
} else {
$obj->response = "There was a problem processing your request.";
$obj->response = 'Token.ERROR';
}
}
}
if( empty($obj) ){ die(); }
header('Content-Type: application/json');
echo json_encode($obj);
Code: Select all
<?php
if(!defined("_EXEC")){ die("No Access"); }
class token {
private $token;
private $stName;
public function __construct( )
{
if( !isset( $_POST['token']) ){
$this->createToken();
$newToken = $this->getToken();
}
$this->token = ( isset( $_POST['token']) ) ? $_POST['token'] : $newToken;
}
private function createToken()
{
$token = md5(uniqid(rand(), TRUE));
$this->stName = 'token_'.md5(session_id().$token);
unset($_SESSION['token']);
$this->token = $_SESSION['token'][$this->stName] = $token;
return $token;
}
public function checkToken()
{
$keys = array_keys($_SESSION['token']);
$key = $keys[0];
$return = ($this->token == $_SESSION['token'][$key]) ? true : false;
// Create a new token
$this->createToken();
return $return;
}
public function getToken()
{
$useToken = (empty($_POST['token'])) ? $this->token : $_POST['token'];
return $useToken;
}
}