Logo

Introducción a Webauthn

Les voy a dar una breve explicación sobre qué es WebAuthn y cómo añadir un nivel adicional de seguridad a nuestras aplicaciones web.

WebAuthn es un sistema de autenticación web desarrollado por W3C y FIDO, cuyo principal objetivo es agregar una capa adicional de seguridad utilizando una llave pública junto con una llave secreta que se almacena en un dispositivo. Este dispositivo puede ser, por ejemplo, un iPhone con Face ID o una llave física de Yubico.

Aplicaciones

Esta capa de seguridad puede aplicarse en diferentes ámbitos tecnológicos, ya sea para iniciar sesión o como factor de doble autenticación.

Su integración consta de dos pasos principales y, a su vez, de tres etapas, ya sea para registrarse o autenticarse.

Registro

Opciones de creación

Así como registramos a un usuario con correo y contraseña, hacemos lo mismo utilizando un identificador único que puede estar asociado a un correo. En lugar de una contraseña escrita, usaremos una llave física.

Estos datos se deben configurar del lado del servidor en una etapa que llamamos Opciones de creación.

{
  user: {
    id: Uint8Array.from("UZSL85T9AFC", c => c.charCodeAt(0)),
    name: "gio@elgio.dev",
    displayName: "Giorgio Acosta",
  },
}

Además de la configuración del usuario, también se configuran las de dominio o “Relaying Party”, que hacen alusión a la organización o plataforma en donde el usuario se está registrando, y su creación estará restringida a ese espacio.

También se genera un identificador llamado “challenge”, que el servidor deberá tener presente al momento de verificar el registro para evitar ataques de replay.

{
  challenge: Uint8Array.from(randomStringFromServer, c => c.charCodeAt(0)),
  rp: {
    name: "Duo Security",
    id: "duosecurity.com",
  },
}

Finalmente, para esta etapa se configuran las opciones de autenticación, como los tipos de llaves públicas permitidas, el dispositivo o la declaración (atestación).

{
  pubKeyCredParams: [{alg: -7, type: "public-key"}],
  authenticatorSelection: {
    authenticatorAttachment: "cross-platform",
  },
  attestation: "direct",
}

Por ejemplo, el tipo de alg (algoritmo) es un número COSE, que en este caso es -7, lo que significa que acepta llaves públicas de tipo Curva Elíptica usando SHA-256.

authenticatorAttachment es un parámetro opcional que indica el tipo de autenticador que se va a usar; cross-platform hace referencia a dispositivos físicos como Yubikey.

attestation, en este caso, lo recomendable sería direct para traer la información del contrato directamente desde el dispositivo autenticador. También permite flexibilidad con los valores none e indirect.

Creación

Una vez que el servidor genera las Opciones de creación con base en sus requerimientos y en la información del usuario que le brinda el navegador, el siguiente paso es la creación.

Este paso se realiza con la API del navegador: navigator.credentials.create(). Como parámetros, toma la configuración descrita en el paso anterior y, posteriormente, el navegador mostrará un modal nativo, cuyo diseño e información pueden variar según el tipo de navegador y el sistema operativo en el que se esté ejecutando. Por ejemplo, en Chrome, sería algo así:

Validación

Finalmente, el resultado de esa creación debe enviarse al servidor para su validación, tomando en cuenta el “challenge”, el “Relaying Party” y su origen para confirmar que la respuesta de creación coincida con las opciones de creación.

Si todo está bien, el servidor deberá guardar los datos del usuario junto con la llave pública, la cual servirá más adelante para autenticarse o confirmar que realmente el usuario es quien dice ser a través de su llave o dispositivo.

Autenticación

El proceso de autenticación requiere también de una primera etapa de creación de opciones en las que se define de nuevo el “challenge”, el “Relaying Party” y los usuarios registrados habilitados.

{
  challenge: Uint8Array.from(randomStringFromServer, c => c.charCodeAt(0)),
  allowCredentials: [{
    id: Uint8Array.from(credentialId, c => c.charCodeAt(0)),
    type: 'public-key',
    transports: ['usb', 'ble', 'nfc'],
  }],
}

Esta lista de usuarios registrados se toma de la información guardada en la base de datos al momento de validar el registro. Por lo que esta generación también debe hacerse desde el servidor.

Obtener firma

Una vez obtenidas las opciones de autenticación, el navegador las recibe, ejecuta la API: navigator.credentials.get() y genera un modal parecido al de registro, con la diferencia de que ahora será para obtener la firma del dispositivo.

Validación

La firma se envía al servidor junto con el “challenge” y el “Relaying Party” para su validación. Este deberá responderle al navegador si el usuario es quien dice ser, usando la llave pública.

Para servidor y cliente en TypeScript, les recomiendo la librería de @simplewebauthn, que ofrece una opción de manejo de registro y autenticación muy fácil de usar. Sin embargo, como todo, les recomiendo profundizar en su manejo sin el uso de librerías para entender mejor su funcionamiento.


webseguridadwebauthnotpllaves