wagnerdelima / drf-social-oauth2

drf-social-oauth2 makes it easy to integrate Django social authentication with major OAuth2 providers, i.e., Facebook, Twitter, Google, etc.
https://drf-social-oauth2.readthedocs.io/en/latest/
MIT License
276 stars 34 forks source link

"Invalid backend parameter" for AzureAD Tenant #27

Open cheslijones opened 4 years ago

cheslijones commented 4 years ago

Been struggling to get this implemented and find the documentation pretty vague. I'd like to help improve it if I can get this working.

Related documentaiton:

https://python-social-auth.readthedocs.io/en/latest/backends/azuread.html

# settings.py
INSTALLED_APPS = [
    ...
    'drf_social_oauth2',
    'oauth2_provider',
    'rest_framework',
    'social_django',
    ...
]

TEMPLATES = [
    {
        ...
        'OPTIONS': {
            'context_processors': [
                ...
                'social_django.context_processors.backends',
                'social_django.context_processors.login_redirect',
            ],
        },
    },
]

REST_FRAMEWORK = {
    ...
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
        'oauth2_provider.contrib.rest_framework.OAuth2Authentication', 
        'drf_social_oauth2.authentication.SocialAuthentication',
    ),
    ...
}

SOCIAL_AUTH_POSTGRES_JSONFIELD = True

SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_KEY = '<azure_client_id>'
SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRET = '<azure_client_secret>'
SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_TENANT_ID = '<azure_tenant_id>'

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'drf_social_oauth2.backends.DjangoOAuth2',
    'social_core.backends.azuread_tenant.AzureADTenantOAuth2',
)
# urls.py

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('api/admin/', admin.site.urls),
    path('api/users/auth/azuread/', include('drf_social_oauth2.urls', namespace='drf')),
    path('api/users/', include('users.urls', namespace='users')),
]

And then this is what I get when I try to run a POST request in Postman:

image

I've tried:

Really isn't clear what the values of backend need to be, what are acceptable, where they can be found, or if it is an arbitrary designation.

Also, in these curl requests:

curl -X POST -d "grant_type=convert_token&client_id=<client_id>&client_secret=<client_secret>&backend=facebook&token=<facebook_token>" http://localhost:8000/auth/convert-token

It seems like <client_id> and <client_secret> are the values for the App in Django Admin, and not for the backend you are using (those are declared in the settings.py).

There isn't a convert_token type for the App in Django Admin, so it is unclear what the Authorization grant type should be set at for the App in there.

Also, token= isn't really discussed at all in the "Integration Examples", nor in the python-social-auth, or oauth2-provider... so not sure if this is a value that is supposed to be generated by the backend provider when you are setting up the integration on their platform, or if it is something that is returned from the OAuth2 request and the /convert-token is a second step...

At any rate, the primary issue is the invalid backend parameter.

wagnerdelima commented 4 years ago

Hi @cheslijones. Thanks for creating the issue.

Let's go by parts as there is a lot of information there. So backend should be what python-social-auth allows us to use, In my case I use backed=facebook to authenticate against Facebook Auth API.

The client_id and clint_secret are the values auto-generated from the Application you must create. Just go the Application in the admin and create a new Application there. With regards of these values:

SOCIAL_AUTH_AZUREAD_OAUTH2_KEY = '' SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET = ''

They must be retrieved from Azure. I myself never used Azure, but in order to authenticate with facebook I had to create a new facebook app. This new app provided me with a client key and client secret. Those must be set up in the settings.py.

the token is indeed not clear. This is the token generated from the authentication against your backend. See this, once you authenticate using your username/email and password, your backend -- in my case Facebook -- will respond back with a token. In facebook this token may be generated for test purposes in this link: https://developers.facebook.com/tools/explorer/. Therefore you need to find a test api for azure of authenticate against them.

wagnerdelima commented 4 years ago

Regarding your INSTALLED_APPS, you have the following:

INSTALLED_APPS = [
    ...
    'drf_social_oauth2',
    'oauth2_provider',
    'rest_framework',
    'social_django',
    ...
]

Please, replace with the following:

INSTALLED_APPS = [
    ...
    'oauth2_provider', # this needs to be listed first.
    'social_django',      # social django uses oauth2_provider
    'drf_social_oauth2', # this guy uses the two listed above
    'rest_framework',
    'rest_framework.authtoken', # see if you need this as well!
     ....
]

Otherwise, the migrations will not properly!

wagnerdelima commented 4 years ago

One more question, where did you see these values at the documentation: backend=azuread_tenant backend=AzureADTenantOAuth2 backend=azureadtenantoauth2

I could not see them.

cheslijones commented 4 years ago

Thanks for the quick reply.

Let's go by parts as there is a lot of information there. So backend should be what python-social-auth allows us to use, In my case I use backed=facebook to authenticate against Facebook Auth API.

One more question, where did you see these values at the documentation: backend=azuread_tenant backend=AzureADTenantOAuth2 backend=azureadtenantoauth2 I could not see them.

These two go together. I'm honestly guessing what it should be. I don't see it notated anywhere what exactly are acceptable values for this. Based on the two Integration examples:

    # Google OAuth2
    'social_core.backends.google.GoogleOAuth2',

    backend=google-oauth2
    # Facebook OAuth2
    'social_core.backends.facebook.FacebookAppOAuth2',

    backend=facebook

So I figured for Azure it had to be one of the following:

backend=azuread_tenant
backend=AzureADTenantOAuth2
backend=azureadtenantoauth2

Based on...

'social_core.backends.azuread_tenant.AzureADTenantOAuth2',

I was apparently wrong in this assumption.

But the python-social-auth documentation doesn't really say what acceptable values are, unless I'm overlooking it.

https://python-social-auth.readthedocs.io/en/latest/backends/azuread.html

Is this value supposed to correspond with the app name for the app created in Django Admin, by chance?

The client_id and client_secret are the values auto-generated from the Application you must create. Just go the Application in the admin and create a new Application there. With regards of these values:

SOCIAL_AUTH_AZUREAD_OAUTH2_KEY = '' SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET = ''

They must be retrieved from Azure. I myself never used Azure, but in order to authenticate with facebook I had to create a new facebook app. This new app provided me with a client key and client secret. Those must be set up in the settings.py.

Thanks for confirming. That is what it sounded like to me.

the token is indeed not clear. This is the token generated from the authentication against your backend. See this, once you authenticate using your username/email and password, your backend -- in my case Facebook -- will respond back with a token. In facebook this token may be generated for test purposes in this link: https://developers.facebook.com/tools/explorer/. Therefore you need to find a test api for azure of authenticate against them.

Thanks, I'll look into this in Azure.

I reorganized my INSTALLED_APPS per your suggestion and I'll keep testing.

cheslijones commented 4 years ago

Well, I finally guessed the backend= correctly... backend=azuread-tenant-oauth2. Now I'm getting:

[api]     data = self.user_data(access_token, *args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/social_core/backends/azuread_tenant.py", line 97, in user_data
[api]     id_token = response.get('id_token')
[api] AttributeError: 'NoneType' object has no attribute 'get'

I still need to sort out the token= in Azure, but at least progress.

wagnerdelima commented 4 years ago

I am happy that you managed to find the backend name. Look, what is the version of python-social-auth you're using? I have just visited this page: https://github.com/python-social-auth/social-core/blob/master/social_core/backends/azuread_tenant.py.

This is the core from the file where your had the error. I can't find the same code in line 97!

wagnerdelima commented 4 years ago

@cheslijones do you have any news about this?

cheslijones commented 4 years ago

@wagnerdelima Hi, I actually had to throw in the towel on this and went with a client-side ReactJS solution for handling it. So far it has worked as expected.

But to answer your question I was using 0.3.6 which I believe is the latest and it doesn't look like it has been updated in four years.

wagnerdelima commented 4 years ago

I am sorry you had a bad experience. I think you were using a very old version. The latest version is 3.4.0 and the last commit is from Jun 21, 2020.

That may be the reason!

cheslijones commented 4 years ago

Ok, I'm back with hopefully a better understanding of how I need to implement this.

My flow, as I understand it should be working between microservices, is the following:

  1. User navigates to domain.com/admin which is my ReactJS FE.
  2. This redirects them to Microsoft login to authenticate against Azure AD using react-aad.
  3. This provides the accessToken.
  4. This accessToken needs to be sent to the Django/DRF API to also be validated because otherwise, any string could be passed from the FE and accepted--the API also needs to verify it.
  5. A combination of drf-social-oauth2, oauth2_provider, and social_django are supposed to take this accessToken, validate it against Azure AD from the API, and convert the token to one to be used between my ReactJS FE and the Django/DRF API.

Is this correct?

If so, I have 1-3 working. Now I'm trying to send it to my API to also be validated for subsequent requests. This is where I'm struggling with the pretty much the same issues I had before.

Here is what I have:

// Authentication.js

// HAVING PARAMS IN THE FE IS A SECURITY ISSUE
// THIS WILL BE CHANGED AND MOVED TO THE BE
// WHEN THIS IS WORKING... THIS IS FOR TESTING ONLY
...
  const testApiAuthentication = async () => {
    let accessToken = await authProvider.getAccessToken();
    setAccessToken(accessToken.accessToken);
    if (accessToken) {
      setAuthenticatingToken(true);
      axios({
        method: 'post',
        url: '/api/auth/convert-token',
        params: {
          grant_type: 'convert_token',
          client_id: '<client_id_from_django_admin>',
          client_secret: '<client_secret_from_django_admin>',
          backend: 'azuread-tenant-oauth2',
          token: accessToken.accessToken
        }
      })
        .then((response) => {
          console.log(response);
        })
        .catch((error) => {
          console.log(error);
        });
    }
  };
...
# requirements.txt
asgiref==3.2.10
astroid==2.4.2
attrs==20.2.0
autobahn==20.7.1
Automat==20.2.0
certifi==2020.6.20
cffi==1.14.3
channels==2.4.0
chardet==3.0.4
constantly==15.1.0
cryptography==3.1.1
daphne==2.5.0
defusedxml==0.7.0rc1
Django==3.1.2
django-oauth-toolkit==1.3.3
djangorestframework==3.12.1
djangorestframework-simplejwt==4.4.0
drf-social-oauth2==1.0.8
hyperlink==20.0.1
idna==2.10
incremental==17.5.0
isort==5.6.4
lazy-object-proxy==1.4.3
mccabe==0.6.1
oauthlib==3.1.0
psycopg2-binary==2.8.6
pyasn1==0.4.8
pyasn1-modules==0.2.8
pycparser==2.20
PyHamcrest==2.0.2
PyJWT==1.7.1
pylint==2.6.0
pyOpenSSL==19.1.0
python3-openid==3.2.0
pytz==2020.1
requests==2.24.0
requests-oauthlib==1.3.0
service-identity==18.1.0
six==1.15.0
social-auth-app-django==4.0.0
social-auth-core==3.3.3
sqlparse==0.4.1
toml==0.10.1
Twisted==20.3.0
txaio==20.4.1
urllib3==1.25.11
whitenoise==5.2.0
wrapt==1.12.1
zope.interface==5.1.2
# settings.py
"""
Django settings for config project.

Generated by 'django-admin startproject' using Django 3.1.2.

For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""
import os
from datetime import timedelta

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ['SECRET_KEY']

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ['DEBUG'] == 'True'

ALLOWED_HOSTS = [
    os.environ['DOMAIN'],
    'companyapp.local',
    '.companyapp.com'
]

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # Third-party packages
    'channels',
    'rest_framework',
    'oauth2_provider',
    'social_django',
    'drf_social_oauth2',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
]

ROOT_URLCONF = 'config.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'social_django.context_processors.backends',
                'social_django.context_processors.login_redirect',                
            ],
        },
    },
]

WSGI_APPLICATION = 'config.wsgi.application'
ASGI_APPLICATION = "config.routing.application"

# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ['PGDATABASE'],
        'USER': os.environ['PGUSER'],
        'PASSWORD': os.environ['PGPASSWORD'],
        'HOST': os.environ['PGHOST'],
        'PORT': 5432
    }
}

# REST Framework settings
# https://www.django-rest-framework.org/
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
        'oauth2_provider.contrib.rest_framework.OAuth2Authentication',  # django-oauth-toolkit >= 1.0.0
        'drf_social_oauth2.authentication.SocialAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ],
}

# SIMPLE_JWT Settings
# https://github.com/davesque/django-rest-framework-simplejwt
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(days=3),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    'SIGNING_KEY': os.environ['SECRET_KEY'],
}

AUTHENTICATION_BACKENDS = (
    'social_core.backends.azuread_tenant.AzureADTenantOAuth2',
    'drf_social_oauth2.backends.DjangoOAuth2',
    'django.contrib.auth.backends.ModelBackend',
)

SOCIAL_AUTH_POSTGRES_JSONFIELD = True

SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_KEY = '<client_id_from_azure_apps>'
SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRET = '<client_secret_from_azure_apps>'
SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_TENANT_ID = '<tenant_id_from_azure>'

# Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'America/Los_Angeles'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/

STATIC_URL = '/static/'

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
# STATIC_URL = '/static/'
STATIC_URL = '/api/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

# Define the upload and download directories
DOWNLOAD_ROOT = '/mnt/company-files/client-downloads/'
MEDIA_ROOT = '/mnt/company-files/client-submissions/'
# urls.py

from django.contrib import admin
from django.urls import path, include

from users.views import TestView

urlpatterns = [
    path('api/admin/', admin.site.urls),
    path('api/users/', TestView.as_view()),
    path('api/auth/', include('drf_social_oauth2.urls', namespace='drf')),
]

And I get the following error:

[api] Internal Server Error: /api/auth/convert-token
[api] Traceback (most recent call last):
[api]   File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
[api]     response = get_response(request)
[api]   File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 179, in _get_response
[api]     response = wrapped_callback(request, *callback_args, **callback_kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
[api]     return view_func(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/django/views/generic/base.py", line 70, in view
[api]     return self.dispatch(request, *args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/django/utils/decorators.py", line 43, in _wrapper
[api]     return bound_method(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
[api]     return view_func(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/drf_social_oauth2/views.py", line 37, in dispatch
[api]     return super(CsrfExemptMixin, self).dispatch(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
[api]     response = self.handle_exception(exc)
[api]   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
[api]     self.raise_uncaught_exception(exc)
[api]   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
[api]     raise exc
[api]   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 506, in dispatch
[api]     response = handler(request, *args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/drf_social_oauth2/views.py", line 93, in post
[api]     url, headers, body, status = self.create_token_response(request._request)
[api]   File "/usr/local/lib/python3.8/site-packages/oauth2_provider/views/mixins.py", line 124, in create_token_response
[api]     return core.create_token_response(request)
[api]   File "/usr/local/lib/python3.8/site-packages/drf_social_oauth2/oauth2_backends.py", line 30, in create_token_response
[api]     return super(KeepRequestCore, self).create_token_response(request)
[api]   File "/usr/local/lib/python3.8/site-packages/oauth2_provider/oauth2_backends.py", line 148, in create_token_response
[api]     headers, body, status = self.server.create_token_response(uri, http_method, body,
[api]   File "/usr/local/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/endpoints/base.py", line 116, in wrapper
[api]     return f(endpoint, uri, *args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/drf_social_oauth2/oauth2_endpoints.py", line 91, in create_token_response
[api]     return grant_type_handler.create_token_response(
[api]   File "/usr/local/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py", line 60, in create_token_response
[api]     self.validate_token_request(request)
[api]   File "/usr/local/lib/python3.8/site-packages/drf_social_oauth2/oauth2_grants.py", line 100, in validate_token_request
[api]     user = backend.do_auth(access_token=request.token)
[api]   File "/usr/local/lib/python3.8/site-packages/social_core/utils.py", line 251, in wrapper
[api]     return func(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/social_core/backends/oauth.py", line 410, in do_auth
[api]     data = self.user_data(access_token, *args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/social_core/backends/azuread_tenant.py", line 97, in user_data
[api]     id_token = response.get('id_token')
[api] AttributeError: 'NoneType' object has no attribute 'get'

I have verified the request is URL params are formed correctly. Reluctant to post the client_id, client_secret, and token though.

Any suggestions as to what I'm doing wrong?

wagnerdelima commented 4 years ago

Yes, it seems you have figured out what you have to do. So, let's try to change the order of your installed apps to:

...
'oauth2_provider',
'social_django',
'drf_social_oauth2',
'rest_framework',
'rest_framework.authtoken',

Your DEFAULT_PERMISSION_CLASSES should also change (unrelated to your problem): 'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated'],

Please try switching the order of things on the installed apps. I really don't think this is the cause of your troubles. I personally don't have a clue why you're getting this. Please let me know if you managed so we can plan a debug otherwise.

cheslijones commented 4 years ago

Gave it a shot and still running into the same issue.

I was playing with DRFSO2_PROPRIETARY_BACKEND_NAME and when I add:

DRFSO2_PROPRIETARY_BACKEND_NAME = 'azuread-tenant-oauth2'

I get a different error so I'm looking into that now:

[api] Internal Server Error: /api/auth/convert-token
[api] Traceback (most recent call last):
[api]   File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
[api]     response = get_response(request)
[api]   File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 179, in _get_response
[api]     response = wrapped_callback(request, *callback_args, **callback_kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
[api]     return view_func(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/django/views/generic/base.py", line 70, in view
[api]     return self.dispatch(request, *args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/django/utils/decorators.py", line 43, in _wrapper
[api]     return bound_method(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
[api]     return view_func(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/drf_social_oauth2/views.py", line 37, in dispatch
[api]     return super(CsrfExemptMixin, self).dispatch(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
[api]     response = self.handle_exception(exc)
[api]   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
[api]     self.raise_uncaught_exception(exc)
[api]   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
[api]     raise exc
[api]   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 506, in dispatch
[api]     response = handler(request, *args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/drf_social_oauth2/views.py", line 93, in post
[api]     url, headers, body, status = self.create_token_response(request._request)
[api]   File "/usr/local/lib/python3.8/site-packages/oauth2_provider/views/mixins.py", line 124, in create_token_response
[api]     return core.create_token_response(request)
[api]   File "/usr/local/lib/python3.8/site-packages/drf_social_oauth2/oauth2_backends.py", line 30, in create_token_response
[api]     return super(KeepRequestCore, self).create_token_response(request)
[api]   File "/usr/local/lib/python3.8/site-packages/oauth2_provider/oauth2_backends.py", line 148, in create_token_response
[api]     headers, body, status = self.server.create_token_response(uri, http_method, body,
[api]   File "/usr/local/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/endpoints/base.py", line 116, in wrapper
[api]     return f(endpoint, uri, *args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/drf_social_oauth2/oauth2_endpoints.py", line 91, in create_token_response
[api]     return grant_type_handler.create_token_response(
[api]   File "/usr/local/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py", line 60, in create_token_response
[api]     self.validate_token_request(request)
[api]   File "/usr/local/lib/python3.8/site-packages/drf_social_oauth2/oauth2_grants.py", line 100, in validate_token_request
[api]     user = backend.do_auth(access_token=request.token)
[api]   File "/usr/local/lib/python3.8/site-packages/social_core/utils.py", line 251, in wrapper
[api]     return func(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/social_core/backends/oauth.py", line 416, in do_auth
[api]     return self.strategy.authenticate(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/social_django/strategy.py", line 107, in authenticate
[api]     return authenticate(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/django/contrib/auth/__init__.py", line 73, in authenticate
[api]     user = backend.authenticate(request, **credentials)
[api]   File "/usr/local/lib/python3.8/site-packages/social_core/backends/base.py", line 80, in authenticate
[api]     return self.pipeline(pipeline, *args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/social_core/backends/base.py", line 83, in pipeline
[api]     out = self.run_pipeline(pipeline, pipeline_index, *args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/social_core/backends/base.py", line 113, in run_pipeline
[api]     result = func(*args, **out) or {}
[api]   File "/usr/local/lib/python3.8/site-packages/social_core/pipeline/social_auth.py", line 5, in social_details
[api]     return {'details': dict(backend.get_user_details(response), **details)}
[api]   File "/usr/local/lib/python3.8/site-packages/social_core/backends/base.py", line 173, in get_user_details
[api]     raise NotImplementedError('Implement in subclass')
[api] NotImplementedError: Implement in subclass
cheslijones commented 4 years ago

Ok, I came across this looking at the subclass issue:

https://github.com/RealmTeam/django-rest-framework-social-oauth2/issues/42

I implemented the class here and added it to my settings.py per here. I did have to comment out the do_auth method in mine and let the DjangoOAuth2 do that part.

At this point it is authenticating and retrieving user info, but creating users "weirdly" so I'll look into that tomorrow. I'll post what I end up doing to get this working.

wagnerdelima commented 4 years ago

Hey @cheslijones can you please show me what are these weirdly looking users?

cheslijones commented 4 years ago

@wagnerdelima Sorry, that was very unhelpful on my part. So it keeps creating a new user in the database even though it is the same user. There is probably a configuration I'm overlooking.

I got this far pretty late in the day and I'm being pulled in a different direction for the rest of the week, so will probably be Monday before I can take a deeper look at this again.

image

wagnerdelima commented 4 years ago

mm.. that's interesting. Integration Oauth2 with facebook generates users such as: NameSurname etc, instead of this unique id?

Perhaps this is something from Azure itself?

Did you manage to generate the token through the /convert-token endpoint though?

wagnerdelima commented 4 years ago

@cheslijones do you have any news on this?

cheslijones commented 4 years ago

Hi sorry, still working on another project for hopefully just another week and then I'll get back on this.

wagnerdelima commented 3 years ago

@cheslijones any feedback here?

cheslijones commented 3 years ago

@wagnerdelima Sorry, I've been on a whole bunch of other projects and hope to circle back to this in February.

MilanZiaran commented 2 years ago

@wagnerdelima Hi, I have the same problem. I see users with id-like usernames in the database. The only difference is that I'm trying to authenticate using GitHub. On top of this problem, I run to IntegrityError:

Traceback (most recent call last):
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "social_auth_usersocialauth_provider_uid_e6b5e668_uniq"
DETAIL:  Key (provider, uid)=(Django, None) already exists.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/rest_framework/views.py", line 497, in dispatch
    self.initial(request, *args, **kwargs)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/rest_framework/views.py", line 414, in initial
    self.perform_authentication(request)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/rest_framework/views.py", line 324, in perform_authentication
    request.user
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/rest_framework/request.py", line 227, in user
    self._authenticate()
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/rest_framework/request.py", line 380, in _authenticate
    user_auth_tuple = authenticator.authenticate(self)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/rest_framework_social_oauth2/authentication.py", line 63, in authenticate
    user = backend.do_auth(access_token=token)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/social_core/utils.py", line 248, in wrapper
    return func(*args, **kwargs)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/social_core/backends/oauth.py", line 403, in do_auth
    return self.strategy.authenticate(*args, **kwargs)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/social_django/strategy.py", line 105, in authenticate
    return authenticate(*args, **kwargs)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/views/decorators/debug.py", line 42, in sensitive_variables_wrapper
    return func(*func_args, **func_kwargs)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/contrib/auth/__init__.py", line 76, in authenticate
    user = backend.authenticate(request, **credentials)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/social_core/backends/base.py", line 80, in authenticate
    return self.pipeline(pipeline, *args, **kwargs)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/social_core/backends/base.py", line 83, in pipeline
    out = self.run_pipeline(pipeline, pipeline_index, *args, **kwargs)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/social_core/backends/base.py", line 113, in run_pipeline
    result = func(*args, **out) or {}
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/social_core/pipeline/social_auth.py", line 35, in associate_user
    social = backend.strategy.storage.user.create_social_auth(
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/social_django/storage.py", line 138, in create_social_auth
    social_auth = cls.objects.create(user=user, uid=uid, provider=provider)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/models/query.py", line 453, in create
    obj.save(force_insert=True, using=self.db)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/models/base.py", line 739, in save
    self.save_base(using=using, force_insert=force_insert,
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/models/base.py", line 776, in save_base
    updated = self._save_table(
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/models/base.py", line 881, in _save_table
    results = self._do_insert(cls._base_manager, using, fields, returning_fields, raw)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/models/base.py", line 919, in _do_insert
    return manager._insert(
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/models/query.py", line 1270, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1416, in execute_sql
    cursor.execute(sql, params)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/debug_toolbar/panels/sql/tracking.py", line 205, in execute
    return self._record(self.cursor.execute, sql, params)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/debug_toolbar/panels/sql/tracking.py", line 140, in _record
    return method(sql, params)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/backends/utils.py", line 98, in execute
    return super().execute(sql, params)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/backends/utils.py", line 66, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/Users/milan/.local/share/virtualenvs/backend-pyqZ1kNv/lib/python3.9/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
django.db.utils.IntegrityError: duplicate key value violates unique constraint "social_auth_usersocialauth_provider_uid_e6b5e668_uniq"
DETAIL:  Key (provider, uid)=(Django, None) already exists.

Do you have any idea what can be causing this? Any help is highly appreciated.

wagnerdelima commented 2 years ago

Hi @MilanZiaran

This is due to a user having the same email in the database. Then, once the user signs on with social OAuth, the integrity error is raised. This is something I will work on in the near future.

MilanZiaran commented 2 years ago

@wagnerdelima Thank you very much for your quick response! Overriding the do_auth and user_data methods from the DjangoOAuth2 as shown here fixed this, I was wondering if there's maybe a cleaner solution for this.

wagnerdelima commented 2 years ago

@MilanZiaran thank you for commenting. There might be an issue with the azure tenant from the social python. Not necessarily with drf-social-oauth2 framework.

I will investigate this in the future for certain. I will leave this issue open.

tiago-peres commented 1 year ago

@wagnerdelima indeed, I've gone through that problem using Django Python Social Auth only. Seems to be linked with this issue => https://github.com/python-social-auth/social-core/issues/70

ex8 commented 8 months ago

I have been trying to implement Azure AD into my application, I get the access token from Microsoft however when I try to send the convert-token request. I get the following error:

Error: token: Array [ "Ensure this field has no more than 2000 characters." ]

Request payload:

const payload: Credentials = {
          backend: 'azuread-v2-tenant-oauth2',
          grant_type: 'convert_token',
          client_id: import.meta.env.PERI_CLIENT_ID,
          client_secret: import.meta.env.PERI_CLIENT_SECRET,
          token: response.accessToken,
        }

I have tried backend: azuread-tenant-oauth2 and azuread-v2-tenant-oauth2

I have scanned as much of the internet and code as I can but I cannot seem to figure out a solution. Does anyone have any insight?

Would it be possible to extend the max_length of the token CharField as identified as below?

class ConvertTokenSerializer(Serializer):
    grant_type = CharField(max_length=50)
    backend = CharField(max_length=200)
    client_id = CharField(max_length=200)
    client_secret = CharField(max_length=500)
    token = CharField(max_length=2000) <--- here

I appreciate your time and response. Thank you.

ex8 commented 8 months ago

Just for clarity, the accessToken that comes back from Microsoft is 2500 characters long. Can we increase the max_length to a higher number, maybe something like 5000?

ex8 commented 7 months ago

@wagnerdelima - Sorry to bother - I would very much appreciate your input here. I want to avoid creating a separate issues if possible as I believe the context to this issue is related to Azure specifically.

I can kindly create a PR to increase the ConvertTokenSerializer token = CharField(max_length=2000) to max_length=5000 if you deem this is a valid solution.

Thank you for your time and effort.

EDIT: After further thought, I deem this a separate issue: https://github.com/wagnerdelima/drf-social-oauth2/issues/214