Back to flin
flin

Autenticacion JWT en 3 lineas de FLIN

Como las funciones JWT integradas de FLIN -- create_token, verify_token, refresh_token -- reducen la autenticacion basada en tokens a tres lineas de codigo con valores seguros por defecto.

Thales & Claude | March 30, 2026 8 min flin
EN/ FR/ ES
flinrust

Los JSON Web Tokens se han convertido en el estandar para la autenticacion sin estado en APIs web. El concepto es simple: crear un token firmado que contenga la identidad del usuario, enviarlo al cliente y verificarlo en cada solicitud. La implementacion, en la mayoria de los frameworks, es cualquier cosa menos simple.

En una aplicacion tipica de Node.js, la autenticacion JWT requiere instalar jsonwebtoken, express-jwt y jwks-rsa. Se genera una clave secreta, se configuran las opciones de firma, se escribe middleware para extraer y verificar tokens, se manejan tokens expirados, se implementa la logica de renovacion y se gestiona la revocacion de tokens. El concepto "simple" se convierte en mas de 200 lineas de configuracion y codigo repetitivo repartidas en multiples archivos.

FLIN reduce esto a tres funciones integradas:

flin// Crear un token
token = create_token(user, { expires: "7d" })

// Verificar un token
claims = verify_token(token)

// Renovar un token
new_token = refresh_token(old_token)

Tres lineas. Tres funciones. El algoritmo de firma, la gestion de claves secretas, el manejo de expiracion y la estructura de claims son manejados por el runtime.

Creacion de tokens

La funcion create_token() genera un JWT firmado:

flin// Minimo: solo la entidad de usuario
token = create_token(user)
// Expiracion por defecto: 24 horas

// Con expiracion personalizada
token = create_token(user, { expires: "7d" })

// Con claims personalizados
token = create_token(user, {
    expires: "30d",
    claims: {
        role: user.role,
        org_id: user.org_id,
        plan: user.plan
    }
})

El token generado contiene claims JWT estandar:

json{
    "sub": "42",
    "email": "[email protected]",
    "name": "Thales Gnimavo",
    "role": "admin",
    "iat": 1711411200,
    "exp": 1712016000,
    "iss": "flin"
}

El claim sub (sujeto) es siempre el ID del usuario. Los claims iat (emitido en) y exp (expiracion) se establecen automaticamente. Los claims personalizados de la opcion claims se fusionan en el payload.

Verificacion de tokens

La funcion verify_token() decodifica y valida un JWT:

flinclaims = verify_token(token)
// Retorna los claims decodificados si es valido, none si es invalido

if claims == none {
    return error(401, "Invalid or expired token")
}

user_id = claims.sub
user_role = claims.role

Verificaciones realizadas:

  1. La firma es valida (el token no fue manipulado).
  2. El token no ha expirado (exp esta en el futuro).
  3. El token fue emitido por esta aplicacion (iss coincide).
  4. La estructura del token es valida (tres segmentos codificados en Base64).

Si alguna verificacion falla, verify_token retorna none. No hay excepcion que capturar, no hay codigo de error que decodificar. O el token es valido y se obtienen los claims, o no lo es y se obtiene none.

El patron de middleware de autenticacion

El uso mas comun de JWT en FLIN es un middleware de autenticacion que extrae el token de la cabecera Authorization:

flin// app/api/_middleware.flin

middleware {
    auth_header = headers["Authorization"]

    if auth_header != "" && auth_header.starts_with("Bearer ") {
        token = auth_header.slice(7)
        claims = verify_token(token)

        if claims != none {
            request.user = claims
            request.user_id = to_int(claims.sub)
        }
    }

    next()
}

Este middleware no rechaza solicitudes no autenticadas -- simplemente adjunta la informacion del usuario si hay un token valido presente. Los guards a nivel de ruta manejan el rechazo:

flin// app/api/profile.flin

guard auth    // Rechaza si request.user no esta establecido

route GET {
    User.find(request.user_id)
}

Esta separacion significa que los endpoints publicos (como listar productos) funcionan sin autenticacion, mientras que los endpoints protegidos (como ver un perfil) estan protegidos explicitamente.

El flujo completo de inicio de sesion

Aqui hay un endpoint completo de inicio de sesion basado en JWT:

flin// app/api/auth/login.flin

guard rate_limit(5, 60)    // 5 intentos por minuto

route POST {
    validate {
        email: text @required @email
        password: text @required
    }

    user = User.where(email == body.email).first

    // Mismo error para "no encontrado" y "contrasena incorrecta"
    if user == none || !verify_password(body.password, user.password) {
        return error(401, "Invalid credentials")
    }

    // Verificar bloqueo de cuenta
    if user.locked_until != none && user.locked_until > now {
        return error(423, "Account locked. Try again later.")
    }

    // Reiniciar intentos de inicio de sesion en caso de exito
    user.login_attempts = 0
    save user

    // Crear tokens
    access_token = create_token(user, {
        expires: "15m",
        claims: { role: user.role }
    })

    refresh_token_val = create_token(user, {
        expires: "7d",
        claims: { type: "refresh" }
    })

    {
        access_token: access_token,
        refresh_token: refresh_token_val,
        expires_in: 900,
        user: {
            id: user.id,
            name: user.name,
            email: user.email,
            role: user.role
        }
    }
}

La respuesta contiene tanto un token de acceso (de corta duracion, 15 minutos) como un token de renovacion (de larga duracion, 7 dias). El token de acceso se envia con cada solicitud API. Cuando expira, el cliente usa el token de renovacion para obtener un nuevo token de acceso.

Renovacion de tokens

La funcion refresh_token() valida un token existente y emite uno nuevo con una expiracion fresca:

flin// app/api/auth/refresh.flin

route POST {
    validate {
        refresh_token: text @required
    }

    claims = verify_token(body.refresh_token)
    if claims == none {
        return error(401, "Invalid refresh token")
    }

    if claims.type != "refresh" {
        return error(401, "Not a refresh token")
    }

    user = User.find(to_int(claims.sub))
    if user == none {
        return error(401, "User not found")
    }

    new_access = create_token(user, {
        expires: "15m",
        claims: { role: user.role }
    })

    new_refresh = create_token(user, {
        expires: "7d",
        claims: { type: "refresh" }
    })

    {
        access_token: new_access,
        refresh_token: new_refresh,
        expires_in: 900
    }
}

El endpoint de renovacion valida el token de renovacion, verifica que sea efectivamente un token de renovacion (no un token de acceso reutilizado), busca al usuario y emite tokens frescos. Si el usuario ha sido eliminado o desactivado desde que se emitio el token de renovacion, la renovacion falla.

Firma y gestion de secretos

FLIN usa HMAC-SHA256 para la firma de tokens por defecto. El secreto de firma se deriva de la variable de entorno JWT_SECRET de la aplicacion:

flin// .env
JWT_SECRET=a-very-long-random-string-at-least-256-bits

Si JWT_SECRET no esta establecido, FLIN genera un secreto aleatorio al inicio y registra una advertencia. Esto significa que el desarrollo funciona sin configuracion, pero la produccion requiere un secreto explicito.

La implementacion en Rust:

rustuse hmac::{Hmac, Mac};
use sha2::Sha256;

type HmacSha256 = Hmac<Sha256>;

fn sign_token(header: &str, payload: &str, secret: &[u8]) -> String {
    let message = format!("{}.{}", header, payload);
    let mut mac = HmacSha256::new_from_slice(secret)
        .expect("HMAC can take key of any size");
    mac.update(message.as_bytes());
    let signature = mac.finalize().into_bytes();
    base64url_encode(&signature)
}

fn verify_signature(token: &str, secret: &[u8]) -> bool {
    let parts: Vec<&str> = token.splitn(3, '.').collect();
    if parts.len() != 3 { return false; }

    let expected = sign_token(parts[0], parts[1], secret);
    constant_time_eq(parts[2].as_bytes(), expected.as_bytes())
}

La funcion constant_time_eq previene ataques de temporizado en la verificacion de firmas. Un atacante no puede determinar cuantos bytes de su firma falsificada son correctos midiendo el tiempo de respuesta.

Autenticacion basada en sesion vs. basada en tokens

FLIN soporta tanto autenticacion basada en sesion como basada en tokens. La eleccion depende de la aplicacion:

Basada en sesion (basada en cookies) es mejor para aplicaciones web tradicionales donde el frontend y el backend estan en el mismo dominio. El sistema de sesiones de FLIN maneja esto automaticamente a traves del objeto session.

Basada en tokens (JWT) es mejor para APIs consumidas por aplicaciones moviles, SPAs en diferentes dominios o integraciones de terceros. Las funciones JWT de FLIN manejan esto.

Muchas aplicaciones FLIN usan ambas: basada en sesion para la interfaz web y JWT para la API. El middleware puede verificar ambas:

flinmiddleware {
    // Verificar sesion primero
    if session.user != "" {
        user = User.where(email == session.user).first
        if user != none {
            request.user = user
        }
    }

    // Verificar JWT si no hay sesion
    if request.user == none {
        auth = headers["Authorization"]
        if auth != "" && auth.starts_with("Bearer ") {
            claims = verify_token(auth.slice(7))
            if claims != none {
                request.user = claims
                request.user_id = to_int(claims.sub)
            }
        }
    }

    next()
}

Garantias de seguridad

La implementacion JWT de FLIN proporciona varias garantias que no son opcionales:

Sin tokens sin firmar. No existe una opcion algorithm: "none". Cada token esta firmado.

Sin algoritmos debiles. El algoritmo minimo de firma es HMAC-SHA256. No hay MD5, no hay SHA-1, no hay RSA con menos de 2048 bits.

Expiracion obligatoria. Cada token tiene un tiempo de expiracion. Si no se especifica uno, se usa el predeterminado (24 horas). No hay forma de crear un token que nunca expire.

Verificacion de tiempo constante. La comparacion de firmas siempre toma la misma cantidad de tiempo, independientemente de cuantos bytes coincidan.

Validacion automatica de claims. La funcion verify_token verifica exp, iss y la validez de la firma. No se puede olvidar verificar la expiracion.

Estas garantias significan que un desarrollador FLIN que use create_token y verify_token obtiene autenticacion JWT segura sin necesitar entender las implicaciones de seguridad de la seleccion de algoritmos, ataques de temporizado o validacion de tokens.

En el proximo articulo, cubrimos la limitacion de tasa y las cabeceras de seguridad automaticas -- las protecciones invisibles que endurecen cada aplicacion FLIN contra el abuso y los ataques web comunes.


Esta es la Parte 108 de la serie "Como construimos FLIN", que documenta como un CEO en Abidjan y un CTO de IA disenaron y construyeron un lenguaje de programacion desde cero.

Navegacion de la serie: - [107] Hashing de contrasenas con Argon2 integrado en FLIN - [108] Autenticacion JWT en 3 lineas de FLIN (estas aqui) - [109] Limitacion de tasa y cabeceras de seguridad - [110] Autenticacion de dos factores (TOTP)

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles