I'm building a website where all data interactions are done over ajax other than the initial page load. Now I discovered the need for CSRF tokens.
I elected to deliver the token in a meta tag like so:
<meta name="csrf" content="JI8qMif9gHuD06m/HByCzg==" />
The function that generates the token is as so:
public function csrf()
{
return str_replace('+','',base64_encode(openssl_random_pseudo_bytes(16)));
}
All controllers for ajax extend a parent controller called JsonController
. The JSON controller handles the check of the CSRF token in its constructor (I'll explain why later):
function __construct()
{
$app = \StarterKit\App::getInstance();
//ensure request is ajax
if(!$app->slim->request->isXhr()){
$app->halt(403,'Access Denied to Ajax Resource');
}
//validate csrf token, then update.
if($app->slim->request->isPost()){
$fail = false;
if(!isset($app->post['csrf'])){
$fail = true;
}else{
if($app->post['csrf'] !== $app->session['csrf']){
$fail = true;
}
}
if($fail !== false){
$app->halt(403,'Access Denied');
}
}
if($app->slim->request->isGet()){
$fail = false;
if(!isset($app->get['csrf'])){
$fail = true;
}else{
if($app->get['csrf'] !== $app->session['csrf']){
$fail = true;
}
}
if($fail !== false){
$app->halt(403,'Access Denied');
}
}
$this->app = $app;
}
Now, for the part where I am still unsure of. How should I return the new CSRF token to the client securely? Json controller has a method called __try()
this method's responsibility is to invoke the particular Ajax route (method), capture its response (or an exception) and return a JSON encoded response. It is here in this function that the CSRF token is updated. I assume it is safe to return the CSRF token to the user in the JSON response here?
public function __try($method)
{
try{
if(!method_exists($this,$method)){
throw new \exception('method not found');
}
$msg = call_user_func([$this,$method]);
}
catch(\exception $e){
if($this->app->debug === true){
$err = $e->getMessage().'|'.$e->getFile().'|'.$e->getLine();
}else{
$err = $e->getMessage();
}
$msg = ['error'=>1,'message'=>$err];
}
$msg['csrf'] = $this->app->session['csrf'] = $this->app->csrf();
echo json_encode($msg, JSON_HEX_QUOT | JSON_HEX_TAG);
}
The JavaScript component of the equation here is insignificant. It basically gets/sets the CSRF token and injects it into the request body of any Ajax request (I've built an API wrapper around $.ajax
so this is an automated process).
I've tested it and it works, but I'm dying to know: is this even secure? It seems like it is to me because I tried copying the session ID into Firefox and using it to access the session with no success. I'm curious to know your thoughts.