I am building a helper class that would allow me to manage facebook account in a windows form application. I am using Facebook C# SDK. As it is suggested in its documentation, to know if the user is authenticated one would get the loginUrl
var loginUrl = oauth.GetLoginUrl(parameters);
and then afterward, navigate to that Url
webBrowser.Navigate(loginUrl);
Since I am on the back end of the application, I wonder how one can write a helper class that will return true or false to show if the user is authenticated or not. I would love to do something like:
public static bool IsUserAunthenticated (string appId, string[] extendedPermissions)
How can this function be written? Any ideas? Remember I am using windows form on .net 4.0
assuming this function is called some time after
webBrowser.Navigate(loginURL)
A solution like this could work:
assuming:
The user has already authorized your application
'facebookClient' is a static class field is already initialized
public static bool isUserAuthenticated() {
try
{
facebookClient.Get("/me");
//the lack of an exception thrown is a sign of success
return true;
}
catch (FacebookOAuthException e)
{
//your access token used to initialize 'facebookClient' is invalid
return false;
} }
Some general notes:
you didn't show any code to handle the retrieval of the actual access
token. I've been assuming you left that out for brevity.
passing in 'appID' and 'extendedPermissions' means that this method
would be used to REGISTER the user, not for testing to see if they
were already a user of your application.
related to the above point, you could pass in the access token as an
argument so you would be able to initialize 'facebookClient' inside
the method and run the authentication test.
To sum that last little bit up, if you're not already aware, you need to realize there are two distinct stages: getting the access token and using the access token.
Related
I've got a Meteor application with a multi-phase sign-up process. The accounts are based on the accounts-password package. In the step prior to account creation, the user needs to provide some profile information.
I'd like the user to be able to launch a Facebook OAuth flow which pre-populates the profile fields with information pulled from Facebook.
This all needs to happen pre-account-creation. I want to implement this with the facebook package that backs accounts-facebook.
At the moment I've got the OAuth flow happening by calling Facebook.requestCredential, but I'm not sure how to get an OAuth access token from the credential token that comes back. I suspect I need to pass this to the server and make an API call to get back an access token.
Any pointers as to how this should work would be much appreciated.
Facebook.requestCredential(function (credentialTokenOrError) {
if (credentialTokenOrError && credentialTokenOrError instanceof Error) {
// Error...
console.log(credentialTokenOrError);
} else {
// Credential Token string
console.log(credentialTokenOrError);
// Now perhaps a Meteor.call to a server method that
// 1. Retrieves an access token
// 2. Hits the graph API to get profile information and returns it to the client
}
});
Thanks,
Chris
I was having the same trouble of converting a credentialToken to an accessToken, only with Github. I've written up a gist that has code that should work very similarly. Essentially, there are two steps:
Within your Facebook.requestCredential callback function, call OAuth._retrieveCredentialSecret(tokenOrError), the result of which is the credentialSecret. Then use Meteor.call, passing in tokenOrError and credentialSecret, to call the Meteor.method you'll set up in the next step.
code (on client):
Github.requestCredential({
loginStyle: 'popup',
requestPermissions: ['gist']
}, function(tokenOrError) {
if (tokenOrError && tokenOrError instanceof Error) {
// Throw a Meteor error
console.log('error getting the token');
return;
}
var credentialSecret = OAuth._retrieveCredentialSecret(tokenOrError);
Meteor.call('getGithubAccessToken', tokenOrError, credentialSecret, function(err, accessToken) {});
});
On the server, set up a Meteor.method that takes your credentialToken and credentialSecret and calls Facebook.retrieveCredential. This function returns a credentials object from the _pendingCredentials Mongo Collection before deleting it from the collection. The access token is credentials.serviceData.accessToken. The credentials object could potentially be persisted in the user object in the Meteor.users collection (as it is in the accounts packages) or sent back to the user.
code (on server):
Meteor.methods({
getGithubAccessToken: function(credentialToken, credentialSecret) {
var credentials = Github.retrieveCredential(credentialToken, credentialSecret);
console.log('accessToken:', credentials.serviceData.accessToken);
return credentials.serviceData.accessToken;
}
});
I'm unfamiliar with the specifics of Facebook's Graph API so after these steps, you're on your own. Good luck!
I'm trying to add facebook login to my application. To that extent I'm using a following snippet of code:
[FacebookAuthorize]
public ActionResult About()
{
ViewBag.Message = "Your app description page.";
return View();
}
And I register a filter FacebookAuthorizeFilter.
When I navigate to /Home/About what I get is an endless redirect to
/Home/About?code=AQAPoxl1J-.......
I can login using facebook if I just use OAuth provided in ASP.NET MVC4 project template.
What am I missing?
I delved into the FacebookAuthorize filter code more. The reason the Filter does not work with non-canvas applications is that inside the filter's OnAuthorization method the the method is relying on Facebook's signed_request being present in the POST requset when the user is redirected back to your application. If signed_request is never present the filter will continue to redirect:
...code omitted...
if (signedRequest == null || String.IsNullOrEmpty(userId) || String.IsNullOrEmpty(accessToken))
{
// Cannot obtain user information from signed_request, redirect to Facebook OAuth dialog.
string redirectUrl = GetRedirectUrl(request);
Uri loginUrl = client.GetLoginUrl(redirectUrl, _config.AppId, null);
filterContext.Result = CreateRedirectResult(loginUrl);
}
...code omitted..
An alternative approach may be to create a similar filter that checks for the existent of the code query string parameter. Once code is obtained you may use your application's appId and appSecret to exchange the code for an access token. Once the access token is obtained you may determine which permissions the user has granted and process appropriately.
After hours spent debugging, reflecting, source-code analyzing I came to the conclusion that FacebookAuthorizeAttribute and FacebookAuthorizeFilter can only meaningfully be used in a Facebook Canvas application.
I think this is because Facebook just made some changes to their login process.
A few days ago, my air app was working with the FacebookDesktop class from the facebook-actionscript-api. Today I opened the project, and noticed that the Facebook login screens are a little different than when I started building my app. After I type my userID and password in, I grant the app access to my public profile, friend list and photos. Then another screen comes up where I grant the app permission to post to my friends on my behalf.
Then the window closes, and I get 'OAuthException 2500 An active access token must be used to query information about the current user.'
When I sign into Facebook through a browser, I see that my app shows up under "Your Apps" but I can't log in and do anything through my app.
I'm using Adobe Flash with GraphAPI_Desktop_1_8_1.swc on OSX Lion.
Here is my code:
public function MyApp(){
FacebookDesktop.init(APP_ID, onInit);
}
protected function onInit(result:Object, fail:Object):void {
if (result) {
trace("onInit, Logged In\n");
} else {
trace("onInit, Not Logged In\n");
var permissions:Array = ["publish_stream", "user_photos"];
FacebookDesktop.login(onLogin, permissions);
}
}
protected function onLogin(result:Object, fail:Object):void {
if (result) {
trace("Logged In as:");
trace(FacebookDesktop.getSession().user.name);
} else {
trace("Login Failed");
trace('code: '+fail.error.code); //code: 2500
trace('message: '+fail.error.message); //message: An active access token must be used to query information about the current user.
trace('type: '+fail.error.type); //type: OAuthException
}
}
I get the same results when I use the FlashDesktopExample provided in the SDK (modified with my app ID of course).
Any help would be great! Thanks in advance!
I ran into this problem, too: the success URL did not have any parameters.
Cadderly82 is correct; it's a problem with secure navigation. Turning off my Facebook account's "Secure Browsing" option fixed the problem, but I didn't want to have to require users to do that.
I was able to solve this by setting ALL of the FacebookURLDefaults values to use "https://".
Now I get a valid user-access-token in the success URL; and I get valid values regardless of my facebook account's secure-browsing setting.
I have figured out the problem:
The LoginWindow class sets the token based on the uri.
This worked fine until the LOGIN_SUCCESS_URL (http://www.facebook.com/connect/login_success.html) and the LOGIN_SUCCESS_SECUREURL (https://www.facebook.com/connect/login_success.html) pages stopped including the access token in the uri.
Hence, the token is never set. Even though my app shows up under the 'Your Apps' section in Facebook after I login, my app still can't do anything without a token.
I have two solutions:
The easiest is to change the LOGIN_SUCCESS_URL and/or the LOGIN_SUCCESS_SECUREURL properties in com.facebook.graph.core.FacebookURLDefaults like the following.
beginning on line 82:
public static var LOGIN_SUCCESS_URL:String = 'http://www.facebook.com/connect/login_success.html';
public static var LOGIN_SUCCESS_SECUREURL:String = 'https://www.facebook.com/connect/login_success.html';
Change to:
public static var LOGIN_SUCCESS_URL:String = 'http://www.yourwebsite.com/';
public static var LOGIN_SUCCESS_SECUREURL:String = 'https://www.yoursecurewebsite.com/';
of course you would use your own website - not 'http://www.yourwebsite.com/'. I tried it using http://localhost/ and it worked great.
Another solution is:
Change com.facebook.graph.windows.LoginWindow with the following code. I have made some changes to my files, so please forgive me if the line numbers are different than what they are in a fresh copy of the SDK.
around line 143:
change:
vars.redirect_uri = FacebookURLDefaults.LOGIN_SUCCESS_URL;
to:
vars.redirect_uri = 'http://localhost/';
In production code, you probably want to use your own website address instead of localhost, but I used localhost for testing purposes.
Then, in the handleLocationChange function (should be around line 168) add this to the else if statements:
}else if (html.location.indexOf ('http://localhost/') == 0) {
loginCallback(getURLVariables(), null);
userClosedWindow = false;
html.stage.nativeWindow.close();
html.removeEventListener(Event.LOCATION_CHANGE, handleLocationChange);
}
As I said before, change localhost to your website address.
I hope this helps!
I was having the same problem for random users and I don't really know if it is a Facebook problem with secure navigation:
Check user properties in Facebook: Security Settings > Secure Browsing
(www.facebook.com/settings?tab=security)
For users with "disabled" state it works fine.
For users with "migration" state it gives the error.
For users with previously "enabled" state it works fine.
When I tried with users in "migration" state and changed to "disabled", it worked fine.
Then if I turned it "enabled", the "migration" state is gone and marked as "enabled", but the error it's still there.
So I guess this is the problem, but don't know how to fix it.
alot has change in a year this is what i think the answer is now
you need a token to login FacebookDesktop.getSession().accessToken
public function MyApp(){FacebookDesktop.init(APP_ID, onInit, FacebookDesktop.getSession().accessToken); }
I'm trying to integrate the scoring system in my canvas app with Facebook's, implemented using MVC 3 and the 5.2.1.0 Facebook SDK.
A simplified hello-world variant of my game controller looks like:
public class MyController : Controller
{
[CanvasAuthorize("publish_action" /*And some others*/)]
public ActionResult Index()
{
var fb = new FacebookWebClient();
var scores = fb.Get("/me/scores"); // Works (I think)
fb.Post("/me/scores", new { score = 10 }); // Throws an exception
}
}
The call to get scores looks like it's giving me something sensible; the call to write a score value throws "(OAuthException) (#15) This method must be called with an app access_token."
What've I missed? The application id and secret are correctly set in my web.config - for example, I can successfully post an application apprequest in other parts of the actual application not shown in this stripped down test copy. Rummaging around with the debugger shows me that the FacebookWebClient object contains a non-empty access token field, and that that's included in the URI that fb.Post eventually uses.
The Facebook scores page (that Björn links to) mentions only publish_actions but I've tried including other pertinent sounding permissions, such as offline_access and user_games_activity to no effect.
I am assuming that the CanvasAuthorize attribute does the login correctly - it certainly seems to let me send an application apprequest, so it looks as if it's doing the right thing...
Your app needs the permission to write to the users profile. You can use the Graph API to request the required permissions from the user. If granted, Facebook will give you the required access token that you can then use in your request to Facebook. This practice ensures that you only perform actions, the user allowed you to.
Edit_: After looking at the docs: Are you sure you have the required permissions from the user like described here http://developers.facebook.com/docs/score/ ?
Please see this link. You'll need to get an facebook app. Using the apiId and SecretId, you can then post using the information in the link below. The key is adding &scope=manage_pages,offline_access,publish_stream to the url.
like so:
"https://graph.facebook.com/oauth/access_token?client_id={0}&client_secret={1}&grant_type=client_credentials&scope=manage_pages,offline_access,publish_stream";
Okay, if cookies are a no-no, then I need a little guidance as to the best way to implement the application(s) that I'm creating.
The scenario is that I'm trying to create a single Asp.Net MVC application that can authenticate a user regardless of whether the user visits a site directly or via an iFrame in Facebook. There are separate actions (in separate controllers, actually) for getting INTO the app depending on whether the user enters via Facebook or not, but there are also places in the Facebook app where I'm opening up a new window to "extended" functionality in other areas of the application that can't really work well within the iFrame. It is supposed to transition seamlessly. It's currently working quite well using cookies, but I've from multiple sources that this is not a good thing for iFrame apps. However, I'm not sure exactly what this means.
Without cookies, can you still somehow get server-side access to the authentication token? If not, then what is the "right" way to handle this. Do I need to resort to manually parsing the token using the JS API and sending an AJAX notification to the server of the fact that the user is authenticated and create a forms auth token? Will the CanvasAuthorize attribute work without cookies? Right now I have added code to the FormsAuthentication_OnAuthenticate event in Global.asax to create the forms auth token if the user is logged in via Facebook (and properly associated with a valid user in the external app) as follows:
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs args)
{
if (FormsAuthentication.CookiesSupported)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] == null)
{
// Attempt to authenticate using Facebook
try
{
FacebookApp fbApp = new FacebookApp();
if (fbApp.Session != null)
{
dynamic me = fbApp.Get("me");
String fbID = "" + me.id;
MembershipUser mUser = AppMembershipProvider.GetUserByFacebookID(fbID);
if (mUser != null)
{
FormsAuthentication.SetAuthCookie(mUser.UserName, false);
AppMembershipProvider.UpdateLastLogin(mUser.UserName);
Session["FacebookLogin"] = true;
}
}
}
catch (Exception e)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(e);
}
}
}
else
{
throw new HttpException("Cookieless Forms Authentication is not " +
"supported for this application.");
}
}
Will I need to change this?
Sorry if this is basic knowledge, but I'm confused as to how best to implement this. Thanks!
First, let me address the issue with the cookies. So, when I say to not use cookies in iFrames I am saying that for a couple reasons. First in IE, there are some security issues. You need to add the following header to your app to make cookies work correctly inside iframes:
P3P: CP="CAO PSA OUR"
The second big issue with cookies in iframe apps is Safari. Due to security settings in Safari, cookies cannot be created by iframes. As such, you will not be able to rely on cookies for authentication inside of iframes.
Give that you are using the app inside and outside of the iframe, you should have cookie support turned on. However, your app must be designed in a way that will work around the iframe issues. That is going to be the hard part.
The most reliable authentication inside iframe apps is the signed request method. What happens is facebook will append a query parameter to your url when the url is rendered inside the iframe. This query parameter contains the user's session. The Facebook C# SDK handles reading this for you, so you dont need to parse it etc. But you need to be aware that it is there. If you view the incoming request url of your iframe app in facebook you will see something like http://www.mysite.com/page/?signed_request={blahblahblah}.
So the key is that you need to make sure that if you are in the iframe you keep that ?signed_request value on the url.
You can do this several ways. First, you can use the CanvasRedirect methods. These are extension methods on System.Web.Mvc.Controller in the Facebook.Web.Mvc namespace. The canvas redirect uses javascript to redirect your page in the top url. This way Facebook is actually handling the redirects and will always add the signed_request to your iframe url. The problem for you is that this method of redirecting will only work in the iframe, not outside.
The second way would be to manually add the ?signed_request to the url when you redirect. You would do something like:
public ActionResult Something() {
return RedirectToAction("something", new { signed_request = Request.Querystring["signed_requets"]);
}
There are other ways also, like storing data in the session or something, but I wouldn't recommend going down that path.
What you are doing is definitely an advanced senario, but hopefully the above will help you get going in the right direction. Feel free to contact me directly if you have any questions. nathan#ntotten.com or #ntotten on twitter.
I am in a similar situation to you. What I do to handle the various situations that can arise is:
Enable cookies in both the C# and
JavaScript SDK.
Create a custom actionfilter that
inherits from
FacebookAuthorizeAttribute and
overrides the
HandleUnauthorizedRequest method to
redirect to either a connect
authorization page or an action
decorated with the
CanvasAuthorizeAttribute.
Pass either the signed_request
(canvas app) or auth_token (connect
app) as a querystring parameter to
everything.
Check for null sessions and oauth
tokens that don't match what has been
passed in the querystring.
The main point is to ensure that both the session and oauth tokens are valid. When inside Facebook the signed_request will ensure this is true. By passing the token from your connect auth page you can ensure you have a valid token to inject into the FacebookApp constructor.
public class FbAuthenticateAttribute : FacebookAuthorizeAttribute
{
private FacebookApp _fbApp;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var accessToken = filterContext.HttpContext.Request.Params["access_token"];
if (FacebookApp.AccessToken != accessToken && !string.IsNullOrEmpty(accessToken))
{
_fbApp = new FacebookApp(accessToken);
}
else
{
_fbApp = FacebookApp;
}
filterContext.Controller.ViewBag.Context = GetContext().ToString();
filterContext.RequestContext.HttpContext.Response.AppendHeader("p3p", "CP=\"CAO PSA OUR\"");
try
{
dynamic user = _fbApp.Get("me");
var signedRequest = filterContext.HttpContext.Request.Params["signed_request"];
filterContext.Controller.ViewBag.QueryString = string.IsNullOrEmpty(signedRequest)
? "?access_token=" + _fbApp.AccessToken
: "?signed_request=" + signedRequest;
}
catch (Exception ex)
{
string url = GetRedirectUrl(filterContext);
filterContext.Result = new RedirectResult(url);
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
string url = GetRedirectUrl(filterContext);
filterContext.Result = new RedirectResult(url);
}
private string GetRedirectUrl(ControllerContext filterContext)
{
return new UrlHelper(filterContext.RequestContext).Action(GetRedirectAction(GetContext()), "Authentication");
}
private Enums.AppContext GetContext()
{
//Note: can't rely on this alone - find something more robust
return FacebookApp.SignedRequest == null ? Enums.AppContext.FBWeb : Enums.AppContext.FBApp;
}
private string GetRedirectAction(Enums.AppContext context)
{
return context == Enums.AppContext.FBWeb ? "ConnectAuthenticate" : "Authenticate";
}
}
It could definitely do with a refactor and still has problems but is the best solution I have found so far.