Una tarjeta de identidad ante dos proyectos arquitectónicos con accesos distintos, uno cerrado y otro abierto.

Autenticación y autorización: identificar a alguien no equivale a darle permiso

Ana puede entrar en una aplicación y seguir sin tener permiso para modificar una entrega. Incluso puede editar en un proyecto y limitarse a leer en otro. Si tu diseño solo pregunta «¿ha iniciado sesión?», acabas de convertir una comprobación de identidad en una licencia para hacer cosas. Muy eficiente: has eliminado varias decisiones importantes sin resolver ninguna.

La autenticación y la autorización responden a preguntas distintas. Vamos a separarlas con dos usuarios y dos proyectos ficticios, y a escribir una pequeña política que permita comprobar esa diferencia. El ejemplo no implementa un login, verifica tokens ni protege una API real. Nos servirá para entender qué decisión debe tomar el servidor después de saber quién está haciendo la petición y cómo probar que una identidad conocida tampoco obtiene acceso por defecto.

Tabla de contenidos

La identidad se comprueba; los permisos se deciden para una acción

Autenticar consiste en comprobar una identidad mediante un mecanismo de confianza. Puede intervenir una contraseña, un segundo factor o un proveedor externo. El resultado que necesita nuestra aplicación es una identidad verificada que pueda relacionar con su propia cuenta. La guía de autenticación de OWASP desarrolla esos mecanismos y sus controles; una pantalla con un nombre y una foto no demuestra por sí sola que el servidor haya realizado la comprobación.

Autorizar consiste en decidir si esa identidad puede realizar una acción sobre un recurso dentro de un contexto. La pregunta completa sería «¿puede Ana editar esta entrega del proyecto p-01?». El proyecto, la entrega y la acción forman parte de la decisión. No basta con que Ana sea usuaria, tenga una cuenta de la empresa o pueda editar otra entrega que aparece cerca en la pantalla. Los datos no heredan permisos por proximidad visual, aunque algunas interfaces parezcan haber construido toda su estrategia alrededor de esa esperanza.

En nuestra aplicación ficticia, Ana tendrá lectura en p-01 y edición en p-02. Luis tendrá edición en p-01 y ningún acceso a p-02. Hemos elegido que edición incluya lectura; otra aplicación podría separar ambos permisos. Lo importante es que esa relación esté definida, no que la deduzca cada pantalla por su cuenta. La misma identidad de Ana obtiene decisiones distintas según el proyecto y la acción solicitada.

Un rol como «editor» es una forma de agrupar acciones, no una respuesta universal. Debe existir una relación entre el usuario, el proyecto y ese rol. Si guardas simplemente editor = true en la cuenta y lo aplicas a todos los proyectos, has ampliado el alcance de una decisión que quizá solo correspondía a uno. En un flujo BIM puede haber además límites por organización, modelo, versión o estado de la entrega. Conviene empezar por el alcance real antes de inventar una lista interminable de cargos.

El servidor debe conocer el recurso, no fiarse del proyecto que declara el cliente

Cuando alguien solicita una entrega por su identificador, el backend debe averiguar a qué proyecto pertenece y aplicar la política sobre ese proyecto. Si el cliente envía a la vez un identificador de entrega y otro de proyecto, hay que comprobar su relación. No se puede autorizar utilizando un proyecto permitido y después recuperar una entrega de otro. Cambiar una referencia en una petición es demasiado fácil como para convertirla en una prueba de pertenencia.

El mismo criterio afecta al usuario. Un campo usuario: ana en el cuerpo JSON es una afirmación del cliente, no una identidad autenticada. El servidor debe obtener la identidad de una sesión o credencial ya validada mediante el mecanismo elegido. Después podrá vincularla con una cuenta local y consultar sus permisos. Mezclar esas etapas permite que una petición se escriba a sí misma una carta de recomendación. Yo preferiría una fuente algo menos interesada en el resultado.

Ocultar un botón o deshabilitar un campo sigue siendo útil para explicar qué puede hacer el usuario, pero la decisión de acceso debe aplicarse también al recibir la operación. El cliente puede modificarse y una API puede llamarse sin utilizar esa pantalla. El control debe ocurrir antes de leer o modificar los datos protegidos. Comprobarlo después y eliminar algunos campos de la respuesta no deshace una escritura ni todas sus posibles consecuencias.

Para esta política adoptaremos tres criterios: permitir únicamente acciones expresas, denegar cuando no exista una relación válida y comprobar cada petición. Son controles recogidos por la guía de autorización de OWASP. En el ejemplo, un rol desconocido o una acción nueva no concederán acceso automáticamente. Así, añadir mañana una operación de borrado no hará que todos los editores puedan usarla por accidente.

Probar una política pequeña sin fingir que ya tenemos un login

Este programa Python representa únicamente la decisión de autorización. Los nombres son identificadores ficticios y las relaciones se guardan en memoria. usuario_autenticado supone una identidad que la aplicación ya ha verificado; en este archivo la proporcionamos directamente para ensayar la política. No copies esa entrada como un campo de usuario que una petición pueda elegir libremente.

Guárdalo como politica_acceso.py. Utiliza Python 3.10 o posterior y no necesita paquetes:

				
					ROLES = {
    "lectura": frozenset({"leer"}),
    "edicion": frozenset({"leer", "editar"}),
}

RELACIONES = {
    ("ana", "p-01"): "lectura",
    ("ana", "p-02"): "edicion",
    ("luis", "p-01"): "edicion",
}


def puede(usuario_autenticado, proyecto, accion):
    if usuario_autenticado is None:
        return False
    rol = RELACIONES.get((usuario_autenticado, proyecto))
    acciones = ROLES.get(rol, frozenset())
    return accion in acciones


casos = [
    ("ana", "p-01", "leer"),
    ("ana", "p-01", "editar"),
    ("ana", "p-02", "editar"),
    ("luis", "p-01", "editar"),
    ("luis", "p-02", "leer"),
    (None, "p-01", "leer"),
    ("ana", "p-02", "borrar"),
]

for usuario, proyecto, accion in casos:
    resultado = "PERMITIDO" if puede(usuario, proyecto, accion) else "DENEGADO"
    print(f"{usuario} | {proyecto} | {accion} | {resultado}")

del RELACIONES[("ana", "p-02")]
resultado = "PERMITIDO" if puede("ana", "p-02", "editar") else "DENEGADO"
print(f"ana | p-02 | editar tras retirar relacion | {resultado}")
				
			

Ejecútalo desde la carpeta donde lo hayas guardado:

				
					python politica_acceso.py
				
			

Las tres primeras filas mostrarán permitido, denegado y permitido. Ana sigue siendo Ana en las tres, pero cambian el proyecto o la operación. Luis podrá editar p-01 y no podrá leer p-02. La ausencia de identidad quedará denegada y borrar tampoco estará permitido, porque no forma parte de las acciones de ningún rol.

La última prueba retira la relación de Ana con p-02 y vuelve a evaluar la misma acción con el mismo identificador. Ahora queda denegada. El archivo se ejecuta desde cero cada vez, por lo que esa retirada solo afecta a la demostración en memoria. No hemos desconectado a Ana, revocado un token ni cambiado permisos de una aplicación externa: hemos comprobado que la política utiliza la relación vigente para decidir.

La función recibe identificadores y acciones de texto, dentro del contrato del ejercicio; no valida entradas arbitrarias ni localiza una entrega en una base de datos. En una API real, una capa anterior tendría que resolver la identidad, localizar el recurso dentro de su ámbito y pasar a la política el proyecto verificado. Las pruebas automatizadas permiten comprobar la función aislada, pero después también harán falta pruebas que demuestren que cada endpoint la llama antes de actuar.

Un token válido no contiene la respuesta a todas las preguntas

Una credencial puede permitir al servidor reconocer al solicitante o aceptar determinadas capacidades, según el protocolo. Eso no implica que todas las entregas existentes le pertenezcan. Además de validar la credencial, el sistema debe comprobar el alcance de la acción sobre el recurso. Un acceso autorizado a p-01 no se extiende a p-02 por mantener la misma sesión abierta.

Si utilizas JWT, leer su contenido no equivale a validarlo. Hay que aplicar las reglas del protocolo y de la biblioteca: firma y algoritmos admitidos, emisor, destinatario y condiciones temporales que correspondan, entre otras. Las buenas prácticas de JWT explican riesgos como aceptar tokens destinados a otro contexto. Nuestro programa no realiza ninguna de esas comprobaciones; por eso su parámetro representa una identidad ya resuelta, no un token que intentemos interpretar con unas líneas de código.

Tampoco conviene intercambiar los nombres de los protocolos. OAuth 2.0 se ocupa de autorización delegada; OpenID Connect añade una capa de identidad sobre OAuth 2.0, tal como describe su especificación. La forma concreta de vincular el acceso de un proveedor con una cuenta de tu aplicación depende del servicio y del flujo. No asumiría que cualquier token recibido sirve para iniciar sesión en cualquier componente o para llamar a cualquier API.

Los permisos también cambian mientras una sesión sigue abierta. Nuestro ejemplo consulta el diccionario vigente en cada decisión. Una aplicación que copie los permisos a una sesión o los guarde en caché tendrá que definir cuánto pueden quedar desactualizados y cómo aplicar una retirada. No hay una duración única que sirva para todos los sistemas. Si retirar acceso es importante, la prueba debe incluir cuándo deja realmente de poder ejecutarse la operación, no solo cuándo desaparece el nombre de una lista de administración.

La comprobación útil incluye intentos que deben fallar

Para evaluar un control de acceso, no me bastaría con entrar como usuario permitido y ver una pantalla. Probaría lectura y escritura por separado, la misma identidad en dos proyectos, un usuario sin relación y una acción que nadie tiene concedida. Después comprobaría peticiones directas al endpoint y referencias a recursos de otro ámbito. La interfaz puede enseñar una intención correcta mientras una ruta del backend conserva una generosidad que nadie había aprobado.

En HTTP, una respuesta 401 señala que faltan credenciales de autenticación válidas para el recurso; 403 expresa que el servidor rechaza atender la petición. El contrato concreto puede usar otros estados para no revelar recursos restringidos y debe manejar también las cabeceras de autenticación correspondientes. La semántica de estos estados ayuda a distinguirlos. Un cliente no debería insistir con la misma operación de edición como si todo rechazo se arreglara mostrando de nuevo el formulario de login.

Además del estado, comprobaría los efectos: que no haya cambiado la entrega cuando la operación se rechaza y que la respuesta no incluya datos de otro proyecto. Un registro de auditoría puede conservar quién intentó qué acción, sobre qué recurso y con qué resultado, evitando almacenar credenciales o información innecesaria. Una línea que diga «acceso denegado» aporta poco si después descubrimos que el cambio ya se había guardado.

La decisión que quiero que te lleves es concreta: primero resolver una identidad de confianza; después decidir sobre usuario, recurso, acción y contexto; por último ejecutar únicamente lo permitido. Si la condición de tu aplicación sigue siendo un único ha_iniciado_sesion, prueba a escribir qué puede hacer esa persona en cada proyecto. Es una tarea menos vistosa que dibujar la pantalla de acceso, pero suele descubrir bastante más sobre el producto que estás construyendo.

Artículos relacionados

Respuestas

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *