JWT "invalid_grant" in Signature in Google OAuth2 - oauth-2.0

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

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.

I had the same problem before and this is what was wrong:
wrong application name (project ID)
wrong service account ID (email)

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

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:
Developer A creates SA 1
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
Developer B creates SA 2
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.

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.

Related

mock auth0 server in continuous integration environment with no internet

I want to write e2e tests that can run in our CI environment which is tightly locked down and has no internet access.
I am using the auth0 react loginWithRedirect function which will attempt to redirect to the auth0 login on their servers and will timeout on CI.
I am able to intercept the call to /authorize in express:
app.get('/auth0/:simulation_id/authorize', middleware, (req, res) => {
const { client_id, redirect_uri, scope, state } = req.query;
Is it now possible for me to generate a mock oauth token that will be accepted by the #auth0/react client code?
Yes. Using the jsrsasign.js library, where accessKey contains a private key:
function createJwt(username, type, realm, scopes, sessionId, expires_in, audience) {
var oHeader = {
alg: 'RS256',
typ: 'JWT'
};
// Payload
var oPayload = {};
var tNow = jwt.KJUR.jws.IntDate.get('now');
var token_expiry = expires_in;
var tEnd = tNow + token_expiry;
oPayload.sub = username;
oPayload.auditTrackingId = uuidv4();
oPayload.iss = "https://mock.org/identities/v1/" + realm + "/token";
oPayload.tokenName = type;
oPayload.token_type = 'Bearer';
oPayload.authGrantId = !sessionId ? uuidv4() : sessionId;
oPayload.aud = (audience ? audience : "https://mock.org/identities/v1/" + realm + "/token");
oPayload.nbf = tNow;
oPayload.scope = scopes;
// oPayload.auth_time = tNow;
oPayload.realm = '/' + realm;
oPayload.exp = tEnd;
oPayload.expires_in = token_expiry * 100000;
oPayload.iat = tNow;
oPayload.jti = uuidv4();
var sHeader = JSON.stringify(oHeader);
var sPayload = JSON.stringify(oPayload);
var prvKey = accessKey;
var sJWT = jwt.KJUR.jws.JWS.sign("RS256", sHeader, sPayload, prvKey);
return sJWT;
}
Set the audience, subject, scp and any other properties in line with the tokens that auth0 is creating.
You also might need to mock out the jwk endpoints, but I doubt the library will care. If it doesn't work it will be due to certain properties not being available on the token.
You might need to create an id token as well, depending on your setup.
Access key is similar to this:
var accessKey = jwt.KEYUTIL.getKey("-----BEGIN PRIVATE KEY-----\n" +
"MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC0g+nePEZ6aFiH\n" +
"ccnle/ryqz1lNvloPGuA5b6GeE8MrT7SATxv54zQ2LnvuRp86cd32elL0YAw3GMc\n" +
... snip ...
"Rno3R82z6SAvy9HgIMzfti5NSVgqC9nmhFEs+ChFWuboGotVV99COVJId9S/567n\n" +
"kz90cLENtD/8JTYAhLea5F/PJBJJHSvQT298ZxR4bw1vQ5Bq2FMnLSYuIOQMkQdr\n" +
"Yqt+8gXW0+3kfyb3cCyI+2HKcQ==\n" +
"-----END PRIVATE KEY-----\n");
And keep going til you've mocked out the entire IdP platform :) (I basically did the same thing with OpenAM a few years ago).
In addition to #stringy05's response; if you've got back-end services (e.g. Java, .NET, etc) that are called by your React app and need to do JWT validation, you might need to statically host your own an OpenID Configuration and JSON Web Key Set endpoints in your own environment.
The reason for this is because JWT validation (by back-end services) would still require communication to the external authorization server. With JWT-based auth this is generally limited to a single (up-front or lazy-loaded) request to get the public key for signature validation, but there could also be additional calls e.g. when the service encounters an unknown kid (key ID) on a JWT, as well as a periodical refresh interval (e.g. daily).
For Auth0 OpenID Configuration and JSON Web Key Set endpoints are hosted at the following locations:
OpenID Configuration: https://[tenant-name].[2-letter-iso-country-code].auth0.com/.well-known/openid-configuration
JSON Web Key Set: https://[tenant-name].[2-letter-iso-country-code].auth0.com/.well-known/jwks.json
These are just static JSON documents. You can probably just copy these from one of your existing tenants, modify accordingly, and then host in your own environment. The modifications you'd need to make include:
OpenID Configuration to reference local jwks_uri endpoint.
Update the key information in the JSON Web Key Set endpoint. Note: you can generate an asymetric key pair using OpenSSL, and see here for information about how to extract modulus and exponent from public key (which are required for the JWKS document). Note: in your React app, you'd need to sign the tokens with the private key that you generate.
Once you've done the above, your back-end services can reference the locally hosted OpenID Configuration and will be able to do JWT validation without communicating to an external authorization server.

Azure Cosmos DB Token Let's Me Hit Endpoint, but Isn't Authenticated

I'm using this code to generate my token. I've just barely changed the sample code for token generation to also return the date to me because I have to send that in the request.
var crypto = require("crypto");
var inputKey = "my-key-from-azure"; // Have tried both primary and secondary master keys from cosmos db
var today = new Date().toUTCString();
console.log(today);
console.log(getAuthorizationTokenUsingMasterKey("POST", "dbs", "dbs/ToDoList", today, inputKey))
function getAuthorizationTokenUsingMasterKey(verb, resourceType, resourceId, date, masterKey)
{
var key = new Buffer(masterKey, "base64");
var text = (verb || "").toLowerCase() + "\n" +
(resourceType || "").toLowerCase() + "\n" +
(resourceId || "") + "\n" +
date.toLowerCase() + "\n" +
"" + "\n";
var body = new Buffer(text, "utf8");
var signature = crypto.createHmac("sha256", key).update(body).digest("base64");
var MasterToken = "master";
var TokenVersion = "1.0";
return encodeURIComponent("type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + signature);
}
Here are the request headers. For the x-ms-date, my DB was created after this, the latest version, so I assume it uses this version; can I verify this somehow?
{
Authorization: [my-auth-string],
x-ms-version: "2017-02-22",
x-ms-date: "Fri, 05 Oct 2018 19:06:17 GMT",
Content-Type: application/json
}
But I'm getting back an error message that is complaining about the validity of my token.
{
"code": "Unauthorized",
"message": "The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'post\nsprocs\ndbs/metrics/colls/LoungeVisits/sprocs/calculateAverage\nfri, 05 oct 2018 19:06:17 gmt\n\n'\r\nActivityId: 41cd36af-ad0e-40c3-84c8-761ebd14bf6d, Microsoft.Azure.Documents.Common/2.1.0.0"
}
The payload was built according to the expected protocol, as far as I can tell from the docs.
Execute a stored procedure, Common Request Headers, Access Control. The request is sent using postman, and I'm copying the values my script generates directly into it. What am I doing wrong?
Azure Cosmos DB has two types of 'keys'.
Master keys - Used for administrative resources: database accounts, databases, users, and permissions
Resource tokens - Used for application resources: containers, documents, attachments, stored procedures, triggers, and UDFs
Securing access to Azure Cosmos DB data
The examples you are providing do not match the examples from the documentation, such as: Code sample to use a master key
As for using tokens:
You can use a resource token (by creating Cosmos DB users and permissions) when you want to provide access to resources in your Cosmos DB account to a client that cannot be trusted with the master key.
And I am not seeing the the following var TokenVersion = "1.0" needed to be used with Master Tokens, etc.

Download revit file from a360 forge bucket

How can I download a file from an Autodesk A360 bucket that I created? The file is a Revit project file and I used 2-legged OAuth for authorization.
You need to use 3 legged authentication to access files from A360, because you need the approval of the user whose account you are accessing - the user is the 3rd leg.
If you are trying to access files from your own application's private bucket on OSS then you do not need a user's approval because the bucket belongs to your app and not a user.
As a side note, in case of trying to access BIM 360 files (in case that's what you are talking about) using the Data Management API, then for the time being you need to use 2 legged authentication but your app needs to be manually approved by us.
2-legged vs 3-legged authentication is covered by Augusto's webcast:
Introduction to oAuth and Data Management API
I don't get a bucket key after attempting to create a bucket in when i change v1 to v2...
see below
public static string GetBucket(string accessToken, string bucketKey, string policy)
{
// (1) Build request
var client = new RestClient();
client.BaseUrl = new System.Uri(baseApiUrl);
// Set resource/end point
var request = new RestRequest();
request.Resource = "oss/v1/buckets";
request.Method = Method.GET;
// Add headers
request.AddHeader("Authorization", "Bearer " + accessToken);
request.AddHeader("Content-Type", "application/json"); // MH: skipping this works.
// Add JSON body. in simplest form.
request.AddJsonBody(new { bucketKey = bucketKey, policy = policy });
// (2) Execute request and get response
IRestResponse response = client.Execute(request);
//TaskDialog.Show("create bucket", response.StatusDescription);
// Save response. This is to see the response for our learning.
m_lastResponse = response;
TaskDialog.Show("response", m_lastResponse.ToString());
// Get the key = bucket name
string key = "";
if (response.StatusCode == HttpStatusCode.OK)
{
JsonDeserializer deserial = new JsonDeserializer();
OssBucketsResponse bucketsResponse = deserial.Deserialize<OssBucketsResponse>(response);
key = bucketsResponse.key;
}
return key; // the bucket name
}

Google oauth1 migration to oauth2: Invalid authorization header

I am stuck with the oauth1 migration to oauth2. I don't want to ask my users to grant contact access again, so I prefer to do migration myself but I am getting hard time figuring out why it doesn't work.
I'm getting this error from Google server:
DEBUG - << " "error" : "invalid_request",[\n]"
DEBUG - << " "error_description" : "Invalid authorization header."[\n]"
here is my code, I did almost the same thing when consuming google api, but for migration it is not working.
GoogleOAuthParameters oauthParameters = new GoogleOAuthParameters();
oauthParameters.setOAuthConsumerKey(getConsumerKey());
oauthParameters.setOAuthConsumerSecret(getConsumerSecret());
oauthParameters.setOAuthToken(token);
ClassPathResource cpr = new ClassPathResource("mykey.pk8");
File file = cpr.getFile();
PrivateKey privKey = getPrivateKey(file);
OAuthRsaSha1Signer signer = new OAuthRsaSha1Signer(privKey);
GoogleOAuthHelper oauthHelper = new GoogleOAuthHelper(signer);
String requestUrl = "https://www.googleapis.com/oauth2/v3/token";
String header = oauthHelper.getAuthorizationHeader(requestUrl, "POST", oauthParameters);
String payload = "grant_type=urn:ietf:params:oauth:grant-type:migration:oauth1&client_id="+com.app.framework.utils.OAuthHelper.OAUTH2_CLIENT_ID+"&client_secret="+com.app.framework.utils.OAuthHelper.OAUTH2_CLIENT_SECRET;
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(requestUrl);
httpPost.addHeader("Authorization", header);
httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
httpPost.setEntity(new ByteArrayEntity(payload.getBytes()));
String response = httpClient.execute(httpPost, new BasicResponseHandler());
After some emails exchange with #Miguel, he successfully points me to the solution.
The issue:
The OAuthHelper that GoogleOAuthHelper extends uses TwoLeggedOAuthHelper to build the base_string. The TwoLeggedOAuthHelper was not injecting 3 required parameters: client_id, client_secret and the grant_type in the base_string.
The solution:
I had to create my own classes: copy/paste code from OAuthHelper to MyOAuthHelper and from TwoLeggedOAuthHelper to MyTwoLeggedOAuthHelper. You need some declarations from GoogleOAuthHelper to resolve compilation errors.
MyOAuthHelper will call MyTwoLeggedOAuthHelper instead of TwoLeggedOAuthHelper.
Now in MyTwoLeggedOAuthHelper, around line 79, locate the
String baseString = OAuthUtil.getSignatureBaseString(baseUrl,
httpMethod,…
and add the following:
String clientId = "client_id%3DXXX123456789XXX.apps.googleusercontent.com%26";
String clientSecret = "client_secret%3DXXXX_XXXX_XX_XX%26";
String grantType = "grant_type%3Durn%253Aietf%253Aparams%253Aoauth%253Agrant-type%253Amigration%253Aoauth1%26";
baseString = StringUtils.replace(baseString, "token&", "token&" + clientId + clientSecret + grantType);
Some notes:
client_id and client_secret must be the one your backend used to get the OAUTH1 access token. Be careful with that especially if you have multiple "Client ID for web application" defined in your Google console.
Notice the crazy grant_type encoded twice.
The Google classes used are located in maven: com/google/gdata/core/1.47.1/core-1.47.1.jar
Kudos to #Miguel
Your request is failing signature verification. Please check out the responses to this related question for detailed instructions on how to construct the base string for your request and sign it.
Hope that helps!

OAuth 2.0 Authorization: GAS and Google Maps Engine

I have a Google Maps Engine project where a datasource can be updated via Google Forms/Google Apps Script. I know that there is a way to configure OAuth in GAS (https://developers.google.com/apps-script/reference/url-fetch/o-auth-config) but I can't figure out how to make it work after spending hours reading through the GAS and GME documentation. I have been able to get around it using the OAuth Playground to obtain an access token, but I need to manually refresh each hour. I know the answer is probably simple, but I am new to OAuth and I can't find a simple guide out there to help me.
How can I get my Google Apps Script to play nicely with Google Maps Engine through OAuth?
I have included how I currently access GME below:
/* This function is called when a new provider is added through the "Medical Providers" form
It sends an HTTP request to Google Maps Engine to add the new provider to the map */
function addNewtoTable(row){
var aPIKey = "MY_API_KEY";
var bearer = "ACCESS_TOKEN_FROM_OAUTH_PLAYGROUND";
var projectID = "MY_PROJECT_ID";
var tableID = "MY_TABLE_ID";
//tutorial here https://developers.google.com/maps-engine/documentation/tutorial
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Providers");
var address = sheet.getRange(row,2).getValue();
var response = Maps.newGeocoder().geocode(address);
for (var j = 0; j < response.results.length; j++) {
var result = response.results[j];
//Logger.log('%s: %s, %s', result.formatted_address, result.geometry.location.lat,
// result.geometry.location.lng);
};
var lat = result.geometry.location.lat;
var long = result.geometry.location.lng;
var name= '"'+sheet.getRange(row,1).getValue()+'"';
var phone= '"'+sheet.getRange(row,4).getValue().toString()+'"';
var email= '"'+sheet.getRange(row,3).getValue()+'"';
var inbounds= '"'+sheet.getRange(row,5).getValue().toString()+'"';
var outbounds = '"'+sheet.getRange(row,6).getValue().toString()+'"';
var lastIn = '" '+sheet.getRange(row,7).getValue().toString()+' "';
var lastOut = '" '+sheet.getRange(row,8).getValue().toString()+' "';
var gxid = '"'+sheet.getRange(row,9).getValue().toString()+'"';
//HTTP request goes here
var payload = '{features:[{type: "Feature",geometry:{type: "Point",coordinates: ['+long+','+lat+']},properties: {gx_id: '+gxid+',name: '+name+',phone:'+phone+',email:'+email+',inbound:'+inbounds+',outbound:'+outbounds+',last_inbound:'+lastIn+',last_outbound:'+lastOut+'}}]}';
Logger.log(payload);
var headers = {"Authorization": "Bearer ACCESS_TOKEN_FROM_OAUTH_PLAYGROUND", "Content-type": "application/json"};
var options ={"method" : "post","headers" : headers, "payload" : payload, "muteHttpExceptions" : true};
var httpresponse = UrlFetchApp.fetch("https://www.googleapis.com/mapsengine/v1/tables/MY_TABLE_ID/features/batchInsert",options);
Logger.log(httpresponse);
if (httpresponse!=""){
MailApp.sendEmail('MY_EMAIL', 'HTTP Request Failed to Send', httpresponse);
};
};
It's certainly possible. The App Script docs have a tutorial explaining how to connect to a remote service using OAuth that uses the Twitter API as an example. This example also shows an OAuth-authorized call being executed.
The main difference in the tutorial for Maps Engine is the first step, where you don't set up with Twitter, you set up in the Developers Console.
You want to create a new OAuth client ID, under APIs & Auth -> Credentials. It's a web application.
Instead of setting the "Callback URL" in Twitter, you'll set the "Authorized Redirect URI" in the console, when creating the client ID. Set the authorized origins to docs.google.com too, just in case.
You'll get your "Consumer Key" and "Consumer Secret" through console.developers.google.com too, they correspond to the Client ID and Client Secret that are referred to in this GME doc.
In addition to the set up, these pointers may help you.
The UrlFetchApp.addOauthService("twitter") calls can use any string as an identifier, there's nothing special about the phrase "twitter", but it needs to match oAuthServiceName
The URLs you need look like they should be these (grabbed from here):
oAuthConfig.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");
oAuthConfig.setRequestTokenUrl("https://www.google.com/accounts/OAuthGetRequestToken?scope="+scope); Scope is explained here.
oAuthConfig.setAuthorizationUrl("https://www.google.com/accounts/OAuthAuthorizeToken");
A little too late for my purposes, but I found that Google themselves made a library for GAS that enables OAuth 2.0. Why this is not included within GAS is beyond me. This also looks to be pretty recent, with some updates as of 5 days ago.

Resources