Как правильно аутентифицироваться с помощью 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 обратного вызова, но сам обратный вызов не работает. Пожалуйста, помогите, любая помощь и идеи будут оценены по достоинству.

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