3

I work at a small company whose mail is hosted through Google. As one of the administrators responsible for managing employee accounts I'm attempting to automate some tasks using Google APIs and one or more dedicated service accounts. I wrote the short Python script below to request an access token according to the documentation Google provides but I continue to get an "invalid_grant" error.

I've done the following:

  • Logged into the Google Developers Console as the service account and created a project
  • Created a client ID of type service account
  • Logged into the Google Admin Console and added client email address and scope URLs I wish the account to have access to

Searches online have yielded answers indicating that either the system time of the machine is off, the limit for refresh tokens has been exceeded or that the client ID instead of the client email has been used. I've synchronized the time on a Windows machine and a Linux machine prior to running the script and the same error appears. I'm not even getting an access token so I doubt the refresh limit has been exceeded. As can be seen below, the iss value is set to the client email address ending with "@developer.gserviceaccount.com".

I suspect that there is something else I am missing and, as I'm new to Python, I suspect it's something embarrassingly simple. Perhaps someone can assist?

import json
import time
import base64
import requests
from datetime import datetime

utc = datetime.utcnow()
iat = time.mktime(utc.timetuple())
exp = iat + 3600

jwt_header = {
    "alg":"RS256",
    "typ":"JWT"
}

jwt_claim_set = {
    "iss":"pleasehelpme@developer.gserviceaccount.com",
    "scope":"https://www.googleapis.com/auth/admin.directory.user",
    "aud":"https://www.googleapis.com/oauth2/v3/token",
    "iat":int(iat),
    "exp":int(exp),
    "sub":"someadminfrustrated@googleoauth.com"
}

jwt_jws = bytearray(str(jwt_header) + '.' + str(jwt_claim_set))

jwt_unencoded = str(jwt_header) + '.' + str(jwt_claim_set) + '.' +     str(jwt_jws)
jwt_encoded = base64.urlsafe_b64encode(str(jwt_unencoded))

url = 'https://www.googleapis.com/oauth2/v3/token'
headers = {
    'content-type': 'application/x-www-form-urlencoded'
}

payload = {
    'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
    'assertion': jwt_encoded
}

response = requests.post(url, headers=headers, params=payload)

print response
print response.json()

The output is the following:

<Response [400]>
{u'error_description': u'Bad Request', u'error': u'invalid_grant'}
r3tic3nc3
  • 31
  • 2
  • I think the grant should be authorization_code or something similar in here. https://developers.google.com/accounts/docs/OAuth2WebServer#offline Seeing that your grant_type is 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', which is why it's having an error – JA Arce Jan 21 '15 at 19:32
  • Thanks for the prompt reply. However the suggestion and accompanying link included in your reply are for access requests from web server applications not service accounts. Making the suggested change results in the script throwing a different error that states `{u'error_description': u'Missing required parameter: code', u'error': u'invalid_request'}` – r3tic3nc3 Jan 22 '15 at 00:50
  • The error message was the result of me using `params` instead of `data`. The former is to be used with get requests while the latter is to be used with post. However, I am now getting an error message that states `{u'error_description': u'Required parameter is missing: grant_type', u'error': u'invalid_request'}`. – r3tic3nc3 Feb 14 '15 at 00:36

1 Answers1

1

I would look at the output http and url to make sure, but it does seem like you are having authentication issues.

I ran into a similar, albeit less complicated problem, and couldn't get my Google Developer credentials in through a python script. I ended up using my Chrome browser cookies with this script to get through that part: http://n8henrie.com/2014/05/decrypt-chrome-cookies-with-python/

Makes everything pretty simple:

url = 'http://www.example.com'
s = requests.Session()
cookies = pyCookieCheat.chrome_cookies(url)
s.get(url, cookies = cookies)
astrok
  • 198
  • 1
  • 8
  • Thanks for the suggestion and the useful info. However, I don't think this is a viable solution for this particular scenario. The account that will be used for this is a service account and it is entirely possible that there will be future service accounts. Having to log in to a Google site with each to create an encrypted cookie which will then need to be decrypted is not practical or scalable. If Google changes their cookies, as the author on that page mentions was the inspiration for his script, that will need to be done again. – r3tic3nc3 Feb 14 '15 at 00:29