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))
Я оставляю этот вопрос открытым, так как считаю, что это не ожидаемое поведение.