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.5k stars 3.03k forks source link

Spotify access token gets saved but refresh token doesn't save to DB. #2917

Closed kwakubiney closed 2 months ago

kwakubiney commented 3 years ago

SocialToken table has access token stored in token field but there's no field for refresh token. I have browsed the issues in this repo and I am seeing a field named token_secret field but it is empty. I am trying to use both refresh token and access token for some other application functionalities but I cannot achieve this because refresh token doesn't save to DB. Is there a workaround for this? I have seen https://github.com/pennersr/django-allauth/issues/420 but it does not help. Not much information about providers for Spotify is present in the docs too.

kwakubiney commented 3 years ago

Turns out token_secret field is not empty and it actually saves in the DB, does not show in the admin though.

eliataylor commented 2 months ago

in what table did you find the data @kwakubiney ? i'm still not seeing it in any socialaccount_* table. Are you using a custom adapter?

kwakubiney commented 2 months ago

Wow it's been a minute @eliataylor, I'm not sure I remember the details. This line makes me believe it exists in the SocialToken table. I think what I was alluding to was a query using .get() (whatever is used to get an object, forgotten the django orm API) probably returned the object with the field being non-empty, although it was looking empty in admin. Details still blurry though

eliataylor commented 2 months ago

@kwakubiney , yeah, i'm using the new Headless mode with Django 5 and no SocialAccount or SocialToken is being stored. I am able to approve the permissions with Spotify and can set breakpoints to see a Token is available here - https://github.com/pennersr/django-allauth/blob/ef8743ca85cf7dc06ac3a9b28d94a3b35b58cf7a/allauth/socialaccount/providers/spotify/views.py#L9 - but it's never saved in the DB or cookie to persist a session.

image

@pennersr , Headless mode and Django 5 is relatively new. Might the Spotify provider be missing a call to save the account and/or token?

pennersr commented 2 months ago

Storing the token is not code that is specific to headless. It occurs here:

https://github.com/pennersr/django-allauth/blob/main/allauth/socialaccount/models.py#L335

Can you try and set a breakpoint there?

If you don't end up there, could it be that you are using email authentication?

https://github.com/pennersr/django-allauth/blob/main/allauth/socialaccount/models.py#L313

eliataylor commented 2 months ago

@pennersr , I am using email authentication along with several others. My settings are


ACCOUNT_EMAIL_VERIFICATION = "optional" # since SMS only is allowed
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = False
ACCOUNT_LOGIN_BY_CODE_ENABLED = True
ACCOUNT_AUTHENTICATION_METHOD = "email"
ACCOUNT_CONFIRM_EMAIL_ON_GET = True

HEADLESS_ONLY = True

DEFAULT_HTTP_PROTOCOL = 'https'
FRONTEND_URL = os.environ.get("FRONTEND_URL")
LOGIN_REDIRECT_URL = f"{FRONTEND_URL}/account/provider/callback"
SIGNUP_REDIRECT_URL = f"{FRONTEND_URL}/account/provider/callback"

SOCIALACCOUNT_ADAPTER = 'example_app.adapter.MySocialAccountAdapter'

HEADLESS_FRONTEND_URLS = {
    "account_confirm_email": f"{FRONTEND_URL}/account/verify-email/{{key}}",
    # Key placeholders are automatically populated. You are free to adjust this to your own needs, e.g.
    "account_reset_password": f"{FRONTEND_URL}/account/password/reset",
    "account_reset_password_from_key": f"{FRONTEND_URL}/account/password/reset/key/{{key}}",
    "account_signup": f"{FRONTEND_URL}/account/signup",
    # Fallback in case the state containing the `next` URL is lost and the handshake
    # with the third-party provider fails.
    "socialaccount_login_error": f"{FRONTEND_URL}/account/provider/callback",
    "socialaccount_login": f"{FRONTEND_URL}/account/provider/callback",
}

MFA_SUPPORTED_TYPES = ["totp", "recovery_codes", "webauthn"]
MFA_PASSKEY_LOGIN_ENABLED = True

SOCIALACCOUNT_EMAIL_AUTHENTICATION=True
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT=True
SOCIALACCOUNT_EMAIL_REQUIRED=True
SOCIALACCOUNT_EMAIL_VERIFICATION=True
SOCIALACCOUNT_STORE_TOKENS=True
ACCOUNT_SESSION_REMEMBER =None
SOCIALACCOUNT_PROVIDERS = {
    'google': {
        'APP': {
            "name" : "google",
            "provider_id": "google",
            'client_id': os.environ.get('GOOGLE_OAUTH_CLIENT_ID', ""),
            'secret': os.environ.get('GOOGLE_OAUTH_SECRET', ""),
            'key': os.environ.get('GOOGLE_OAUTH_KEY', ""),
        },
        'FETCH_USERINFO': True,
        'SCOPE': [
            'profile',
            'email',
        ],
        'AUTH_PARAMS': {
            'access_type': 'online',
        }
    },
    "apple": {
        "APP": {
            "name" : "apple",
            "provider_id": "apple",

            # Your service identifier.
            "client_id": os.environ.get("APPLE_BUNDLE_ID", ""),

            # The Key ID (visible in the "View Key Details" page).
            "secret": os.environ.get("APPLE_KEY_ID", ""),

            # Member ID/App ID Prefix -- you can find it below your name
            # at the top right corner of the page, or it’s your App ID
            # Prefix in your App ID.
            "key": os.environ.get("APPLE_TEAM_ID", ""),

            "settings": {
                # The certificate you downloaded when generating the key.
                "certificate_key": os.environ.get("APPLE_KEY")
            }
        }
    },
    "spotify": {
        'SCOPE': ['user-read-email', 'user-top-read', 'user-read-recently-played', 'playlist-read-collaborative'],
        'AUTH_PARAMS': {'access_type': 'offline'},
        'METHOD': 'oauth2',
        'FETCH_USERINFO': True,
        'VERIFIED_EMAIL': False,
        'VERSION': 'v1',
        "APP": {

            "name" : "spotify",
            "provider_id": "spotify",

            "client_id": os.environ.get("SPOTIFY_CLIENT_ID"),
            "secret": os.environ.get("SPOTIFY_SECRET")
        },
    }
}
eliataylor commented 2 months ago

and I did try a breakpoint on _store_token which did not get hit when using the Spotify provider, but DOES get hit when using the Google provider. @pennersr

pennersr commented 2 months ago

Tokens are not stored when email authentication kicks in. TODO:

eliataylor commented 2 months ago

@pennersr , what does "email authentication" mean in the context of a Social Provider login / registration?

(i'll test SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = False meanwhile)

pennersr commented 2 months ago

SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT is not the issue, SOCIALACCOUNT_EMAIL_AUTHENTICATION = True is. Check the documentation what that is about.

eliataylor commented 2 months ago

ah. i had read these docs and didn't realized that would compromise storing the token. I should also add the session isn't even persisting for Spotity. My server logs these requests from the initial page load to the final redirect.

image

I've updated my above settings with

SOCIALACCOUNT_EMAIL_AUTHENTICATION=False
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT=True
SOCIALACCOUNT_EMAIL_REQUIRED=False
SOCIALACCOUNT_EMAIL_VERIFICATION=True
SOCIALACCOUNT_STORE_TOKENS=True
ACCOUNT_SESSION_REMEMBER =None

ACCOUNT_EMAIL_VERIFICATION = "optional" # since SMS only is allowed
ACCOUNT_EMAIL_REQUIRED = False
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = False
ACCOUNT_LOGIN_BY_CODE_ENABLED = True
# ACCOUNT_AUTHENTICATION_METHOD = "email"
ACCOUNT_CONFIRM_EMAIL_ON_GET = True
pennersr commented 2 months ago

ah. i had read these docs and didn't realized that would compromise storing the token.

It shouldn't, so it's a bug. Now, if SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT is False then it is still not possible to store the tokens as SocialToken points to a SocialAccount which is not persisted without having that setting set to True.

pennersr commented 2 months ago

@eliataylor I am afraid I was jumping to conclusions here. I have added a test case specifically targetting this to confirm, and tokens are stored fine, provided auto connect is turned on. Can you also confirm on your end that this is the case?

So the only thing that can be done here is indeed add a note about this to the documentation of SOCIALACCOUNT_STORE_TOKENS.

pennersr commented 2 months ago

See #4032 -- it adds the test and note to the docs.

pennersr commented 2 months ago

Merged.

eliataylor commented 2 months ago

Thanks @pennersr !!!

I did discover my own tests were in the case of SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT where the email Spotify has a preexisting user (my admin). The SPA flow was routing me to account/provider/signup and I wasn't realizing that part the flow. I'm still running through the variations of Spotify accounts with and without existing users. Thanks