En el post anterior explicamos como configurar un servidor Oauth 2 para php que nos genere tokens de acceso validos con la intención de securizar nuestra API. En esta segunda parte del post vamos a desarrollar nuestra api apoyándonos en un microframework como Slim Framework y realizando conexiones CURL a nuestro servidor Oauth 2 para que nos valide si los token de accesos que enviamos están autorizados a acceder a la información de la API. Usaremos scopes para controlar los permisos de acceso a todos los métodos de nuestra API, vamos a ello.
/**
* functions.php - Funciones para la conexión CURL que comproborá el token de acceso a la API
* entities.php - Entidades que usaremos al extraer los datos desde la Base de datos
*/
require 'functions.php';
require 'entities.php';
/**
* Autoloading SlimFramewok
*/
require 'vendor/autoload.php';
//
/**
* $pdo - Adaptador para la base de datos usada para almacenar los valores oauth en la BD
* $app - Instanciación de Slim
*/
$pdo = new PDO('mysql:host=localhost;dbname=bdname', 'user', 'password', array(
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'",
));
$app = new \Slim\Slim();
Para conectar a todos los métodos de nuestra api vamos a usar envío POST. A continuación el listado de métodos:
* Listado de usuarios
* url ejemplo: http://miaplicacion/usuarios/
*/
$app->post('/usuarios(/)', function () use ($app, $pdo) {
if ($app->request()->post('access_token')) {
if (curlCheckToken($app->request()->post('access_token') ,'usuarios')) {
$stmt = $pdo->prepare("SELECT * FROM usuarios");
$stmt->execute();
$resultado = $stmt->fetchAll(PDO::FETCH_CLASS, "Usuarios");
if ($resultado) {
echo json_encode($resultado);
}
else {
echo json_encode(array(
'error' => 'No hay registros'
));
}
}
}
});
/**
* Información de un usuario concreto, $id = id del usuario
* Url ejemplo: http://miaplicacion/usuarios/1/
*/
$app->post('/usuarios/:id(/)', function ($id) use ($app, $pdo) {
if ($app->request()->post('access_token')) {
if (curlCheckToken($app->request()->post('access_token') ,'usuario')) {
$stmt = $pdo->prepare("SELECT * FROM usuarios WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$resultado = $stmt->fetchAll(PDO::FETCH_CLASS, "Usuario");
if ($resultado) {
echo json_encode($resultado);
}
else {
echo json_encode(array(
'error' => 'Usuario no encontrado'
));
}
}
}
});
/**
* Listado de mensajes de un usuario, $id = id del usuario
* Url ejemplo: http://miaplicacion/usuarios/1/mensajes/
*/
$app->post('/usuarios/:id/mensajes(/)', function ($id) use ($app, $pdo) {
if ($app->request()->post('access_token')) {
if (curlCheckToken($app->request()->post('access_token') ,'mensajes')) {
$stmt = $pdo->prepare("SELECT * FROM mensajes WHERE usuario_id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$resultado = $stmt->fetchAll(PDO::FETCH_CLASS, "Mensajes");
if ($resultado) {
echo json_encode($resultado);
}
else {
echo json_encode(array(
'error' => 'No hay mensajes de este usuario'
));
}
}
}
});
/**
* Un mensaje de un usuario en concreto via $id = id del usuario y $mensajes = id del mensaje
* Url ejemplo: http://miaplicacion/usuarios/1/mensajes/3/
*/
$app->post('/usuarios/:id/mensajes/:mensaje(/)', function ($id, $mensaje) use ($app, $pdo) {
if ($app->request()->post('access_token')) {
if (curlCheckToken($app->request()->post('access_token') ,'mensaje')) {
$stmt = $pdo->prepare("SELECT * FROM mensajes WHERE usuario_id = :id AND id = :mensaje");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->bindParam(':mensaje', $mensaje, PDO::PARAM_INT);
$stmt->execute();
$resultado = $stmt->fetchAll(PDO::FETCH_CLASS, "Mensaje");
if ($resultado) {
echo json_encode($resultado);
}
else {
echo json_encode(array(
'error' => 'No existe el mensaje solicitado'
));
}
}
}
});
$app->run();
Funcion curl:
<?php
/**
* Realiza la conexion curl para comprobar si el token es valido en la api para el scope solicitado
* $token - Token a comprobar
* $scope - permisos para el usuario. Cada metodo de nuestra api va a tener su propio scope: en este caso los posibles valores son "usuarios", "usuario", "mensajes", "mensaje"
*/
function curlCheckToken($token_value, $scope = null) {
$ch = curl_init();
//Url al servidor Oauth
curl_setopt($ch, CURLOPT_URL, "http://localhost/oauth-demo/response.php");
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, 'access_token=' . $token_value . '&scope=' . urlencode($scope));
//Json response
$raw_data = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw_data);
/**
* Procesamos la respuesta de curlhttp://localhost/api-slimframework/listado/
* Si en el array devuelto se encuentra la clave success con valor true es que hemos tenido exito
* Si no, mostramos los errores que nos devuelve la api
*/
if (isset($data->success) AND $data->success == true) {
return true;
}
else {
echo $raw_data;
}
}
Clases usadas para formatear los datos que se extraigan:
<?php
class Usuarios {
public $usuarios ;
function __construct($usuarios = null) {
$this->usuarios = $usuarios;
}
}
class Usuario {
public $id;
public $nombre;
}
class Mensajes {
public $mensajes ;
function __construct($mensajes = null) {
$this->mensajes = $mensajes;
}
}
class Mensaje {
public $id;
public $texto;
}
Comprobación del token:
<?php
/**
* En este fichero se chequea que el token y el scope, y duelve si es valido o no, y en caso de que no el porqué
*/
require 'vendor/autoload.php';
$dsn = 'mysql:host=localhost;dbname=bdname';
$username = "user";
$password = "password";
$storage = new OAuth2\Storage\Pdo(array(
'dsn' => $dsn,
'username' => $username,
'password' => $password
));
$config = array();
$server = new OAuth2\Server($storage, $config);
$request = OAuth2\Request::createFromGlobals();
$response = new OAuth2\Response();
$scope = $_POST['scope'];
//Aquí es donde comprobamos si con los datos envíados tenemos acceso al recurso
if (!$server->verifyResourceRequest($request, $response, $scope)) {
// En caso de error, se mostrará los detalles aquí
$response->send();
die;
}
//Respuesta en caso de éxito
echo json_encode(array('success' => true));
Como se puede ver, cada uno de los métodos básicamente comprueba mediante CURL que el token de acceso tiene permisos para cada scope, si todo va bien conecta a la base de datos y extrae la info requerida, en caso contrario lanzará un error con una descripción de porque ha fallado( Token expirado, Token sin permisos para ese método o no hay datos disponibles según sea el caso).