I'm trying to use the Facebook Graph API to get the latest status from a public page, let's say http://www.facebook.com/microsoft
According to http://developers.facebook.com/tools/explorer/?method=GET&path=microsoft%2Fstatuses - I need an access token. As the Microsoft page is 'public', is this definitely the case? Is there no way for me to access these public status' without an access token?
If this is the case, how is the correct method of creating an access token for my website? I have an App ID, however all of the examples at http://developers.facebook.com/docs/authentication/ describe handling user login. I simply want to get the latest status update on the Microsoft page and display it on my site.
This is by design. Once it was possible to fetch the latest status from a public page without access token. That was changed in order to block unidentified anonymous access to the API. You can get an access token for the application (if you don't have a Facebook application set for your website - you should create it) with the following call using graph API:
https://graph.facebook.com/oauth/access_token?
client_id=YOUR_APP_ID&client_secret=YOUR_APP_SECRET&
grant_type=client_credentials
This is called App Access Token. Then you proceed with the actual API call using the app access token from above.
hope this helps
You can use AppID and Secret key to get the public posts/feed of any page. This way you don't need to get the access-token. Call it like below.
https://graph.facebook.com/PAGE-ID/feed?access_token=APP-ID|APP-SECRET
And to get posts.
https://graph.facebook.com/PAGE-ID/posts?access_token=APP-ID|APP-SECRET
It's no more possible to use Facebook Graph API without access token for reading public page statuses, what is called Page Public Content Access in Facebook API permissions. Access token even is not enough. You have to use appsecret_proof along with the access token in order to validate that you are the legitimate user. https://developers.facebook.com/blog/post/v2/2018/12/10/verification-for-individual-developers/.
If you are individual developer, you have access to three pages of the data (limited), unless you own a business app.
You can get the posts by simply requesting the site that your browser would request and then extracting the posts from the HTML.
In NodeJS you can do it like this:
// npm i request cheerio request-promise-native
const rp = require('request-promise-native'); // requires installation of `request`
const cheerio = require('cheerio');
function GetFbPosts(pageUrl) {
const requestOptions = {
url: pageUrl,
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0'
}
};
return rp.get(requestOptions).then( postsHtml => {
const $ = cheerio.load(postsHtml);
const timeLinePostEls = $('.userContent').map((i,el)=>$(el)).get();
const posts = timeLinePostEls.map(post=>{
return {
message: post.html(),
created_at: post.parents('.userContentWrapper').find('.timestampContent').html()
}
});
return posts;
});
}
GetFbPosts('https://www.facebook.com/pg/officialstackoverflow/posts/').then(posts=>{
// Log all posts
for (const post of posts) {
console.log(post.created_at, post.message);
}
});
For more information and an example of how to retrieve more than 20 posts see: https://stackoverflow.com/a/54267937/2879085
I had a similar use case for some weeks and I used this API:
https://rapidapi.com/axesso/api/axesso-facebook-data-service/
I could fetch all posts and comments in some minutes, worked quite well for me.
Related
I'm trying to mock up some persisted log-in for my first web application so the site is still functional after a refresh. When I print the token (which is saved in cookies) in the console, it prints normally. And when I use postman with the token in the header, I get the correct JSON response. However, when using it in the mounted method, I get a 401. So I believe it is an issue with the way I'm am implementing my headers in my fetch. Thanks in advance, as I am extremely new to coding.
mounted: function() {
console.log(this.$cookies.get('token'));
let t = JSON.parse(JSON.stringify(this.$cookies.get('token')));
let h = new Headers();
h.append('Authentication', `Bearer ${t}`);
fetch('http://localhost:8080/api/owner/persist', {
method: 'GET',
headers: h
})
.then((response) => {
return response.json();
})
.then((data) => {
this.jwtUser = data;
})
Java Controller below: if I have the PreAuthorize Tag, I get a 401 error, and if I take it away I get a null pointer exception. I think its just something wrong with the formatting of my header. Which I have been messing around with a lot.
#PreAuthorize("isAuthenticated()")
#RequestMapping(path = "api/owner/persist", method = RequestMethod.GET)
public Owner persistedLogin(Principal principal) {
Owner o = new Owner();
o = ownerDAO.getOwnerInfoByName(principal.getName());
return o;
}
The standard way to transport access tokens, and especially JWTs, is the header called Authorization.
In your code example you are using Authentication which is from a description point of view correct as JWTs are in the first step authenticating a request and only at the second step source for authorization. But the standard header is like it is and was named Authorization. Your formatting of the header-value (Bearer <token>) looks correct to me.
Double check the correct name of your header that needs to carry the token, and verify you are using the correct one which is working as you stated in your test with Postman.
Best,
cobz
I am trying to fetch git azure devops api to get information about repositories and branches in js.
In order to achieve that, I made a little application with the following code :
$(document).ready(function() {
var personalToken = btoa(':'+'<personnalAccessToken>');
fetch('https://dev.azure.com/<company>/<project>/_apis/git/repositories?api-version=5.1', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
'Authorization': 'Basic '+ personalToken
}
}).then(function(response) {
return response.json();
}).then(function(repositories) {
console.log("There are "+repositories.count+" repositories");
}).catch(function(error) {
console.log('Fetch error: ' + error.message);
});
This code is working great but as you can see there is my personnalAccessToken writen directly inside the code... which is really bad...
When I am using git in command line, I don't have to specify any credential information because I use git credential manager for windows. Which means my personnalAccessToken is already stored, cached and automatically used everytime I use a git command, like clone, etc.
So, I would like my js code to use the same thing, I would like it to use my stored credentials automatically to fetch the api without being required to set my personnalAccessToken in code.
I have already searched for hours but can't find out if it is possible.
I have already searched for hours but can't find out if it is
possible.
Sorry but as I know it's impossible. The way you're calling the Rest API is similar to use Invoke-RestMethod to call rest api in Powershell.
In both these two scenarios, the process will try to fetch PAT for authentication in current session/context and it won't even try to search the cache in Git Credential Manager.
You should distinguish the difference between accessing Azure Devops service via Rest API and by Code:
Rest API:
POST https://dev.azure.com/{organization}/{project}/{team}/_apis/wit/wiql?api-version=5.1
Request Body:
{
"query": "Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.WorkItemType] = 'Task' AND [State] <> 'Closed' AND [State] <> 'Removed' order by [Microsoft.VSTS.Common.Priority] asc, [System.CreatedDate] desc"
}
Corresponding Code in C#:
VssConnection connection = new VssConnection(new Uri(azureDevOpsOrganizationUrl), new VssClientCredentials());
//create http client and query for resutls
WorkItemTrackingHttpClient witClient = connection.GetClient<WorkItemTrackingHttpClient>();
Wiql query = new Wiql() { Query = "SELECT [Id], [Title], [State] FROM workitems WHERE [Work Item Type] = 'Bug' AND [Assigned To] = #Me" };
WorkItemQueryResult queryResults = witClient.QueryByWiqlAsync(query).Result;
Maybe you can consider using a limited PAT, limit its scope to Code only:
I know there exists other Authentication mechanism
:
For Interactive JavaScript project: ADALJS and Microsoft-supported Client Libraries.
You can give it a try but I'm not sure if it works for you since you're not using real Code way to access the Azure Devops Service... Hope it makes some help :)
If you have the script set up in an Azure Runbook you can set it as an encrypted variable there and have it pull it from there before running rather than having it directly written into the code.
$encryptedPatVarName = "ADO_PAT"
$adoPat = Get-AutomationVariable -Name $encryptedPatVarName
$adoPatToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($adoPat)"))
$adoHeader = #{authorization = "Basic $adoPatToken"}
The above is the Powershell version of it. I have seen some people do it with other
Months ago, Instagram began rendering their public API inoperable by removing most features and refusing to accept new applications for most permissions scopes. Further changes were made this week which further constricts developer options.
Many of us have turned to Instagram's private web API to implement the functionality we previously had. One standout ping/instagram_private_api manages to rebuild most of the prior functionality, however, with the publicly announced changes this week, Instagram also made underlying changes to their private API, requiring in magic variables, user-agents, and MD5 hashing to make web scraping requests possible. This can be seen by following the recent releases on the previously linked git repository, and the exact changes needed to continue fetching data can be seen here.
These changes include:
Persisting the User Agent & CSRF token between requests.
Making an initial request to https://instagram.com/ to grab an rhx_gis magic key from the response body.
Setting the X-Instagram-GIS header, which is formed by magically concatenating the rhx_gis key and query variables before passing them through an MD5 hash.
Anything less than this will result in a 403 error. These changes have been implemented successfully in the above repository, however, my attempt in JS continues to fail. In the below code, I am attempting to fetch the first 9 posts from a user timeline. The query parameters which determine this are:
query_hash of 42323d64886122307be10013ad2dcc44 (fetch media from the user's timeline).
variables.id of any user ID as a string (the user to fetch media from).
variables.first, the number of posts to fetch, as an integer.
Previously, this request could be made without any of the above changes by simply GETting from https://www.instagram.com/graphql/query/?query_hash=42323d64886122307be10013ad2dcc44&variables=%7B%22id%22%3A%225380311726%22%2C%22first%22%3A1%7D, as the URL was unprotected.
However, my attempt at implementing the functionality to successfully written in the above repository is not working, and I only receive 403 responses from Instagram. I'm using superagent as my requests library, in a node environment.
/*
** Retrieve an arbitrary cookie value by a given key.
*/
const getCookieValueFromKey = function(key, cookies) {
const cookie = cookies.find(c => c.indexOf(key) !== -1);
if (!cookie) {
throw new Error('No key found.');
}
return (RegExp(key + '=(.*?);', 'g').exec(cookie))[1];
};
/*
** Calculate the value of the X-Instagram-GIS header by md5 hashing together the rhx_gis variable and the query variables for the request.
*/
const generateRequestSignature = function(rhxGis, queryVariables) {
return crypto.createHash('md5').update(`${rhxGis}:${queryVariables}`, 'utf8').digest("hex");
};
/*
** Begin
*/
const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5';
// Make an initial request to get the rhx_gis string
const initResponse = await superagent.get('https://www.instagram.com/');
const rhxGis = (RegExp('"rhx_gis":"([a-f0-9]{32})"', 'g')).exec(initResponse.text)[1];
const csrfTokenCookie = getCookieValueFromKey('csrftoken', initResponse.header['set-cookie']);
const queryVariables = JSON.stringify({
id: "123456789",
first: 9
});
const signature = generateRequestSignature(rhxGis, queryVariables);
const res = await superagent.get('https://www.instagram.com/graphql/query/')
.query({
query_hash: '42323d64886122307be10013ad2dcc44',
variables: queryVariables
})
.set({
'User-Agent': userAgent,
'X-Instagram-GIS': signature,
'Cookie': `rur=FRC;csrftoken=${csrfTokenCookie};ig_pr=1`
}));
What else should I try? What makes my code fail, and the provided code in the repository above work just fine?
Update (2018-04-17)
For at least the 3rd time in a week, Instagram has again updated their API. The change no longer requires the CSRF Token to form part of the hashed signature.
The question above has been updated to reflect this.
Update (2018-04-14)
Instagram has again updated their private graphql API. As far as anyone can figure out:
User Agent is no longer needed to be included in the X-Instagram-Gis md5 calculation.
The question above has been updated to reflect this.
Values to persist
You aren't persisting the User Agent (a requirement) in the first query to Instagram:
const initResponse = await superagent.get('https://www.instagram.com/');
Should be:
const initResponse = await superagent.get('https://www.instagram.com/')
.set('User-Agent', userAgent);
This must be persisted in each request, along with the csrftoken cookie.
X-Instagram-GIS header generation
As your answer shows, you must generate the X-Instagram-GIS header from two properties, the rhx_gis value which is found in your initial request, and the query variables in your next request. These must be md5 hashed, as shown in your function above:
const generateRequestSignature = function(rhxGis, queryVariables) {
return crypto.createHash('md5').update(`${rhxGis}:${queryVariables}`, 'utf8').digest("hex");
};
So in order to call instagram query you need to generate x-instagram-gis header.
To generate this header you need to calculate a md5 hash of the next string "{rhx_gis}:{path}". The rhx_gis value is stored in the source code of instagram page in the window._sharedData global js variable.
Example:
If you try to GET user info request like this https://www.instagram.com/{username}/?__a=1
You need to add http header x-instagram-gis to request which value is
MD5("{rhx_gis}:/{username}/")
This is tested and works 100%, so feel free to ask if something goes wrong.
Uhm... I don't have Node installed on my machine, so I cannot verify for sure, but looks like to me that you are missing a crucial part of the parameters in querystring, that is the after field:
const queryVariables = JSON.stringify({
id: "123456789",
first: 4,
after: "YOUR_END_CURSOR"
});
From those queryVariables depend your MD5 hash, that, then, doesn't match the expected one. Try that: I expect it to work.
EDIT:
Reading carefully your code, it doesn't make much sense unfortunately. I infer that you are trying to fetch the full stream of pictures from a user's feed.
Then, what you need to do is not calling the Instagram home page as you are doing now (superagent.get('https://www.instagram.com/')), but rather the user's stream (superagent.get('https://www.instagram.com/your_user')).
Beware: you need to hardcode the very same user agent you're going to use below (and it doesn't look like you are...).
Then, you need to extract the query ID (it's not hardcoded, it changes every few hours, sometimes minutes; hardcoding it is foolish – however, for this POC, you can keep it hardcoded), and the end_cursor. For the end cursor I'd go for something like this:
const endCursor = (RegExp('end_cursor":"([^"]*)"', 'g')).exec(initResponse.text)[1];
Now you have everything you need to make the second request:
const queryVariables = JSON.stringify({
id: "123456789",
first: 9,
after: endCursor
});
const signature = generateRequestSignature(rhxGis, csrfTokenCookie, queryVariables);
const res = await superagent.get('https://www.instagram.com/graphql/query/')
.query({
query_hash: '42323d64886122307be10013ad2dcc44',
variables: queryVariables
})
.set({
'User-Agent': userAgent,
'Accept': '*/*',
'Accept-Language': 'en-US',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'close',
'X-Instagram-GIS': signature,
'Cookie': `rur=${rurCookie};csrftoken=${csrfTokenCookie};mid=${midCookie};ig_pr=1`
}).send();
query_hash is not constant and keep changing over time.
For example ProfilePage scripts included these scripts:
https://www.instagram.com/static/bundles/base/ConsumerCommons.js/9e645e0f38c3.js
https://www.instagram.com/static/bundles/base/Consumer.js/1c9217689868.js
The hash is located in one of the above script, e.g. for edge_followed_by:
const res = await fetch(scriptUrl, { credentials: 'include' });
const rawBody = await res.text();
const body = rawBody.slice(0, rawBody.lastIndexOf('edge_followed_by'));
const hashes = body.match(/"\w{32}"/g);
// hashes[hashes.length - 2]; = edge_followed_by
// hashes[hashes.length - 1]; = edge_follow
So, I'm building a new app, using the Javascript API to log in (v2.4), and the latest version (v5) of the PHP API. I created a Test App for it, and am working with that. Using the Javascript API, I do the login and authentication, and save the access token that I get back from getLoginStatus:
FB.getLoginStatus(function(response) {
handleLoginStatusChange(response);
});
function handleLoginStatusChange(response)
{
. . .
else if (response.status == 'connected')
{
// make ajax call to save access token
. . .
}
Then, inside my PHP code, I pick up that access token, and try to use it. I calculate an appsecret_proof, as well.:
$this->fb = new Facebook\Facebook([
'app_id' => <my app id>,
'app_secret'=> <my app secret>,
'default_graph_version' => 'v2.3'
]);
$this->fbApp = new Facebook\FacebookApp( <my app id>,
<my app secret );
$appsecret_proof = hash_hmac('sha256', <access token>,
<app secret>);
Then I try to post a link to my FB account:
$linkData = [
'link' => 'https://google.com',
'message' => 'This is a test post',
];
$response = $this->fb->post('/me/feed', $linkData, <access token>);
This gives me the dreaded " Invalid appsecret_proof provided in the API argument" error. I don't know if this is the problem, but if I put the access token into the debugger, it tells me that this access token is for my actual app, not my test app, so the appID's don't match. I don't think that's a problem, if I understand test apps, especially because I am using the test appID and test appSecret all through this code.
Any ideas? I see several things here on StackOverflow, but they either are older versions of the API, or talk of editing the API code itself to disable things.
it tells me that this access token is for my actual app, not my test app, so the appID's don't match
That is why it does not work.
Think about what happens on Facebook’s side: They need the app secret that was used to calculate the app-secret proof, so they have to look that up via the app id. And where do they get that app id from? From the access token …!
So if you pass an access token for a different app, that will make them use the wrong app secret to calculate the app-secret proof – and so it will not match the value that you sent.
You need to use an access token that belongs to your test app.
(And the PHP SDK takes care of calculating and sending the app secret proof itself, you don’t need to handle that manually.)
How does one request, store, and use an access token from an API in the Meteor framework? I am currently trying to make requests from the (Instagram API)[https://instagram.com/developer/authentication/], but I first need to request an access token and store it for later use.
What is the general structure for doing this? I have my Client Id and Client Secret stored in the settings.json and have the services configuration package loaded. I think I need to create some sort of Method using http.get, but if someone could give a brief walkthrough that would be greatly appreciated ! Not much on this in the Meteor Docs.
You can use Bozhao Package for this.
Just install it.
meteor add bozhao:accounts-instagram
And this will work exactly like tha core accounts - facebook || google || twitter
and you can do something like this on the accountsOnCreateUser Methods
if (user.services.instagram) {
console.log("-- REGISTED USER WITH INSTAGRAM ");
instagramProfile = {
socialProfileUrl: user.services.instagram.profile_picture,
socialName: user.services.instagram.full_name,
service: "Instagram",
profileUrl: "https://instagram.com/"+ user.services.instagram.username
};
user.profile = instagramProfile;
}
Now knowing this, you can see that we have the user data inside the user.services.instagram object, there should be a accessToken and id field that you make POST / GET http request to the https://instagram.com/api/v1/.
I have never done a HTTP request to the Instagram API but it should be similar to facebook (if not sorry the below code dosnt help you to much).
Simple http call using the params.
Meteor.http.get("https://instagram.com/api/v1/", {
headers: {
"User-Agent": "Meteor/1.0"
},
params: {
access_token: user.services.accessToken
}
},function(error,result){
if(!error){
console.log(result);
}
});