django-allauth: Tokens do not get stored when using settings-based provider credentials

Version

django-allauth: 0.41.0
Django: 3.0.3
Python: 3.8.1

Description Like #2423, when I configure provider credentials in SOCIALACCOUNT_PROVIDERS[<provider>]['APP'], tokens do not get stored in the database even when SOCIALACCOUNT_STORE_TOKENS is True.

Somewhat like #2310, when I set SOCIALACCOUNT_AUTO_SIGNUP to False and try to sign up with a Google account, I get the following error once I complete the required form:

RelatedObjectDoesNotExist at /accounts/social/signup/

SocialToken has no app.

This error traces back to the following line of code: https://github.com/pennersr/django-allauth/blob/9e8e8b1c531fc81d5bc4bfb4b69312c00b88b284/allauth/socialaccount/models.py#L233

Since app is a non-null ForeignKey field of SocialToken (as seen below) and Django is raising a RelatedObjectDoesNotExist error, self.token.app is either None or does not exist in the database and cannot be accessed via ForeignKey. https://github.com/pennersr/django-allauth/blob/9e8e8b1c531fc81d5bc4bfb4b69312c00b88b284/allauth/socialaccount/models.py#L127-L128

I never had to create a SocialApp because I’m using settings-based provider credentials. I found the current implementation (as seen below) does create a SocialApp derived from settings, but never saves it to the database which, again, means it cannot be accessed via ForeignKey. https://github.com/pennersr/django-allauth/blob/9f65d3c9f1171e58a61014b85a04c0f41a82b809/allauth/socialaccount/adapter.py#L193-L204

It should be noted that:

  • This error occurs before an associated EmailAddress can be created and stored for the new user which may cause further issues depending on email verification settings.
  • This error only occurs when SOCIALACCOUNT_AUTO_SIGNUP is False. When SOCIALACCOUNT_AUTO_SIGNUP is True, I can sign up with a Google account without encountering this error.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 16
  • Comments: 18 (2 by maintainers)

Commits related to this issue

Most upvoted comments

Work-around: Define a custom SocialAccountAdapter in settings SOCIALACCOUNT_ADAPTER = "my_project.my_socialaccount_adapter.MySocialAccountAdapter"

from allauth.socialaccount.adapter import DefaultSocialAccountAdapter


class MySocialAccountAdapter(DefaultSocialAccountAdapter):
    """
    Temporary work-around for: https://github.com/pennersr/django-allauth/issues/2467
    """

    def get_app(self, request, provider):
        # NOTE: Avoid loading models at top due to registry boot...
        from allauth.socialaccount.models import SocialApp

        # 1 added line here
        from allauth.socialaccount import app_settings

        config = app_settings.PROVIDERS.get(provider, {}).get('APP')
        if config:
            app = SocialApp(provider=provider)
            for field in ['client_id', 'secret', 'key']:
                setattr(app, field, config.get(field))

            # 3 added lines here
            app.key = app.key or "unset"
            app.name = app.name or provider
            app.save()

        else:
            app = SocialApp.objects.get_current(provider, request)
        return app

I faced similar issue when I enabled social signup page which is what I wanted. Looking at the error message ( if condition) I got a clue and tried to turn off Social Token as follows in settings.py SOCIALACCOUNT_STORE_TOKENS = False

Things work as developer expected it to work without coding for custom Social Account Adapter.

Here’s my workaround, taking from the others who have posted here:

class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
    def get_app(self, request, provider):
        # NOTE: Avoid loading models at top due to registry boot...
        from allauth.socialaccount.models import SocialApp

        # 1 added line here
        from allauth.socialaccount import app_settings

        config = app_settings.PROVIDERS.get(provider, {}).get('APP')
        app = SocialApp.objects.get_or_create(provider=provider)[0]
        app.client_id = config['client_id']
        app.secret = config['secret']
        app.key = config.get('key', '')
        return app

i didn’t see the point of saving the secrets in raw to the db.

This was fixed via 639b4668

So the current workaround is to not use settings based credentials and to make sure that when creating the social application instance in the admin, to ensure the correct site is included in the list.

also, overriding scope is ok in the settings, you just can’t use the “APP” key

    "vimeo_oauth2": {
        "SCOPE": ["public"],
    },

I had this issue on a newly setup Django project and I was able to figure out the root of this issue in my case. Looking at the socialapp_sites table which has the app_id and site_id I noticed the site_id was set to 3 (I set my settings.py SITE_ID = 2). I only have two sites, the example.com and the 127.0.0.1 which i created in admin panel. After looking at django_site table it showed that #2 was assigned to example.com so when I created the local host site it was given #3. How did this happen? I copied the settings from the first attempt of my project and the site_id was set to 2. I migrated that and then went ahead and created the 127.0.0.1 site. (In my first attempt I had site_id = 1 before migrating). So simply changing the SITE_ID to 3 in settings.py and making sure that SOCIALACCOUNT_STORE_TOKENS was set to True solved this for me.

Thanks @benjaoming for the workaround! As of November 16, 2022 the issue (and the workaround) still exist for me:

django==4.0
djangorestframework==3.14.0
django-allauth==0.51.0

SocialToken’s require a matching SocialApp to link to via a ForeignKey there is nothing conditional about the SocialApp object existing, it must exist. What this workaround is doing is creating the SocialApp with dummy data so that the client id and secret are still loaded from the SOCIALACCOUNT_PROVIDERS[...]['APP'] .

I think the proposed workaround needs some rethinking because it’ll be creating a new SocialApp entry every time get_app is called. It needs to check for an existing entry.