Reputation: 23
Here's my code. I've swapped in fake service account JSON credentials but have preserved all of the attributes of the JSON object and the general character set of each of the values. The code generates a signed JWT for me but when I actually submit it with the cfhttp POST the reponse is { "error": "invalid_request", "error_description": "Bad Request" }
. Does anyone have any thoughts on what I might have wrong here? Thanks in advance for the help! The code is largely templated off of the firebase example here: Creating JWT in Coldfusion for google Service account
<cfscript>
variables.service_json = deserializeJSON(
'{
"type": "service_account",
"project_id": "my-project-123456",
"private_key_id": "11111aa22222bb33333cc44444dd55555ee66666",
"private_key": "-----BEGIN PRIVATE KEY-----\naaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000==\n-----END PRIVATE KEY-----\n",
"client_email": "my-service-account-email@my-project-123456.iam.gserviceaccount.com",
"client_id": "111222333444555666777",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/my-service-account-email%40my-project-123456.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}'
);
variables.timestamp = dateDiff("s", CreateDate(1970,1,1), now());
variables.timestampUTC = timestamp + 8*60*60; //add 8 hours to convert to utc
//generate jwt
variables.jwt_header = {
'alg': 'RS256',
'typ': 'JWT'
};
variables.jwt_header = serializeJSON(variables.jwt_header);
variables.jwt_header = toBase64(variables.jwt_header);
variables.jwt_claim = {
'iss': service_json.client_email,
'scope': 'https://www.googleapis.com/auth/analytics.readonly',
'aud': 'https://oauth2.googleapis.com/token',
'iat': timestampUTC,
'exp': (timestampUTC + 3600)
};
variables.jwt_claim = serializeJSON(variables.jwt_claim);
variables.jwt_claim = toBase64(variables.jwt_claim);
variables.jwt = variables.jwt_header & '.' & variables.jwt_claim;
//sign jwt
variables.keyText = reReplace( service_json.private_key, "-----(BEGIN|END)[^\r\n]+", "", "all" );
variables.keyText = trim( keyText );
variables.privateKeySpec = createObject( "java", "java.security.spec.PKCS8EncodedKeySpec" ).init(binaryDecode( variables.keyText, "base64" ));
variables.privateKey = createObject( "java", "java.security.KeyFactory" ).getInstance( javaCast( "string", "RSA" ) ).generatePrivate( privateKeySpec );
variables.signer = createObject( "java", "java.security.Signature" ).getInstance( javaCast( "string", 'SHA256withRSA' ));
variables.signer.initSign( variables.privateKey );
variables.signer.update( charsetDecode( variables.jwt, "utf-8" ) );
variables.signedBytes = signer.sign();
variables.signedBase64 = toBase64(signedBytes);
variables.jwt_signed = variables.jwt & '.' & variables.signedBase64;
</cfscript>
<cfhttp url="https://oauth2.googleapis.com/token" method="POST" result="res">
<cfhttpparam type="formfield" name="grant_type" value="urn:ietf:params:oauth:grant-type:jwt-bearer">
<cfhttpparam type="formfield" name="assertion" value="#variables.jwt_signed#">
</cfhttp>
<cfdump var="#variables.jwt_signed#">
<cfdump var="#res#">
I've tried a whole array of options but I need to use a Service Account, not an OAuth 2.0 Client ID. Within the Service Account approach I've tried using Ben Nadel's https://github.com/bennadel/JSONWebTokens.cfc, but that required a public key which google oauth2 doesn't provide. And so my best / closest attempt has been the code I have posted here.
Upvotes: 0
Views: 87
Reputation: 21
Not sure it it makes a difference but I notice you are POSTing vars that I send in the BODY...
<cfhttp url="https://oauth2.googleapis.com/token" method="POST" result="res">
<cfhttpparam type="formfield" name="grant_type" value="urn:ietf:params:oauth:grant-type:jwt-bearer">
<cfhttpparam type="formfield" name="assertion" value="#variables.jwt_signed#">
</cfhttp>
// Final transformation to URL encoded pairs
var transformedRequest = 'grant_type=#URLEncodedFormat(grantType)#&assertion=#URLEncodedFormat(jwt_signed)#';
// The POST to the auth endpoint
cfhttp(method='POST', charset='utf-8', url='https://oauth2.googleapis.com/token', result='response') {
cfhttpparam(type='header', name='Content-Type', value='application/x-www-form-urlencoded');
cfhttpparam(type='body', value=transformedRequest);
}
Upvotes: 0