Rails sostenible (IX): autenticación y APIs
Novena entrega de la serie Rails sostenible, sobre el libro de David Bryant Copeland. Penúltima parada. Tiempo de lectura estimado: 12 minutos.
Entramos en la tercera parte del libro, "más allá de Rails", donde Copeland aborda territorios que toda app real acaba necesitando. Hoy, dos: cómo gestionar quién entra (autenticación y autorización) y cómo exponer una API sin que se convierta en una fuente perpetua de carrying cost. En ambos casos, su consejo se resume en una palabra que ya conocéis bien a estas alturas de la serie: simplicidad.
Autenticación: en la duda, Devise u OmniAuth
La autenticación es uno de esos problemas que parecen sencillos y están plagados de minas: hashing de contraseñas, tokens de recuperación, sesiones, timing attacks, confirmación por email... Copeland es directo: a menos que tengas una razón de peso, no escribas tu propio sistema de autenticación. Usa algo probado en batalla.
- Devise para autenticación clásica con email y contraseña. Lleva más de una década recibiendo escrutinio de seguridad de miles de aplicaciones.
- OmniAuth cuando quieras delegar en terceros ("entra con Google/GitHub").
El argumento es de carrying cost y de riesgo: un bug en tu sistema de autenticación casero no es un bug cualquiera, es una brecha de seguridad. El coste de mantener seguro tu propio código de auth a lo largo de los años es enorme, y el coste de equivocarte, catastrófico. Apoyarte en una solución madura traslada ese coste a un proyecto con muchos más ojos encima.
(Apunte de 2026: Rails moderno incluye ya un generador de autenticación básico propio, una alternativa razonable cuando tus necesidades son simples. El principio de Copeland se mantiene: parte de algo probado, no reinventes la criptografía.)
Autenticación no es autorización
Una distinción que mucha gente confunde y que el libro separa con cuidado:
- Autenticación: quién eres. Demostrar tu identidad (login).
- Autorización: qué puedes hacer. Decidir si tienes permiso para una acción concreta.
Son dos problemas distintos y conviene resolverlos por separado. Para la autorización, Copeland habla de controles de acceso basados en roles (RBAC): el usuario tiene uno o varios roles, y los roles determinan permisos. Lo importante desde la sostenibilidad es que la lógica de autorización sea explícita y esté en un sitio claro —a menudo encaja en el seam de negocio que montamos en la tercera entrega, porque "¿puede este usuario cancelar este pedido?" suele ser una regla de dominio, no un detalle de framework.

Testea los controles de acceso en system tests
Y aquí un consejo que vale oro y que casi nadie aplica del todo: testea los controles de acceso en system tests. No basta con comprobar que un admin puede entrar; hay que comprobar que un usuario sin permiso NO puede. Los agujeros de autorización casi siempre están en el camino negativo —la ruta que nadie probó porque "obviamente" estaba protegida.
test "un usuario normal no puede ver el panel de admin" do
sign_in users(:normal)
visit admin_dashboard_path
assert_text "No tienes permiso"
assert_no_text "Panel de administración"
end
Ese test, multiplicado por cada recurso sensible, es lo que convierte la seguridad de "creo que está protegido" en "sé que está protegido".
APIs: ten claro para qué —y para quién— son
El capítulo de APIs empieza preguntando lo que casi nadie se pregunta: ¿para quién es esta API? No es lo mismo una API interna que consume tu propio frontend, que una API para un puñado de socios conocidos, que una API pública para miles de terceros desconocidos. El público determina cuánto rigor necesitas en versionado, documentación, estabilidad y autenticación. Diseñar una API pública blindada para algo que solo consume tu app es carrying cost gratuito.
Escribe la API como escribes todo lo demás
La idea liberadora: una API no es especial. Es otra clase frontera. Recibe una petición, la traduce, delega en la misma lógica de negocio del seam, y renderiza —JSON en vez de HTML—. No necesita su propia arquitectura paralela ni una capa de servicios distinta. El mismo Orders.new.cancel(...) que usa tu controlador web sirve a tu endpoint de API.
module Api
module V1
class OrdersController < Api::V1::BaseController
def show
order = Order.find(params[:id])
render json: order.as_json(only: [:id, :status, :total_cents])
end
end
end
end
Decisiones concretas que ahorran carrying cost
El capítulo es una colección de consejos pragmáticos, casi reglas de pulgar, todos orientados a no complicarse:
- La autenticación más simple que puedas. Para muchísimas APIs, un token Bearer en la cabecera
Authorizationes más que suficiente. No montes OAuth2 con todos sus flujos si no lo necesitas: es de las piezas con más carrying cost que existen. - El content type más simple que puedas. JSON, y punto, salvo que tengas una razón real para otra cosa.
- Pon la versión en la URL. Nada de versionado por cabeceras o por content-type negociado, que es elegante en la teoría y un infierno de depurar en la práctica.
/api/v1/...es lo más simple, lo más explícito y lo más fácil de probar concurl. - Usa
.to_json/as_json. Antes de meter gemas de serialización (jbuilder, fast_jsonapi y compañía), pregúntate sito_jsoncon unas_jsonbien hecho ya resuelve. Suele bastar, y cada gema de serialización es una dependencia y un patrón más que mantener. - Testea los endpoints. Tests que verifican status, formato del JSON y autenticación. Una API sin tests es un contrato sin garantías.
Me hace gracia lo bien que esto describe la API de este mismo blog: prefijo /api/v1/, autenticación por token Bearer, respuestas JSON, sin CORS porque el consumo es local. Cuando lo monté no había leído a Copeland, pero llegué a las mismas conclusiones por el mismo camino: la opción simple es la que no te arrepientes de mantener.
Mi versión
Lo de no escribir tu propia autenticación lo suscribo con sangre. He visto sistemas de login caseros con fallos de libro —comparaciones de tokens no constantes en tiempo, recuperación de contraseña adivinable— hechos por gente competente que simplemente no es especialista en seguridad. El riesgo no compensa jamás. Devise, OmniAuth o el generador nativo de Rails, según el caso, pero nunca criptografía artesanal.
Y la regla de "la versión en la URL" me ahorró debates eternos. El versionado por cabeceras suena sofisticado en una charla de arquitectura y luego no hay quien lo depure cuando un cliente reporta un bug y no sabes qué versión estaba pidiendo. /api/v1/ lo ve cualquiera, lo prueba cualquiera con curl, y se documenta solo. Simplicidad agresiva, otra vez, como hilo conductor de todo el libro.
Donde matizo: Copeland prefiere to_json sobre gemas de serialización, y estoy de acuerdo hasta cierto tamaño. Cuando la API crece y los payloads se vuelven complejos y compartidos, una capa de serialización explícita puede ganarse su carrying cost. Pero como punto de partida, as_json bien usado evita arrastrar dependencias desde el día uno. Empezar simple y escalar cuando duela: esa es la cadencia correcta.
Lo que viene
Llegamos al último post. En el cierre de la serie subimos a la vista de pájaro: proceso sostenible, operaciones y liderazgo. Veremos la integración continua como vía de despliegue, la actualización frecuente de dependencias, la observabilidad y el manejo de excepciones en producción, la gestión de secretos, el eterno debate monolito vs microservicios, y la idea con la que Copeland cierra el libro: que la sostenibilidad, al final, es una cuestión de liderazgo técnico y valores compartidos.