leontp587 leontp587 - 3 months ago 17
Python Question

Use oauth2 service account to authenticate to Google API in python

I've followed the directions in https://developers.google.com/accounts/docs/OAuth2ServiceAccount to use a service account to authenticate to the Google Cloud Storage API. I tried to send a JWT to google's authenticate servers in python, but got an error:

urllib2.HTTPError: HTTP Error 400: Bad Request


It looks like there's something wrong with the way I'm making, signing, or sending the JWT? The error wasn't specific so it could be any part of the process. Does anyone have any ideas?

import Crypto.PublicKey.RSA as RSA
import Crypto.Hash.SHA as SHA
import Crypto.Signature.PKCS1_v1_5 as PKCS1_v1_5
import base64
import json
import time
import urllib2
import urllib

# Settings
json_key_file = 'GooglePM-9f75ad112f87-service.json'

# Load the private key associated with the Google service account
with open(json_key_file) as json_file:
json_data = json.load(json_file)
key = RSA.importKey(json_data['private_key'])

# Create an PKCS1_v1_5 object
signer = PKCS1_v1_5.new(key)

# Encode the JWT header
header_b64 = base64.urlsafe_b64encode(json.dumps({'alg':'RS256','typ':'JWT'}))

# JWT claims
jwt = {
'iss': json_data['client_email'],
'scope': 'https://www.googleapis.com/auth/devstorage.read_write',
'aud': 'https://accounts.google.com/o/oauth2/token',
'exp': int(time.time())+3600,
'iat': int(time.time())
}
jwt_json = json.dumps(jwt)

# Encode the JWT claims
jwt_json_b64 = base64.urlsafe_b64encode(jwt_json)

# Sign the JWT header and claims
msg_hash = SHA.new(header_b64 + "." + jwt_json_b64)
signature_b64 = base64.urlsafe_b64encode(signer.sign(msg_hash))

# Make the complete message
jwt_complete = header_b64 + "." + jwt_json_b64 + "." + signature_b64

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

f = urllib2.urlopen("https://accounts.google.com/o/oauth2/token", urllib.urlencode(data))

print f.read()


If I try to use curl to post to the server, I get the invalid grants error:

(venv)$ curl -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiAiUlMyNTYiLCAidHlwIjogIkpXVCJ9.eyJpc3MiOiAiMTM1MDY3NjIyMTk4LWVhbWUwZnFqdTNvamRoZ29zdDg2dnBpdTBsYW91NnZlQGRldmVsb3Blci5nc2VydmljZWFjY291bnQuY29tIiwgInNjb3BlIjogImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvZGV2c3RvcmFnZS5yZWFkX3dyaXRlIiwgImF1ZCI6ICJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20vby9vYXV0aDIvdG9rZW4iLCAiZXhwIjogMTQwODY1MTU2OCwgImlhdCI6IDE0MDg2NDg1NTh9.HWC7h3QiOy7QsSuta4leq_Gjwmy9IdF-MUwflPhiohzAJ-Amykd56Ye4Y_Saf_sAc5STzOCmrSPzOTYvGXr6X_T_AmSTxXK2AJ2SpAiEUs2_Wp5h18xTUY3Y_hkKvSZLh5bRzeJ_0xRcmRIPE6tua0FHFwUDdnCIGdh4DGg6i4E%3D' https://accounts.google.com/o/oauth2/token
{
"error" : "invalid_grant"
}

Answer

Ok so there's a better way to do this! Google already has a python client API that handles some of the complexity. The following code works after installing google python client API: https://developers.google.com/api-client-library/python/guide/aaa_oauth

from oauth2client.client import SignedJwtAssertionCredentials
import json
import urllib
import urllib2

# Settings
json_key_file = 'GooglePM-9f75ad112f87-service.json'

# Load the private key associated with the Google service account
with open(json_key_file) as json_file:
    json_data = json.load(json_file)

# Get and sign JWT
credential = SignedJwtAssertionCredentials(json_data['client_email'], json_data['private_key'], 'https://www.googleapis.com/auth/devstorage.read_write')
jwt_complete = credential._generate_assertion()

# Get token from server
data = {'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', 
    'assertion': jwt_complete}
f = urllib2.urlopen("https://accounts.google.com/o/oauth2/token", urllib.urlencode(data))

print f.read()
Comments