Curso exprés · No. 08
Dos preguntas se esconden detrás de una sola palabra, «auth». La autenticación prueba quién sos; la autorización decide qué tenés permitido hacer. Confundilas, salteá cualquiera de las dos, o confiá en el cliente, y la puerta queda abierta.
Solo la esencia · Una imagen por idea · Estándares antes que hacerlo a mano
Detrás de la palabra «auth» se esconden dos preguntas distintas. La mayoría de los bugs de seguridad vienen de confundirlas — o de responder una y olvidar la otra.
Autenticación: ¿quién sos?
Mostrar tu pasaporte en el aeropuerto — probar que realmente sos la persona que decís ser.
La autenticación (authN) es probar la identidad: un login, una passkey, un token que dice «esta realmente es Alice». Normalmente ocurre una vez por sesión. Si la errás, un impostor entra haciéndose pasar por otra persona. Pero probar quién sos no dice nada sobre qué te está permitido hacer — esa es la otra pregunta, completamente distinta.
Autorización: ¿qué te está permitido hacer?
Una tarjeta llave de hotel — no prueba nada sobre tu nombre, pero abre tu habitación y no las otras, y no la oficina del gerente.
La autorización (authZ) es decidir qué tiene permitido hacer una identidad: leer esto, editar aquello, nunca tocar el panel de admin. A diferencia de la authN, hay que verificarla en cada request, porque un solo usuario logueado puede tocar cien cosas distintas. Saber quién es alguien no es permiso para hacer nada en particular.
Dos capas — nunca las colapses
Un club nocturno: el patovica revisa tu documento en la puerta una vez, y el barman igual revisa que tengas la edad cada vez que pedís un trago. Dos chequeos, dos momentos.
El error clásico es fusionar las dos — probar la identidad una vez y confiar en eso para siempre, o meter cada permiso dentro de un token de login y nunca volver a verificar. La authN ocurre en la puerta; la authZ ocurre en cada puerta de adentro. Mantenelas separadas, y verificá la segunda constantemente.
Nunca lo hagas por tu cuenta
No soldás tu propia bóveda de banco con chapa en el garaje — comprás una construida y probada por gente que no hace otra cosa.
La identidad es el cimiento sobre el que se para todo lo demás, y es brutalmente fácil de equivocar de forma sutil. Usá estándares y librerías probados — protocolos establecidos, una librería o un proveedor de auth a prueba de balas — no un esquema ingenioso que inventaste. La historia de la seguridad está empedrada de auth casera que parecía estar bien hasta que de golpe no lo estuvo.
La autenticación es la puerta. La autorización es cada puerta de adentro. Salteá la segunda y la primera no significa nada.
La autenticación se reduce a probar que sos vos. Los factores van desde la contraseña débil-pero-universal hasta la passkey moderna y resistente al phishing.
Contraseñas: el default débil
Una palabra secreta compartida con el portero — bien hasta que alguien la escucha, la adivina, o usás la misma en todos lados.
Las contraseñas son universales y el factor común más débil: reutilizadas, adivinables, phishables. Si tenés que usarlas, la regla innegociable es nunca guardarlas como texto — guardá un hash lento y salteado (Argon2, bcrypt), para que una base de datos robada no entregue la contraseña de todos. (Más sobre esto en el capítulo de errores.)
Multifactor: algo que sabés, tenés, sos
Una caja de seguridad necesita tu llave y la llave del banco. Una sola no abre nada.
El MFA (dos factores) exige una segunda prueba más allá de la contraseña — algo que tenés (un código de una app, una llave de seguridad) o algo que sos (una huella digital). Hasta una contraseña robada queda entonces inútil por sí sola. Un código de app autenticadora (TOTP) es sólido; los códigos por SMS son débiles (interceptables) pero mejores que nada. El MFA es la mejora de seguridad más grande y barata que podés agregar.
Passkeys: el futuro sin contraseña
Una llave que solo funciona en la única puerta para la que fue cortada — falsificá el sitio web, copiá la foto de la cerradura, y la llave simplemente no gira.
Las passkeys (construidas sobre WebAuthn) reemplazan las contraseñas con un par de claves criptográficas atado a tu dispositivo y al sitio real, así que no hay nada que phishear, reutilizar o filtrar — y Apple, Google y Microsoft ahora las usan por defecto. La clave privada nunca sale de tu dispositivo; el sitio solo ve una firma. Hacia acá va la autenticación: nada que robar.
Una contraseña es un secreto que podés perder. Una passkey es una prueba que no podés. Agregá un segundo factor en cualquier caso.
HTTP te olvida entre requests, así que después de loguearte el sistema necesita una forma de seguir reconociéndote. Dos enfoques, con un trade-off real.
El problema: HTTP no tiene memoria
Una tienda donde el vendedor te olvida apenas te das vuelta — así que necesitás un ticket para probar, en tu próximo paso, que ya pagaste.
Cada request HTTP es independiente; el servidor no recuerda que te logueaste hace un segundo. Así que después de la autenticación te entregan algo — una cookie o un token — que enviás con cada request para decir «sigo siendo yo». Todo lo de abajo son dos maneras de hacerlo. (Esta es la falta de estado del curso de protocolos.)
Sesiones: el servidor recuerda
Un guardarropa — vos sostenés un ticketito numerado, y toda la información real está detrás del mostrador bajo ese número.
Con sesiones, el servidor guarda quién sos y te entrega una cookie que contiene solo un session id aleatorio. En cada request, busca ese id. El servidor es la fuente de verdad, así que desloguear o banear a alguien es instantáneo — solo borrás la sesión. El costo es que el servidor tiene que guardar y buscar ese estado. El default correcto para la mayoría de las apps web.
JWT: el token lleva la verdad
Una pulsera de festival con tu nivel de acceso ya impreso y sellado contra manipulación — el guardia revisa el sello, no una lista de invitados.
Un JWT es un token firmado que el cliente sostiene y que contiene la identidad y los claims; el servidor solo verifica la firma, sin necesidad de buscar. Eso lo hace sin estado y fácil de escalar entre servicios — pero difícil de revocar: un token filtrado sigue siendo válido hasta que expira. Así que mantenés los tiempos de vida cortos y lo emparejás con un refresh token. Excelente para APIs y microservicios; demasiado para una app web simple.
Dónde lo guardás importa
Una llave en un cajón con cerradura versus una llave pegada con cinta a la puerta de entrada — la misma llave, una seguridad muy distinta.
Un token en el navegador pertenece en una cookie httpOnly (JavaScript no puede leerla), no en localStorage, donde cualquier script inyectado puede robarlo. El lugar de guardado es parte de la seguridad: si lo errás, un solo bug de XSS le entrega a un atacante cada sesión. Cómo transportás y guardás la credencial es tan importante como la credencial misma.
Sesiones: el servidor recuerda, y puede olvidarte al instante. JWT: el token recuerda, y no podés revocarlo fácilmente.
A menudo no querés manejar la contraseña en absoluto — querés que los usuarios inicien sesión con una cuenta que ya tienen, o que un servicio actúe en nombre de otro. Para eso están OAuth2 y OIDC.
OAuth2: otorgar acceso sin compartir una contraseña
Una llave de valet que arranca el auto y abre la puerta, pero no el baúl ni la guantera — entregás acceso limitado, no todo tu llavero.
OAuth2 le permite a un usuario otorgarle a tu app acceso limitado a su cuenta en otro servicio — tu app lee su Google Calendar — sin darte nunca su contraseña de Google. El otro servicio le entrega a tu app un access token acotado («leer calendario, nada más»). Se trata de autorización delegada, no de identidad.
OIDC: OAuth2 que además dice quién sos
La llave de valet, ahora con un documento con foto prendido — no solo «qué abre esta llave», sino «y acá está a quién pertenece».
OAuth2 por sí solo te dice a qué puede acceder una app, no quién es el usuario. OpenID Connect (OIDC) agrega una capa fina de identidad — un ID token con claims verificados (esta es Alice, acá está su email) — encima. Esto es lo que impulsa «Log in with Google» y el single sign-on. Cuando querés login social o SSO, OIDC es el estándar.
API keys: la credencial simple para máquinas
Una llave de repuesto que le das a un contratista de confianza — sin nombre asociado, solo «quien tenga esta puede entrar».
Una API key es una cadena secreta larga que identifica a un programa, no a una persona — usada para acceso de servicio a servicio y programático. Simple y efectiva, pero es la credencial: cualquiera que la encuentre está adentro. Así que la acotás, la rotás, y nunca la commiteás a git ni la ponés en una URL. Buena para máquinas; no un sustituto de una identidad de usuario real.
OAuth2 toma prestado el acceso sin la contraseña. OIDC agrega «y acá está quién es». Usá el estándar — no inventes tu propio baile de tokens.
Una vez que sabés quién es alguien, decidís qué puede tocar. Los modelos van desde roles gruesos hasta política de grano fino — y la regla debajo de todos ellos es denegar por defecto.
Denegar por defecto, mínimo privilegio
El gafete de un empleado nuevo que no abre nada hasta que cada puerta se otorga explícitamente — en vez de abrir todo hasta que alguien se acuerde de trabar unas pocas.
El cimiento de la autorización: todo está prohibido salvo que se permita explícitamente, y cada identidad recibe el acceso mínimo que necesita, no más. Los sistemas abiertos por defecto filtran — alguien siempre se olvida de restringir el endpoint nuevo. Los sistemas cerrados por defecto fallan de forma segura. Empezá desde «no» y otorgá deliberadamente.
RBAC: permisos por rol
Un teatro donde el tipo de tu entrada — platea, palco, pase de backstage — decide adónde podés ir, sin nombrarte personalmente.
El Control de Acceso Basado en Roles agrupa permisos en roles — admin, editor, viewer — y asigna usuarios a roles. Simple, legible, y suficiente para la mayoría de las apps: «los editores pueden publicar, los viewers no». Se vuelve grueso cuando las reglas dependen del contexto o del registro específico, que es donde entran los modelos siguientes.
ABAC: permisos por política
Una regla en vez de una lista — «los gerentes pueden aprobar gastos por menos de 10k, en horario laboral, desde un dispositivo de la empresa» — evaluada de nuevo cada vez.
El Control de Acceso Basado en Atributos decide a partir de atributos del usuario, del recurso y del contexto — rol y monto y hora y dispositivo. Mucho más flexible que los roles fijos, y mucho más complejo. La señal para adoptarlo: estás apilando más de un par de condiciones if personalizadas sobre tus chequeos de rol.
Propiedad: ¿podés tocar este registro?
Tenés una tarjeta de biblioteca válida (un rol), pero eso no te deja leer las notas garabateadas en el libro prestado de otra persona.
El chequeo más olvidado: hasta un «editor» válido solo debe editar sus propios documentos, no los de todos. El rol dice «los editores pueden editar»; igual tenés que verificar que este editor sea dueño de esta fila. Olvidarse de esto es el bug de control de acceso más común de todos — cambiá el id en la URL y estás dentro de los datos de otra persona. Siempre verificá el objeto, no solo el rol.
Denegá por defecto. Otorgá lo mínimo. Y preguntá siempre no solo «qué rol» sino «de quién es el registro».
La auth falla de un puñado de formas bien conocidas — las mismas, una y otra vez, app tras app. Conocerlas es la mitad de la defensa.
Control de acceso roto: olvidarse de verificar
Un edificio donde la puerta de entrada está custodiada pero cada puerta interna está sin traba — una vez adentro, podés caminar a cualquier lado.
La vulnerabilidad web número uno: un endpoint autentica al usuario pero se olvida de verificar que esté autorizado para lo que pidió. Su forma más común es IDOR — cambiar /orders/123 por /orders/124 y ver la orden de otra persona, porque el servidor nunca verificó la propiedad. Filtraciones de millones de registros se rastrean a exactamente esto. Verificá la authZ en cada request, del lado del servidor, contra el objeto real.
Guardar mal las contraseñas
Tener las llaves de repuesto de cada casa de la cuadra en un frasco de vidrio en el porche.
Guardar contraseñas como texto plano — o con un hash rápido como MD5 — significa que una sola filtración de base de datos expone a todos, y las contraseñas reutilizadas comprometen también sus otras cuentas. Las contraseñas deben estar hasheadas lenta e individualmente (Argon2, bcrypt) para que sean caras de crackear incluso cuando se las roban. Que el hash sea lento es el punto, no un defecto.
Filtrar y confiar en los tokens
Escribir el código de la alarma en un papelito pegado a la puerta — la cerradura está bien; solo regalaste el secreto.
Los tokens y las keys se filtran a través de URLs (que quedan logueadas), el localStorage del navegador (que un script puede leer), y secretos commiteados a git. Y un token debe ser verificado, no confiado solo porque apareció — uno manipulado o expirado debe ser rechazado. Tratá cada credencial como algo que se va a filtrar algún día: tiempos de vida cortos, rotación, y nunca en un lugar que se loguee o se scrapee.
Confiar en el cliente
Una tienda que deja que los clientes escriban sus propias etiquetas de precio y nunca las revisa en la caja.
Cualquier cosa que el cliente envía — un campo oculto de formulario, un rol en una cookie, un flag isAdmin=false — puede ser falsificada. El navegador, la app, el request están todos bajo el control del usuario. Cada chequeo real tiene que ocurrir en el servidor, que el usuario no puede editar. El cliente es una sugerencia; el servidor es la autoridad.
Casi toda filtración de auth es una de estas: un chequeo que se salteó, un secreto guardado mal, o el cliente al que se le creyó.
La buena auth se trata mayormente de usar herramientas probadas correctamente y de verificar permisos sin descanso — no de ingenio. Acá está cómo elegir.
Usá una librería o un proveedor
No fabricás tus propios cinturones de seguridad — montás unos certificados, diseñados por especialistas y probados en choques durante años.
Para casi todo el mundo, la jugada correcta es no construir la auth vos mismo: usá la auth de tu framework, o un proveedor (Auth0, Clerk, Supabase, Cognito y similares) que maneje el hashing, las sesiones, el MFA, OAuth y los casos límite que nunca se te ocurrirían. Tu trabajo es cablearlo correctamente y adueñarte de la autorización — la parte que solo vos conocés.
Ajustá el mecanismo a la app
Una llave de casa, una llave de auto y una tarjeta llave de hotel son todas «llaves» — elegís por la puerta, no por la moda.
Sesiones para una app web normal (simple, revocable al instante); JWTs de vida corta más refresh cuando necesitás escala sin estado entre servicios; OIDC para login social y SSO; API keys para máquina a máquina. Agregá MFA en todos lados donde importe, especialmente en las cuentas de admin. Elegí por la forma de tu app, no por lo que suene avanzado.
- ¿Están las contraseñas hasheadas lentamente (Argon2/bcrypt), nunca guardadas como texto? - ¿Está el MFA disponible, y requerido para los admins? - ¿Cada endpoint verifica la authZ — el rol y la propiedad del objeto? - ¿Están los tokens en cookies httpOnly, de vida corta, y nunca en URLs ni en git? - ¿Está el chequeo real en el servidor, sin confiar nunca en roles enviados por el cliente? - ¿Estoy usando una librería/proveedor probado en vez de un esquema hecho a mano?
- Cambiar un id en la URL muestra los datos de otra persona. - Un permiso vive en una cookie o campo oculto que el cliente puede editar. - Contraseñas guardadas en texto plano o MD5. - No hay forma de revocar un usuario logueado salvo esperar a que expire. - Sin MFA en las cuentas que pueden hacer más daño. - Escribiste tu propio esquema de tokens o de criptografía.
- La authN y la authZ están separadas, y la authZ se verifica en cada request. - Cada acceso a un objeto verifica que este usuario sea dueño de esta cosa. - Las credenciales están hasheadas, acotadas, de vida corta y rotables. - El servidor es la autoridad; nada confía en el cliente. - Podés revocar el acceso y ver quién hizo qué en un log. - El trabajo pesado se apoya en una librería o proveedor probado, no en tu propio código.
La auth no es donde te ponés ingenioso. Es donde usás los estándares correctamente, denegás por defecto, y nunca, jamás, confiás en el cliente.