Estoy creando el backend para una aplicación web. Cuando un nuevo usuario ingresa al sitio y hace clic en el botón Registrarse , completará un formulario súper simple que les pedirá su nombre de usuario y contraseña y los enviará. Esto solicita al servidor que envíe un correo electrónico de verificación a esa dirección de correo electrónico. Luego revisarán su correo electrónico, harán clic en un enlace (que verifica su correo electrónico) y luego serán enviados a la página de inicio de sesión para que puedan iniciar sesión si así lo desean.
Para verificar su correo electrónico, cuando el servidor genere el correo electrónico, deberá crear (y almacenar) un token de verificación (probablemente un UUID) y adjuntarlo a este enlace en el correo electrónico, para que el enlace se vea algo como:
" enlace "
Donde vt=12345
es el "token de verificación" (también es probable que sea un UUID). Así que el usuario hace clic en este enlace y mi punto final GET v1/users/verify
mira el token, de alguna manera confirma su validez y realiza algunas actualizaciones de la base de datos para "activar" al usuario. Ahora pueden iniciar sesión.
Escenarios similares para cuando un usuario desea darse de baja de recibir correos electrónicos, o cuando no puede recordar su contraseña y necesita recuperarla para poder iniciar sesión.
Cancelar suscripción
El usuario quiere dejar de recibir correos electrónicos, pero aún quiere usar la aplicación. Hacen clic en el enlace " Cancelar suscripción " en un boletín semanal que les enviamos. Este enlace debe contener algún tipo de "token de cancelación de suscripción" similar que, como el token de verificación anterior, se genera + almacena en el servidor y se usa para autenticar la solicitud del usuario para cancelar la suscripción del correo electrónico.
Recuperar contraseña
Aquí el usuario ha olvidado su contraseña y necesita recuperarla. Así que en la pantalla de inicio de sesión, hacen clic en el enlace " Olvidé mi contraseña " y se les presenta un formulario donde deben completar su dirección de correo electrónico. El servidor envía un correo electrónico a esa dirección. Verifican este correo electrónico y contiene un enlace a un formulario donde pueden ingresar su nueva contraseña. Este enlace debe contener un "token de restablecimiento de contraseña" que, al igual que el token de verificación anterior, se genera + almacena en el servidor y se usa para autenticar la solicitud del usuario para cambiar su contraseña.
Por lo tanto, aquí tenemos tres problemas muy similares que resolver, todos requieren el uso de lo que llamo " tokens de seguridad de una sola vez (OTO) ". Estos tokens OTO:
- Se debe generar en el lado del servidor y persistir (tal vez en una tabla
security_tokens
) - Debe ser algo que se pueda adjuntar a los enlaces que expondremos desde el interior de los correos electrónicos
- Solo debe ser válido una vez: una vez que hacen clic, el token se "usa" y no se puede reutilizar.
Mi pregunta
La solución que encontré fue simple ... casi demasiado simple.
Para los tokens, estoy generando UUID aleatorios (36 caracteres) y almacenándolos en una tabla security_tokens
que tiene los siguientes campos:
[security_tokens]
---
id (PK)
user_id (FK to [users] table)
token (the token itself)
status (UNCLAIMED or CLAIMED)
generated_on (DATETIME when created)
Cuando el servidor los crea, son "NO RECLAMADOS". Cuando el usuario hace clic en un enlace dentro de la tabla, está "RECLAMADO". Un trabajo de segundo plano se ejecutará periódicamente para limpiar los tokens RECLAMADOS o para eliminar los tokens NO RECLAMADOS que hayan "caducado" (según sus campos generated_on
). La aplicación también ignorará cualquier token que haya sido RECLAMADO anteriormente (y que aún no se haya limpiado).
Pienso que esta solución funcionaría, pero no soy un tipo de súper seguridad y me preocupa que este enfoque:
- Posiblemente deja mi aplicación abierta a algún tipo de ataque / explotación; y
- Posiblemente reinvente la rueda cuando alguna otra solución podría funcionar igual de bien
Al igual que para el segundo anterior, me pregunto si debería usar un mecanismo relacionado con hash / HMAC / JWT en lugar de un UUID simple muerto. Tal vez hay gente inteligente de seguridad / criptografía que encontró una manera de hacer que estos tokens contengan el estado de RECLAMACIÓN y la fecha de caducidad de una manera segura / inmutable, etc.