Как правильно аутентифицироваться с помощью OAuth, используя google-auth-oauthlib и Django?
Я создаю Django-проект, который работает с видео и должен загружать его на Youtube. Для этого мне нужны учетные данные oauth. Я не могу пройти аутентификацию с помощью Oauth, хотя URI перенаправления корректен, и я разбираю запрос на код и передаю его в поток. Я использую google-auth-oauthlib и oauth для аутентификации. Вот как выглядит ошибка:
Traceback (most recent call last): File "/home/team/lotteh/venv/lib/python3.12/site-packages/django/core/handlers/exception.py", line 55, in inner response = get_response(request) ^^^^^^^^^^^^^^^^^^^^^ File "/home/team/lotteh/venv/lib/python3.12/site-packages/django/core/handlers/base.py", line 197, in _get_response response = wrapped_callback(request,
*callback_args, **callback_kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/team/lotteh/venv/lib/python3.12/site-packages/sentry_sdk/integrations/django/views.py", line 89, in sentry_wrapped_callback return callback(request, *args,
**kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/team/lotteh/venv/lib/python3.12/site-packages/django/views/decorators/csrf.py", line 65, in _view_wrapper return view_func(request, *args,
**kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/team/lotteh/users/views.py", line 47, in google_auth_callback email, token, refresh = parse_callback_url(request, code) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/team/lotteh/users/oauth.py", line 66, in parse_callback_url flow.fetch_token(code=token_url) File "/home/team/lotteh/venv/lib/python3.12/site-packages/google_auth_oauthlib/flow.py", line 285, in fetch_token return self.oauth2session.fetch_token(self.client_config["token_uri"],
**kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/team/lotteh/venv/lib/python3.12/site-packages/requests_oauthlib/oauth2_session.py", line 406, in fetch_token self._client.parse_request_body_response(r.text, scope=self.scope) File "/home/team/lotteh/venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 415, in parse_request_body_response self.token = parse_token_response(body, scope=scope) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/team/lotteh/venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 425, in parse_token_response validate_token_parameters(params) File "/home/team/lotteh/venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 432, in validate_token_parameters raise_from_error(params.get('error'), params) File "/home/team/lotteh/venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/errors.py", line 405, in raise_from_error raise cls(**kwargs)oauthlib.oauth2.rfc6749.errors.InvalidGrantError: (invalid_grant) Bad Request
А виды:
def google_auth(request):
from django.shortcuts import redirect
from users.oauth import get_auth_url
import uuid
url, state = get_auth_url(request, request.user.email if request.user.is_authenticated else None)
print(state)
request.session['state'] = state
return redirect(url)
#@login_required
#@user_passes_test(is_superuser_or_vendor)
@csrf_exempt
def google_auth_callback(request):
from users.oauth import parse_callback_url
from security.middleware import get_qs
from django.shortcuts import redirect
from django.urls import reverse
from django.conf import settings
url = settings.BASE_URL + request.path + request.GET.urlencode()
code = request.GET['code']
email, token, refresh = parse_callback_url(request, code)
from django.contrib.auth.models import User
user = User.objects.filter(email=email).order_by('-last_seen').last()
if not user:
from users.username_generator import generate_username as get_random_username
user = User.objects.create_user(email=e, username=get_random_username(), password=get_random_string(length=8))
from django.contrib import messages
messages.success(request, 'You are now subscribed, check your email for a confirmation. When you get the chance, fill out the form below to make an account.')
user.profile.token = token
user.profile.refresh_token = refresh
user.profile.save()
from django.contrib.auth import login as auth_login
auth_login(request, user, backend='django.contrib.auth.backends.ModelBackend')
from django.contrib import messages
messages.success(request, 'Successfully linked Google account')
return redirect(reverse('/'))
И вспомогательные функции, показанные выше (users/oauth.py)
import google.oauth2.credentials
import google_auth_oauthlib.flow
from django.conf import settings
from django.urls import reverse
import os
flows = {}
def get_auth_url(request, email):
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(str(os.path.join(settings.BASE_DIR, 'client_secret.json')),
scopes=['https://www.googleapis.com/auth/youtube.force-ssl',
'https://www.googleapis.com/auth/youtube.upload',
'https://www.googleapis.com/auth/youtube',
'https://www.googleapis.com/auth/userinfo.email',
])
flow.redirect_uri = settings.BASE_URL + reverse('users:oauth')
authorization_url, state = None, None
if email:
authorization_url, state = flow.authorization_url(
access_type='offline',
include_granted_scopes='true',
state=request.session.get('state'),
login_hint=email,
prompt='consent')
else:
authorization_url, state = flow.authorization_url(
access_type='offline',
include_granted_scopes='true',
state=request.session.get('state'),
prompt='consent')
global flows
flows[state] = flow
return authorization_url, state
def get_user_info(credentials):
"""Send a request to the UserInfo API to retrieve the user's information.
Args:
credentials: oauth2client.client.OAuth2Credentials instance to authorize the
request.
Returns:
User information as a dict.
"""
user_info_service = build(
serviceName='oauth2', version='v2',
http=credentials.authorize(httplib2.Http()))
user_info = None
try:
user_info = user_info_service.userinfo().get().execute()
except (errors.HttpError, e):
logging.error('An error occurred: %s', e)
if user_info and user_info.get('id'):
return user_info
else:
raise Exception()
def parse_callback_url(request, token_url):
global flows
flow = flows[request.GET.get('state')]
flow.fetch_token(code=token_url)
credentials = flow.credentials
return get_user_info(credentials)['email'], credentials.token, credentials.refresh_token
Вот строка, которая не работает, в конце:
flow.fetch_token(code=token_url)
Все остальное, похоже, работает правильно. Пожалуйста, дайте мне знать, если вы знаете какой-либо способ исправить это. Мне не удалось заставить OAuth работать с пакетом, хотя я пробовал Django allauth. Я использую Django 5, последнюю версию, на Ubuntu 24 с сервером Apache. Веб-сервер работает и перенаправляет на URL обратного вызова, но сам обратный вызов не работает. Пожалуйста, помогите, любая помощь и идеи будут оценены по достоинству.