Authorizing Facebook Applications in AIR

I was asked to make AIR compatibile version of FacebookOAuthGraph class. It was a nice little challenge for me, where I learn some new things about AIR. E.g. how easy it is to define the JavaScript callback directly from ActionScipt (HTMLLoader.window.methodName)… There are more ways to make authorization process work for you, I have chosen the one with popup window. In order to make FacebookOAuthGraph work for AIR, the short extending is required, instead of creating popup window from JavaScript, popup is created by HTMLLoader.createRootWindow (no ExternalInterface required)…

Lets start with the main AIR Application file:

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                       xmlns:s="library://ns.adobe.com/flex/spark" 
                       xmlns:mx="library://ns.adobe.com/flex/mx"
                       applicationComplete="init()">
    <s:layout>
        <s:VerticalLayout />
    </s:layout>
    
    ... here comes the same content as in:
    "http://blog.yoz.sk/2010/05/facebook-graph-api-and-oauth-2-and-flash/"
    ...
    just change redirectURI to:
    "http://blog.yoz.sk/examples/FacebookOAuthGraph/aircallback.html"
    ...

</s:WindowedApplication>

Do not bother with callback.html (you do not need any, if you do, create an empty .html file) and make your Facebook class look something like this:

package
{
    import flash.display.NativeWindowInitOptions;
    import flash.events.Event;
    import flash.geom.Rectangle;
    import flash.html.HTMLLoader;
    import flash.net.URLRequest;
    
    public class Facebook extends FacebookOAuthGraph
    {
        public var loader:HTMLLoader;
        
        public function Facebook()
        {
            super();
        }
        
        override public function connect():void
        {
            var options:NativeWindowInitOptions = new NativeWindowInitOptions();
            var boundaries:Rectangle = new Rectangle(200,200,500,500);
            loader = HTMLLoader.createRootWindow(true, options, true, boundaries);
            loader.load(new URLRequest(authorizationURL));
            loader.addEventListener(Event.LOCATION_CHANGE, onLocationChange);
        }
        
        private function onLocationChange(event:Event):void
        {
            var hash:String = locationExtractHash(loader.location);
            var error:String = locationExtractError(loader.location);
            if(hash)
                confirmConnection(hash);
            if(hash || error)
                destroyLoader();
        }
        
        protected function locationExtractHash(url:String):String
        {
            var index:int = url.indexOf("#");
            if(index <= -1)
                return null;
            var hash:String = url.substr(index + 1);
            return hashToToken(hash) ? hash : null;
        }
        
        protected function locationExtractError(url:String):String
        {
            var index:int = url.indexOf("?");
            if(index <= -1)
                return null;
            var query:String = url.substr(index + 1);
            var variables:URLVariables = new URLVariables(query);
            return variables.hasOwnProperty("error") ? variables.error : null;
        }

        private function destroyLoader():void
        {
            loader.removeEventListener(Event.LOCATION_CHANGE, onLocationChange);
            loader.removeEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
            loader.stage.nativeWindow.close();
            loader = null;
        }
    }
}

How does this works? You do not need any callback.html, just generate some redirect_uri that correctly targets your domain, it can be anything, even 404.html, or asdf-foo-bar.html file that does not exist. Once the Facebook, redirects you back to the redirect_uri, the url contains access_token within url hash

http://domain/whatever-or-404.html#access_token=123

… now, if it does, it is parsed by locationExtractHash(), if it does not, the url contains error (user clicked Cancel button), the url contains error:

http://domain/whatever-or-404.html?error_reason=user_denied&error=access_denied&error_description=The+user+denied+your+request.

In case of error locationExtractError() parses and error. In both cases (error or access_token), the HTMLLoader window is closed and token is handled via confirmConnection(). Yeah, this way you can authorize !ANY! existing facebook application for your AIR app.

Depricated version using JavaScript in callback

Keep the changes in main AIR Application file (see above), and make small changes in aircallback.html, in fact it got even simplier comparing to the original:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="sk" lang="sk" dir="ltr">
<head>
	<script type="text/javascript">
	<!--
		confirmFacebookConnection(window.location.hash);
	//-->
	</script>
</head>
<body>
<p>You may now close this window.</p>
</body>
</html>

… finally some method changes in FacebookOAuthGraph class:

package
{
    import flash.display.NativeWindowInitOptions;
    import flash.events.Event;
    import flash.geom.Rectangle;
    import flash.html.HTMLLoader;
    import flash.net.URLRequest;
    
    import sk.yoz.net.FacebookOAuthGraph;
    
    public class Facebook extends FacebookOAuthGraph
    {
        public var loader:HTMLLoader;
        
        public function Facebook()
        {
            super();
        }
        
        override public function connect():void
        {
            var options:NativeWindowInitOptions = new NativeWindowInitOptions();
            var boundaries:Rectangle = new Rectangle(200,200,500,500);
            loader = HTMLLoader.createRootWindow(true, options, true, boundaries);
            loader.load(new URLRequest(authorizationURL));
            loader.addEventListener(Event.HTML_DOM_INITIALIZE, onDOMInit);
        }
        
        private function onDOMInit(event:Event):void
        {
            loader.window[jsConfirm] = confirmConnection;
        }
        
        override public function confirmConnection(hash:String):void
        {
            super.confirmConnection(hash);
            loader.stage.nativeWindow.close();
        }
    }
}

What it does is, it pushes JavaScript callback into HTMLLoader that is called via aircallback.html JavaScript…

Where to go from here:

22 comments so far

  1. verg September 21, 2010 23:47

    wow. Thanks for the quick response. I’m going to try this implementation out now… brb.

  2. Davide September 28, 2010 15:20

    I’m a little bit confused… what is this “hash” that i should use to confirm my authentication? And how can I retreive it? THanks!

  3. Jozef Chúťka September 28, 2010 15:22

    @Davide, please read first two articles on http://blog.yoz.sk/facebookoauthgraph/
    hash is access token used with OAuth

  4. Davide September 28, 2010 15:55

    Thanks for your answer, but in your articles you said that callback.html pastes hash code retreived from facebook to flash. In my case I have an AIR application… how can I retreive this code? I’m sure I’m missing something…

  5. Davide September 28, 2010 16:26

    Now it works… I don’t know what happened…

  6. sHTiF October 1, 2010 01:23

    Hi nice article however i am curious why do you use javascript? I did the OAuth FB authentification within AIR for my android facebook application discovery a while ago and i’ve never even thought about using js so i am curious. Is there a special point to it? To me it seems unnecessary since we can easily grab the token from within the HTMLLoader (WebView when on Android AIR) instead.

    Maybe i am missing something but skipping the JS you also get great advantage that you can practically connect to any facebook application no matter what they callback url is since you don’t need to communicate or have special functionaity within it, pretty much didn’t even need to exist.

  7. Jozef Chúťka October 1, 2010 10:03

    Hi sHTiF,
    are you guys in flash-core Slovak?
    this article/code was created as a working AIR solutions with the less possible changes to flash player version ( http://blog.yoz.sk/2010/05/facebook-graph-api-and-oauth-2-and-flash/ ), however I realize that there is a bigger spectrum of solutions…
    You gave me a great idea of remaking this to work as you describe, can you leave some notes how your solution works?
    - do you send redirectURI to facebook authorization request (even no existing file just to maintain fb app defined domain)?
    - do you listen for Event.LOCATION_CHANGE and grab session/access_token directly from HTMLLoader.location?
    thnx for the tip

  8. sHTiF October 1, 2010 10:43

    Hi, yep we are from Slovakia ;)

    About the solution yep you still need to pass the redirectURI since it needs to be defined for each application on facebook side and once its defined if you make a call without it you will get bad redirectURI error.

    You can still use the HTML_DOM_INITIALIZE event and check for the HTMLLoader.location and parse the acess_token from there. Of course you can work with the LOCATION_CHANGE for example i work with HTML_DOM_INITIALIZE in AIR for desktop but work with LOCATION_CHANGE once working in AIR for android. Since AIR on Android doesn’t support HTMLLoader class and you need to use special StageWebView class that doesn’t dispatch HTML_DOM_INITIALIZE events.

    So you can modify your example just slightly and instead of using the JS parse the HTMLLoader.location inside the onDOMInit check if there is access_token and if its there you are done just parse it and close your HTMLLoader window. ;)

  9. Jozef Chúťka October 1, 2010 11:33

    sHTiF, that sounds good. I will give it a try when some free time.
    nice to see slovak guys doing good stuff in flash ;)

  10. sHTiF October 1, 2010 11:56

    Jozef, thanks ;) BTW you inspired me so i wrote a small summary on how to authentificate a user on Facebook from within an Android AIR. So check check it out on my blog if you want ;)

  11. Jozef Chúťka October 1, 2010 14:11

    @sHTiF, cool!

  12. Gar October 1, 2010 22:30

    I’m trying to create a Flash app that will run on a kiosk. The app will need to connect to Facebook. Is it possible to log into Facebook using a virtual keyboard from Flash?

    Any lead would be appreciated :)

    - Gar

  13. Jozef Chúťka October 4, 2010 11:32

    @Gar, I guess it is possible, it depends on how HTMLLoader (native air class) handles virtual keyboard

  14. vErG October 6, 2010 00:51

    Hi Jozef,
    I got everything working on my end, thanks for all you work and additional posts. I was wondering if you know of any way to clear the data for the autofill form fields. When a user closes my application and it is relaunched, the facebook login page still contains the previous user’s email account. I’ve looked at the adobe documentation and it tells you how to delete all cookies from the control panel, but that is not a viable solution for a kiosk. Thanks!

  15. Jozef Chúťka October 6, 2010 17:28

    Hi vErG,
    I guess you are talking about html form fields history, it depends on your browser… ctrl+shift+del and select remove form data does the work in chrome. HTML and JavaScript within Adobe AIR are handled by the WebKit HTML/JavaScript engine… Sory I do not know how to handle history in there.

  16. [...] do not need a callback.html file, as in previous AIR example, you can use non existing redirect_uri targeting correct domain + a simple extending of [...]

  17. Mira November 4, 2010 15:16

    Hi Jozef,

    I recently found out that Adobe/FB released a new AS3FB API.
    http://code.google.com/p/facebook-actionscript-api/
    And now I was wondering if you still use your very own Facebook classes and if so why? I tried to use the new API but got stuck because it is a pain in the ***** to use it for local testing and debugging. Do you have any sort of advice on how to avoid continually publishing the swf to a server to see if things work out?

    Thank you!!!

  18. Jozef Chúťka November 4, 2010 16:14

    Hi Mira,
    Yes, Adobe finaly released new official FB library. However, I keep using my own lib on all of my current projects as well as for future projects… I must admit I am not much interested in using official lib while the auth process and communication with graph api is easy to understand and make it work for you the way you want is easier with lightweighter lib (I find my one very light) … Local testing is one of the reasons (see “Advanced Crossdomain Working Authorization” in http://blog.yoz.sk/2010/06/extending-facebookoauthgraph-class/ ), but the main reason is that I am the master of my own lib, I can fix whenever I find an issue, do not have to wait for someone else to fix it… I can make it work with flash player, air, android etc.

  19. chestnut January 19, 2011 04:13

    it shows blank white popup instead of requesting permissions popup (login works fine). i can’t find workaround of this :(
    have u find solution?

  20. Jozef Chúťka January 19, 2011 11:11

    @chestnut,
    thank you for noticing this. I was able to replicate it with my account :( What I believe is, this is a temporary facebook bug. I do not have much time to do workarounds but I hope it gets fixed itself soon, as it sometimes does. If not I will take a look at it.

  21. chestnut January 19, 2011 14:40
  22. Jozef Chúťka January 19, 2011 18:25

    @chestnut, damn facebook again :(

Leave a comment

Please be polite and on topic. Your e-mail will never be published.