tutorial - Acceso social Django allauth: vinculación automática de perfiles de sitios sociales mediante el correo electrónico registrado
django-allauth login with email (5)
Mi objetivo es crear la experiencia de inicio de sesión más sencilla posible para los usuarios de mi sitio Django. Me imagino algo así como:
- La pantalla de inicio de sesión se presenta al usuario
- El usuario selecciona para iniciar sesión con Facebook o Google
- Usuario ingrese la contraseña en un sitio externo
- El usuario puede interactuar con mi sitio como un usuario autenticado
Ok, esta parte es fácil, solo tienes que instalar django-allauth y configurarla.
Pero también quiero dar la opción de usar el sitio con un usuario local. Tendría otro paso:
- La pantalla de inicio de sesión se presenta al usuario
- El usuario selecciona para registrarse
- El usuario ingresa las credenciales
- El sitio envía un correo electrónico de verificación
- El usuario hace clic en el enlace de correo electrónico y puede interactuar con mi sitio como usuario autenticado
De acuerdo, tanto la autenticación predeterminada como Allauth pueden hacerlo. Pero ahora es la pregunta del millón de dólares.
Si cambian la forma en que inician sesión, ¿cómo puedo asociar automáticamente su Google, FB y cuentas locales?
Mira que de cualquier forma que inicien sesión, tengo su dirección de correo electrónico. ¿Es posible hacerlo usando django-allauth? Sé que puedo hacerlo con la intervención del usuario . Hoy el comportamiento predeterminado es rechazar el inicio de sesión diciendo que el correo electrónico ya está registrado.
Si no es posible hacerlo solo con la configuración, aceptaré la respuesta que me da cierta orientación acerca de qué modificaciones debo hacer en el código Allauth para admitir este flujo de trabajo.
Hay muchas razones para hacer esto. Los usuarios olvidarán qué método usaron para autenticarse, y algunas veces usarán Google, a veces FB y algunas veces la cuenta de usuario local. Ya tenemos muchas cuentas de usuarios locales y las cuentas sociales serán una nueva característica. Quiero que los usuarios mantengan su identidad. Visualizo la posibilidad de pedir la lista de amigos de los usuarios, así que si se registraron usando Google, también me gustaría tener su cuenta de FB.
Es un sitio de hobby, no hay grandes requisitos de seguridad, así que no responda que esta no es una implementación de seguridad inteligente.
Más tarde, crearía un modelo de usuario personalizado para tener solo el correo electrónico como ID de inicio de sesión. Pero estaré contento con una respuesta que me permita asociar automáticamente una cuenta del modelo de usuario predeterminado que tiene un nombre de usuario requerido.
Estoy usando Django == 1.5.4 y django-allauth == 0.13.0
Si cambian la forma en que inician sesión, ¿cómo puedo asociar automáticamente su Google, FB y cuentas locales?
Es posible, pero debe tener cuidado con los problemas de seguridad. Verifique el escenario:
- El usuario crea una cuenta por correo electrónico y contraseña en su sitio. El usuario no tiene Facebook.
- Attacker crea una cuenta en Facebook con el correo electrónico del usuario. (Escenario hipotético, pero no controla si la red social verifica el correo electrónico).
- El atacante inicia sesión en su sitio con Facebook y automáticamente obtiene acceso a la cuenta original del usuario.
Pero puedes arreglarlo. Describo la solución del ticket https://github.com/pennersr/django-allauth/issues/1149
Feliz escenario debería ser:
- El usuario crea una cuenta por correo electrónico y contraseña en su sitio. Usuario desconectado
- El usuario se olvida de su cuenta e intenta iniciar sesión a través de su Facebook.
- Sistema autenticar usuario a través de Facebook y averiguar, él ya creó la cuenta a través de otro método (los correos electrónicos son los mismos). El sistema redirige al usuario a la página de inicio de sesión normal con el mensaje "Usted ya creó su cuenta usando el correo electrónico y la contraseña. Inicie sesión de esta manera. Después de iniciar sesión, podrá usar e iniciar sesión con Facebook".
- Inicio de sesión de usuario por correo electrónico y contraseña.
- El sistema conecta automáticamente su inicio de sesión de Facebook con su cuenta. La próxima vez, el usuario puede usar el inicio de sesión de Facebook o el correo electrónico y la contraseña.
Acabo de encontrar este comentario en el código fuente:
if account_settings.UNIQUE_EMAIL:
if email_address_exists(email):
# Oops, another user already has this address. We
# cannot simply connect this social account to the
# existing user. Reason is that the email adress may
# not be verified, meaning, the user may be a hacker
# that has added your email address to his account in
# the hope that you fall in his trap. We cannot check
# on ''email_address.verified'' either, because
# ''email_address'' is not guaranteed to be verified.
entonces, es imposible de hacer por diseño.
Estoy tratando de mejorar este tipo de caso de uso y se me ocurrió la siguiente solución:
from allauth.account.models import EmailAddress
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
class SocialAccountAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
"""
Invoked just after a user successfully authenticates via a
social provider, but before the login is actually processed
(and before the pre_social_login signal is emitted).
We''re trying to solve different use cases:
- social account already exists, just go on
- social account has no email or email is unknown, just go on
- social account''s email exists, link social account to existing user
"""
# Ignore existing social accounts, just do this stuff for new ones
if sociallogin.is_existing:
return
# some social logins don''t have an email address, e.g. facebook accounts
# with mobile numbers only, but allauth takes care of this case so just
# ignore it
if ''email'' not in sociallogin.account.extra_data:
return
# check if given email address already exists.
# Note: __iexact is used to ignore cases
try:
email = sociallogin.account.extra_data[''email''].lower()
email_address = EmailAddress.objects.get(email__iexact=email)
# if it does not, let allauth take care of this new social account
except EmailAddress.DoesNotExist:
return
# if it does, connect this new social login to the existing user
user = email_address.user
sociallogin.connect(request, user)
Por lo que puedo probarlo, parece funcionar bien. ¡Pero las sugerencias y sugerencias son bienvenidas!
Según el comentario de Babus sobre este hilo relacionado , las respuestas propuestas arriba introducen un gran agujero de seguridad, documentado en los documentos de Allauth:
"No está claro en la documentación de Facebook si el hecho de que la cuenta esté verificada implica que la dirección de correo electrónico también se verifica. Por ejemplo, la verificación también se puede hacer por teléfono o tarjeta de crédito. lado, lo predeterminado es tratar las direcciones de correo electrónico de Facebook como no verificadas ".
Al decirlo, puedo registrarme en Facebook con su ID de correo electrónico o cambiar mi correo electrónico al suyo en Facebook e iniciar sesión en el sitio web para obtener acceso a su cuenta.
Así que, teniendo esto en cuenta y basándose en la respuesta de @sspross, mi enfoque es redirigir al usuario a la página de inicio de sesión y notificarle el duplicado, e invitarlo a iniciar sesión con su otra cuenta, y vincularlos una vez que están conectados. Reconozco que difiere de la pregunta original, pero al hacerlo, no se introduce ningún agujero de seguridad.
Por lo tanto, mi adaptador se ve así:
from django.contrib.auth.models import User
from allauth.account.models import EmailAddress
from allauth.exceptions import ImmediateHttpResponse
from django.shortcuts import redirect
from django.contrib import messages
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
class MyAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
"""
Invoked just after a user successfully authenticates via a
social provider, but before the login is actually processed
(and before the pre_social_login signal is emitted).
We''re trying to solve different use cases:
- social account already exists, just go on
- social account has no email or email is unknown, just go on
- social account''s email exists, link social account to existing user
"""
# Ignore existing social accounts, just do this stuff for new ones
if sociallogin.is_existing:
return
# some social logins don''t have an email address, e.g. facebook accounts
# with mobile numbers only, but allauth takes care of this case so just
# ignore it
if ''email'' not in sociallogin.account.extra_data:
return
# check if given email address already exists.
# Note: __iexact is used to ignore cases
try:
email = sociallogin.account.extra_data[''email''].lower()
email_address = EmailAddress.objects.get(email__iexact=email)
# if it does not, let allauth take care of this new social account
except EmailAddress.DoesNotExist:
return
# if it does, bounce back to the login page
account = User.objects.get(email=email).socialaccount_set.first()
messages.error(request, "A "+account.provider.capitalize()+" account already exists associated to "+email_address.email+". Log in with that instead, and connect your "+sociallogin.account.provider.capitalize()+" account through your profile page to link them together.")
raise ImmediateHttpResponse(redirect(''/accounts/login''))
Tendrá que anular el adaptador sociallogin, específicamente, el método pre_social_login
, que se llama después de la autenticación con el proveedor social, pero antes de que Allauth procese este inicio de sesión.
En my_adapter.py
, haz algo como esto
from django.contrib.auth.models import User
from allauth.account.models import EmailAccount
from allauth.exceptions import ImmediateHttpResponse
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
class MyAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
# This isn''t tested, but should work
try:
user = User.objects.get(email=sociallogin.email)
sociallogin.connect(request, user)
# Create a response object
raise ImmediateHttpResponse(response)
except User.DoesNotExist:
pass
Y en su configuración, cambie el adaptador social a su adaptador
SOCIALACCOUNT_ADAPTER = ''myapp.my_adapter.MyAdapter`
Y debería poder conectar múltiples cuentas sociales a un usuario de esta manera.