'JWT "invalid_grant" in Signature in Google OAuth2

I am writing some code to try to get a token to use from Google in OAuth2. This is for a service account, so the instructions are here:

https://developers.google.com/identity/protocols/OAuth2ServiceAccount

I keep getting this error when I post the JWT to Google:

{ "error": "invalid_grant", "error_description": "Invalid JWT Signature." }

Here is the code:

try{        
    var nowInSeconds : Number = (Date.now() / 1000);
    nowInSeconds = Math.round(nowInSeconds);
    var fiftyNineMinutesFromNowInSeconds : Number = nowInSeconds + (59 * 60);


    var claimSet : Object = {};
    claimSet.iss   = "{{RemovedForPrivacy}}";        
    claimSet.scope = "https://www.googleapis.com/auth/plus.business.manage";
    claimSet.aud   = "https://www.googleapis.com/oauth2/v4/token";
    claimSet.iat   = nowInSeconds; 
    claimSet.exp   = fiftyNineMinutesFromNowInSeconds;

    var header : Object = {};
    header.alg = "RS256";
    header.typ = "JWT";

    /* Stringify These */
    var claimSetString = JSON.stringify(claimSet);
    var headerString = JSON.stringify(header);

    /* Base64 Encode These */
    var claimSetBaseSixtyFour = StringUtils.encodeBase64(claimSetString);
    var headerBaseSixtyFour = StringUtils.encodeBase64(headerString);

    var privateKey = "{{RemovedForPrivacy}}";

    /* Create the signature */
    var signature : Signature = Signature();
    signature =  signature.sign(headerBaseSixtyFour + "." + claimSetBaseSixtyFour, privateKey , "SHA256withRSA");

    /* Concatenate the whole JWT */
    var JWT = headerBaseSixtyFour + "." + claimSetBaseSixtyFour + "." + signature;

    /* Set Grant Type */
    var grantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"

    /* Create and encode the body of the token post request */
    var assertions : String = "grant_type=" + dw.crypto.Encoding.toURI(grantType) + "&assertion=" + dw.crypto.Encoding.toURI(JWT);

    /* Connect to Google And Ask for Token */
    /* TODO Upload Certs? */
    var httpClient : HTTPClient = new HTTPClient();
    httpClient.setRequestHeader("content-type", "application/x-www-form-urlencoded; charset=utf-8");
    httpClient.timeout = 30000;
    httpClient.open('POST', "https://www.googleapis.com/oauth2/v4/token");
    httpClient.send(assertions);

    if (httpClient.statusCode == 200) {
       //nothing
    } else {
       pdict.errorMessage = httpClient.errorText;
    }  

}
catch(e){
    Logger.error("The error with the OAuth Token Generator is --> " + e);
}

Does anyone know why the JWT is failing?

Thanks so much! Brad



Solution 1:[1]

The problem might be related to the fact that your StringUtils.encodeBase64() method is likely to perform a standard base64 encoding.

According to the JWT spec, however, it's not the standard base64 encoding that needs to be used, but the the URL- and filename-safe Base64 encoding, with the = padding characters omitted.

If you don't have a utility method handy for base64URL encoding, you can verify by

  • replacing all + with -;
  • replacing all / with _;
  • removing all =

in your base64-encoded strings.

Also, is your signature also base64-encoded? It needs to be, following the same rules as described above.

Solution 2:[2]

I had the same problem before and this is what was wrong:

  • wrong application name (project ID)
  • wrong service account ID (email)

Solution 3:[3]

The another reason for this error could be "Your service account is not activated", With gsutil installed from the Cloud SDK, you should authenticate with service account credentials.

1- Use an existing service account or create a new one, and download the associated private key.

2- Use gcloud auth activate-service-account to authenticate with the service account:

gcloud auth activate-service-account --key-file [KEY_FILE]

Where [KEY_FILE] is the name of the file that contains your service account credentials.

Link for more detail: Activate service account

Solution 4:[4]

I had this same error occur when using a service account. I couldn't figure out what was wrong so I came back to it the next day and it worked. So maybe Google Cloud takes some time to propagate every once in a while.

Solution 5:[5]

This could also happen if a developer mistakenly copies, edits, and uses a service account key file for a purpose other than the one for which the file was originally intended. For example:

  1. Developer A creates SA 1
  2. Developer A uses gcloud iam service-accounts keys create ... to create the secret file for SA 1, encrypts it, and checks it in to source control
  3. Developer B creates SA 2
  4. Developer B (mistakenly) decrypts and copies the secret file from step 2, modifies some of its fields with data from SA 2, then attempts to use it in an application

The resolution in this scenario obviously is for Developer B to get rid of the copied/edited file and create a new secret file with gcloud like Developer A did in step 2.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Community
Solution 2 sklimkovitch
Solution 3 mohit uprim
Solution 4 smoosh911
Solution 5 Logan May