Django DRF + Allauth: OAuth2Error: Ошибка при получении маркера доступа на производственной сборке

Мы интегрируем DRF (dj_rest_auth) и allauth с фронтенд-приложением на базе React. Недавно был добавлен социальный логин для обработки входа через LinkedIn, Facebook, Google и GitHub. Все работало хорошо на localhost с каждым из провайдеров. После развертывания staging я обновил секреты и социальные приложения для нового домена. Генерация URL для социального входа работает нормально, пользователь перенаправляется на страницу входа провайдера и получает доступ к входу в наше приложение, но после перенаправления обратно на страницу фронтенда, отвечающую за вход - возникает ошибка: (пример для LinkedIn, происходит для всех провайдеров)

allauth.socialaccount.providers.oauth2.client.OAuth2Error:
Error retrieving access token:
b'{"error":"invalid_redirect_uri","error_description":"Unable to retrieve access token: appid/redirect uri/code verifier does not match authorization code. Or authorization code expired. Or external member binding exists"}'

Наш поток:

go to frontend page -> click on provider's icon ->
redirect to {BACKEND_URL}/rest-auth/linkedin/url/ to make it a POST request (user submits the form) ->
login on provider's page ->
go back to our frontend page {frontend}/social-auth?source=linkedin&code={the code we are sending to rest-auth/$provider$ endpoint}&state={state}->
confirm the code & show the profile completion page

Определение адаптера (одинаковое для каждого провайдера):

class LinkedInLogin(SocialLoginView):
    adapter_class = LinkedInOAuth2Adapter
    client_class = OAuth2Client

    @property
    def callback_url(self):
        return self.request.build_absolute_uri(reverse('linkedin_oauth2_callback'))

Определение обратного вызова:

def linkedin_callback(request):
    params = urllib.parse.urlencode(request.GET)
    return redirect(f'{settings.HTTP_PROTOCOL}://{settings.FRONTEND_HOST}/social-auth?source=linkedin&{params}')

URLs:

path('rest-auth/linkedin/', LinkedInLogin.as_view(), name='linkedin_oauth2_callback'),
path('rest-auth/linkedin/callback/', linkedin_callback, name='linkedin_oauth2_callback'),
path('rest-auth/linkedin/url/', linkedin_views.oauth2_login),

Вызов фронтенда для отправки access_token/code:

  const handleSocialLogin = () => {
    postSocialAuth({
      code: decodeURIComponent(codeOrAccessToken),
      provider: provider
    }).then(response => {
      if (!response.error) return history.push(`/complete-profile?source=${provider}`);

      NotificationManager.error(
        `There was an error while trying to log you in via ${provider}`,
        "Error",
        3000
      );

      return history.push("/login");
    }).catch(_error => {
      NotificationManager.error(
        `There was an error while trying to log you in via ${provider}`,
        "Error",
        3000
      );

      return history.push("/login");
    });
  }

Мутация:

const postSocialUserAuth = builder => builder.mutation({
  query: (data) => {
    const payload = {
      code: data?.code,
    };

    return {
      url: `${API_BASE_URL}/rest-auth/${data?.provider}/`,
      method: 'POST',
      body: payload,
    }
  }

URLs обратного вызова и учетные данные клиента установлены для среды staging как в нашей панели администратора (Django), так и в панели провайдера (т.е. developers.linkedin.com)

Опять же - все из этой установки работает нормально в локальной среде.

ВАЖНО Мы используем два разных домена для бэкенда и фронтенда - фронтенд имеет другой домен, чем бэкенд

Решением было полностью изменить генерацию URL обратного вызова

Для тех, кто ищет решение в будущем:

class LinkedInLogin(SocialLoginView):
    adapter_class = CustomAdapterLinkedin
    client_class = OAuth2Client

    @property
    def callback_url(self):
        callback_url = reverse('linkedin_oauth2_callback')
         site = Site.objects.get_current()
         return f"{settings.HTTP_PROTOCOL}://{site}{callback_url}"

Пользовательский адаптер:

class CustomAdapterLinkedin(LinkedInOAuth2Adapter):
    def get_callback_url(self, request, app):
        callback_url = reverse(provider_id + "_callback")
        site = Site.objects.get_current()

        return f"{settings.HTTP_PROTOCOL}://{site}{callback_url}"

Важно изменить ваши маршруты, поэтому для генерации URL:

path('rest-auth/linkedin/url/', OAuth2LoginView.adapter_view(CustomAdapterLinkedin))

Я оставляю этот вопрос открытым, так как считаю, что это не ожидаемое поведение.

Вернуться на верх