I am working on a little web app to help me manage my gmail. I have set it up through Google's API with the following function using the OAuth token I received through django-allauth
.
import google.oauth2.credentials
from .choices import GMAIL
from allauth.socialaccount.models import SocialToken, SocialAccount
from apiclient.discovery import build
def get_credentials(user):
account = SocialAccount.objects.get(user=user.id)
token = SocialToken.objects.get(app=GMAIL, account=account).token
credentials = google.oauth2.credentials.Credentials(token)
service = build('gmail', 'v1', credentials=credentials)
return service
This seems to work sometimes, but unfortunately, it isn't very reliable. It times out frequently at the build()
function, only succeeding about a third of the time. I am wondering what could cause this behavior and if there is a more reliable way to access the API?
I found the following AuthorizedSession
class from these docs:
from google.auth.transport.requests import AuthorizedSession
authed_session = AuthorizedSession(credentials)
response = authed_session.request(
'GET', 'https://www.googleapis.com/storage/v1/b')
But I don't know how to turn it into the kind of object that works with Google's API:
def get_labels(user):
service = get_credentials(user)
results = service.users().labels().list(userId='me').execute()
labels = results.get('labels', [])
return labels
Unfortunately, Google's docs recommend using a deprecated package that I was hoping to avoid.
This is my first time really using an OAuth-enforced API. Does anyone have any advice?
EDIT: I posted after trying from my Macbook. I tried it on my Windows machine as well, where it works more consistently, but it takes about 20 seconds each time just to do build()
. I feel like I am doing something wrong.
This is working much better this morning after I finally added the refresh token into the mix. Perhaps it's absence was causing some issues on the other end. I will continue testing, but it everything is working well right now:
Here is my full solution:
import google.oauth2.credentials
from google.auth.transport.requests import Request
from apiclient import errors, discovery
from myproject.settings import GMAIL_CLIENT_API_KEY, GMAIL_CLIENT_API_SECRET
def get_credentials(user):
token_set = user.socialaccount_set.first().socialtoken_set.first()
token = token_set.token
refresh_token = token_set.token_secret
credentials = google.oauth2.credentials.Credentials(
token,
refresh_token=refresh_token,
client_id=GMAIL_CLIENT_API_KEY,
client_secret=GMAIL_CLIENT_API_SECRET,
token_uri= google.oauth2.credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT,
)
if credentials.expired:
request = Request()
credentials.refresh(request)
service = discovery.build('gmail', 'v1', credentials=credentials)
return service
As you can see, I added my API keys into my settings file and referenced them here. I happened to stumble upon the token_uri
while nosing around in the source files yesterday. The refresh_token
was the piece that took the longest to find.
The good news is that django-allauth will save the refresh token to SocialToken
model under the token_secret
column. However, it will only do this if the following is in your settings:
SOCIALACCOUNT_PROVIDERS = {
'google': {
'SCOPE': [
'profile',
'email',
'https://www.googleapis.com/auth/gmail.labels',
'https://www.googleapis.com/auth/gmail.modify'
],
'AUTH_PARAMS': {
'access_type': 'offline',
}
}
}
Specifically, the access_type
in AUTH_PARAMS
must be set to 'offline'
according to the docs.
Now, this will still give you trouble if you connected your account before you implemented this change, so you'll also need to revoke access to your app through your Google permissions to obtain a new refresh token. More about that can be found in this question.
I'm not sure if this is the proper/intended way of doing this, but until Google updates their docs for Django, this approach should work at least for testing purposes.