the sea in storm
the sea in storm

Reputation: 23

Implementing google oauth2 with a service account in coldfusion 2018?

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

Answers (1)

Greg Seedsman
Greg Seedsman

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

Related Questions