I am building a Django project that works with video and needs to upload to Youtube. For this, I need oauth credentials. I am unable to authenticate with Oauth even though my redirect URI is correct and I am parsing the request for the code and passing it to the flow.
I am using google-auth-oauthlib and oauth to authenticate. Here is what the error looks like:
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
And the views:
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('/'))
And the helper functions seen above (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
This is the line that fails, at the end:
flow.fetch_token(code=token_url)
Everything else seems to work properly. Please let me know if you know any way to fix this. I haven’t been able to get OAuth to work with a package, although I tried Django allauth. I am using Django 5, the latest version, on Ubuntu 24 with an Apache server. The web server is working and redirects to the callback URL, but the callback is broken. Please help, any help and ideas is appreciated.