Gmail with Oauth rejects my token/password

I am trying to send mail from Django. I've tried two different ways, both of which fail in different ways.

When I tried using an app password, I had no errors, but the email just never arrived. And the sender's outbox is empty, too.

But that was after I tried the modern method, OAuth. After a process that I would never have finished without ChatGPT (because nobody else has created a guide that I can find), I set up something that almost works. I have a file with a refresh token in it, and a custom backend that uses it.

But when it's time to send an email, I'm told "Username and Password not accepted."

Here's what the token file is like:

{
    "token": "[secret]",
    "refresh_token": "[secret]",
    "token_uri": "https://oauth2.googleapis.com/token",
    "client_id": "[secret]",
    "client_secret": "[secret]",
    "scopes": ["https://www.googleapis.com/auth/gmail.send"],
    "universe_domain": "googleapis.com",
    "account": "",
    "expiry": "2025-01-05T07:39:06.980351Z"
}

And this is the backend:

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from django.core.mail.backends.smtp import EmailBackend
from pathlib import Path
import os

class GmailOAuth2Backend(EmailBackend):
    def _get_access_token(self):
        dir = Path(__file__).resolve().parent
        file = os.path.join(dir, 'oauth2tokenfile.secret.json')
        creds = Credentials.from_authorized_user_file(file)
        if creds.expired and creds.refresh_token:
            creds.refresh(Request())
        return creds.token

    def open(self):
        self.password = self._get_access_token()
        super().open()

And finally, my settings

EMAIL_BACKEND = "[secret].GmailOAuth2Backend"
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = '[my email]@gmail.com'

According to the debugger, creds.valid = True (after refresh).

He is not allowed not to eat this came. I have a MFE with a react.

The "Username and Password not accepted" error typically occurs when gmail's smtp server doesn't recognize or accept the credentials being used.

Debug the access token which is being passed as a password to gmail's smtp server this may help

def _get_access_token(self):
    dir = Path(__file__).resolve().parent
    file = os.path.join(dir, 'oauth2tokenfile.secret.json')

    try:
        creds = Credentials.from_authorized_user_file(file)
        if creds.expired and creds.refresh_token:
            creds.refresh(Request())
        print(f"Access Token: {creds.token}")  # debugging
        return creds.token
    except Exception as e:
        raise Exception(f"Error loading or refreshing credentials: {e}")

One more thing you can try which is to isolate the issue and test the SMTP authentication manually using a library like smtplib

import smtplib

# Ensure the EMAIL_HOST_USER in your Django settings matches the account associated with the oauth2 token (email field)

EMAIL = 'your_email@gmail.com'
ACCESS_TOKEN = 'your_access_token'

try:
    server = smtplib.SMTP('smtp.gmail.com', 587)
    server.starttls()
    server.login(EMAIL, ACCESS_TOKEN)
    print("login successful!")
except smtplib.SMTPAuthenticationError as e:
    print("authentication failed:", e)

If this test fails with the same error, the issue is with your token or Gmail account settings.

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