Autenticación con Access Touch: Implementación y Funcionamiento
Resumen
Está forma es la más compleja de todas, pero también es la forma más segura utilizada para información muy sensible. El servidor al que le van a hacer las consultas se va a partir en dos:
Uno se va a encargar específicamente de la autenticación.
El otro se va a encargar de desplegar los recursos de la API.
El flujo de la petición es la siguiente:
Nuestro usuario hace una petición al servidor de autenticación para pedir un token.
El servidor le devuelve el token.
El usuario hace una petición al servidor para pedir recursos de la API.
El servidor con los recursos hace una petición al servidor de autenticación para verificar que el token sea válido.
Una vez verificado el token, el servidor le devuelve los recursos al cliente.
Los tokens de acceso se utilizan en la autenticación basada en tokens para permitir que una aplicación acceda a una API. La aplicación recibe un token de acceso después de que un usuario autentica y autoriza el acceso con éxito, luego pasa el token de acceso como una credencial cuando llama a la API de destino. El token pasado informa a la API que el portador del token ha sido autorizado para acceder a la API y realizar acciones específicas especificadas por el alcance otorgado durante la autorización.
Aquí les dejo una imagen del flujo de información, durante la autorización por Access Token
Muy claro y visual el concepto de la clase. Asi lo comprendi mejor.
Instalar antes esto, sino falla!
sudo apt install php-curl
Gracias, gran aporte. Le hizo falta al curso poder decir que necesitabamos de esto para que funcionara bien.
Gracias Pablo.
Si a alguno no le regresa nada, aun haciendo tal cual las cosas que indica el profesor, intenten con esto:
Vayan al archivo php.ini (donde lo hayan instalado) y editen esta linea quitandole el punto y coma.
;extension=php_curl.dll
Si usan ubuntu en windows, ingresen este comando:
sudo apt-get install php-curl
Reinicien las terminales y luego intenten de nuevo el ejercicio. Me dicen si les funciona.
No me funciona ninguna de las 2.
Podrías ayudarme??
// SERVIDOR DE AUTENTICACION$method =strtoupper($_SERVER['REQUEST_METHOD']);$token =sha1('Esto es secreto!!!');// Token generado con funcion de encriptamiento sh1 mediante un codigo secretoif($method ==='POST'){// Para obtenener token// Verificar existencia de los headersif(!array_key_exists('HTTP_X_CLIENT_ID', $_SERVER)||!array_key_exists('HTTP_X_SECRET', $_SERVER)){die('Faltan parametros');}// Tomar los headers $clientId = $_SERVER['HTTP_X_CLIENT_ID']; $secret = $_SERVER['HTTP_X_SECRET'];// Verificar credencialesif($clientId !=='1'|| $secret !=='SuperSecreto!'){http_response_code(403);// Forbiddendie('No autorizado');} echo "$token";}elseif($method ==='GET'){// Para validad token// Verificar existencia de tokenif(!array_key_exists('HTTP_X_TOKEN', $_SERVER)){http_response_code(400);// Bad Requestdie('Faltan patrametros');}if($_SERVER['HTTP_X_TOKEN']== $token){ echo 'true';}else{ echo 'false';}}else{ echo 'false';}```
Pedido al servidor de autenticación:
$ curl http://localhost:8001 -X 'POST' -H 'X-Client-Id: 1' -H 'X-Secret:SuperSecreto!'
Pedido al servidor de recursos:
$ curl http://localhost:8000/books -H 'X-Token: ***'
Esta clase vale oro, cada segundo es esencial para entender todo este mundo de las api rest.
Archivo con los 3 métodos de autenticación y los comandos de consola:
<?php
/* Autenticacion via HTTP
$user = array_key_exists('PHP_AUTH_USER',$_SERVER) ? $_SERVER['PHP_AUTH_USER'] : '';
$pwd = array_key_exists('PHP_AUTH_PW',$_SERVER) ? $_SERVER['PHP_AUTH_PW'] : '';
if ($user !== 'mauro' || $pwd !== '1234') {
die;
}
*//* Autenticacion via HMAC
if (!array_key_exists('HTTP_X_HASH',$_SERVER) ||
!array_key_exists('HTTP_X_TIMESTAMP',$_SERVER) ||
!array_key_exists('HTTP_X_UID',$_SERVER)) {
die;
}
list($hash, $uid, $timestamp) = [
$_SERVER['HTTP_X_HASH'],
$_SERVER['HTTP_X_UID'],
$_SERVER['HTTP_X_TIMESTAMP']
];
$secret = 'Sh!! No se lo cuentes a nadie!';
$newHash = sha1($uid.$timestamp.$secret);
if ($newHash !== $hash) {
die;
}
*//* Autenticación via Access TOKEN
header( 'Content-Type: application/json' );
if ( !array_key_exists( 'HTTP_X_TOKEN', $_SERVER ) ) {
die;
}
$url = 'https://'.$_SERVER['HTTP_HOST'].'/auth';
$ch = curl_init( $url );
curl_setopt( $ch, CURLOPT_HTTPHEADER, [
"X-Token: {$_SERVER['HTTP_X_TOKEN']}",
]);
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
$ret = curl_exec( $ch );
if ( curl_errno($ch) != 0 ) {
die ( curl_error($ch) );
}
if ( $ret !== 'true' ) {
http_response_code( 403 );
die;
}
*/// Definimos los recursos disponibles$allowedResourceTypes =['books','authors','genres',];// Validamos que el recurso este disponible$resourceType = $_GET['resource_type'];if(!in_array($resourceType,$allowedResourceTypes)){ die;}// Defino los recursos$books =[1=>['titulo'=>'Lo que el viento se llevo','id_autor'=>2,'id_genero'=>2,],2=>['titulo'=>'La Iliada','id_autor'=>1,'id_genero'=>1,],3=>['titulo'=>'La Odisea','id_autor'=>1,'id_genero'=>1,],];// Se indica al cliente que lo que recibirá es un jsonheader('Content-Type: application/json');// Levantamos el id del recurso buscado// utilizando un operador ternario$resourceId =array_key_exists('resource_id', $_GET)? $_GET['resource_id']:'';// Generamos la respuesta asumiendo que el pedido es correctoswitch(strtoupper($_SERVER['REQUEST_METHOD'])){case'GET':if(empty($resourceId)){ echo json_encode($books);}else{if(array_key_exists($resourceId,$books)){ echo json_encode($books[$resourceId]);}}break;case'POST': $json =file_get_contents('php://input'); $books[]=json_decode($json,true);//echo array_keys($books)[count($books)-1];end($books);// move the internal pointer to the end of the array $key =key($books);// fetches the key of the element pointed to by the internal pointer echo json_encode($books[$key]);break;case'PUT':// Validamos que el recurso buscado existaif(!empty($resourceId)&&array_key_exists($resourceId,$books)){// Tomamos la entrada curda $json =file_get_contents('php://input');// Tansformamos el json recibido a un nuevo elemento $books[$resourceId]=json_decode($json,true); echo json_encode($books[$resourceId]);}break;case'DELETE':// Validamos que el recurso buscado existaif(!empty($resourceId)&&array_key_exists($resourceId,$books)){unset($books[$resourceId]); echo json_encode($books);}break;}// Inicio el servidor en la terminal 1, aqui le asignamos el puerto 8000// php -S localhost:8000 server.php// Terminal 2 ejecutar// curl http://localhost:8000 -v// curl http://localhost:8000/\?resource_type\=books// curl http://localhost:8000/\?resource_type\=books | jq// ver la comunicacion a través de los encabezados:// $ curl http://localhost:8000/\?resource_type\=books -v > /dev/null// consulta//$curl "http://localhost:8000?resource_type=books&resource_id=1"// Método POST//curl -X 'POST' http://localhost:8000/books -d '{"titulo":"Nuevo Libro","id_autor":1,"id_genero":2}'// Método Put - el recurso 1 será reemplazado por el libro que estoy creando// $ curl -X 'PUT' http://localhost:8000/books/1 -d '{"titulo": "Nuevo Libro", "id_autor": 1, "id_genero": 2}'// Método Delete// curl -X 'DELETE' http://localhost:8000/books/1// curl http://localhost:8001 -X 'POST' -H 'X-Client-Id: 1' -H 'X-Secret:SuperSecreto!'// curl http://localhost:8000 -H 'X-Token: (token)'
gracias!
Mi intento en javascript
Hice mi servidor internamente, con una ruta post a /register para obtener el token mandando solo el X-Client-Id ya que el secreto lo tendría el servidor.
Para esto usé el token de JsonWebToken. Que facilita mucho este trabajo
app.post('/register',(req, res)=>{const uid = req.headers['x-client-id']||'1';// Creamos el token con JsonWebTokenconst token = jwt.sign(uid, secret);// Retornamos el token res.json({ token });})
Código de mi servidor de autenticación (verificacion de token)
app.get('/login',(req, res)=>{// Obtiene el token de los headersconst token = req.headers['x-token']||'';// De esta forma evitamos errores de verificacióntry{// Obtenemos el id verificando que el hash coincida con el secretconst uid = jwt.verify(token, secret);// Verificamos si el id obtenido es igual a 1if(uid ==="1"){// Devuelve verdadero si coincide res.send({auth:true})}else{// Devuelve falso si no coincide res.send({auth:false})}}catch(e){// Devuelve falso si hay un error res.send({auth:false})}})
Y al final agrego otra excepción a mi middleware de autenticación. Para así poder usar HMAC, HTTP y TOKEN
constbookAuthenticator=async(req, res, next)=>{// Obtiene la autenticación como "username:password" // Y lo convierte en un array como ["username", "password"]let authorization =[];// Verificamos si se envian headers de autenticación HTTPif(req.headers['authorization']){// req.headers.authorization es encodeado en base64, tal como: "Basic bHVjbmVvbmN0OjEyMzQ="// Con un Buffer lo decodeamos: Buffer.from('string','base64') authorization =Buffer// Separamos en dos arreglos, para usar solo el string encodeado// ["Basic", "bHVjbmVvbmN0OjEyMzQ="].from(req.headers.authorization.split(" ")[1],'base64')// Lo transformamos en utf-8// "lucneonct:1234".toString('utf-8')// Lo separamos por el ":" en un array// ["lucneonct", "1234"].split(':');}// Verificamos si se envian headers de autenticación HMACelseif(req.headers['x-hash']){// Recibe los parametros enviados en req.headers// Si no hay se lo deja en blancoconst hash = req.headers['x-hash']||'';const uid = req.headers['x-uid']||'';const timestamp = req.headers['x-timestamp']||'';// Usamos el paquete de crypto que viene con NodeJS para crear el SHA1const hmac = crypto.createHmac('sha1', secret);// Pasamos nuestro UID y TIMESTAMP al método update hmac.update( uid + timestamp );// Comparamos que el hash enviado y el creado sean iguales// Usamos hmac.digest('hex') para que nos muestre el hash en hexadecimalif(hash === hmac.digest('hex')){// Rellenamos nuestro arreglo de autorización para pasar la validación posterior authorization =['lucneonct','1234'];}}// Verificamos si se envian headers de autenticación TOKENelseif(req.headers['x-token']){awaitaxios({method:'GET',url:'localhost:5000/login',headers:{"x-token": req.headers['x-token']}}).then(response=>{if(response.data.auth){ authorization =['lucneonct','1234'];}});}// Obtiene username del primer indice del arreglo y la contraseña del segundoconst authUser = authorization[0];const authPassword = authorization[1];// Verificamos si los datos no coinciden con nuestro usuario hardcodeadoif( authUser !=="lucneonct"|| authPassword !=="1234"){// Rompe la cadena y retorna un 403 Forbiddenreturn res.status(403).json({status:'Forbidden: missing or invalid auth'});}next()}
**¿Cuántos actores intervienen en la comunicación vía Acces Tokens?
**
¿2 (servidor de recursos y el de autenticación)?
¿O 3 (usuario, servidor de recursos, servidor de autenticación?
A mi parecer son 3!
3(cliente, servidor de autenticación y servidor de recursos)
¿Que hay de los JWT?
Creo que tambn cuenta como un método de esta clase.
Tengo la misma duda.
Luego de unas 4-5 horas por fin lo logre en flask usando python
API FLASK AUTH TOKENS
Como es que lo utilizas, bro?
Al intentar usar con curl, me sale que auth de request.authorization siempre esta vacío.
¿Como es que lo mandas con CURL?
Genial😭 desde hace tiempo queria saber como implementar tokens para mi API. Voy a usar JWT para los tokens para que no tenga que estar consultando la base de datos para verificar si el token existe.
Pero aun no habia pensado lo del client y el secret.
El código que adjunta Mauro es un poco diferente al que explican en el video :/
Me parece muy interesante como funcionan los servicios rest full esta forma de aprender el conecto es muy fácil aunque en este curso se debería usar post man para poder consumir los servicios que unos levanta
Quien necesita postman cuando tiene curl
todo muy bien pero como lo puedo usar en producción no creo que el cliente valla a utilizar la consola para obtener los datos
Hola Alejandro!
En este curso te da la idea y las estructuras de como armar y usar una API RESTful (Con ejemplos en la consola de comandos para ver resultados inmediatos y rapidos), cuando entres al mundo backend sabras como implementar esto, por ahora ve tranquilo que estas son las bases (Teoria) para que luego las puedas integrar a tu gusto en varias tecnologias.
Espero y te haya ayudado
Y la otra parte, es que el usuario en caso que sea para la web, pulsara un boton que por dentro tenga una funcion o varias para hacer la conexion con la API, y asi el usuario tiene la interfaz amigable.
En el cmd de windows puse con comillas dobles y me funciono
curl http://localhost:8001 -X "POST" -H "X-Client-id:1" -H "X-Secret:SuperSecreto!"curl http://localhost:8000/books -H "X-Token:5d0937455b6744.68357201"
Tengo el siguiente error, alguien me da una mano?
y en la línea 12 del archivo server.php tengo esto: $ch = curl_init($url);
Comparte captura del error para poder ver que es lo que pasa.
De igual manera en los recursos puedes descargar el código y compararlo a detalle con el que tienes.
Esto podría ser un mismo servidor para las dos cosas o entonces perdería el sentido? lo digo porque yo he visto códigos donde se hace la autenticación se devuelve un token que se graba en la base de datos a modo temporal con caducidad y las siguientes peticiones del usuario son con dicho token, que será valido hasta que caduque.... pero por ejemplo el api es la misma una ruta para los recursos y otra para la autenticación y devolver el token.... en la base de datos lo mismo una tabla donde almacenar los token y en otra tabla los passwords todo esto en la misma base de datos y mismo servidor y todo igual....
Hola, Javier.
Sí, es absolutamente válido y común tener tanto la autenticación como el API en el mismo servidor cuando se utiliza la autenticación mediante tokens de acceso. De hecho, esto puede ser una práctica recomendada en muchos casos, ya que simplifica la gestión y el control de la autenticación y la autorización.
La autenticación mediante tokens de acceso implica que cuando un usuario se autentica correctamente (usualmente proporcionando credenciales como nombre de usuario y contraseña), se le devuelve un token de acceso. Este token se almacena en el cliente (por ejemplo, en una cookie o en el almacenamiento local del navegador) y se incluye en las cabeceras de las solicitudes posteriores para acceder a recursos protegidos en el servidor.
Gracias Axel
Pregunta:
¿Cómo solucionar la respuesta de autenticación servidor auth_server.php ?
Tengo problema con esta clase y realmente llevo dos días trancado.
Problema:
Al realizar la petición php el servidor de validación muestra lo siguiente:
Invalid request (Unsupported SSL request)
;// validación de que el servidor de recursos recibió un tokenif(!array_key_exists('HTTP_X_TOKEN',$_SERVER)){die;}// $url = 'https://' . $_SERVER['HTTP_HOST'] . '/auth';// Se debe validar el token recibido con el servidor,// de autenticación ejecutando una llamada a tráves// de curl.$url='https://localhost:8001';// iniciamos la llamada de curl$ch=curl_init($url);// Se configura curl para enviar// el token y validarlo con el serrvidor// de validación.curl_setopt($ch,CURLOPT_HTTPHEADER,["X-Token: {$_SERVER['HTTP_X_TOKEN']}",]);// Se configura curl nuevamente para recibir la// respuesta sobre el token que enviamos// del servidor de validacióncurl_setopt($ch,CURLOPT_RETURNTRANSFER,true);// Se obtine la respuesta del servidor// de validación$ret=curl_exec($ch);// finalmente se compara la respuesta del servidor// de autenticación, si el resultado no es true// entonces el usuario no ha sido autenticado// correctamenteif($ret!=='true'){http_response_code(403);die;}$books=[1=>['titulo'=>'Lo que el viento se llevo','id_autor'=>2,'id_genero'=>2,],2=>['titulo'=>'La Iliada','id_autor'=>1,'id_genero'=>1,],3=>['titulo'=>'La Odisea','id_autor'=>1,'id_genero'=>1,],];$allowedResourceTypes=['books','authors','genres',];$resourceType=$_GET['resource_type'];if(!in_array($resourceType,$allowedResourceTypes)){header('Status-Code: 400');echojson_encode(['error'=>"Resource type '$resourceType' is un unkown",]);die;}$resourceId=array_key_exists('resource_id',$_GET)?$_GET['resource_id']:'';$method=$_SERVER['REQUEST_METHOD'];switch(strtoupper($method)){case'GET':if("books"!==$resourceType){header('Status-Code: 404');echojson_encode(['error'=>$resourceType.' not yet implemented :(',]);die;}if(!empty($resourceId)){if(array_key_exists($resourceId,$books)){echojson_encode($books[$resourceId]);}else{header('Status-Code: 404');echojson_encode(['error'=>'Book '.$resourceId.' not found :(',]);}}else{echojson_encode($books);}die;break;case'POST':$json=file_get_contents('php://input');$books[]=json_decode($json);echoarray_keys($books)[count($books)-1];break;case'PUT':if(!empty($resourceId)&&array_key_exists($resourceId,$books)){$json=file_get_contents('php://input');$books[$resourceId]=json_decode($json,true);echo$resourceId;}break;case'DELETE':if(!empty($resourceId)&&array_key_exists($resourceId,$books)){unset($books[$resourceId]);}break;default:header('Status-Code: 404');echojson_encode(['error'=>$method.' not yet implemented :(',]);break;}
<?php$method=strtoupper($_SERVER['REQUEST_METHOD']);// $token = "5d0937455b6744.68357201";$token=sha1('Esto es secreto!!');if($method==='POST'){if(!array_key_exists('HTTP_X_CLIENT_ID',$_SERVER)||!array_key_exists('HTTP_X_SECRET',$_SERVER)){http_response_code(400);die('Faltan parametros');}$clientId=$_SERVER['HTTP_X_CLIENT_ID'];$secret=$_SERVER['HTTP_X_SECRET'];if($clientId!=='1'||$secret!=='SuperSecreto!'){http_response_code(403);die("No autorizado");}echo"$token";}elseif($method==='GET'){if(!array_key_exists('HTTP_X_TOKEN',$_SERVER)){http_response_code(400);die('Faltan parametros');}if($_SERVER['HTTP_X_TOKEN']==$token){echo'true';}else{echo'false';}}else{echo'false';}
pasos para replicar el problema
Los códigos anteriormente presentados, están metidos en una sola carpeta. Cada código presentado anteriormente está separado en archivos distintos:
server.php
router.php
auth_server.php
En la terminal de linux se abre una pestaña para cada servidor y petición que se muestra a continuación
a. Se inicia el servidor router
$ php -S localhost:8000 router.php
b. Se inicia el servidor de autenticación
$ php -S localhost:80001 auth_server.php
c. Se realiza la petición al servidor de autenticación para pedir un token, el servidor crea el token y lo devuelvo en la terminal. Es token se debe copiar y enviarlo con la petición GET que se muestra en el siguiente paso
e. finalmente después de la anterior consulta no se muestra nada.
El comportamiento esperado:
Mostrar la lista de libros.
El Comportamiento actual:
No muestra la lista de libros y en la pestaña donde se ejeucta el servidor de autenticación muesta el mensaje de:
[Wed Jun 2 12:42:21 2021] ::1:48248 Invalid request (Unsupported SSL request)
La caracterisitcas de mi de mi sistema
version de php
especificaciones de mi sistema operativo
Quítale la "s" a "https" e inténtalo de nuevo ;)
$url ='https://localhost:8001';
hola @RetaxMaster, nunca he usado la "s" para este tipo de petición. si te das cuenta en todo momento no usa la "s" en http