Android Facebook C# login not loading internally - facebook

I'm trying to follow Prabir's blog post about Facebook authentication and adapt it in an Xamarin Android-based application. I have a login page that has the following code defined to generate the URL;
private Uri GenerateLoginUrl(string appId, string extendedPermissions)
{
var parameters = new Dictionary<String, Object> ();
parameters.Add("client_id", appId);
parameters.Add ("redirect_uri", "https://www.facebook.com/connect/login_success.html");
parameters.Add("response_type", "token");
parameters.Add("display", "popup");
if (!string.IsNullOrWhiteSpace(extendedPermissions))
parameters.Add("scope", extendedPermissions);
var fb = new FacebookClient();
return fb.GetLoginUrl(parameters);
}
On create of the view, I call this method and assign it to a WebView element:
var browser = this.FindViewById<WebView>(Resource.Id.LoginView);
Uri url = GenerateLoginUrl("<app id>", "");
browser.LoadUrl (url.ToString ());
In the view, I have the following defined:
<WebView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/LoginView" />
When LoadUrl is called, it prompts to open a browser externally (open in Chrome|Firefox|Opera options), not login through the WebView control defined in my view tied to the LoginActivity. I don't know what is going on here, and why this is happening. How can I display the request within the webview control, within the application itself and not externally?

So it sounds like the system URL handling is getting in your way. What you need to do is set a WebViewClient on your WebView. This will allow you to return true from shouldOverrideUrlLoading (which I believe is the default) which will then allow the requested resource to be loaded by your application.
See the relevant documentation here in WebViewClient, shouldOverrideUrlLoading specifically. But the takeaway is
If WebViewClient is not provided, by default WebView will ask Activity Manager to choose the proper handler for the url.
Which in the case of an http-based URI, will most certainly be the system browser.

Related

Error redirecting to specfic page after Smartsheet Auth

#RequestMapping(value = "/smartsheet.htm", method= RequestMethod.GET)
public void smartSheetAuth(HttpServletRequest req,HttpServletResponse resp,HttpSession session){
String userEmail = ************;
StringBuilder authUrl = new StringBuilder();
authUrl.append("https://app.smartsheet.com/b/orgsso/");
authUrl.append(gPlusUser.getDomain());
/*
authUrl.append("?loginEmail=");
authUrl.append(userEmail);*/
oauth = new OAuthFlowBuilder()
.setAuthorizationURL(authUrl.toString())
.setClientId(EnvironmentVariables.SMARTSHEET_CLIENT_ID)
.setClientSecret(EnvironmentVariables.SMARTSHEET_CLIENT_SECRET)
.setRedirectURL( APPLICATION_BASE_URL + RequestMapping value to be redirected to)
.build();
EnumSet<AccessScope> smartsheetAccessScopes = EnumSet.of(AccessScope.READ_SHEETS,AccessScope.SHARE_SHEETS);
String url = oauth.newAuthorizationURL(smartsheetAccessScopes, "MY_STATE");
System.out.println("url >>>"+url);
resp.sendRedirect(url);
}
What I am trying to do is Organizational Login . But, after I am authenticated instead of going to the redirect url specified in OAuthFlowBuilder ,it takes me to my home page of smartsheet. If I dont setAuthorizationURL() , I have to select the Oraganization Login from the options. It kinda works, but what I am trying to achieve is to get directly to SSO login page instead of clicking organization login option and then going further.
I have made an app using developer tools option of smartsheet and mentioned the same redirect url there also. Nothing seems to work.
Now redirect url is optional , but if dont specify that I am prompted with IllegalStateException.
Using smartsheet-sdk-java-2.0.0.jar
The flow that you're trying to achieve is not possible. i.e., https://app.smartsheet.com/b/orgsso/ is not a valid authorization URL for Smartsheet oAuth.
Instead (as described in the Smartsheet oAuth documentation), you must use this URL: https://app.smartsheet.com/b/authorize. From there, the user can select the appropriate option to specify how they'd like to login.

FacebookAuthorizeFilter endless redirect

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.

facebook sdk authorization on multiple pages

var auth = new CanvasAuthorizer();
auth.Permissions = new[] { "user_about_me" };
if (auth.Authorize())
{ get needed data...
If i place this code in default.aspx (Page_Load method), it works fine.
If I place this in a viewscore.aspx (Page_Load method), Authorize() returns false.
I get to viewscore.aspx by using a link from default.aspx, nothing special. Why is this difference?
I'm not sure, but doesn't Facebook allow canvas authorization only on page which has been given as a Canvas URL?

Best practice for dual-use iFrame + External authentication for Facebook enabled app

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.

Deeplinking using GWT History Token within a Facebook iFrame Canvas

I would like to deep link directly to a GWT app page within a Facebook iFrame Canvas.
The first part is simple using GWT's History token with URLs like:
http://www.example.com/MyApp/#page1
which would open page1 within my app.
Facebook Apps use an application url like:
http://apps.facebook.com/myAppName
which frames my Canvas Callback URL
http://www.example.com/MyApp/
Is there a way to specify a canvas callback url (or bookmark url) which will take the user to a specific page rather than the index page?
Why? you may ask. Besides all the benefits of deep links...
I want the "Go To Application" url to take users to an index page w/ marketing material (the canvas callback url)
I want the "Bookmark URL" to take (likely returning) users to a login page and bypass downloading the marketing content (and that huge SWF file).
This may seem to be a hack but here it goes.
Facebook allows the application to tack on parameters to the url ?x=123
So I'm checking the window location to see if it contains my special 'page' parameter and loading that page. Below is my solution given that I'm using GWT + gwt-presenter's PlaceManager class.
The application deep url ends up being http://apps.facebook.com/myAppName?page=page1
EventBus eventBus = injector.getEventBus();
// Load PlaceManager so it can start listening
PlaceManager placeManager = injector.getPlaceManager();
String currentPlace = History.getToken();
String place = Window.Location.getParameter( "page" );
if (place != null && !place.isEmpty()) {
// send user to the place on the URL line
eventBus.fireEvent( new PlaceRequestEvent( new PlaceRequest( new Place (place) ) ));
} else if ("".equals(currentPlace)) {
// Nothing in URL, load default GWTpage
eventBus.fireEvent( new PlaceRequestEvent(new PlaceRequest( IndexPresenter.PLACE)));
} else {
// fire a body to the Place Manager to activate the requsted Place
placeManager.fireCurrentPlace();
}