pennersr / django-allauth

Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication.
https://allauth.org
MIT License
9.51k stars 3.03k forks source link

SocialTokens table empty after Google Login #3309

Closed FSchianchi closed 1 year ago

FSchianchi commented 1 year ago

Hello there,

I'm working on a webpage which I have to connect to the Company Google Workspace. I correctly managed the login, with no issues. Now, I have to connect to the Gmail of the authenticated user.

For connecting to Google Apps I need a Token that, as I understood, should be in the Database in the table SocialTokens (automatically filled-in after the login phase).

I populated Sites and Social Application manually, as below: Sites SocialApp

The authentication works properly, but after the login the model SocialToken (table "Social application tokens") is still empty. The account is correctly created: SocialAccount

But I cannot figure out why the table SocialTokens is not populated after the correct Google login. SocialTokens

Here my settings.py

SITE_ID = 1

INSTALLED_APPS = [
    'django.contrib.admin',
    ...,
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.google',
]

SOCIALACCOUNT_PROVIDERS = {
    'google':{
        'SCOPE': [
            'profile',
            'email',
        ],
        'AUTH_PARAMS': {'access_type':'offline'},
    }
}

SOCIALACCOUNT_STORE_TOKEN = True

SOCIALACCOUNT_AUTO_SIGNUP = True

I tried what below:

Could anyone tell me if I wrote something wrong or if I missed anything, or if I misunderstood anything?

Thanks for any help.

Jaswine commented 1 year ago

Have you solved your problem?

FSchianchi commented 1 year ago

Not yet. Also, if this is not the right place in which leaves this kind of Issues, please let me know.

FSchianchi commented 1 year ago

Hi, thanks for the reply. I added credentials on db model allauth, in the table SocialApp (second image of the issue #3309), is that the one you meant? It doesn't work if you mean it.

Il giorno gio 4 mag 2023 alle ore 15:42 Thomas Schwärzl < @.***> ha scritto:

Tokens don't get stored if you configure your oauth app (google) via settings.py. You have to add it into the database model of allauth or use a custom provider which fakes the database model entry using e.g. ENV vars.

Related:

2467 https://github.com/pennersr/django-allauth/issues/2467

2467 (comment)

https://github.com/pennersr/django-allauth/issues/2467#issuecomment-663015581

3198 https://github.com/pennersr/django-allauth/issues/3198

— Reply to this email directly, view it on GitHub https://github.com/pennersr/django-allauth/issues/3309#issuecomment-1534805241, or unsubscribe https://github.com/notifications/unsubscribe-auth/AKQSZLO3FASRXBNIGXOHOD3XEOW35ANCNFSM6AAAAAAXUL4ABQ . You are receiving this because you authored the thread.Message ID: @.***>

Jaswine commented 1 year ago

I have the same problem. Did you see what comes during registration?

FSchianchi commented 1 year ago

No, I am looking for the right place in which Google writes down logs of useful information. Have you found anything interesting?

Il giorno ven 5 mag 2023 alle ore 08:43 Vaseles @.***> ha scritto:

I have the same problem. Did you see what comes during registration?

— Reply to this email directly, view it on GitHub https://github.com/pennersr/django-allauth/issues/3309#issuecomment-1535789446, or unsubscribe https://github.com/notifications/unsubscribe-auth/AKQSZLI5NQQFQAUCCM5VTDDXESOQRANCNFSM6AAAAAAXUL4ABQ . You are receiving this because you authored the thread.Message ID: @.***>

FSchianchi commented 1 year ago

I found a workaround, not a solution yet.

With a condition based on your code, only the first time you need the Access Token, you can make a GET request to https://accounts.google.com/o/oauth2/v2/auth adding prompt=consent to the URL for Auth Code request (just before &access_type=offline). This string let you be able again to retrieve the Refresh Token, or Token Secret, after the log-in phase and all fields to create the DB object you will use from here on.

After the first time use the Refresh Token to update the Access Token when it expires (3599 ms - 1h)

Jaswine commented 1 year ago

I found information about adding an adapter SOCIALACCOUNT_ADAPTER = 'allauth.socialaccount.providers.google.views.GoogleOAuth2Adapter', but it also doesn't work for me

FSchianchi commented 1 year ago

For me neither. Try also to flush your db, because the Token it's created only during the first request; but it didn't work in my case.

Jaswine commented 1 year ago

Today I deleted this database 5 times and migrated it again

FSchianchi commented 1 year ago

I got you, for me the same. Try the "unclean" workaround, if it is urgent, I will try to find a solution and if you find one please let me know.

Jaswine commented 1 year ago

hi, I found more settings for google, did u solve this problem? 'google': { 'SCOPE': [ 'profile', 'email', 'https://www.googleapis.com/auth/gmail.readonly', ], 'AUTH_PARAMS': { 'access_type': 'online',

'prompt': 'consent',

    },
    'APP': {
        'client_id': os.environ.get('GOOGLE_CLIENT_ID'),
        'secret': os.environ.get('GOOGLE_SECRET'),
        'key': ''
    },
    'METHOD': 'oauth2',
    'ACCESS_TOKEN_METHOD': 'POST',
    'PROVIDER_ID': 'google',
    # 'ACCESS_TOKEN_URL': 'https://oauth2.googleapis.com/token',
    # 'AUTHORIZATION_URL': 'https://accounts.google.com/o/oauth2/auth',
    'REQUEST_TOKEN_URL': None,
    # 'USER_DATA_URL': 'https://www.googleapis.com/oauth2/v2/userinfo',
    'VERIFIED_EMAIL': True,
    'USE_SSL': True,
    'REDIRECT_URI': 'http://localhost:8000/accounts/google/login/callback/',
    # 'SOCIAL_TOKEN_MODEL': 'allauth.models.SocialToken', # custom model

} but it doesn't work. When i use ACCESS_TOKEN_URL and AUTHORIZATION_URL i see a white screen

nikklavzar commented 1 year ago

Any updates on this? I am having the same issue with Django 4.2. SOCIALACCOUNT_STORE_TOKEN = True does nothing. The Social Account is created, but the Social Token table is empty.

Jaswine commented 1 year ago

Any updates on this? I am having the same issue with Django 4.2. SOCIALACCOUNT_STORE_TOKEN = True does nothing. The Social Account is created, but the Social Token table is empty.

no, I also have this problem

Jaswine commented 1 year ago

I've already logged in, nothing, everything is supposedly fine in the logs, all that's left is to completely rewrite the function (or roll back the Django version back +- django-allauth

Jaswine commented 1 year ago

please write if you solve this problem

nikklavzar commented 1 year ago

please write if you solve this problem

As a workaround, I'm using the pre_social_login signal to catch the token. I noticed the SocialToken object is missing the app_id and account_id, so I set them here myself and save it to the database.

import allauth.models

from allauth.socialaccount.signals import pre_social_login
from django.apps import AppConfig

def pre_social_login_callback(request, sociallogin, **kwargs):
    socialtoken = sociallogin.token
    socialaccount = sociallogin.account
    google_socialapp = allauth.models.SocialApp.objects.get(provider="google")
    socialtoken.app_id = google_socialapp.id
    socialtoken.account_id = socialaccount.id
    socialtoken.save()

class CoreConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "core"

    def ready(self):
        pre_social_login.connect(pre_social_login_callback)

Docs: https://django-allauth.readthedocs.io/en/latest/signals.html#allauth-socialaccount

Jaswine commented 1 year ago

How to fix error " RelatedObjectDoesNotExist at /accounts/google/login/callback/ SocialAccount has no user. " when user registers for the first time?

spiridon-alexandru commented 1 year ago

It might be a typo, it seems that you're using SOCIALACCOUNT_STORE_TOKEN, but from the docs, plural is used SOCIALACCOUNT_STORE_TOKENS.

nikklavzar commented 1 year ago

It might be a typo, it seems that you're using SOCIALACCOUNT_STORE_TOKEN, but from the docs, plural is used SOCIALACCOUNT_STORE_TOKENS.

Oh, I'm actually using the plural, made a typo in the comment

nikklavzar commented 1 year ago

How to fix error " RelatedObjectDoesNotExist at /accounts/google/login/callback/ SocialAccount has no user. " when user registers for the first time?

The base user is not automatically created, only the social account is, so you have to add something like this to the pre_social_login_callback hook to create the user if it doesn't yet exist:

user = socialaccount.user if hasattr(socialaccount, "user") else None

if not user:
    user = User.objects.create_user(
        username=socialaccount.uid, email=socialaccount.extra_data["email"]
    )
    sociallogin.connect(request, user)

You don't have to use socialaccount.uid for the username.

Jaswine commented 1 year ago

more thanks for answer

FSchianchi commented 1 year ago

Perfect, I'll try it within today. Thank you so much for the answare!

If it worked, the problem would be the SOCIALACCOUNT_PROVIDERS in the wrong place.

About the SITE_ID I tested both 1 and 2, with 1 I deleted example.com in the list of sites and it works, with 2 I keep example.com in the list of sites and it works as well.

pennersr commented 1 year ago

The SocialToken model has a foreign key to the SocialApp the token originates from. Therefore, storing the token currently only works when having the app configuration in the database (in the SocialApp model) and not in the settings.

jnoring commented 1 year ago

The SocialToken model has a foreign key to the SocialApp the token originates from. Therefore, storing the token currently only works when having the app configuration in the database (in the SocialApp model) and not in the settings.

It looks like allauth.socialaccount.models.SocialLogin.save is where this token is persisted, and reading the code, what you're saying does check out:

    def save(self, request, connect=False):
        """
        Saves a new account. Note that while the account is new,
        the user may be an existing one (when connecting accounts)
        """
        user = self.user
        user.save()
        self.account.user = user
        self.account.save()
        if app_settings.STORE_TOKENS and self.token and self.token.app_id:
            self.token.account = self.account
            self.token.save()
        if connect:
            # TODO: Add any new email addresses automatically?
            pass
        else:
            setup_user_email(request, user, self.email_addresses)

The check to see if the token links to an app_id would preclude storing tokens for applications that have opted to do a settings.py configuration verses configuring a SocialApp reference.

Looking through the code, why not make SocialToken's foreign key to SocialApp nullable? Maybe there are other consequences of this, but I don't see why SocialToken is reliant on having a clear link to SocialApp. And that this fails with no explanation at all is frustrating, and that it means I need to do some sort of django admin work to enable SSO rather than having it be a part of application configuration is also problematic (also, it's not at all clear to me how to configure scopes and other settings when using the SocialApp model).

Would you welcome an MR that fixed this issue for applications opting to resolve this in settings? A simple change to make that column nullable (which is also generally a very safe database migration) seems like it would fix it, but possible I don't understand other issues.

pennersr commented 1 year ago

@jnoring You're right, it's changed, see: d15c49b7

jnoring commented 1 year ago

@jnoring You're right, it's changed, see: d15c49b

Oh man: I owe you. I was in the process of monkeypatching to effectively do the same change, but I really appreciate it. If you're willing to push out a release, I'm happy to test that immediately and give you feedback today? Alternately, I can try out my monkeypatch and report back on if I hit any issues before you do a release (I'm most of the way there, and my django migrate generated identical code to what you just wrote)

pennersr commented 1 year ago

No problem. It's a bit too early for a release, but just install using:

pip install git+https://github.com/pennersr/django-allauth.git@main#egg=django-allauth`
jnoring commented 1 year ago

One quick issue I run into:

allauth.account.middleware.AccountMiddleware must be added to settings.MIDDLEWARE

Let me investigate that real quick.

jnoring commented 1 year ago

@pennersr Looks like one issue with the implementation I proposed above:


Internal Server Error: /sso/google/login/callback/
--
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/allauth/socialaccount/providers/oauth2/views.py", line 86, in view
return self.dispatch(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/allauth/socialaccount/providers/oauth2/views.py", line 163, in dispatch
return complete_social_login(request, login)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/allauth/socialaccount/helpers.py", line 202, in complete_social_login
return _complete_social_login(request, sociallogin)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/allauth/socialaccount/helpers.py", line 223, in _complete_social_login
ret = _process_signup(request, sociallogin)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/allauth/socialaccount/helpers.py", line 91, in _process_signup
get_adapter().save_user(request, sociallogin, form=None)
File "/usr/local/lib/python3.11/site-packages/allauth/socialaccount/adapter.py", line 90, in save_user
sociallogin.save(request)
File "/usr/local/lib/python3.11/site-packages/allauth/socialaccount/models.py", line 262, in save
self.token.save()
File "/usr/local/lib/python3.11/site-packages/django/db/models/base.py", line 778, in save
self._prepare_related_fields_for_save(operation_name="save")
File "/usr/local/lib/python3.11/site-packages/django/db/models/base.py", line 1093, in _prepare_related_fields_for_save
raise ValueError(
ValueError: save() prohibited to prevent data loss due to unsaved related object 'app'.

What's happening is: token.app is set, but it points to a SocialApp instance that has not been saved (in other words, self.token.app_id is not set, but self.token.app is). So, when django goes to save the token, it explodes.

I think adding:

        if app_settings.STORE_TOKENS and self.token:
            if self.token.app and not self.token.app_id:
                self.token.app = None
            self.token.account = self.account
            self.token.save()

Would do the trick? Although unclear what other consequences there are to this change.

jnoring commented 1 year ago

(also happy to submit a pull request if that would help you)

pennersr commented 1 year ago

Please use this: 639b4668

jnoring commented 1 year ago

@pennersr can confirm: that works! Thanks so much for your help on this, really appreciate it.