WEB3DEV Español

Cover image for Rust Story: Generador de Token de Autenticación Simple en Rust usando Tokio
Hector
Hector

Posted on

Rust Story: Generador de Token de Autenticación Simple en Rust usando Tokio

En el reino digital donde la seguridad es vital y el tiempo es esencial, administrar el acceso de tokens de forma efectiva se vuelve un arte así como ciencia. Mira un mundo donde cada apretón de mano está gobernado por un código de acceso con límite de tiempo, un token que tiene un código de acceso con límite de tiempo, un token que mantiene la clave para comunicaciones seguras y autorizadas. Este es el cuento de tales tokens, su intrincada danza con el tiempo y los engranajes de Rusty que mantienen que esta danza se mantenga en perfecta harmonía.

Nuestra historia se desarrolla en el escenario del lado del servidor, un lugar donde la confianza es vital y cada interacción es examinada. Aquí, un servidor debe enviar una respuesta al cliente con algo especial: un token de autenticación. Pero este token no es una clave ordinaria; es similar al carruaje de Cenicienta, mágico pero limitado al tiempo. Mientras el reloj avanza y llega a la marca de los 20 minutos el token, como el carruaje, se transforma, asegurando que ningún token caducado sobrepase su bienvenida.

En esta danza de tokens, nos iremos a Rust, el lenguaje conocido por su robustez y confiabilidad, para coreografiar nuestros pasos. ¿Nuestras herramientas? Tokio para crear tareas que mantienen un ojo en el reloj, y Axum para construir un servidor, el escenario para nuestra performance.

Imagina un nuevo proyecto, token_manager, nuestro bastidor, donde la magia del código se vuelve en una performance. Aquí, las dependencias como Axum y Tokio son los tramoyistas que establecen la escena para nuestra presentación.

Nuestro script está escrito en routes.rs, donde AuthToken y TokenManager toman los roles principales. AuthToken es un personaje meticuloso, siempre consciente del reloj, mientras que TokenManager es la mente maestra, asegurando que los tokens son frescos y que la performance sea fluida.

Mientras nuestra historia continúa, TokenManager viene a la vida, malabareando AuthTokens con precisión. Es un espectáculo de tareas asíncronas, donde los tokens son generados, monitoreados y refrescados, todos en una danza rítmica, orquestradas por los ticks de Tokio.

Nuestro escenario se establece con lazy_static!, asegurando que nuestro TokenManager siempre esté listo, siempre alerta. El tiempo de expiración por defecto y la cuenta de los ticks son como el tiempo de nuestra música, guiando el ritmo de nuestra performance.

En el gran final, nuestro main.rs, la cortina se eleva en el servidor Axum. Las rutas son expuestas como rutas en nuestro escenario, cada una lleva a un diferente acto del cuento de nuestro token. Como la audiencia, puedes ver la generación del token, una maravilla en sí mismo, refrescado cada 5 segundos, un testamento a la precisión de nuestro código.

Mientras navegas http://localhost:3000, no sólo estás accesando una ruta: estás entrando en un mundo donde los tokens bailan al ritmo de Rust, donde la seguridad es la performance y donde cada expiración del token es una señal para que el próximo tome el escenario.

En este mundo, another_route es tu ventana al show en curso, un vistazo detrás de los bastidores del token de nuestro teatro. Aquí, puedes ver el token activo, una estrella por derecho propio, realizando su rol sin problemas hasta que llega la hora de pasar la batuta.

Y así, nuestra historia de la administración del token en Rust concluye, pero el performance realmente nunca termina. Es un ciclo, un bucle de seguridad y eficiencia continuo , un ballet de bytes y tokens, todos haciendo sus partes en el vasto teatro digital. Bienvenido al mundo de la administración de token en Rust: segura, eficiente y siempre en tiempo.

Vamos a crear un nuevo proyecto token_manager y actualizar el toml como a continuación:

[package]
name = "token_manager"
version = "0.1.0"
edition = "2021"

# Ve más claves y sus definiciones en https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = "0.6.20"
hyper = { version = "0.14.27", features = ["full"] }
chrono = {version = "0.4.31" }

tokio = { version = "1.34.0", features = ["full"] }
lazy_static = "1.4.0"
Enter fullscreen mode Exit fullscreen mode

Crea una caja (crate) routes.rs el cual tendrá la estructura del token y la estructura token_manager para exponer el monitoreo de la API del token como sigue:

//routes.rs
use chrono::{DateTime, Utc};
use std::sync::{Arc, RwLock};
use tokio::time::{interval, Duration};

lazy_static! {
   pub static ref GLOBAL_TOKEN_MANAGER: TokenManager = TokenManager::new(5, 1);
}

struct AuthToken {
   token: String,
   exp_time: DateTime<Utc>,
   expire_seconds: i64,
}

impl AuthToken {
   pub fn new(expire_seconds: i64) -> Self {
       AuthToken {
           token: String::new(),
           exp_time: Utc::now() + chrono::Duration::seconds(expire_seconds),
           expire_seconds,
       }
   }

   pub async fn update_token(&mut self) {
       println!("Generating new token!");
       self.token = AuthToken::generate_secure_token().await;
       self.exp_time = Utc::now() + chrono::Duration::seconds(self.expire_seconds);
   }

   pub fn refresh_token(&mut self, token: &str) {
       println!("Generating new token!");
       self.token = token.to_owned();
       self.exp_time = Utc::now() + chrono::Duration::seconds(self.expire_seconds);
   }

   pub fn is_expired(&self) -> bool {
       Utc::now() > self.exp_time
   }

   pub fn get_token(&self) -> String {
       self.token.clone()
   }

   pub async fn generate_secure_token() -> String {
       let mut interval = interval(Duration::from_secs(1));
       interval.tick().await;
       Utc::now().to_string()
   }
}

pub struct TokenManager {
   auth_token: Arc<RwLock<AuthToken>>,
   tick_interval_secs: u64,
}

impl TokenManager {
   pub fn new(expire_seconds: i64, tick_interval_secs: u64) -> Self {
       Self {
           auth_token: Arc::new(RwLock::new(AuthToken::new(expire_seconds))),
           tick_interval_secs,
       }
   }

   pub fn fetch_token(&self) -> Option<String> {
       let token = self.auth_token.clone();
       let token_string = match token.read() {
           Ok(t) => Some(t.get_token()),
           Err(e) => {
               eprintln!("Failed to acquire lock: {}", e);
               None
           }
       };
       token_string
   }

   pub async fn refresh_token(&self) {
       let token_ref = self.auth_token.clone();
       let new_token = AuthToken::generate_secure_token().await;
       match token_ref.write() {
           Ok(mut t) => t.refresh_token(&new_token),
           Err(e) => {
               eprintln!("Failed to acquire lock: {}", e);
           }
       };
   }

   pub async fn generate_tokens(&self) -> tokio::task::JoinHandle<()> {
       let token_ref = self.auth_token.clone();
       let interval_secs = self.tick_interval_secs;
       tokio::spawn(async move {
           let mut interval = interval(Duration::from_secs(interval_secs));
           loop {
               interval.tick().await;

               // Revisa si el token necesita ser refrescado
               let needs_refresh = {
                   let token = match token_ref.read() {
                       Ok(t) => t,
                       Err(e) => {
                           eprintln!("Failed to acquire lock: {}", e);
                           continue;
                       }
                   };
                   token.is_expired()
               };

               // Refresca el token si es necesario
               if needs_refresh {
                   let new_token = AuthToken::generate_secure_token().await;
                   let mut token = match token_ref.write() {
                       Ok(t) => t,
                       Err(e) => {
                           eprintln!("Failed to acquire lock: {}", e);
                           continue;
                       }
                   };
                   token.refresh_token(&new_token);
               }
           }
       })
   }
}
Enter fullscreen mode Exit fullscreen mode

La estructura AuthToken tiene los siguientes atributos:

struct AuthToken {
token: String, // esto contendrá los tokens reales
exp_time: DateTime<Utc>, // esto mantendrá el tiempo de expiración del token actual activo
expire_seconds: i64,// esto mantendrá información del tiempo de expiración de un token
}
Enter fullscreen mode Exit fullscreen mode

Ahora crea un TokenManager, el cual contendrá AuthToken, RwLock y la cuenta de los ticks para que esperen antes de revisar el tiempo de expiración del token para un token activo.

pub struct TokenManager {
   auth_token: Arc<RwLock<AuthToken>>, // la estructura del objeto del token
   tick_interval_secs: u64,// período de espera para revisar el tiempo de expiración de un token activo
}
Enter fullscreen mode Exit fullscreen mode

TokenManager tendrá el método de ayuda pública para generar y buscar el token, cuando sea necesario. Es importante entender cómo el método generate_token funciona, basado en nuestro requisito.

generate_token hace aparecer una tarea tokio asincrónica, y hace una llamada de lectura al objeto auth_token para revisar si el token está expirado o no. Si el token está expirado, entonces el valor de retorno verdadero de la etiqueta need_refresh y una vez que la marca need_refresh sea puesta como verdadera, escribir el bloqueo adquirido en auth_token y el nuevo token será generado antes del método de llamada refresh_token.

pub async fn generate_tokens(&self) -> tokio::task::JoinHandle<()> {
       let token_ref = self.auth_token.clone();
       let interval_secs = self.tick_interval_secs;
       tokio::spawn(async move {
           let mut interval = interval(Duration::from_secs(interval_secs));
           loop {
               interval.tick().await;

               // Revisa si el token necesita ser refrescado
               let needs_refresh = {
                   let token = match token_ref.read() {
                       Ok(t) => t,
                       Err(e) => {
                           eprintln!("Failed to acquire lock: {}", e);
                           continue;
                       }
                   };
                   token.is_expired()
               };

               // Refresca el token de ser necesario
               if needs_refresh {
                   let new_token = AuthToken::generate_secure_token().await;
                   let mut token = match token_ref.write() {
                       Ok(t) => t,
                       Err(e) => {
                           eprintln!("Failed to acquire lock: {}", e);
                           continue;
                       }
                   };
                   token.refresh_token(&new_token);
               }
           }
       })
   }
}
Enter fullscreen mode Exit fullscreen mode

Vamos a crear una variable estática para inicializar y acceder TokenManger globalmente.

lazy_static! {
   pub static ref GLOBAL_TOKEN_MANAGER: TokenManager = TokenManager::new(5, 1);
}
Enter fullscreen mode Exit fullscreen mode

Podemos establecer por defecto expiration_time y tick_count.

Ahora, vamos a crear una función principal para hacer el demo del requisito:

//main.rs

use axum::{routing::get, Router};

// usa axum_server::Server;
#[macro_use]
extern crate lazy_static;

mod routes;
use crate::routes::route;


async fn token_logic() -> tokio::task::JoinHandle<()> {
   route::GLOBAL_TOKEN_MANAGER.refresh_token().await;
   let handle = route::GLOBAL_TOKEN_MANAGER.generate_tokens().await;

   handle
}

pub async fn another_route() -> String {
   if let Some(token) = route::GLOBAL_TOKEN_MANAGER.fetch_token() {
       String::from(token)
   } else {
       String::new()
   }
   // "This is another route."
}

#[tokio::main]
async fn main() {
   // construye nuestra aplicación con una ruta simple
   let app = Router::new()
       .route("/", get(|| async { "Hello, World!" }))
       .route("/another", axum::routing::get(another_route));

   let handle = token_logic().await;

   // ejecútalo con hyper en localhost:3000
   axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
       .serve(app.into_make_service())
       .await
       .unwrap();
   // handle.abort();
   // println!("token aborted");
}
Enter fullscreen mode Exit fullscreen mode

En main, vamos a crear una app el cual generará el token y comenzar el servidor axum con las rutas expuestas, si ejecutas http://localhost:3000/ en tu navegador, verás que la generación del token comenzó y el token se refresca cada 5 segundos, luego de revisar el tiempo de expiración del tiempo del token generado, de forma continua, cada 1 segundo.

verás el valor del token activo accediendo a la ruta “another_route” http://localhost:3000/another_route

Puedes actualizar generate_secure_token logic para crear el token basado en tus requisitos.

¡Así es como podemos crear una aplicación de acceso al token en Rust!

Este artículo es una traducción de Shobhit Chaturvedi, hecha por Héctor Botero. Puedes encontrar el artículo original aquí.
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_es en Twitter.

Discussion (0)