How to properly generate Facebook Graph API App Secret Proof in Javascript - javascript

I am making a server side Facebook Graph API call to the all_mutual_friends edge: https://developers.facebook.com/docs/graph-api/reference/user-context/all_mutual_friends/
The call works when the two users are friends, but returns no useful data when they users aren't friends. According to the docs, this is because I must sign the call with the appsecret_proof parameter. No matter what I try, I am not able to successfully pass this parameter. I am using jsrsasign running on Parse. I have tried every configuration of using the access token as the message and my appSecret as the key, and vice versa. I have also tried multiple combinations of utf8 and hex. Every time I receive the error: invalid appsecret_proof provided in the API argument
Code:
var Signer = require("cloud/vendor/jsrsasign/lib/jsrsasign.js");
var userId = request.params.userId;
var accessToken = request.params.accessToken;
var appSecret = "redactedStringPastedFromFacebook";
var signer = new Signer.Mac({alg: "hmacsha256", pass: appSecret});
var appSecretString = signer.doFinalString(accessToken);
var appSecretHex = signer.doFinalHex(accessToken);
var graphRequestURL = "https://graph.facebook.com/v2.5/" + userId;
var fields = "?fields=context.fields(all_mutual_friends.fields(name,picture.width(200).height(200)))";
//var authorization = "&access_token=" + accessToken; //this works, but only for existing friends
var authorization = "&access_token=" + accessToken + "&appsecret_proof=" + appSecretHex;
return Parse.Cloud.httpRequest({
url: graphRequestURL + fields + authorization,
method: "GET",
})
Most examples I have seen are in PHP or Python and the crypto routines are a bit more clear. This works in that both appSecretString and appSecretHex don't throw errors and look reasonable, however the values are always rejected by Facebook.
Notes:
I have triple checked the App Secret value provided by Facebook
I have been approved by Facebook to use the all_mutual_friends feature, which is a requirement for this particular call
I am using Parse, which isn't Node, and can't use NPM modules that have external dependencies, which is why I am using jsrsasign. I also tried using CryptoJS directly, but it is no longer maintained and doesn't have proper module support and jsrsasign seems to wrap it anyway.

Here it is:
import CryptoJS from 'crypto-js';
const accessToken = <your_page_access_token>;
const clientSecret = <your_app_client_secret>;
const appsecretProof = CryptoJS.HmacSHA256(accessToken, clientSecret).toString(CryptoJS.enc.Hex);

Related

What's the equivalent of auth.getIdTokenClient() on Android?

I need to query a token from a GCP function, for that, I want to do what the js function GoogleAuth<JSONClient>.getIdTokenClient(targetAudience) does but on Android.
Right now I'm using this code to generate an auth token:
GoogleCredentials
.fromStream(
app.assets.open("my_config_file.json")
)
.createScoped(
listOf(
"https://www.googleapis.com/auth/cloud-platform"
)
)
But the token generated is a ya29.c., wherewith the getIdToken I get a valid token.
How can I get a valid token as getIdToken on my Android app?
For Java based applications you have the google-auth-library-java. Looking in the docs for this library you have the IdTokenCredentials.Builder and IdTokenCredentials classes.
Also you have an example use case:
String credPath = "/path/to/svc_account.json";
String targetAudience = "https://example.com";
// For Application Default Credentials (as ServiceAccountCredentials)
// export GOOGLE_APPLICATION_CREDENTIALS=/path/to/svc.json
GoogleCredentials adcCreds = GoogleCredentials.getApplicationDefault();
if (!adcCreds instanceof IdTokenProvider) {
// handle error message
}
IdTokenCredentials tokenCredential = IdTokenCredentials.newBuilder()
.setIdTokenProvider(adcCreds)
.setTargetAudience(targetAudience).build();
// Use the IdTokenCredential in an authorized transport
GenericUrl genericUrl = new GenericUrl("https://example.com");
HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(tokenCredential);
HttpTransport transport = new NetHttpTransport();
HttpRequest request = transport.createRequestFactory(adapter).buildGetRequest(genericUrl);
HttpResponse response = request.execute();
// Print the token, expiration and the audience
System.out.println(tokenCredential.getIdToken().getTokenValue());
System.out.println(tokenCredential.getIdToken().getJsonWebSignature().getPayload().getAudienceAsList());
System.out.println(tokenCredential.getIdToken().getJsonWebSignature().getPayload().getExpirationTimeSeconds());
Documentation:
Github repository
Using OAuth 2.0 with the Google API Client Library for Java

Accessing a private Google sheet with a google cloud service account

I have a private Google Spreadsheet and I’m trying to access it programmatically using Google Visualization/Google Charts. I've created the service account, and I have tried using the google-auth-library and googleapis npm packages to create an access token that I can then use to access the spreadsheet. But, when I try to use that access token to read from the spreadsheet, the request fails with HTTP code 401 (Unauthorized). What do I need to do in order to make this work?
This is my code:
const { auth } = require('google-auth-library');
const keys = require('./jwt.keys.json');
const id = '{Spreadsheet ID}';
const sheetId = '{Sheet ID}';
const query = "{Query}";
async function getClient(){
const client = auth.fromJSON(keys);
client.scopes = ['https://www.googleapis.com/auth/spreadsheets.readonly'];
console.log(client);
return client;
}
async function main(){
const client = await getClient();
const token = await client.getAccessToken();
console.log(token);
console.log(token.token);
const url = `https://docs.google.com/spreadsheets/d/${id}/gviz/tq?tqx=out:csv&tq=${encodeURIComponent(query)}&access_token=${token.token}#gid=${sheetId}`;
const res = await client.request({url});
console.log(res);
}
main().catch(console.error);
When I saw your script, I thought that it is required modifying the scope. I had got the same situation as you (the status code 401 using your endpoint). Unfortunately, it seems that your endpoint cannot be used using the scope of https://www.googleapis.com/auth/spreadsheets.readonly. So, for example, how about changing it as follows, and testing it again?
From:
https://www.googleapis.com/auth/spreadsheets.readonly
To:
https://www.googleapis.com/auth/spreadsheets
or
https://www.googleapis.com/auth/drive.readonly
Note:
When I tested your endpoint using the modified scope, I confirmed that no error occurred. But if you tested it and when an error occurs, please check other part, again.

Azure Blob Storage 403 Authentication Failed Due To Authorization Header

Problem
I have uploaded a set of images (blobs) to a private Azure Blob Storage account, but when I try to access them, I am faced with the following error.
GET https://<account-name>.blob.core.windows.net/<container-name>/<blob-name> 403 (Server failed
to authenticate the request. Make sure the value of Authorization header is formed correctly
including the signature.)
I don't have any problems uploading this data as this is done through the server-side using a Django app. I wish to be able to successfully retrieve this uploaded blob data using client-side JavaScript.
Background
I have thoroughly read through and implemented the steps from the Microsoft Azure documentation for authorizing access to my private account via the use of Shared Keys. This includes everything from constructing my signature string to hashing this data using the HMAC SHA-256 algorithm, as detailed in the link above.
I am running everything on Docker containers except for the client-side Vue-based interface which is attempting to invoke the Get Blob API endpoint, as you will see below.
Minimum Reproducible Example
The code that raises this error is as follows:
// Add imports
const crypto = require('crypto');
const axios = require('axios');
// Set Azure blob storage data
const account = "<azure-blob-storage-private-account-name>"
const version = "2020-04-08"
const blob = "<blob-name>"
const container = "<container-name>"
const blob_uri = `https://${account}.blob.core.windows.net/${container}/${blob}`;
const today = new Date().toGMTString();
// Construct signature string
const CanonicalisedHeaders = `x-ms-date:${today}\nx-ms-version:${version}\n`;
const CanonicalisedResource = `/${account}/${container}/${blob}`;
const StringToSign = `GET\n\n\n\n\n\n\n\n\n\n\n\n` + CanonicalisedHeaders + CanonicalisedResource;
// Hash string using HMAC Sha-256 and encode to base64
const key = "<shared-access-key-in-base64>";
const utf8encoded = Buffer.from(key, 'base64').toString('utf8');
const signature = crypto.createHmac('sha256', utf8encoded).update(StringToSign).digest("base64");
// Construct the headers and invoke the API call
const blob_config = {
headers: {
"Authorization": `SharedKey ${account}:${signature}`,
"x-ms-date": today,
"x-ms-version": version
}
}
await axios.get(blob_uri, blob_config)
.then((data) => console.log(data))
.catch((error) => console.log(error.message));
What I have tried
I have tried the following, but none of them have helped me resolve the issue at hand.
Updated CORS settings to avoid CORS-related 403 Forbidden Access issues.
Regenerated my key and connection strings.
Checked the DateTime settings on my local machine and on my Docker containers to ensure they are on the correct GMT time.
Checked that my signature string's components (canonicalized headers, resources, etc.) are constructed according to the rules defined here.
Read through similar StackOverflow and Azure forum posts in search of a solution.
Please try by changing the following lines of code:
const utf8encoded = Buffer.from(key, 'base64').toString('utf8');
const signature = crypto.createHmac('sha256', utf8encoded).update(StringToSign).digest("base64");
to
const keyBuffer = Buffer.from(key, 'base64');
const signature = crypto.createHmac('sha256', keyBuffer).update(StringToSign).digest("base64");
I don't think you need to convert the key buffer to a UTF8 encoded string.
Few other things:
Considering you're using it in the browser, there's a massive security risk as you're exposing your storage keys to your users.
Is there a reason you're using REST API directly instead of using Azure Storage Blob SDK?
In browser-based environments, you should be using Shared Access Signature based authorization instead of Shared Access Key based authorization.

Unexpected error on UrlFetchApp.fetch in Google Apps Script using basic authentication

I have the following code in Google Apps Script which retrieves CSV data from a webpage via HTTP using basic authentication and places it into a spreadsheet:
CSVImport.gs
function parseCSVtoSheet(sheetName, url)
{
// Credentials
var username = "myusername";
var password = "mypassword";
var header = "Basic " + Utilities.base64Encode(username + ":" + password);
// Setting the authorization header for basic HTTP authentication
var options = {
"headers": {
"Authorization": header
}
};
// Getting the ID of the sheet with the name passed as parameter
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName(sheetName);
var sheetId = sheet.getSheetId();
// Getting the CSV data and placing it into the spreadsheet
var csvContent = UrlFetchApp.fetch(url, options).getContentText();
var resource = {requests: [{pasteData: {data: csvContent, coordinate: {sheetId: sheetId}, delimiter: ","}}]};
Sheets.Spreadsheets.batchUpdate(resource, spreadsheet.getId());
}
This has been working up until recently where randomly I get the following error on the UrlFetchApp.fetch line:
Exception: Unexpected error: http://www.myurl.com/data/myfile.csv (line 21, file "CSVImport")
I have tried:
Putting the credentials directly in the URL instead of in an Authorization header (I received a different error saying "Login information disallowed").
Encoding the credentials to base64 right when I pass it into the headers object (didn't work, same error).
Removing authentication altogether (predictably I received a 401 response from the HTTP page).
I'm not sure what else to try and why this randomly broke down all of a sudden. Any advice?
This is related to a new bug, see here
Many users are affected, I recommend you to "star" the issue to increase visibility and hopefully accelerate the process.
I had the same situation. At that time, I could noticed that when the built-in function of Google Spreadsheet is used for the URL, the values can be retrieved. In that case, as the current workaround, I used the following flow.
Put a formula of =IMPORTDATA(URL).
Retrieve the values from the sheet.
When above flow is reflected to your URL of http://www.myurl.com/data/myfile.csv, it becomes as follows.
About basic authorization for URL:
When I saw your script, I confirmed that you are using the basic authorization. In this case, the user name and password can be used for the URL like http://username:password#www.myurl.com/data/myfile.csv.
From your script, when the values of username and password are myusername and mypassword, respectively, you can use the URL as http://myusername:mypassword#www.myurl.com/data/myfile.csv.
Here, there is an important point. If the specific characters are included in username and password, please do the url encode for them.
Sample script:
function myFunction() {
const url = "http://myusername:mypassword#www.myurl.com/data/myfile.csv"; // This is your URL.
// Retrieve the values from URL.
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName(sheetName);
sheet.clear();
var range = sheet.getRange("A1");
range.setFormula(`=IMPORTDATA("${url}")`);
// Retrieve the values from sheet to an array.
SpreadsheetApp.flush();
var values = sheet.getDataRange().getValues();
range.clear();
console.log(values)
}
When above script is run, the values from the URL are put to the sheet, and the values are retrieved as 2 dimensional array for values. If you want to leave only values without the formula, I think that you can copy and paste the values.
In this answer, I used IMPORTDATA. But for each situation, other functions might be suitable. In that case, please check them.
Note:
This is the current workaround. So when this issue was removed, I think that you can use your original script.
References:
IMPORTDATA
setFormula()
Disable Chrome V8 Runtime Engine until Google fix this.
To disable:
From Menu click on Run > Disable new Apps Script runtime powered by Chrome V8
As per #346 from the official issue tracker https://issuetracker.google.com/issues/175141974
hey guys I have found a possible solution that is working for me try
inputting empty {} like this
UrlFetchApp.fetch(apiLink, {});
it worked for me
I tried this, and it too is working for me. Works even when using the "new Apps Script runtime powered by Chrome V8".

How to get accessToken for firebase 3 using javascript

I need to make a shallow rest call using javascript with firebases REST Api, in the past version I needed to pass the access token like this:
var authKey = ref.getAuth().token;
var s = firebaseUrl + '/.json?shallow=true&auth=' + authKey;
$http.get(s)
How do I do this now with firebase 3?
firebase.auth().signInWithEmailAndPassword(u, p).then(function(result){
result.getToken().then(function(token){
$rootScope.userLoginToken = token;
});
});
The access token is no longer available in the authData when you're using version 3.x of the SDK. But you can get is when the user gets authenticated with.
var auth = firebase.auth();
var provider = new firebase.auth.GoogleAuthProvider();
auth.signInWithPopup(provider).then(function(result) {
var accessToken = result.credential.accessToken;
});
For this and a lot more information that is relevant when migrating your web app, see the upgrade guide for web apps (where I copied the code from).

Categories