I'm trying out Restler to build web api for my new project.
One of requirements is simple authentication.
I found nice example on SO https://stackoverflow.com/a/7969250/965722 but it's for Restler 2.
Using manual I managed to convert that class to Restler 3.0
<?php
class BasicAuthentication implements iAuthenticate
{
const REALM = 'Restricted API';
public static $currentUser;
public static $requires = 'user';
public static $role = 'user';
public function __isAllowed()
{
if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']))
{
$user = $_SERVER['PHP_AUTH_USER'];
$pass = $_SERVER['PHP_AUTH_PW'];
$user = mysql_real_escape_string($user);
$pass = mysql_real_escape_string($pass);
$roles = array('12345' => 'user', '67890' => 'admin');
if (!isset($pass) || !array_key_exists($pass, $roles))
{
return false;
}
static ::$role = $roles[$pass];
Resources::$accessControlFunction = 'AccessControl::verifyAccess';
return static ::$requires == static ::$role || static ::$role == 'admin';
}
header('WWW-Authenticate: Basic realm="' . self::REALM . '"');
throw new RestException(401, 'Basic Authentication Required');
}
/**
* @access private
*/
public static function verifyAccess(array $m)
{
$requires = isset($m['class']['AccessControl']['properties']['requires']) ? $m['class']['AccessControl']['properties']['requires'] : false;
return $requires ? static ::$role == 'admin' || static ::$role == $requires : true;
}
}
?>
My sample api class looks like so:
<?php
class Api
{
/**
* @url GET
* @url GET hello
* @url GET hello/{to}
*/
function hello($to = 'world')
{
return "Hello $to!";
}
/**
* @access protected
* @class AccessControl {@requires user}
*/
public function user()
{
return "protected api, only user and admin can access";
}
/**
* @access protected
* @class AccessControl {@requires admin}
*/
public function admin()
{
return "protected api, only admin can access";
}
}
any my index.php
<?php
require_once 'vendor/restler.php';
use Luracast\Restler\Restler;
$r = new Restler();
$r->addAPIClass('Api', '');
$r->addAuthenticationClass('BasicAuthentication');
$r->handle();
?>
When I navigate to my webpage standard login and password form shows, but even if I put correct username and password nothing happens - login window reopens.
I'm probably making some silly mistake, but I can't find it. This is my first attempt to basic authentication, so please be kind.
Do I need some special configuration on server?
EDIT:
It looks like I need some special config because my server runs as CGI/FastCGI.
I've tried adding code from this comment: http://php.net/manual/en/features.http-auth.php#106285
but I am unable to configure .htaccess correctly.
This is the default Restler htaccess file:
Options -MultiViews
DirectoryIndex index.php
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^$ index.php [QSA,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
<IfModule mod_php5.c>
php_flag display_errors Off
</IfModule>
and these lines are required by workaround from previous link to work:
RewriteEngine on
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last]
How to combine both?
I don't know why but I had to implement iUseAuthentication
in my BasicAuthentication
class. (there's nothing in docs about it, probably because this is RC release and docs and samples are modified).
This way everything started working.
Also I modified htaccess like so:
Options -MultiViews
DirectoryIndex index.php
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^$ index.php [QSA,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last]
</IfModule>
<IfModule mod_php5.c>
php_flag display_errors Off
</IfModule>
Hope this helps someone :)
EDIT:
I've found my code and I'm posting it here, maybe someone will find it usefull.
<?php
use \Luracast\Restler\iAuthenticate;
use \Luracast\Restler\Resources;
class BasicAuthentication implements iAuthenticate
{
const REALM = 'Restricted API';
public static $requires = 'user';
public static $role = 'user';
public function __isAllowed()
{
//set http auth headers for apache+php-cgi work around
if (isset($_SERVER['HTTP_AUTHORIZATION']) && preg_match('/Basic\s+(.*)$/i', $_SERVER['HTTP_AUTHORIZATION'], $matches))
{
list($name, $password) = explode(':', base64_decode($matches[1]));
$_SERVER['PHP_AUTH_USER'] = strip_tags($name);
$_SERVER['PHP_AUTH_PW'] = strip_tags($password);
}
//set http auth headers for apache+php-cgi work around if variable gets renamed by apache
if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && preg_match('/Basic\s+(.*)$/i', $_SERVER['REDIRECT_HTTP_AUTHORIZATION'], $matches))
{
list($name, $password) = explode(':', base64_decode($matches[1]));
$_SERVER['PHP_AUTH_USER'] = strip_tags($name);
$_SERVER['PHP_AUTH_PW'] = strip_tags($password);
}
if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']))
{
$user = $_SERVER['PHP_AUTH_USER'];
$pass = $_SERVER['PHP_AUTH_PW'];
$roles = array('12345' => 'user', '67890' => 'admin');
if (!isset($pass) || !array_key_exists($pass, $roles))
{
return false;
}
static ::$role = $roles[$pass];
Resources::$accessControlFunction = 'BasicAuthentication::verifyAccess';
$x = static ::$requires == static ::$role || static ::$role == 'admin';
$file = 'a.txt';
$current = file_get_contents($file);
$current .= static ::$requires." ".static::$role . "\n";
file_put_contents($file, $current);
return $x;
}
header('WWW-Authenticate: Basic realm="' . self::REALM . '"');
throw new RestException(401, 'Basic Authentication Required');
}
/**
* @access private
*/
public static function verifyAccess(array $m)
{
$requires = isset($m['class']['BasicAuthentication']['properties']['requires']) ? $m['class']['BasicAuthentication']['properties']['requires'] : false;
$file = 'a.txt';
$current = file_get_contents($file);
$current .= $requires." - ".static::$role . "\n";
file_put_contents($file, $current);
return $requires ? static ::$role == 'admin' || static ::$role == $requires : true;
}
}
?>
and my sample API class:
<?php
class Api implements iUseAuthentication
{
private $_authenticated = false;
/**
* This method will be called first for filter classes and api classes so
* that they can respond accordingly for filer method call and api method
* calls
*
*
* @param bool $isAuthenticated passes true when the authentication is
* done, false otherwise
*
* @return mixed
*/
public function __setAuthenticationStatus($isAuthenticated = false)
{
$this->_authenticated = $isAuthenticated;
}
/**
* @access protected
* @class BasicAuthentication {@requires user}
*/
public function user()
{
return "protected api, only user and admin can access";
}
/**
* @access protected
* @class BasicAuthentication {@requires admin}
*/
public function admin()
{
return "protected api, only admin can access";
}
}
?>