django-allauth icon indicating copy to clipboard operation
django-allauth copied to clipboard

How to disable form login/signup?

Open fjcaetano opened this issue 10 years ago • 21 comments

Hey guys, first of all, thank you for this app. It's amazing and it has already saved me a bunch of time.

I have the following problem because I couldn't find anything in the docs, so I thought it would be better to ask.

How can I completely disable form login/signup and enable only the providers I set? In my business model, the user can only login/signup with Facebook and, whenever I use the @login_required decorator, it redirects the user to a login page where he can input emal/password.

Thanks in advance.

fjcaetano avatar Jul 31 '13 13:07 fjcaetano

There is no easy setting for this, yet, though it can be realized as follows:

You should let is_open_for_signup on the account adapter always return False. The counterpart in the socialaccount adapter should always return True.

Then, in your templates simply do not include the local signup/login forms.

Furthermore, make sure validate_disconnect (SocialAccountAdapter) does not allow to disconnect the last social account.

pennersr avatar Aug 31 '13 07:08 pennersr

There's a potential security loophole here.

If you setup allauth to use a social auth provider as the signup/signin process.

Then use a custom SocialAccountAdapter to check the domain of email address to remove the need for email confirmation (set to optional) to remove friction from the process. You can do this with the pre_social_login method of DefaultSocialAccountAdapter

As the DefaultAccountAdapter always returns True from the is_open_for_signup method, someone can just hit the /accounts/signup/ page and create an account which then doesn't need to be verified.

Whilst this is obviously down to the developer to configure correctly, it's not so clear that this would be the case.

I think adding a configuration option to turn on/off the standard account signup page would be sensible and maybe even setting everything to off by default might also be wise, then developers can just switch on what they need.

markunsworth avatar Apr 20 '15 12:04 markunsworth

I support this feature request! :-)

wetneb avatar Aug 27 '15 07:08 wetneb

I believe direct sign up, should be defined as another optional backends

aliva avatar Mar 11 '16 17:03 aliva

here is my adapters as @pennersr mentioned in his comment:

class AccountAdapter(DefaultAccountAdapter):
    def is_open_for_signup(self, request):
        if request.path.rstrip("/") == reverse("account_signup").rstrip("/"):
            return False
        return True


class SocialAccountAdapter(DefaultSocialAccountAdapter):
    def validate_disconnect(self, account, accounts):
        raise ValidationError("Can not disconnect")

but there is another problem here, It's with EmailView (accounts/email) in this view users can add/remove email addresses to their account - I think if you want completly use providers (disable direct login/singup) email management may cause some problems

also password mangement doesn't make sense. (set password/change password/reset password)

aliva avatar Mar 12 '16 08:03 aliva

I too think configuring social login + registration only should be made easier. I'd even prefer that the Account models/tables were not populated/created at all. In fact, let's look at the setup:

INSTALLED_APPS = [
    # ...
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    # ...
]

I always had the impression it is actually by design that there is a separation of account and socialaccount. Hence, if I would omit 'allauth.account' from INSTALLED_APPS I would get a django-allauth setup without regular, email-based registration + login. Unfortunately, this does not seem to be the case.

@pennersr Is there an important reason why this is not strictly separated that way?

bittner avatar Jan 03 '17 19:01 bittner

Even if you would go the social-only route, it typically still makes sense to store the user email address for user communication purposes. So, if you would architect things differently you would either need to dupe email storage (& handling) in both apps, or, you would need to introduce a third base app dedicated to this purpose.

pennersr avatar Jan 03 '17 20:01 pennersr

This makes sense---in some way.

Though, really, if I want to communicate with a user who has authenticated with my application, say, 6 months ago, and has, potentially, changed their email address twice in that time span ... how much worth is the email address stored in my Django model? - Not much, right? Maybe zero.

In theory, at least, I should be able to query the social provider again and ask for an updated version of the user details when I need them. Everything else to me seems like a second-class solution. I must admit I don't know if it's actually possible to query the provider at any time. I hope it is.

bittner avatar Jan 03 '17 22:01 bittner

here is my adapters as @pennersr mentioned in his comment:

class AccountAdapter(DefaultAccountAdapter):
    def is_open_for_signup(self, request):
        if request.path.rstrip("/") == reverse("account_signup").rstrip("/"):
            return False
        return True


class SocialAccountAdapter(DefaultSocialAccountAdapter):
    def validate_disconnect(self, account, accounts):
        raise ValidationError("Can not disconnect")

but there is another problem here, It's with EmailView (accounts/email) in this view users can add/remove email addresses to their account - I think if you want completly use providers (disable direct login/singup) email management may cause some problems

also password mangement doesn't make sense. (set password/change password/reset password)

Your solution was quite good for me.

I decided to override forms to disable password and email managements as mentioned:

class MemberChangePasswordForm(allauthforms.ChangePasswordForm):
    def clean(self):
        raise forms.ValidationError(_('You cannot change password.'))


class MemberSetPasswordForm(allauthforms.SetPasswordForm):
    def clean(self):
        raise forms.ValidationError(_('You cannot set password.'))


class MemberResetPasswordForm(allauthforms.ResetPasswordForm):
    def clean(self):
        raise forms.ValidationError(_('You cannot reset password.'))


class MemberAddEmailForm(allauthforms.AddEmailForm):
    def clean(self):
        raise forms.ValidationError(_('You cannot add an email.'))

and I set their template page as blank.

I believe that it will be okay if we not provide hyperlinks to password/email management pages. Even if some malicious users access those pages directly, the functions won't work.

pincoin avatar Aug 01 '20 19:08 pincoin

Linking the following PR here since it relates:

https://github.com/pennersr/django-allauth/pull/2181

9mido avatar Jan 10 '21 18:01 9mido

FYI I also want exactly this: enable social login, disable regular login. I am reading this thread, and upvoting the related PR.

Thanks for your work on this project.

dfrankow avatar Mar 25 '21 18:03 dfrankow

I decided to override forms to disable password and email managements as mentioned:

@pincoin - how did you hook these objects into the django-allauth flow?

dfrankow avatar Mar 25 '21 21:03 dfrankow

What do you all think of overriding the URLs themselves? So for example, adding entries like these to urls.py before including allauth.urls:

from django.urls import path, re_path
from django.views.generic import TemplateView, RedirectView

urlpatterns = [
    # These URLs shadow django-allauth URLs to shut them down:
    path('password/change/', RedirectView.as_view(url='/')),
    path('password/set/', RedirectView.as_view(url='/')),
    path('password/reset/', RedirectView.as_view(url='/')),
    path('password/reset/done/', RedirectView.as_view(url='/')),
    re_path('^password/reset/key/(?P<uidb36>[0-9A-Za-z]+)-(?P<key>.+)/$',
            RedirectView.as_view(url='/')),
    path('password/reset/key/done/', RedirectView.as_view(url='/')),
    path('email/', RedirectView.as_view(url='/')),
    path('confirm-email/', RedirectView.as_view(url='/')),
    re_path('^confirm-email/(?P<key>[-:\\w]+)/$',
            RedirectView.as_view(url='/')),
]

dfrankow avatar Mar 25 '21 22:03 dfrankow

Maybe combine both signup form templates?

I should be able to query the social provider again and ask for an updated version of the user details when I need them

That can be done with userinfo/ url for each provider (maybe not Facebook).

Andrew-Chen-Wang avatar May 02 '21 17:05 Andrew-Chen-Wang

So at this point, the best solution is to just not include all the allauth URLs and include the social login URLs? for instance for Google and Facebook we do:

So my views.py would be:

from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from rest_auth.registration.views import SocialLoginView
class FacebookLogin(SocialLoginView):
    adapter_class = FacebookOAuth2Adapter
class GoogleLogin(SocialLoginView):
    adapter_class = GoogleOAuth2Adapter 

and my urls would be:

urls = [
...
path('rest-auth/facebook/', FacebookLogin.as_view(), name='fb_login'),
path('rest-auth/google/', GoogleLogin.as_view(), name='google_login')
...
]

source: https://medium.com/@pratique/social-login-with-react-and-django-i-c380fe8982e2

ashmlk avatar May 08 '21 02:05 ashmlk

In my project, I want to only allow google socail login/signup for member system. I don't want the users to access any pages exposed by default allauth setting. This is my example to import partial url.

## setting.py
from allauth.account.views import logout
from allauth.socialaccount.providers.google.views import oauth2_login, oauth2_callback

urlpatterns = [
    # path('accounts/', include('allauth.urls')), ## don't include all allauth urls
    path('accounts/logout/', logout, name="account_logout"),
    path('accounts/google/login/', oauth2_login, name="google_login"),
    path('accounts/google/login/callback/', oauth2_callback, name="google_callback"),
   ...
]

It could also block other urls such as signup, reset_passwrod...

rocmewtwo avatar May 16 '22 05:05 rocmewtwo

Close as discussion

derek-adair avatar Sep 18 '23 02:09 derek-adair

What do you all think of overriding the URLs themselves? So for example, adding entries like these to urls.py before including allauth.urls:

from django.urls import path, re_path
from django.views.generic import TemplateView, RedirectView

urlpatterns = [
    # These URLs shadow django-allauth URLs to shut them down:
    path('password/change/', RedirectView.as_view(url='/')),
    path('password/set/', RedirectView.as_view(url='/')),
    path('password/reset/', RedirectView.as_view(url='/')),
    path('password/reset/done/', RedirectView.as_view(url='/')),
    re_path('^password/reset/key/(?P<uidb36>[0-9A-Za-z]+)-(?P<key>.+)/$',
            RedirectView.as_view(url='/')),
    path('password/reset/key/done/', RedirectView.as_view(url='/')),
    path('email/', RedirectView.as_view(url='/')),
    path('confirm-email/', RedirectView.as_view(url='/')),
    re_path('^confirm-email/(?P<key>[-:\\w]+)/$',
            RedirectView.as_view(url='/')),
]

@dfrankow , can you elaborate why we need to override the 3 email URLs? Is it because it's more clean / secure / others? What if we don't override them?

shawnngtq avatar Oct 15 '23 01:10 shawnngtq

@dfrankow , can you elaborate why we need to override the 3 email URLs? Is it because it's more clean / secure / others? What if we don't override them?

It's been awhile, I don't remember the details.

I think I just picked every URL I thought was associated with creating an account. I probably thought the email links were confirmation emails for signing up for an account. Why allow account confirmation emails if you can't sign up for an account?

If you don't override them, I suppose you would leave some email functionality in place that might or might not work because there are no local accounts. Seems like a small hole that could enable some security vulnerabilities if there are any.

dfrankow avatar Oct 15 '23 13:10 dfrankow

This thread is a long meandering discussion going nowhere.

  1. @pennersr has clearly laid out a way to do this.
  2. @markunsworth's security loophole is a corner case that can be fixed by simply making your own account adapter that returns false always.
  3. This project was never intended to be used for pure social auth and would be a major undertaking to support such a request for very little gain. If the solutions proposed by @pennersr are not to your liking, there are other social auth applications you can implement that are just as high quality as this one.

derek-adair avatar Oct 15 '23 15:10 derek-adair

I've been working to implement django-two-factor-auth alongside allauth. I am using this package instead of django-allauth--2fa because we want to offer email and text message-based 2FA, and django-allauth--2fa only supports authenticator apps. Everything is working fine except that I need to ensure only the django-two-factor-auth login url is the one used for signing in when using username/password. It would be great if there was simply a settings variable such as DISABLE_ALLAUTH_LOGIN_URL (default=False) which, if True, simply prevented account_login from being included in the allauth urlconf.

DanielSwain avatar Jan 15 '24 16:01 DanielSwain