Facebook API - access token for web application - javascript

I am making a web app that pulls the latest posts from our Facebook page and processes them.
This is all working fine with a hard-coded access token generated from this page.
The problem is that this token expires, so i am looking for a solution to generate a new token every time the page loads or a non-expiring token - (i have read somewhere that non expiring tokens don't exist anymore).
So of course i did some research, here, here and here.
But non of these examples seem to be working.
Before any complaints of some code that i have tried so far, this is my working example - with an expiring access token:
var Facebook = function () {
this.token = 'MYTOKEN';
this.lastPost = parseInt((new Date().getTime()) / 1000);
this.posts = [];
};
Facebook.prototype.GetPosts = function () {
var self = this;
var deffered = $q.defer();
var url = 'https://graph.facebook.com/fql?q=SELECT created_time, message, attachment FROM stream WHERE created_time < ' + self.lastPost + ' AND source_id = 437526302958567 ORDER BY created_time desc LIMIT 5&?access_token=' + this.token + '';
$http.get(url)
.success(function (response) {
angular.forEach(response.data, function (post) {
self.posts.push(new Post(post.message, post.attachment.media, post.attachment.media[0].src, post.created_time, 'facebook'));
});
self.lastPost = response.data[response.data.length -1].created_time;
deffered.resolve(self.posts);
self.posts = [];
});
return deffered.promise;
};
return Facebook;
Any help / suggestion will be greatly appreciated.

First off, it is important to remember that Facebook has just launched the Version 2 of the Graph API. From April 2014 on, if you have issues with your app, you need to tell us when you created it on Facebook Developers (new apps use the Version 2 by default).
In order manage pages, your app needs to have manage_pages permission. Make sure that the user you want to manage fan pages for has authorized you. If your app uses the Version 2, make sure that Facebook (the Facebook staff) has authorized you to ask users that kind of permission, otherwise your app won't work.
Once you get your token, exchange it for a permanent token (or a token with long expiry date). Make sure you use the token of the fan page, not the token of the user.
If instead you want to read the stream of public fan pages, you need an access token with read_stream permissions. This permission needs to be approved by Facebook (see above) and this specific type of permission takes time to approve, if you're using the Version 2 of the Graph API. If you're using the old API (Version 1), you can still do that without pre-approval on Facebook's side.
The URL to ask for the permission to read the stream is as follows: https://www.facebook.com/dialog/oauth?client_id=$YOUR_APP_ID&redirect_uri=$YOUR_URL&scope=read_stream,manage_pages (i've added manage_pages in this case, you may not need it).
That url will prompt for authorization. Once the user has authorized the app, you'll be recirected to the URL you chose, with a code= variable.
At that point, call this other url:
https://graph.facebook.com/oauth/access_token?client_id={$app_id}&redirect_uri=$someurl&client_secret={$app_secret}&code={$code}
You'll get a response that has the access_token=variable in it. Grab that access token, exchange it for a long one, with the following URL:
https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id={$app_id}&client_secret={$app_secret}&fb_exchange_token={$token_you_have_just_grabbed}
The response will give you a token that lasts for some time. Previously, Facebook had decided to have these "long duration tokens" expire after one month. I have found out, though, that they may have changed their mind: if you put a user token in the debugger, you'll see it never expires.
This is the authorization flow for users who visit with a browser. There's the app authorization flow too. If all you need is a stream from your own Fan page, you want to do the following (with Graph API V.1):
make an HTTP GET request using the following URL:
https://graph.facebook.com/oauth/access_token?type=client_cred&client_id={$app_id}&client_secret={$app_secret}
Use the resulting token to make another HTTP GET call, like so:
https://graph.facebook.com/{$your_page_id}/feed?{$authToken}&limit=10
//ten posts
Decode the json object
You're done.

Related

Unity and Google OAuth2 Authorization Code Flow

I am trying to build a Unity application to be deployed with WebGL. I am trying to incorporate Google Sign-In into the application, and so far, this was what I've managed to make work in the Unity WebGL build in Chrome:
User presses on the "Login with Google" button on Unity application, in Tab A.
User is directed to Google Sign In page on another Tab B.
User signs in with Google account, and is redirected to my redirect_uri, which is simply https://localhost, with the auth code parameter.
My question is, is it possible for me to do the following, possible with .jslib files:
Instead of going to redirect_uri on Tab B, instead go back to Tab A without reloading, passing along the auth code.
Building on the line above, have javascript handlers, that:
When auth code is received, initiate request to exchange auth code for the id_token as instructed here.
When id_token is received, call a C# Script function to do further actions with the id_token.
Alternatively, I can set redirect_uri to be an endpoint on my backend server, and perform the auth token -> id_token flow using the Google client SDKs. However, for this approach, I would like to know if i am able to
After the auth token -> id_token flow is completed on the backend server, close the current window, Tab B, and go back to Tab A.
After we’re back on Tab A, redirect Unity to a specific scene (not the login scene anymore, but a home page that users are directed to after they are authenticated).
Would very much appreciate any help i can get :')
EDIT: For better clarity, what I want to achieve is something that FacebookSDK for Unity has done in their FB.LogInWithReadPermissions(). The whole auth code -> access_token flow is seamless, and i get redirected back to the Unity application in Tab A at the end with the access_token.
I managed to find a Javascript solution to achieve my first method. The differences are that because
My application will never be in production
Consistency with my Facebook OAuth implementation,
I used the implicit flow instead of the authorization code flow, despite it being not the recommended way due to security concerns. However, I think you can easily use the authorization code flow, retrieving the authorization code and passing it on to your backend to exchange for an id token. (as far as I know, you cannot use Javascript/XHR requests to do this exchange)
So, the flow is that from my C# script, I call a Javascript function from a .jslib file. Basically, the function detects when the OAuth window has redirected back to my redirect_uri, then gets the access_token parameter from the redirected URI, and calls a C# Script function. From there, you should be able to do whatever you need to do (change scene, send to your backend, etc.). Note that there is a try/catch because there will be errors if you attempt to get information from the Google Sign In pages.
The file is as follows:
mergeInto(LibraryManager.library, {
OpenOAuthInExternalTab: function (url, callback) {
var urlString = Pointer_stringify(url);
var callbackString = Pointer_stringify(callback);
var child = window.open(urlString, "_blank");
var interval = setInterval(function() {
try {
// When redirected back to redirect_uri
if (child.location.hostname === location.hostname) {
clearInterval(interval) // Stop Interval
// // Auth Code Flow -- Not used due to relative complexity
// const urlParams = new URLSearchParams(child.location.search);
// const authCode = urlParams.get('code');
// console.log("Auth Code: " + authCode.toString());
// console.log("Callback: " + callbackString);
// window.unityInstance.SendMessage('Auth', callbackString, authCode);
// Implicit Flow
var fragmentString = child.location.hash.substr(1);
var fragment = {};
var fragmentItemStrings = fragmentString.split('&');
for (var i in fragmentItemStrings) {
var fragmentItem = fragmentItemStrings[i].split('=');
if (fragmentItem.length !== 2) {
continue;
}
fragment[fragmentItem[0]] = fragmentItem[1];
}
var accessToken = fragment['access_token'] || '';
console.log("access_token: " + accessToken);
child.close();
// Invoke callback function
window.unityInstance.SendMessage('Auth', callbackString, accessToken);l
}
}
catch(e) {
// Child window in another domain
console.log("Still logging in ...");
}
}, 50);
}
});
Then, in my C# script, I call this function using the following:
public class GoogleHelper : MonoBehaviour
{
[DllImport("__Internal")]
private static extern void OpenOAuthInExternalTab(string url, string callbackFunctionName);
// ...
public void Login(string callbackFunctionName) {
var redirectUri = "https://localhost";
var url = "https://accounts.google.com/o/oauth2/v2/auth"
+ $"?client_id={clientId}"
+ "&response_type=token"
+ "&scope=openid%20email%20profile"
+ $"&redirect_uri={redirectUri}";
OpenOAuthInExternalTab(url, callbackFunctionName);
}
// ...
}
Of course, this is super hacky, and I'm not very familiar with Javascript and so don't really know the implication of the code above, but it works for my use case.

AngularJs bookmarkable url and query parameters

I'm trying to fix one mistake which one of the previous developer has did. Currently in project we have half-bookmarkable pages. Why half? Because if token has expired user will be redirect to resourse where he has to provide his credentials and on success he will be redirect to previous resource which he has used before. Sounds good for now. The problem here is next, some of resources have server side filtering and highly complicated logic which comes in the end to the one object like this:
param = {
limit: 10,
offset: 0,
orderBy: 'value',
query: 'string query can include 10 more filters'
};
then thru the service it sends a request to the end point
var custom = {};
function data(param) {
custom.params = param;
return $http.get('/data_endpoint', custom)
.then(function (response) {
return response.data;
});
From this part request works fine and response is correct but url isn't. User cannot store current url with search params because url doesn't have it and if user will try to get back to previous page with applied filters he won't be able to do that.
Also there is interceptor in this application, which add one more query param to every api request, because every api request requires some specific validation. This param is always the same.
I have tried to add $location.search(param) exactly to this data service but it breaks the app with infinity loop of login / logout looping, because interceptor stops working and all other request are sending without special query param
Then I thought to add this params inside interceptor like this:
config.params.hasOwnProperty('query') ? $location.search(config.params) : $location.search();
But it still breaks app to infinity loop.
This method adds query to url but doesn't allow to modify it, so if user applied additional filtering, it doesn't send new modified request but sends old one.
if (config.params.hasOwnProperty('query')){
for (var key in config.params){
$location.search(key, config.params[key])
}
}
That's why I decided to ask question here, probably somebody gives an advice how to solve this problem. Thanks in advance.

Meteor.js - Fetch/Get Enrollment token (from Accounts.sendEnrollmentEmail)

I can't figure out how to get the enrollment token from the Accounts.sendEnrollmentEmail function.
I know this function sends a direct mail towards the user which in the end looks something like this:
http://localhost:3000/#/enroll-account/FCXzBbqHInZgBlLaOpu8Iv11jP9DJEG-e1auAHDsh6S
However, I would need to somehow get only to the token part FCXzBbqHInZgBlLaOpu8Iv11jP9DJEG-e1auAHDsh6S as I want to send enrollment mail trough a different service (e.g Postmark)
How to do this?
The Accounts.sendEnrollmentEmail(userId, email) function generates a random token and saves it in the user's services.password.reset.token field.
The code that generates the token is:
var token = Random.secret();
var when = new Date();
var tokenRecord = {
token: token,
email: email,
when: when
};
Meteor.users.update(userId, {$set: {
"services.password.reset": tokenRecord
}});
(You can view the function's source code here).
It then sends an email to the user using the Email package. If you want to use a different service to send the email, you basically have 2 options:
Use the same convention yourself (i.e, create the same record and use your own email service in your own function).
Use the existing function, allow the mail delivery to fail silently and then query the user's document for the token and send the email yourself.
Neither is a particularly good option, but both will work for the time being. I wish they had refactored this part into its own function.
Note that the accounts packages are expected to undergo some changes towards the release of the next Meteor versions.
BTW, this function is very similar to Accounts.sendResetPasswordEmail, which you may also wish to override or create your own version.

Content Service for Google Apps Script returning HTML instead of JSON

Trying out the Content Service API and the example is returning HTML when it should be returning JSON, what am I doing wrong?
https://developers.google.com/apps-script/guides/content
function doGet(request) {
var events = CalendarApp.getEvents(
new Date(Number(request.parameters.start) * 1000),
new Date(Number(request.parameters.end) * 1000));
var result = {
available: events.length == 0
};
return ContentService.createTextOutput(JSON.stringify(result))
.setMimeType(ContentService.MimeType.JSON);
}
GAS from another file trying to make the request:
function myFunction() {
var url = "published URL";
url+="?start=1325437200&end=1325439000";
var options = {
method:"get"
}
var response = UrlFetchApp.fetch(url,options).getContentText();
response = JSON.parse(response); //error, unexpected token <
}
Your usage of ContentService is correct, the code works exactly as is. Here is a link to my copy of your code published as a web app:
https://script.google.com/macros/s/AKfycbzx2L643LHw0oQAq1jBmKQh2ju_znGfmdj78dUypj36iF-s91w/exec
The problem you are running into is related to Authorization or Authentication, if the published script is not authorized, an HTML error message is returned.
To check if that is your issue, simply access the published URL directly in your browser. If you see JSON displayed, then Authorization is not the problem. If you see the "Authorization is required to perform that action" error message, open your published script and choose "doGet" from the Run menu, then follow the authorization prompts.
More likely, the problem is related to how your script is published. In order to access your published script from another script, it must be published with the "Who has access to the app" setting as "Anyone, Even anonymous". If you use any other value, Google returns an HTML login page instead of your JSON response, and you get the error you are seeing.
This happens because requests sent from Google Apps Script via URLFetchApp are not authenticated, they don't carry the credentials of the user running the code with them, and come in as anonymous requests.
If you don't allow "Anyone, even anonymous" in your publishing settings, Google redirects non-authenticated requests to the Google login page.

javascript - make facebook page post

I am not using the Javascript SDK because that is client-side whereas I'm making a server-side call.
I want to make a page post so that I can make an ad creative with it. I can do the call perfectly fine in the Graph API Explorer tool, but I cannot make the same call (with the same long-lived access tokens that continue to work in the Graph Explorer) from Javascript. Here is my code:
tok = <valid and never expiring user token>;
var pg_tok = <valid and never expiring page token>;
var act_id = <account_id>;
var pg_id = <page_id>;
var call_to_action = 'INSTALL_MOBILE_APP';
var fb_app_url = 'https://itunes.apple.com/us/app/id284882215';
var msg = 'Test creative, ya see';
var pic_url = 'https://s3.amazonaws.com/<path_to_my_image>';
var ROOT = 'https://graph.facebook.com/';
var pagepost_endpoint = ROOT+pg_id+'/feed';
console.log(pagepost_endpoint);
var pagepost_params = {
access_token: pg_tok,
call_to_action: {
type: call_to_action,
value: {link: fb_app_url}
},
message: msg,
picture: pic_url,
published: false
};
console.log(pagepost_params);
var pagepost_res = HTTP.post(pagepost_endpoint, {params: pagepost_params});
console.log(pagepost_res);
I have played around a bunch with params vs. data for where pagepost_params goes in the HTTP.post that is giving the error (that is Meteor's HTTP btw).
-Putting everything in params gives the error: {"error":{"type":"Exception","message":"No Call To Action Type was parseable. Please refer to the call to action api documentation","code":1373054,"is_transient":false}}.
-Putting everything in data gives the error: {"error":{"message":"(#200) This API call requires a valid app_id.","type":"OAuthException","code":200}}.
-Putting access_token in params and everything else in data gives the error: {"error":{"message":"Invalid parameter","type":"FacebookApiException","code":100,"error_subcode":1349125}}.
One more clue for everyone, if I change the HTTP.post to HTTP.get, and just put access_token in params and include no other parameters (in params or in data), the call succeeds and I see past posts I have made on this page through the Graph Explorer (only the ones with published: true, though), so the access token and endpoint do work, just something is faulty about POST-ing instead of GET-ing and the specific parameters I'm using.
Have you tried posting to /photos instead of /feed? The error subcode is the same as mentioned here Posting to facebook wall using graph api
Hope this helps
Turned out to be an issue with Meteor's HTTP. It does not handle nested JSON very well, and we're going to submit a pull request for that. But for those seeing this, the important thing to take away is that the call_to_action may not be a valid JSON object, and even if it is, it may not be being stringified/parsed as expected. My fix was using request.post instead of HTTP.post. (then instead of params or data, you use form. look up node's request https://github.com/mikeal/request)

Categories