No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Autenticación vía Access Tokens

13/19
Recursos

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:

  1. Nuestro usuario hace una petición al servidor de autenticación para pedir un token.
  2. El servidor le devuelve el token.
  3. El usuario hace una petición al servidor para pedir recursos de la API.
  4. El servidor con los recursos hace una petición al servidor de autenticación para verificar que el token sea válido.
  5. Una vez verificado el token, el servidor le devuelve los recursos al cliente.

Aportes 39

Preguntas 9

Ordenar por:

Los aportes, preguntas y respuestas son vitales para aprender en comunidad. Regístrate o inicia sesión para participar.

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.

Fuente: https://auth0.com/docs/tokens/access-tokens

Aquí les dejo una imagen del flujo de información, durante la autorización por Access Token

Instalar antes esto, sino falla!

sudo apt install php-curl

Esta clase vale oro, cada segundo es esencial para entender todo este mundo de las api rest.

// SERVIDOR DE AUTENTICACION

$method = strtoupper($_SERVER['REQUEST_METHOD']);

$token = sha1('Esto es secreto!!!'); // Token generado con funcion de encriptamiento sh1 mediante un codigo secreto

if($method === 'POST'){ // Para obtenener token
    // Verificar existencia de los headers
    if(!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 credenciales
    if($clientId !== '1' || $secret !== 'SuperSecreto!'){
        http_response_code(403); // Forbidden
        die('No autorizado');
    }

    echo "$token";
} elseif ($method === 'GET'){ // Para validad token
    // Verificar existencia de token
    if(!array_key_exists('HTTP_X_TOKEN', $_SERVER)){
        http_response_code(400); // Bad Request
        die('Faltan patrametros');
    }
    
    if($_SERVER['HTTP_X_TOKEN'] == $token){
        echo 'true';
    } else {
        echo 'false';
    }
} else {
    echo 'false';    
}```

Si a alguno no le regresa nada, aun haciendo tal cual las cosas que indica el profesor, intenten con esto:

  1. Vayan al archivo php.ini (donde lo hayan instalado) y editen esta linea quitandole el punto y coma.
    ;extension=php_curl.dll

  2. 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.

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

curl localhost:5000/register -X "POST" -H "X-Client-Id: 1" | jq

El token también se podría obtener en https://jwt.io/

Código de mi servidor de autenticación (registro)

app.post('/register', (req, res) => {  
  const uid = req.headers['x-client-id'] || '1';

  // Creamos el token con JsonWebToken
  const 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 headers
  const token = req.headers['x-token'] || '';

  // De esta forma evitamos errores de verificación
  try {
    // Obtenemos el id verificando que el hash coincida con el secret
    const uid = jwt.verify(token, secret);

    // Verificamos si el id obtenido es igual a 1
    if(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

const bookAuthenticator = 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 HTTP
  if(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 HMAC
  else if(req.headers['x-hash']) {
    // Recibe los parametros enviados en req.headers
    // Si no hay se lo deja en blanco
    const 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 SHA1
    const 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 hexadecimal
    if(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 TOKEN
  else if(req.headers['x-token']) {
    await axios({
      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 segundo
  const authUser = authorization[0];
  const authPassword = authorization[1];

  // Verificamos si los datos no coinciden con nuestro usuario hardcodeado
  if( authUser !== "lucneonct" || authPassword !== "1234") {
    // Rompe la cadena y retorna un 403 Forbidden
    return res.status(403).json({ status: 'Forbidden: missing or invalid auth' });
  }

  next()
}

Acceso por CURL

curl localhost:5000/books -H "X-Token: eyJhbGciOiJIUzI1NiJ9.MQ.c_c6EThmu2UPNUlq36-9bQ-0xEinTfNqW1S5rLAUEHU" | jq


Enviar un token incorrecto

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 json
header('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 correcto
switch (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 exista
        if (!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 exista
        if (!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)'

¿Que hay de los JWT?

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: ***'

Luego de unas 4-5 horas por fin lo logre en flask usando python
API FLASK AUTH TOKENS

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

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

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"

Ya aprendimos a publicar una API y diferentes formas de autenticación, ahora veamos como manejar los errores …

De primera no le tenía tanta fe al tema del curso, pero habiendo hecho 12 clases lo encontré genial y ya se me ocurren cosas nuevas, excelente platzi y el profesor que ha explicado super bien 😉

Actuan 3 actores?

le entendí perfecto con el diagrama del flujo de trabajo

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.

Notas por si te está dando problemas:

1-Instala curl como dice en un comentario Juan Pablo De León

2-Ve al archivo php.ini y en la línea “;extension=curl” (viene sin comillas) borra el punto y coma (😉. Esto es para la versión 8.1 de php. Si tienes otra versión, el comentario de Alberto dice cómo viene. Para quitar el ; puedes usar el comando: sudo nano php.ini como viene aquí: https://stackoverflow.com/questions/37587251/edit-and-save-php-ini-over-terminal

3-Asegúrate de correr el servidor del puerto 8001 en el directorio donde tienes guardado el archivo auth_server.php

4-El archivo auth_server.php que viene para descargar viene con una clave por default, por lo que no estás usando realmente un hash. Cambia la clave “5d0937455b6744.68357201” que viene en $token por sha1(‘Esto es secreto!!’)

Espero te sirva!

Hola, aquí un script en Python 3 haciendo uso de flask_jwt_extended para la autentificación con token, me parece que es una libreria sencilla de usar.

import datetime

from flask import Flask, jsonify, request, make_response, render_template
from flask_restful import Api, Resource, abort, reqparse
from flask_jwt_extended import create_access_token
from flask_jwt_extended import jwt_required
from flask_jwt_extended import JWTManager

# Create flask app and configure SECRET_KEY. The SECRET_KEY is required to create the access token
app = Flask(__name__)
app.config['SECRET_KEY'] = 'Super-Secret!'

# Configure the API
api = Api(app)

# Setup the Flask-JWT-Extended extension
jwt = JWTManager(app)

# Database
BOOKS = {
    1: {
        'isbn': '744586',
        'title': 'Cien años de soledad',
        'description': 'Lorem insup lol.',
        'author': 'Gabriel Garcia Marquez'
    },
    2: {
        'isbn': '789456',
        'title': 'De animales a dioses',
        'description': 'Lorem insup lol.',
        'author': 'Yuval Noah Harari'
    },
    3: {
        'isbn': '546398',
        'title': 'La metamorfosis',
        'description': 'Lorem insup lol.',
        'author': 'Franz Kafka'
    }
}

# The parser will split the JSON that you send in your request, according to the types that you add. In this case: 'isbn', 'title', 'description' and ' author' 
book_parser = reqparse.RequestParser()
# help value: what to display to sender if he don't send needed arguments
book_parser.add_argument('isbn', type=str)
book_parser.add_argument('title', type=str, help='title of the book required', required=True)
book_parser.add_argument('description', type=str)
book_parser.add_argument('author', type=str, help='author of the book required', required=True)

# Create a route to authenticate your users and return JWTs. The create_access_token() function is used to actually generate the JWT.
@app.route('/login', methods=["POST"])
def login():
    username = request.json.get("username", None)
    password = request.json.get("password", None)
    if username != "test" or password != "test":
        return jsonify({"message": "Bad username or password"}), 401

    access_token = create_access_token(identity=username, expires_delta=datetime.timedelta(minutes=5))
    return jsonify(access_token=access_token)


def abort_if_book_doesnt_exist(book_id):
    if book_id not in BOOKS:
        abort(404, message=f'The book with the id {book_id} does not exist')


class BookList(Resource):
    def get(self):
        return make_response(jsonify({'Books data': BOOKS}), 200)

    # Protect a route with jwt_required, which will kick out requests without a valid JWT present.
    @jwt_required()
    def post(self):
        args = book_parser.parse_args()
        BOOKS[max(BOOKS.keys()) + 1] = args
        return make_response(jsonify({'message': 'Product added succesfully', 'Books data': BOOKS}), 201)   #201


class Book(Resource):
    def get(self, book_id):
        abort_if_book_doesnt_exist(book_id)
        return make_response(jsonify({'Books data': BOOKS[book_id]}), 200)

    @jwt_required()
    def put(self, book_id):
        abort_if_book_doesnt_exist(book_id)
        args = book_parser.parse_args()
        BOOKS[book_id] = args
        return make_response(jsonify({'message': f'Product id:{book_id} updated', 'Books data': BOOKS}), 200)

    @jwt_required()
    def delete(self, book_id):
        abort_if_book_doesnt_exist(book_id)
        del BOOKS[book_id]
        return make_response(jsonify({'message': f'Product id:{book_id} deleted', 'Books data': BOOKS}), 204)   #204


api.add_resource(BookList, '/books')
api.add_resource(Book, '/books/<int:book_id>')


if __name__ == '__main__':
    app.run(debug=True)

Espero les sirva, lo pueden encontrar en este repositorio: https://github.com/danpeco/API-REST-flask_restful

a

Mi implementacion en Go:

TokenAuth:

func tokenAuth(next http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		token := r.Header.Get("X_TOKEN")
		if token == "" {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}

		url := "http  :/ / l o c a l h o st : 8 0 85"
		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		req.Header.Set("X_TOKEN", token)
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		if resp.StatusCode != http.StatusOK {
			http.Error(w, string(body), resp.StatusCode)
			return
		}

		if string(body) != "OK" {
			http.Error(w, string(body), http.StatusUnauthorized)
			return
		}

		next.ServeHTTP(w, r)

	})
}

Auth Server

package main

import (
	"crypto/sha1"
	"fmt"
	"net/http"
	"time"
)

var Tokens = make(map[string]time.Time)

func main() {
	http.Handle("/", http.HandlerFunc(Auth))

	http.ListenAndServe(":8085", nil)
}

func Auth(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	switch r.Method {
	case http.MethodGet:
		getToken(w, r)
	case http.MethodPost:
		postToken(w, r)
	}

}

func generateToken(uid string) string {
	sha := sha1.New()
	sha.Write([]byte(uid + time.Now().String()))

	token := fmt.Sprintf("%x", sha.Sum(nil))
	return token
}

func postToken(w http.ResponseWriter, r *http.Request) {
	uid := r.Header.Get("X_UID")
	pwd := r.Header.Get("X_PWD")

	if uid == "12" && pwd == "secret" {
		token := generateToken(uid)
		Tokens[token] = time.Now()
		w.Write([]byte(token))
	} else {
		w.WriteHeader(http.StatusUnauthorized)
	}
}

func getToken(w http.ResponseWriter, r *http.Request) {
	token := r.Header.Get("X_TOKEN")

	if token == "" {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	ts, ok := Tokens[token]
	if !ok {
		w.WriteHeader(http.StatusUnauthorized)
		return
	}

	if time.Now().Sub(ts) > (3 * time.Minute) {
		w.WriteHeader(http.StatusUnauthorized)
		return
	} else {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))
	}
}

muy interesante todas las formas que se han mostrado de autenticación

Este tutorial me ayudo para hacerlo en Python

¿Los tokens deben de guardarse en base de datos?

Cómo puedo hacer en el caso que el token es fijo (No cambia, me lo dan para usar) y necesito enviarlo para hacer peticiones a ese API privado, pero quiero usar algún framework como Reactjs, angular o svelte y mostrar los datos o ejecutar acciones desde mi frontend, al finanl es Javascript y sería fácil que encontraran ese token inspeccionando el código, ¿Cómo le doy seguridad a eso y evitar que alguien puede copiarlo?, gracias

Estos tres sistemas de autentificación en realidad pueden ser usado para cualquier servicio WEB

se puede utilizar docker si solo se cuenta con un servidor, es una de las herramientas indispensables hoy en di a y una de las mejores.

Esta como complejo el diagrama de el primer minuto

Apuntes:
Esta es la manera más compleja de autenticación para REST, lo que provee una seguridad más óptima para proteger información. Esta autenticación requiere de dos servidores, uno para la labor de autenticación y el otro para desplegar los recursos API.
curl http://URL:8001 -X “POST” -H “X-Client-Id: 1” -H "X-Secret: SuperSecreto!"
curl http://localhost:8000/books -H ‘X-Token: 9281a90d6e722bf50549f53e2414058dd94c2636’

Autenticación vía Access Tokens


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:

  1. Nuestro usuario hace una petición al servidor de autenticación para pedir un token.
  2. El servidor le devuelve el token.
  3. El usuario hace una petición al servidor para pedir recursos de la API.
  4. El servidor con los recursos hace una petición al servidor de autenticación para verificar que el token sea válido.
  5. Una vez verificado el token, el servidor le devuelve los recursos al cliente.

genial… es muy usado el acceso via token

Para los nuevos que les salga la consola de GitBash con datos extras al momento de hacer la autenticación asi como en mi caso

Pueden probar metiéndole un echo “\n” para que puedan identificar cual es token, así:

Excelente, he implementado este tipo de autenticacion pero en NodeJS

Para instalar php-curl en windows

Vale, practicamente un le enviamos nuestra autenticación a un servidor tercero y el genera un token que nos lo da y es ese token el que le mandamos al servidor de datos