タグ : oauth

facebook OAuth認証について

facebookbookの認証は既にairforandroidでも使えるライブラリがリリースされています。
GraphAPI

メモ:
facebookの認証について
facebookの認証は以下の4つのプラットフォームに対応していている。
・javascript
・デスクトップアプリケーション
・モバイルウェブ
・キャンバス??
airforandroidで認証を行う場合、モバイルウェブかデスクトップアプリケーションの認証でライブラリのGraphAPI Desktop(HTMLLoaderを使った認証)を使えばクリアできそう。

アプリの登録はこちらから

Facebookデスクトップアプリケーション用の認証を使った簡易Auth認証サンプル

GraphAPIのoauth認証はHTMLLoaderを使っているのでstageWebViewで自作したもので差し替えてます。

Main.as(ドキュメントクラス)

package
{
	import flash.display.Sprite;

	import com.facebook.graph.FacebookDesktop;

	public class Main extends Sprite
	{
		private const API_KEY:String = "アプリID";
		private const API_PASS:String = "シークレットキー";
		private const API_CALLBACK:String = "コールバックするURL";
		private var token:String = "";
		private var auth:FaceBookAuth;

		public function Main()
		{
			StageReference.init(stage);
			init();
		}
		private function init():void {
			auth = new FaceBookAuth(stage, API_KEY, API_CALLBACK, authCallback);
		}

		public function authCallback(key:String):void {
			auth = null;
			//↓のtokenを保存しておけばOK
			token = key;
			//facebookDesktopの初期化
			FacebookDesktop.init(API_KEY, null, key);
			//自分のアカウントの友達一覧を取得します。
			FacebookDesktop.api('/me/friends', handleFriendsLoad);
		}
		private function handleFriendsLoad(response:Object, fail:Object):void {
			var friends:Array = response as Array;
			var l:int = friends.length;
			for (var i:int = 0; i < l; i++) {
				trace(friends[i].name, friends[i].id);
			}
		}
	}
}

FaceBookAuth.as

package
{
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.geom.Rectangle;
	import flash.media.StageWebView;

	public class FaceBookAuth extends Sprite
	{
		private const API_URL_AUTH:String = "https://graph.facebook.com/oauth/authorize?";
		private const API_URL_GETTOKEN:String = "";
		private var apiKey:String;
		private var callBackURL:String;
		private var callBackFunc:Function;
		private var webview:StageWebView;

		public function FaceBookAuth(stg:Stage, apikey:String, callbackurl:String, callbackfunc:Function)
		{

			callBackFunc = callbackfunc;
			callBackURL = callbackurl;

			webview = new StageWebView();
			webview.stage = stg;
			webview.viewPort = new Rectangle(0, 0, StageReference.stageWidth, StageReference.stageHeight);
			webview.loadURL(createAuthorizeURL(apikey,callbackurl));
			webview.addEventListener(Event.LOCATION_CHANGE, onLocationChange);
		}

		private function createAuthorizeURL(apikey:String,callbackurl:String):String {
			var url:String = API_URL_AUTH;
			url += "client_id=" + apikey + "&";
			url += "redirect_uri=" + callbackurl + "&";
			url += "type=user_agent" + "&";
			url += "display=popup";
			return url;
		}

		private function onLocationChange(e:Event):void {
			var num:int = webview.location.indexOf(callBackURL);
			if (num >= 0) {
				var token:String = webview.location.split("n=")[1].split("&")[0];
				callBackFunc(token);
				webview.removeEventListener(Event.LOCATION_CHANGE, onLocationChange);
				webview.dispose();
			}
		}
	}
}

flickrのOAuth認証について

airforandroidでstageWebViewをつかったflickrのOAuth認証を行うサンプルです。

※ただしyahoo!のログイン画面がモバイルに最適化されていない?もしくはflickrのリダイレクト先ログイン画面がPCになっているため、若干難有りですが以下の方法でOAuthさせることができます。

その前にとりあえず作業の流れは以下のような感じです。

おおまかな流れ

  • flickrにAPIキーとシークレットコードを送信してfrobをもらいます。
  • frobをもらったら認証画面(yahoo.comにログインしてなかったらログイン画面)へ
  • リダイレクト先のURLの末尾の新しいfrobを取得したら再度それを送りつけます。
  • トークンをゲット!!して認証完了。このトークンを利用して各種APIの操作を行うことが出来ます。

参考資料:

利用するライブラリ:

下のサンプルではAuth完了後に自分のフォトセットのリストを取得しに行ってます。

現状as3flickrlibにはUploadが未実装なので時間のあるときにCameraRollから写真をアップロードできる物を試してみます。

package
{
	import com.adobe.webapis.flickr.AuthResult;
	import com.adobe.webapis.flickr.FlickrService;
	import com.adobe.webapis.flickr.methodgroups.Auth;
	import com.adobe.webapis.flickr.AuthPerm;
	import com.adobe.webapis.flickr.events.FlickrResultEvent;
	import com.adobe.webapis.flickr.PhotoSet;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Rectangle;
	import flash.media.StageWebView;

	public class Main extends Sprite
	{
		private const APIKEY:String = "APIキー";
		private const APISECRET:String = "シークレットコード";
		private const callbackURL:String = "コールバックするURL";
		private var service:FlickrService;
		private var auth:Auth;
		private var frob:String = "";
		private var sid:String = "";
		private var webview:StageWebView;

		public function Main()
		{
			//flickrAPIの初期化
			service = new FlickrService(APIKEY);
			service.secret = APISECRET;
			service.addEventListener(FlickrResultEvent.AUTH_GET_FROB, onGetFrob);
			service.addEventListener(FlickrResultEvent.AUTH_GET_TOKEN, onGetToken);

			auth = new Auth(service);
			auth.getFrob();

		}
		//frobをリクエスト
		private function onGetFrob(e:FlickrResultEvent):void {
			if (e.success) {
				frob = e.data.frob;
				var url:String = service.getLoginURL(frob, AuthPerm.WRITE);
				createStageWebView(url);
			}
			service.removeEventListener(FlickrResultEvent.AUTH_GET_FROB, onGetFrob);
		}

		//stageWebViewを生成
		private function createStageWebView(url:String):void {
			webview = new StageWebView();
			webview.stage = this.stage;
			webview.viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
			webview.loadURL(url);
			webview.addEventListener(Event.LOCATION_CHANGE, onLocationChange);
		}

		//認証後のアクション
		private function onLocationChange(e:Event):void {
			if (webview) {
				var checkcnt:int = webview.location.indexOf(callbackURL);
				//ジャンプ先URLがcallbackURLと一致した場合↓
				if (checkcnt >= 0) {

					//新しいfrobを取得
					var urlarray:Array = webview.location.split("=");
					frob = urlarray[1] as String;

					//webviewを廃棄
					webview.removeEventListener(Event.LOCATION_CHANGE, onLocationChange);
					webview.dispose();

					//getTokenをリクエスト
					auth.getToken(frob);
				}
			}
		}

		//auth完了
		private function onGetToken(e:FlickrResultEvent):void {
			if (e.success) {
				var authResult:AuthResult = AuthResult(e.data.auth);
				sid = authResult.user.nsid;

				service.token = authResult.token;
				service.permission = authResult.perms;

				//これで完了あとは焼くなり、煮るなり!!↓ではphotoSetを取得
				service.photosets.getList(sid);
				service.addEventListener(FlickrResultEvent.PHOTOSETS_GET_LIST,onGetList);

			}else {
				trace("failed");
			}
		}

		private function onGetList(e:FlickrResultEvent):void {
			var psets:Array = e.data.photoSets as Array;
			var firstSet:PhotoSet = psets[0] as PhotoSet;
			trace(firstSet.description);
		}
	}
}

Twitter のOAUTHを通過させるサンプル

※tweetr1.0b3での動作確認ができています(2012/11/14)

AIR for AndroidでTwitterのOAuthを通過させるサンプルを作ってみました。

その前にOAuthって何って方はこちらをご参照ください。

言葉は知ってるけど仕組みとかは分からないって方はこちら

TwitterのPublicTimelineなどwebから誰でもアクセス出来る部分は認証なしでAPIを叩けますが、つぶやきをPOSTしたり@メンションを取得したりするのは認証が必要です。この認証をtwitterではOAuth認証というものを使っています。OAuthはWEBサービスなどで一般的に用いることが出来ますが、いままでswf単体では実現不可能な機能でした。(※javascriptとの連携なら可能)

swf単体では難しいこの認証ですがAIRではHTMLLoaderという(swf内に指定したURLのHTML表示できる)機能が実装されていてAIRアプリケーションであれば単体で実装が可能になっています。

で、AIR2.5にあたるairforandroidではHTMLLoaderによく似たStageWebViewというものが実装されました。このStageWebViewを使ってOAuthを実装したサンプルを作ってみます。

TwitterのAPIを叩くライブラリは既にいくつか存在します。

今回はOAuthにも対応しているTweetrを使いました。しかし、TweetrではOAuthをHTMLLoaderを使っているので、この部分を改造してAIRforAndroidで使えるようにStageWebViewで置き換える処理を追加、編集しました。実際にテストしてみたい場合はダウンロードしたTweetrのライブラリ内のOAuth.asを下記のサンプルに置き換えれば動くと思います。

プロジェクトは下のような感じ。

編集するファイルは Main.as  と com.swfjunkie.tweetr.oauth.OAuth.as の2点

Main.as

package
{
	import com.swfjunkie.tweetr.oauth.OAuth;
	import com.swfjunkie.tweetr.oauth.events.OAuthEvent;
	import com.swfjunkie.tweetr.Tweetr;
	import flash.display.Loader;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Rectangle;
	import flash.html.HTMLLoader;
	import flash.media.StageWebView;
	import flash.net.URLLoader;
	import flash.net.URLRequest;

	public class Main extends Sprite
	{
		private var tweetr:Tweetr;
		private var oauth:OAuth;
		private var webview:StageWebView;
		private const CONSUMER_KEY:String = "取得したKEY";
		private const CONSUMER_SECRET:String = "取得したパス";

		public function Main()
		{
			stage.scaleMode = "noScale";
            		stage.align = "TL";
			init();
		}
		private function init():void {

			tweetr = new Tweetr();

 			oauth = new OAuth();
			oauth.consumerKey = CONSUMER_KEY;
			oauth.consumerSecret = CONSUMER_SECRET;
			oauth.callbackURL = "auth後にコールバックするURL(なんでもいい)";
			oauth.pinlessAuth = true;

			oauth.addEventListener(OAuthEvent.COMPLETE, handleOAuthEvent);
			oauth.addEventListener(OAuthEvent.ERROR, handleOAuthEvent);

			webview = new StageWebView();
			webview.stage = this.stage;
			webview.viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
			oauth.webview = webview;
			oauth.getAuthorizationRequest();

		}
		private function handleOAuthEvent(event:OAuthEvent):void{
			if (event.type == OAuthEvent.COMPLETE)
			{
				tweetr.oAuth = oauth;

				//メッセージ(ステータス更新)を送る
				//tweetr.updateStatus("testpost");

				//タイムラインを取得する
				//tweetr.getPublicTimeLine();

				//OAuthが終わったらTweetrでやりたい放題できます。

				// prints username, user id and the final tokens
				trace("outh.string", oauth.toString());
			}
			else
			{
				trace("ERROR: "+event.text);
			}
		}
	}
}

OAuth.as

package com.swfjunkie.tweetr.oauth
{
    import com.hurlant.crypto.Crypto;
    import com.hurlant.crypto.hash.HMAC;
    import com.hurlant.util.Base64;
    import com.hurlant.util.Hex;
    import com.swfjunkie.tweetr.oauth.events.OAuthEvent;

    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.SecurityErrorEvent;
    import flash.external.ExternalInterface;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.net.URLVariables;
    import flash.utils.ByteArray;

    //CONFIG::AIR
	import flash.media.StageWebView;

    /**
     * Dispatched when the OAuth has succesfully completed a Request.
     * @eventType com.swfjunkie.tweetr.oauth.events.OAuthEvent.COMPLETE
     */
    [Event(name="complete", type="com.swfjunkie.tweetr.oauth.OAuthEvent")]

    /**
     * Dispatched when something goes wrong while trying to authorize
     * @eventType com.swfjunkie.tweetr.oauth.events.OAuthEvent.ERROR
     */
    [Event(name="error", type="com.swfjunkie.tweetr.oauth.OAuthEvent")]

    /**
     * OAuth Authentication Utility - requires the <a href="http://code.google.com/p/as3crypto/" target="_blank">as3crypto library</a> to work.
     * @author Sandro Ducceschi [swfjunkie.com, Switzerland]
     */
    public class OAuth extends EventDispatcher implements IOAuth
    {
        //
        //  Class variables
        //
        //--------------------------------------------------------------------------
        private static const OAUTH_DOMAIN:String = "http://twitter.com";
        private static const REQUEST_TOKEN:String = "/oauth/request_token";
        private static const AUTHORIZE:String = "/oauth/authorize";
        private static const ACCESS:String = "/oauth/access_token";
        //--------------------------------------------------------------------------
        //
        //  Initialization
        //
        //--------------------------------------------------------------------------

        /**
         * Creates a new OAuth Instance
         */
        public function OAuth()
        {
            super();
            urlLoader = new URLLoader();
            urlLoader.addEventListener(Event.COMPLETE, handleComplete);
            urlLoader.addEventListener(IOErrorEvent.IO_ERROR, handleError);
            urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleSecurityError);
        }

        //--------------------------------------------------------------------------
        //
        //  Variables
        //
        //--------------------------------------------------------------------------
        private var request:String;
        private var urlLoader:URLLoader;
        private var verifier:String;
        //--------------------------------------------------------------------------
        //
        //  Properties
        //
        //--------------------------------------------------------------------------
        /**
         * Get/Set the Consumer Key for your Application
         */
        public var consumerKey:String = "";
        /**
         * Get/set the Consumer Secret for your Application
         */
        public var consumerSecret:String = "";
        /**
         * Get/Set the User Token
         */
        public var oauthToken:String = "";
        /**
         * Get/Set the User Token Secret
         */
        public var oauthTokenSecret:String = "";

        private var _userId:String;
        /**
         * Get the twitter user_id (retrieval only available after successful user authorization)
         */
        public function get userId():String
        {
            if (_userId)
                return _userId;
            return null;
        }

        private var _callbackURL:String = "oob";
        /**
         * <b><font color="#00AA00">NEW</font></b> - Get/Set the OAuth Callback URL
         */
        public function get callbackURL():String
        {
            return decodeURIComponent(_callbackURL);
        }
        public function set callbackURL(value:String):void
        {
            _callbackURL = encodeURIComponent(value);
        }

        private var _username:String;
        /**
         * Get/set the twitter screen_name (retrieval only available after successful user authorization)
         */
        public function get username():String
        {
            if (_username)
                return _username;
            return null;
        }
        public function set username(value:String):void
        {
            _username = value;
        }

        private var _serviceHost:String = OAUTH_DOMAIN;
        /**
         * Service Host URL you want to use.
         * This has to be changed if you are going to use tweetr
         * from a web app. Since the crossdomain policy of twitter.com
         * is very restrictive. use Tweetr's own PHPProxy Class for this.
         */
        public function get serviceHost():String
        {
            return _serviceHost;
        }
        public function set serviceHost(value:String):void
        {
            if (value.indexOf("http://") == -1 && value.indexOf("https://") == -1)
                _serviceHost = "http://"+value;
            else
                _serviceHost = value;
        }

        /**
         * <b><font color="#00AA00">NEW</font></b> - Whether to use pinless OAuth or not.<br/>
         * If you set this to true, you will have to supply
         * a callback url via <code>callbackURL</code>
         */
        public var pinlessAuth:Boolean = false;

        /**
         * <div class="airIcon"><b><font color="#00AA00">NEW</font></b> - <b>AIR only!</b> The HTMLLoader to be used to display the OAuth
         * Authentication Process from Twitter in.</div>
         */
        //CONFIG::AIR
        public var webview:StageWebView;
        //--------------------------------------------------------------------------
        //
        //  Additional getters and setters
        //
        //--------------------------------------------------------------------------
        private function get time():String
        {
            return Math.round(new Date().getTime() / 1000).toString();
        }

        private function get nonce():String
        {
            return Math.round(Math.random() * 99999).toString();
        }
        //--------------------------------------------------------------------------
        //
        // Overridden API
        //
        //--------------------------------------------------------------------------

        //--------------------------------------------------------------------------
        //
        //  API
        //
        //--------------------------------------------------------------------------
        /**
         * Requests a OAuth Authorization Token and will build the proper authorization URL if successful.
         * When the URL has been created a <code>OAuthEvent.COMPLETE</code> will be fired containing the url.
         */
        public function getAuthorizationRequest():void
        {
            request = REQUEST_TOKEN;
            var urlRequest:URLRequest = new URLRequest(OAUTH_DOMAIN+REQUEST_TOKEN);
            urlRequest.url = _serviceHost + REQUEST_TOKEN + "?"+ getSignedRequest("GET", urlRequest.url);
            urlLoader.load(urlRequest);
        }

        /**
         * Requests the final Access Token to finish the OAuth Authorization.
         * When the Request succeeds a <code>OAuthEvent.COMPLETE</code> will be fired and the OAuth Instance will contain all the information needed to successfully call any Twitter API Method.
         * @param verifier   PIN or verifier_token given by Twitter on the Authorization Page.
         */
        public function requestAccessToken(verifier:String):void
        {
            request = ACCESS;
            this.verifier = verifier;
            var urlRequest:URLRequest = new URLRequest(OAUTH_DOMAIN+ACCESS);
            urlRequest.url = _serviceHost + ACCESS + "?"+ getSignedRequest("GET", urlRequest.url);
            urlLoader.load(urlRequest);
        }

        /**
         * Signs a Request and returns an proper encoded argument string.<br/>
         * <b>There usually is no need to call this by yourself.</b><br/><br/>
         * @param method    The URLRequest Method used. Valid values are POST and GET
         * @param url       The Request URL
         * @param urlVars   URLVariables that need to be signed
         */
        public function getSignedRequest(method:String, url:String, urlVars:URLVariables = null):String
        {
            var args:Array = [];

            if (request)
                args.push({name: "oauth_callback", value: _callbackURL});
            args.push({name: "oauth_consumer_key", value: consumerKey});
            args.push({name: "oauth_nonce", value: nonce});
            args.push({name: "oauth_signature_method", value: "HMAC-SHA1"});
            args.push({name: "oauth_timestamp", value: time});
            args.push({name: "oauth_version", value: "1.0"});

            if (!request || request == ACCESS)
            {
                args.push({name: "oauth_token", value: oauthToken});
                if (request == ACCESS)
                    args.push({name: "oauth_verifier", value: verifier});
            }

            for (var nameValue:String in urlVars)
                args.push({name: nameValue, value: urlVars[nameValue]});

            args.sortOn("name");

            var n:int = args.length;
            var vars:String = "";
            for (var i:int = 0; i < n; i++)
            {
                if (args[i]["name"] != "_method")
                {
                    vars += args[i]["name"]+"="+args[i]["value"];
                    if (i != n-1)
                        vars += "&";
                }
            }
            var signString:String = method.toUpperCase() +"&" + encodeURIComponent(url) + "&" + encodeURIComponent(vars);
            var hmac:HMAC =  Crypto.getHMAC("sha1");
            var key:ByteArray = Hex.toArray( Hex.fromString(encodeURIComponent(consumerSecret) + "&" + encodeURIComponent(oauthTokenSecret)));
            var data:ByteArray = Hex.toArray( Hex.fromString( signString ) );
            var sha:String = Base64.encodeByteArray( hmac.compute( key, data ) );
            vars += "&oauth_signature="+encodeURIComponent(sha);
            return vars;
        }

        /**
         * <b><font color="#00AA00">NEW</font></b> - Returns username, userid, oauth token
         * and secret in a practical string 😉
         */
        override public function toString():String
        {
            return "Username: "+_username+"\n"+
                    "User Id: "+_userId+"\n"+
                    "OAuth Token: "+oauthToken+"\n"+
                    "OAuth Token Secret: "+oauthTokenSecret;
        }

        //--------------------------------------------------------------------------
        //
        //  Overridden methods: _SuperClassName_
        //
        //--------------------------------------------------------------------------

        //--------------------------------------------------------------------------
        //
        //  Methods
        //
        //--------------------------------------------------------------------------

        private function buildAuthorizationRequest(data:String):void
        {
            var splitArr:Array = data.split("&");
            var n:int = splitArr.length;
            for (var i:int = 0; i < n; i++)
            {
                var element:Array = String(splitArr[i]).split("=");
                if (element[0] == "oauth_token")
                {
                    oauthToken = element[1];
                    break;
                }
            }
            var url:String = OAUTH_DOMAIN + AUTHORIZE +"?oauth_token="+encodeURIComponent(oauthToken);

            if (!pinlessAuth)
                dispatchEvent(new OAuthEvent(OAuthEvent.COMPLETE, url));
            else
                callAuthorize(url);
        }

        private function parseAccessResponse(data:String):void
        {
            var splitArr:Array = data.split("&");
            var n:int = splitArr.length;
            for (var i:int = 0; i < n; i++)
            {
                var element:Array = String(splitArr[i]).split("=");
                switch (element[0])
                {
                    case "oauth_token":
                    {
                        oauthToken = element[1];
                        break;
                    }
                    case "oauth_token_secret":
                    {
                        oauthTokenSecret = element[1];
                        break;
                    }
                    case "user_id":
                    {
                        _userId = element[1];
                        break;
                    }
                    case "screen_name":
                    {
                        _username = element[1];
                        break;
                    }
                }
            }
            dispatchEvent(new OAuthEvent(OAuthEvent.COMPLETE));
        }

        //------------------------------------------
        //  Conditional Authorize Call Methods
        //------------------------------------------

        //CONFIG::WEB
		/*
        private function callAuthorize(url:String):void
        {
            if (ExternalInterface.available)
            {
                ExternalInterface.addCallback("setVerifier", requestAccessToken);
                ExternalInterface.call("OAuth.callAuthorize", url);
            }
        }
        */

        //CONFIG::AIR
        private function callAuthorize(url:String):void
        {
            if (webview)
            {
                webview.addEventListener(Event.LOCATION_CHANGE, handleDocumentComplete);
				webview.loadURL(url);
            }
        }

        //--------------------------------------------------------------------------
        //
        //  Broadcasting
        //
        //--------------------------------------------------------------------------

        //--------------------------------------------------------------------------
        //
        //  Eventhandling
        //
        //--------------------------------------------------------------------------

        private function handleComplete(event:Event):void
        {

            if (request == REQUEST_TOKEN)
                buildAuthorizationRequest(urlLoader.data);

            if (request == ACCESS)
                parseAccessResponse(urlLoader.data);
        }

        private function handleError(event:IOErrorEvent):void
        {
            dispatchEvent(new OAuthEvent(OAuthEvent.ERROR, null, event.text));
        }

        private function handleSecurityError(event:SecurityErrorEvent):void
        {
            dispatchEvent(new OAuthEvent(OAuthEvent.ERROR, null, event.text));
        }

        //CONFIG::AIR
        private function handleDocumentComplete(event:Event):void
        {
            var sStr:String = "oauth_verifier=";
            var location:String = webview.location;
            var hasLocation:Boolean = webview.location.indexOf(location) != -1;
            var oAuthVerifierIndex:int = location.indexOf(sStr);

            if (hasLocation && oAuthVerifierIndex != -1)
            {
                webview.removeEventListener(Event.LOCATION_CHANGE, handleDocumentComplete);
				webview.dispose();
                verifier = location.substr(oAuthVerifierIndex + sStr.length, location.length);
                requestAccessToken(verifier);
            }
        }
    }
}