Creando un Sistema de Autenticación JWT desde Cero en Ruby con Sinatra
Este tutorial paso a paso te guía en la construcción de una API REST segura en Ruby utilizando Sinatra, aplicando autenticación basada en JWT, encriptación de contraseñas con BCrypt y manejo de sesiones sin estado.
🚀 Introducción a la Autenticación basada en Tokens en Ruby
La autenticación es uno de los pilares fundamentales en el desarrollo de aplicaciones web y APIs modernas. A diferencia de las sesiones tradicionales basadas en cookies, los JSON Web Tokens (JWT) ofrecen un enfoque sin estado (stateless) altamente escalable y perfecto para arquitecturas desacopladas.
En este tutorial completo, aprenderemos a construir un sistema de autenticación robusto utilizando Ruby y Sinatra, un framework ligero y rápido ideal para microservicios y APIs. No utilizaremos frameworks mastodónticos como Rails; en su lugar, implementaremos todo desde cero para comprender profundamente cómo funcionan los tokens bajo el capó.
🛠️ Configuración del Entorno y Dependencias
Para dar vida a nuestro proyecto, necesitamos crear un directorio de trabajo y configurar nuestro archivo Gemfile. Las gemas principales que utilizaremos son sinatra para el servidor web, jwt para la codificación y decodificación de tokens, bcrypt para el hash seguro de contraseñas, y sqlite3 junto con sequel para la persistencia de datos.
Crea un nuevo directorio para tu proyecto y añade el siguiente contenido en tu archivo Gemfile:
source 'https://rubygems.org'
gem 'sinatra', '~> 3.0'
gem 'sinatra-contrib', '~> 3.0'
gem 'jwt', '~> 2.7'
gem 'bcrypt', '~> 3.1'
gem 'sqlite3', '~> 1.6'
gem 'sequel', '~> 5.70'
gem 'dotenv', '~> 3.0'
Instala las dependencias ejecutando el siguiente comando en tu terminal:
bundle install
🗄️ Estructura de la Base de Datos y Modelo de Usuario
Antes de escribir la lógica de autenticación, necesitamos una tabla de usuarios para almacenar credenciales de forma segura. Utilizaremos Sequel como ORM ligero para interactuar con SQLite.
Crea un archivo llamado database.rb para inicializar la conexión y definir la estructura de la base de datos:
require 'sequel'
require 'bcrypt'
DB = Sequel.sqlite('development.db')
unless DB.table_exists?(:users)
DB.create_table :users do
primary_key :id
String :email, null: false, unique: true
String :password_hash, null: false
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
end
end
class User < Sequel::Model
plugin :validation_helpers
def password=(new_password)
self.password_hash = BCrypt::Password.create(new_password)
end
def authenticate(attempted_password)
BCrypt::Password.new(password_hash) == attempted_password
end
def validate
super
validates_presence [:email, :password_hash]
validates_unique :email
end
end
🔑 Implementación del Generador y Decodificador de JWT
El núcleo de nuestro sistema de seguridad es la emisión y validación de los tokens JWT. Crearemos una clase de utilidad para gestionar este proceso utilizando una clave secreta.
Crea un archivo llamado auth_token.rb:
require 'jwt'
class AuthToken
SECRET_KEY = ENV.fetch('JWT_SECRET_KEY') { 'super_secret_key_change_me_in_production' }
def self.encode(payload, exp = 24.hours.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, SECRET_KEY)
end
def self.decode(token)
body = JWT.decode(token, SECRET_KEY)[0]
HashWithIndifferentAccess.new(body)
rescue JWT::ExpiredSignature, JWT::VerificationError => e
nil
end
end
dotenv.🌐 Construcción de los Endpoints en Sinatra
Ahora uniremos todas las piezas en nuestra aplicación principal con Sinatra (app.rb). Implementaremos rutas para el registro de usuarios, el inicio de sesión (que genera el token) y una ruta protegida que requiere que el cliente envíe el token en las cabeceras HTTP.
require 'sinatra'
require 'sinatra/json'
require 'dotenv/load'
require_relative 'database'
require_relative 'auth_token'
class AuthApiApp < Sinatra::Base
helpers do
def current_user
auth_header = request.env['HTTP_AUTHORIZATION']
return nil unless auth_header && auth_header.start_with?('Bearer ')
token = auth_header.split(' ').last
decoded_payload = AuthToken.decode(token)
return nil unless decoded_payload
@current_user ||= User[decoded_payload['user_id']]
end
def authenticate!
halt 401, json({ error: 'No autorizado. Token faltante o inválido.' }) unless current_user
end
end
post '/api/v1/register' do
data = JSON.parse(request.body.read)
user = User.new(email: data['email'])
user.password = data['password']
if user.save
status 201
json({ message: 'Usuario registrado exitosamente', user_id: user.id })
else
status 422
json({ errors: user.errors })
end
end
post '/api/v1/login' do
data = JSON.parse(request.body.read)
user = User.where(email: data['email']).first
if user && user.authenticate(data['password'])
token = AuthToken.encode(user_id: user.id)
json({ message: 'Login exitoso', token: token })
else
status 401
json({ error: 'Credenciales inválidas' })
end
end
get '/api/v1/profile' do
authenticate!
json({
id: current_user.id,
email: current_user.email,
created_at: current_user.created_at
})
end
end
🧪 Pruebas de la API con cURL
Es momento de probar nuestro sistema de autenticación en funcionamiento. Inicia el servidor ejecutando:
ruby app.rb
1. Registrar un nuevo usuario
curl -X POST http://localhost:4567/api/v1/register \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com", "password": "mi_password_seguro"}'
2. Iniciar sesión para obtener el token
curl -X POST http://localhost:4567/api/v1/login \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com", "password": "mi_password_seguro"}'
La respuesta devolverá un JSON conteniendo tu token JWT. Cópialo y utilízalo en el siguiente paso.
3. Acceder a la ruta protegida
curl -X GET http://localhost:4567/api/v1/profile \
-H "Authorization: Bearer <PEGA_TU_TOKEN_AQUI>"
401 Unauthorized.📋 Resumen de Buenas Prácticas
Para mantener tu sistema de autenticación seguro en entornos de producción, ten en cuenta las siguientes recomendaciones:
¿Qué hacer si necesito revocar un token JWT?
Dado que los JWT son sin estado, no se pueden invalidar directamente en el servidor antes de su expiración a menos que implementes una lista negra (blacklist) utilizando una base de datos en memoria como Redis.🎉 Conclusión
¡Felicidades! Avanzado Has construido exitosamente un sistema de autenticación JWT completamente funcional desde cero en Ruby utilizando Sinatra. Este enfoque te otorga control absoluto sobre cada capa de seguridad de tu aplicación sin la complejidad innecesaria de frameworks monolíticos.
Tutoriales relacionados
- ¡Explorando los Mixins en Ruby con `include` y `extend`! Reutilización de Código sin Herenciaintermediate20 min
- ¡Maestría en Procesamiento de Eventos! Explorando los Hooks de Observadores en Ruby para Arquitecturas Reactivasintermediate15 min
- Optimización del Rendimiento en Aplicaciones Ruby: Estrategias y Herramientas Esencialesintermediate15 min
- ¡Maestría en Gestión de Errores! Explorando las Excepciones en Ruby para un Código Robustointermediate15 min
- Manipulación Avanzada de Colecciones en Ruby: Patrones de Transformaadvanced8 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!